fix(ollama): keep structured tools prompt-guided#7095
Conversation
WareWolf-MoonWall
left a comment
There was a problem hiding this comment.
Review on head of PR #7095. CI is green (13/13 checks passing). No prior reviews exist. I read the PR body (comprehensive validation section, supersede attribution), the diff (single file, ~200 lines, structured refactor + new test), and cross-checked against the linked issue #5962 and superseded PR #6029.
🟢 What looks good — Fixes the advertised contract violation
Ollama advertises supports_native_tools() == false, but the structured chat() path was still serializing ToolSpec values into a native /api/chat tools array whenever tools were present. This made tool-using prompts depend on model-side native tool support even when the provider explicitly says it doesn't support that mode. The fix is correct: structured tool requests now stay on the prompt-guided path by injecting tool instructions into the system message and omitting the native tools payload. This closes a real bug (#5962) where Windows users with models lacking native tool support saw {"error":"EOF"} failures.
🟢 What looks good — Regression test verifies the fix at the wire level
The new chat_with_tool_specs_omits_native_tools_payload test spins up a local Axum server, captures the outgoing /api/chat request body, and verifies three things: (1) no top-level tools key is present, (2) the system message includes the prompt-guided tool protocol instructions with the file_read tool schema, and (3) the response preserves Ollama's token usage counters. This is exactly the right level of test — it verifies the serialized wire format, not just the Rust-level structure.
🟢 What looks good — Clean refactor with shared response parsing
The patch extracts the duplicated response-to-ChatResponse logic into response_to_chat_response() and the message-prep logic into with_prompt_guided_tool_instructions(). Both the structured chat() path and the explicit chat_with_tools() path now share the same response parsing, which means token usage, native tool calls (if Ollama still returns them), reasoning fallback, and text normalization are consistent across both entry points. No logic changes to chat_with_tools() — the refactor is scoped to the structured path.
🟢 What looks good — Supersede attribution is exemplary
The PR body includes a full "Supersede Attribution" section that explains what #6029 identified, what this PR carries forward, and why no Co-authored-by trailer was added (no direct code from #6029). This is the model for how to attribute a superseding PR.
🟡 Warning — Combined validation stack is good practice but not required
The PR body notes "Temporary combined validation stack: current master + #7091 + this PR" with full broad clippy and nextest results. This is excellent due diligence — it verifies the patch doesn't conflict with other in-flight work — but it's not a formal requirement. The green CI on this PR's own head is sufficient for approval. Calling this out as a positive example of going beyond minimum validation.
🔵 Suggestion — Consider live validation with reporter's model before v0.7.6 tag
The validation evidence states "I did not run live Ollama against the reporter's Windows models. The focused provider regression verifies the corrected request shape directly." The regression test is sufficient for approving this PR, but before the v0.7.6 release tag, consider asking the #5962 reporter (or another Windows user with the affected models) to verify the fix end-to-end. Not blocking, but it would close the loop on the original bug report. If that's not practical, the wire-level test is good enough.
Verdict
APPROVED. This is a well-executed provider bug fix with the right scope (structured chat path only), the right test (wire-level request capture), and exemplary supersede attribution. The fix directly addresses the #5962 EOF errors without changing the explicit native-tool path or other providers. CI is green. Milestone v0.7.6 is correct for a targeted provider bug fix.
No #6848 compatibility concerns — this doesn't touch provider selection, model-first patterns, or agent configuration.
|
This commit somehow passed the commit gates, but breaks the build. A previous commit #6848 changes the signature of |
The ci gate needs harded. |
The bare tokio::spawn in the prompt-guided-tools test trips the disallowed_methods clippy lint (-D warnings), failing the CI lint gate. This was latent on master because zeroclaw-labs#7095's CI ran against a stale base. Match the established test-server pattern (factory.rs, openrouter.rs) so the spawned task inherits the caller's attribution span.
… send_request Restores workspace build broken by zeroclaw-labs#7095. The chat() method at line 1049 shadows the Option<f64> `temperature` parameter via `unwrap_or(self.default_temperature())`, producing a bare f64. Line 1058 then passes that bare f64 into `send_request(..., Option<f64>, ...)` — fresh E0308: mismatched types on master. Wraps the unwrapped value in `Some(...)` at the single offending call site (chat()). chat_with_tools() at line 1024 already forwards the unshadowed Option<f64> correctly and is left alone. Bundled in this PR as an unblock/prep commit so the five Lark/Feishu changes that follow can be verified end-to-end on a clean master. The commit is fully self-contained — cherry-picks cleanly out of this PR if a reviewer prefers it as its own PR. Risk: Low (1-line fix; restores pre-zeroclaw-labs#7095 behavior).
|
@bheatwole thanks again for catching this. I merged #7095, so the merge break is on me. The immediate issue is fixed by #7231, but the process issue is that #7095 had green checks from an older base and then landed after #6848 changed the same Ollama request path/signature shape. A few comments have suggested reinstating the strict "must be up to date with base before merge" rule. That would have prevented this, but we have tried that before and it created a lot of update/recheck churn during active review periods. GitHub's merge queue looks like the better compromise: instead of requiring every PR branch to stay manually current, the queue tests the exact merge group against current Until we have that, the maintainer-side rule should be: if a PR is behind/stale and current |
Summary
mastersupports_native_tools() == false, but the structuredchat()path still convertedToolSpecvalues into a native/api/chat.toolspayload whenever tools were present./api/chatwithout a nativetoolsarray.chat()andchat_with_tools()now preserves returned text, native tool calls if Ollama still returns them, reasoning fallback behavior, and Ollama token usage counts.tools, includes the prompt-guidedfile_readschema, and preserves response usage.chat_with_tools()native JSON path. It only aligns the structured providerchat()path with Ollama's advertised non-native-tools contract.bug,risk: medium,size: M,provider,provider:ollama.Validation Evidence (required)
/api/chat: no top-level nativetoolskey is present, the injected system instructions include the prompt-guided tool protocol, thefile_readtool name, and thepathschema field, and the returned Ollama usage counters are preserved inChatResponse.usage. I also merged this PR locally with fix(cron): expose DingTalk in cron delivery schemas #7091 over currentmasterand ran the repo-shaped broad clippy and nextest gates on that combined stack.Security & Privacy Impact (required)
NoNoNoNoYes, describe the risk and mitigation: N/AThe new test uses a local loopback HTTP server only. The production change removes a native tool payload from the structured Ollama request path; it does not add a new network destination, credential path, filesystem operation, or autonomous capability.
Compatibility (required)
YesNoNoorYesto either: No user migration is required.The user-visible behavior change is that structured Ollama tool requests now follow the existing prompt-guided tool contract instead of sending native Ollama tool arrays from a provider that advertises native tools as unsupported.
Rollback (required for
risk: mediumandrisk: high)git revert 6e0c2f9e9{"error":"EOF"}on models that do not support Ollama native tool arrays.Supersede Attribution (required only when
Supersedes #is used)#<pr> by @<author>, one per line): fix(ollama): avoid native tool payload on fallback path #6029 by @TeoConnexiohmaster, keeps the fix in the structuredchat()path, adds request-body regression coverage, preserves response usage, and keeps the explicit native JSON helper path scoped separately.Co-authored-bytrailers added in commit messages for incorporated contributors?NoNo, why (inspiration-only, no direct code/design carry-over): No direct code was copied from fix(ollama): avoid native tool payload on fallback path #6029. The public attribution is theSupersedes #6029relationship and this section.