Repository navigation
Serialize assistant tool_calls and tool-result name on the OpenAI-compat path - #2236
Conversation
…pat path (closes #2198) OpenAiMessage only carried role/content/tool_call_id, so replaying an assistant tool-call turn dropped the tool_calls array and the following tool message's name — a shape strict OpenAI-compatible servers reject outright after the first tool call, and one that leaves the model blind to its own prior calls even on lax servers like Ollama. Co-Authored-By: Claude <noreply@anthropic.com>
Code Review — PR #2236 (initial review)Verdict: APPROVE. (Posted as a comment; GitHub blocks self-approval.) SummaryA tight, correctly-scoped protocol fix. I verified the fix rather than reading it only:
Acceptance criteria
What the change gets rightThe shape is genuinely correct, not merely plausible. Conditional emission is right. Extraction to a named function is the right call. The fix connects to its producer. I traced the history construction: Test naming and comments earn their place. FindingsNothing blocking. Two observations, both optional. [Nit] #[cfg(test)]
use crate::agent_types::{Role, ToolCallRaw};This works and is warning-free, but a test-only import at module scope is slightly unusual — the conventional home is inside [Nit] Assistant tool-call turns serialize
Scope and processAtomic and single-purpose — one struct, one extracted function, three tests, no drive-by refactoring. No lint suppression, no Correctly scoped out: strict-server end-to-end confirmation stays in #2189, and the issue is explicit that a green Ollama run proves no regression, not the fix — the PR's evidence structure honors that distinction rather than overclaiming from the smoke test. No security surface: no user input parsing, no new network behavior, no secrets, no error-path data exposure. No performance concern — the per-message clones were already present in the code this replaces, and the allocation count is unchanged for non-tool turns. RecommendationApprove and merge. This is a clear net improvement to code health, verified rather than asserted, and it unblocks #2189. |
malibio
left a comment
There was a problem hiding this comment.
Verdict: APPROVE. Submitted as a comment because GitHub blocks self-approval on an authored PR; the full review is in the preceding comment.
Verified locally: 16/16 unit tests pass (including the 3 new wire-shape tests), clippy clean, and the live Ollama smoke test passes — confirming AC #4 empirically. All five acceptance criteria met. Two nits only, both optional and non-blocking.
|
Additional verification beyond the unit tests: ran a real two-turn tool-calling conversation through the actual
[
{"role": "user", "content": "What is the weather in Paris right now? Use the tool."},
{
"role": "assistant", "content": "",
"tool_calls": [{"id": "call_e5l5jzrv", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\":\"Paris\"}"}}]
},
{"role": "tool", "tool_call_id": "call_e5l5jzrv", "name": "get_weather", "content": "{\"temperature_c\": 22, \"condition\": \"sunny\"}"}
]This matches the OpenAI wire contract from the issue exactly. The verification script was a throwaway test file, not committed — the shape is what the new unit tests already assert in isolation; this confirms it also works end-to-end against a live server. |
Cosmetic only, per PR #2236 review — the #[cfg(test)]-gated module-scope import compiled and warned cleanly either way. Co-Authored-By: Claude <noreply@anthropic.com>
Address Review — PR #2236Both findings from the initial review were explicitly non-blocking (reviewer: "I would not hold a merge for it" / "I am explicitly not asking for a change"). Addressed one, deferred the other per the reviewer's own recommendation: ✅ Addressed — ⏭️ Skipped — Verified after the change: Re-Review DecisionDecision: NO RE-REVIEW NEEDED Rationale: The only change since the approved review is a one-line import relocation with no logic, behavior, or test-coverage change — confirmed by an identical 16/16 test pass and clean clippy/fmt. The second finding was deliberately left as-is per the reviewer's own guidance. Nothing here could plausibly change the APPROVE verdict. |
Summary
OpenAiMessageonly serializedrole/content/tool_call_id. When the ReAct loop replays an assistant turn that issued tool calls, that turn hascontent: ""plus structured tool calls tracked separately — so on the OpenAI-compat path the replayed history became{"role":"assistant","content":""}followed by a{"role":"tool","tool_call_id":...}message with no precedingtool_callsto justify it, and the tool message'snamefield was dropped too.messages with role "tool" must be a response to a preceding message with "tool_calls") — every tool-using conversation hard-fails right after the first tool executes./v1, LM Studio): the malformed shape is accepted, but the model can never see which tool it called or with what arguments, degrading multi-step tool use.Fix
tool_calls: Option<Vec<OpenAiRequestToolCall>>andname: Option<String>toOpenAiMessage, mirroring the OpenAI wire contract ({id, type: "function", function: {name, arguments}}).to_openai_message(&ChatMessage) -> OpenAiMessage, now populating both new fields from the internalChatMessage's existing (previously untapped)tool_calls/namefields.Test plan
tool_callswith correctid/type/function.name/function.argumentsnamecargo test -p nodespace-agent --lib— 545 passed, 0 failedlive_openai_compat_smoke.rsagainst local Ollama: reproduces an unrelated pre-existing failure (mistral:7bon this Ollama install returns a degenerate empty response — confirmed via rawcurlbypassing all NodeSpace code, and confirmed byte-identical on unmodifiedmainbefore this change).llama3.1:8bon the same Ollama server tool-calls correctly. Not a regression from this change.bun run test:all— full suite passes (frontend, scripts, skill, Rust)NODESPACE_PROMPT_DUMPused to confirm the outgoing request shape and that no API key is written to the dumpStrict-server (real hosted provider) confirmation is out of scope here — that belongs to #2189, which this issue unblocks.
Closes #2198
🤖 Generated with Claude Code