docs(llm-providers): explain the structured tool_calls requirement for local endpoints (#520) - #901
Merged
Merged
Conversation
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Contributor
Greptile SummaryDocuments the structured
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Reviews (2): Last reviewed commit: "docs(llm-providers): explain the structu..." | Re-trigger Greptile |
Resolves the docs/llm-providers/local.mdx conflict by keeping both additions: main's LLM_EXTRA_HEADERS gateway subsection and the new structured tool_calls troubleshooting section. Probe now forwards llm.extra_headers so gateways that require custom headers don't trip a false-positive abort.
…g guidance If every probe attempt fails before a response arrives and no error points at tool configuration, re-raise the original error rather than claiming the endpoint can't do structured tool calls.
…lling Found while running the preflight probe end-to-end against live local gateways: an OpenAI-compatible gateway that echoes the request payload in its error body made a 401 match the tool-config markers, so an auth failure told the user to go fix their chat template. - keep the probe's own payload free of the marker words it scans for - treat auth/permission/rate-limit errors as never-a-capability-problem (connection errors stay eligible: LiteLLM surfaces Ollama's "tools param requires --jinja flag" 500 as APIConnectionError)
Add sampling defaults for open-weight families that emit malformed tool calls at their stock temperature (qwen, kimi-k2, glm-4, minimax-m2), overridable with STRIX_TEMPERATURE. Reasoning and Claude models keep the provider default. Recover from an invalid tool call instead of failing the agent: an unknown or miscased tool name, or unparseable arguments, now feeds the rejection plus the valid tool names back to the model and replays the turn (bounded at 2 attempts). Classify preflight probe failures by HTTP status as well as message markers, so a proxy's HTML 401/403 body can't be read as a tool-calling capability error. Document the Ollama num_ctx floor, including that the probe's single tiny tool passes even when num_ctx is too small for a real scan.
…r local endpoints Merge main and reduce this branch to documentation only. The preflight probe, per-family sampling defaults, invalid-tool-call recovery and their tests are dropped: main now nudges a text-only turn back into a tool call, which covers the user-visible symptom without a probe request on every local scan. What remains is guidance for the actual cause -- an inference server that emits tool tokens as assistant text instead of structured tool_calls -- with the per-server settings that fix it (llama.cpp --jinja and chat template, Ollama tool-capable template plus thinking off and a larger num_ctx, vLLM tool-call and reasoning parsers).
Contributor
Author
0xallam
approved these changes
Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #520.
Documents why local/self-hosted endpoints "return tool calls as text" and how to fix it on the inference server. Docs only — no code changes. The runtime half of #520 already landed in main (f6f9469, 6eec34d): a text-only turn is nudged back into a tool call instead of parking the agent. What was missing is guidance for the underlying cause, which is server configuration.
Strix is entirely tool-driven, so a turn only does work if the endpoint returns a native
tool_callsfield. When a server isn't configured to parse the model's tool tokens, they land incontentas text:The Agents SDK correctly reads that as an ordinary assistant message, so no action ever executes. This is server configuration, not the model or Strix — and it can't be fixed client-side without parsing model text into shell commands, which we must never do.
Verified by reproducing it on real local servers and confirming the fix flips it:
llama-serverb10152--jinja(now default)llama-serverb10152--no-jinja500 tools param requires --jinja flagllama-cpp-python0.3.34A correctly configured endpoint either returns a structured call or rejects the request — it never leaks. So the new docs section gives the per-server settings: llama.cpp
--jinja+ a matching chat template and reasoning aligned/off; Ollama a tool-capable template, thinking off (it pushes the call intocontent), andnum_ctxat 16k–32k so the tool schemas aren't truncated out of the prompt; vLLM--enable-auto-tool-choicewith matching--tool-call-parserand--reasoning-parser.Scope change
This branch previously carried a preflight tool-calling probe, per-model-family sampling defaults, and invalid-tool-call recovery. All of it has been dropped and the branch rebuilt as docs-only:
glm-4where upstream gates onglm-4.6/4.7, and used 0.6 for allkimi-k2where thinking/2.5 variants want 1.0) and would drift with every new model release.Not carried over: catching
ModelBehaviorErrorin_run_cycle. It's a genuinely distinct path from main's fix — a call that was emitted structurally but is invalid (hallucinated or miscased name, unparseable arguments) — and today it fails the agent or kills a non-interactive scan. Happy to raise that separately if wanted.Link to Devin session: https://app.devin.ai/sessions/f74044feadc04f02b17d2ccfdc4df7d7
Requested by: @0xallam