Azure Foundry (v1) support, dependency fix, LangGraph state fix, SearXNG - #4
Azure Foundry (v1) support, dependency fix, LangGraph state fix, SearXNG#4breakingcircuits1337 wants to merge 4 commits into
Conversation
…arXNG - azure_openai: use AsyncOpenAI against the Foundry /openai/v1 endpoint; reasoning models use max_completion_tokens and omit temperature (they reject temperature != 1), classic models (Mistral) keep max_tokens+temperature - registry: point role defaults at live Foundry deployments - pyproject: move anthropic/google/groq/mistral/tavily SDKs to an optional 'providers' extra to resolve an httpx version conflict (base install = openai) - research/search: add self-hosted SearXNG adapter (SEARXNG_URL) tried before Tavily/Exa, with tests - orchestrator: rehydrate LangGraph's dict state into MerlinState (LangGraph >=1.x) - Dockerfile: drop apt (lxml/cryptography wheels are self-contained), add pip retries/timeout for slow links - docker-compose: stop publishing redis/postgres ports to the host Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request optimizes the Docker build process, migrates the Azure OpenAI adapter to the unified Azure AI Foundry endpoint, introduces SearXNG as a search provider, and refactors provider dependencies into an optional group. Feedback focuses on improving the robustness of reasoning model detection in the Azure OpenAI adapter and ensuring that the newly introduced default models are added to the model aliases to prevent runtime errors during model overrides.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| def _is_reasoning_model(self) -> bool: | ||
| # Mistral is the only classic (non-reasoning) chat model in use; every | ||
| # other configured deployment is a reasoning model. | ||
| return "mistral" not in self.model.lower() |
There was a problem hiding this comment.
Using "mistral" not in self.model.lower() to identify reasoning models is very fragile. If a user configures or overrides the model to use classic models like gpt-4o, gpt-4, or deepseek-v3 (which are common classic models), they will be incorrectly classified as reasoning models. This will force temperature to be omitted (defaulting to 1.0) and use max_completion_tokens, which can cause API errors or severely degrade output quality (especially for tasks requiring low temperature like code generation).
Consider explicitly checking for known reasoning model patterns (e.g., containing gpt-5, kimi, o1, o3, deepseek-v4, r1) or maintaining an explicit list of classic/reasoning models.
| def _is_reasoning_model(self) -> bool: | |
| # Mistral is the only classic (non-reasoning) chat model in use; every | |
| # other configured deployment is a reasoning model. | |
| return "mistral" not in self.model.lower() | |
| def _is_reasoning_model(self) -> bool: | |
| model_lower = self.model.lower() | |
| return any(k in model_lower for k in ["gpt-5", "kimi", "o1", "o3", "deepseek-v4", "r1"]) |
| _DEFAULTS: dict[str, tuple[str, str, str | None]] = { | ||
| # Fast factual retrieval — DeepSeek V3.2 is fast and strong at research synthesis | ||
| "researcher": ("app.providers.azure_openai", "AzureOpenAIAdapter", "deepseek-v3.2"), | ||
| # Evidence weighing — Kimi-K2 Thinking's extended reasoning excels here | ||
| "evidence": ("app.providers.azure_openai", "AzureOpenAIAdapter", "kimi-k2-thinking"), | ||
| # Strategic planning — GPT-5.4 Pro flagship for complex multi-step reasoning | ||
| "planner": ("app.providers.azure_openai", "AzureOpenAIAdapter", "gpt-5.4-pro"), | ||
| # Adversarial critique — DeepSeek V3.2-Speciale tuned for deep critical analysis | ||
| "critic": ("app.providers.azure_openai", "AzureOpenAIAdapter", "deepseek-v3.2-speciale"), | ||
| # Final verdict — GPT-5.4 Pro for authoritative evaluation | ||
| "judge": ("app.providers.azure_openai", "AzureOpenAIAdapter", "gpt-5.4-pro"), | ||
| # Code generation — GPT-5.1 Codex Max, purpose-built for code | ||
| "builder": ("app.providers.azure_openai", "AzureOpenAIAdapter", "gpt-5.1-codex-max"), | ||
| # Debugging — GPT-5.1 Codex Max, code-native model | ||
| "debugger": ("app.providers.azure_openai", "AzureOpenAIAdapter", "gpt-5.1-codex-max"), | ||
| # Deployment names below match the live Azure AI Foundry resource | ||
| # (brokencircuits-1334) validated against /openai/v1/chat/completions. | ||
| # Fast factual retrieval | ||
| "researcher": ("app.providers.azure_openai", "AzureOpenAIAdapter", "DeepSeek-V4-Pro"), | ||
| # Evidence weighing — Kimi's extended reasoning excels here | ||
| "evidence": ("app.providers.azure_openai", "AzureOpenAIAdapter", "Kimi-K2.6"), | ||
| # Strategic planning — GPT-5.5 flagship reasoning | ||
| "planner": ("app.providers.azure_openai", "AzureOpenAIAdapter", "gpt-5.5"), | ||
| # Adversarial critique — Mistral Large 3 (classic chat model) | ||
| "critic": ("app.providers.azure_openai", "AzureOpenAIAdapter", "Mistral-Large-3"), | ||
| # Final verdict — GPT-5.5 for authoritative evaluation | ||
| "judge": ("app.providers.azure_openai", "AzureOpenAIAdapter", "gpt-5.5"), | ||
| # Code generation — GPT-5.5 (gpt-5.3-codex is Responses-API only, unusable here) | ||
| "builder": ("app.providers.azure_openai", "AzureOpenAIAdapter", "gpt-5.5"), | ||
| # Debugging — GPT-5.5 | ||
| "debugger": ("app.providers.azure_openai", "AzureOpenAIAdapter", "gpt-5.5"), | ||
| } |
There was a problem hiding this comment.
The new default models (DeepSeek-V4-Pro, Kimi-K2.6, gpt-5.5, Mistral-Large-3) are not defined in _MODEL_ALIASES. If a user attempts to explicitly override a role's model using one of these new models (e.g., via azure:gpt-5.5), get_adapter will raise a ValueError because the alias is missing.
Consider adding aliases for these new models to _MODEL_ALIASES to keep them consistent with the defaults.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ea0984f7c7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if self._is_reasoning_model: | ||
| # Reasoning models: token budget via max_completion_tokens and | ||
| # temperature must stay at the default (1) — omit it entirely. | ||
| kwargs["max_completion_tokens"] = max_tokens |
There was a problem hiding this comment.
Bump the OpenAI SDK minimum for max_completion_tokens
This new reasoning-model branch is used by every default Azure role except Mistral, but pyproject.toml still permits openai==1.30.0; that SDK's async chat-completions method does not accept max_completion_tokens, so an install satisfying the declared dependency can fail with TypeError before any Azure request is sent. Please raise the minimum OpenAI SDK version to one that supports this parameter, or send it through a compatible extension path.
Useful? React with 👍 / 👎.
| api_key=settings.azure_openai_api_key, | ||
| azure_endpoint=settings.azure_openai_endpoint, | ||
| api_version=settings.azure_openai_api_version, | ||
| base_url=settings.azure_openai_endpoint.rstrip("/"), |
There was a problem hiding this comment.
Normalize the Azure v1 base URL before use
The client now appends /chat/completions directly to AZURE_OPENAI_ENDPOINT, but the repo's existing .env.example and README still tell operators to use a root Azure endpoint such as https://your-resource.openai.azure.com/. In that configuration all default Azure roles call https://.../chat/completions instead of the required /openai/v1/chat/completions route, so fresh or existing deployments following the repo config will fail until the suffix is added or validated here.
Useful? React with 👍 / 👎.
Single self-contained static page served by the FastAPI app (FileResponse at /ui, / redirects there). Submits to /v1/quests same-origin with an X-API-Key from localStorage, shows an animated eight-knight progress view during the synchronous wait, then renders the markdown answer with confidence, judge verdict, debate critiques, and sources. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stream the LangGraph workflow with a wall-clock deadline (via astream) and keep the latest state after each super-step. When the budget is exhausted before the workflow finishes — common in build mode where slow reasoning models make each planner/debate/judge revision cycle 1-2 minutes — stop after the current node and return the best draft produced so far (final_answer -> draft_answer -> implementation_plan -> evidence_report -> research_notes) with status 'partial', rather than hard-cancelling and returning 'The quest timed out before completion'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Critic (debate) fix:
- CRITIC prompt now returns a JSON object {"critiques":[{issue,severity,
recommendation}]} instead of loose ISSUE:/SEVERITY: lines that the reasoning
models rendered as prose. debate_node parses it via _extract_json, treats an
empty array as 'plan is sound', and only falls back to raw text on genuine
parse failure. The judge now sees clean severity-tagged critiques and approves
in fewer rounds (verified: a research quest approved in one debate round
instead of looping to the time budget).
SSE streaming:
- New POST /v1/quests/stream returns text/event-stream: a 'node' event as each
knight finishes, then a 'result' event with the full QuestResponse. Shared
response-building refactored into _build_response used by both endpoints.
- Web console consumes the stream and lights each knight in real time.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Changes made while deploying KTRT to a Proxmox LXC against a live Azure AI Foundry resource.
Fixes
azure_openai.pynow usesAsyncOpenAIpointed at the/openai/v1OpenAI-compatible surface. Reasoning models (gpt-5.x, Kimi, DeepSeek) usemax_completion_tokensand omittemperature(they reject any value ≠ 1); classic models (Mistral) keepmax_tokens+temperature.httpxranges, makingpip installunresolvable. Moved anthropic/google-generativeai/groq/mistralai/tavily-python to an optionalprovidersextra; base install is justopenai. The other adapters are lazily imported only when their model aliases are used.graph.ainvokereturns a plain dict even for a pydantic state schema;orchestrator.pyrehydrates it intoMerlinStatebefore attribute access (wasAttributeError: 'dict' object has no attribute 'run_status').Features / hardening
SEARXNG_URL) tried before Tavily/Exa, with tests.Verified end-to-end: a research quest completes with judge APPROVED, confidence 0.86.
🤖 Generated with Claude Code