OpenAI multi-turn tool history reaches chat templates with string-valued arguments
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 423
- Forks
- 53
- Avg merge
- 20h 26m
- Merged PRs (30d)
- 310
Description
Row: SERVE-TOOL-HISTORY-ARGS
Bug
The OpenAI Chat Completions protocol carries assistant.tool_calls[].function.arguments as a JSON-encoded string. vllm.cpp preserves that string through ChatMessage and BuildMessages() and hands it directly to Jinja. This diverges from pinned vLLM vllm/entrypoints/chat_utils.py::_postprocess_messages, which json.loads() each historical argument string before chat-template rendering (empty/null becomes {}).
Gemma 4 exposes the failure because its canonical DSL requires an arguments mapping. A valid first call such as {"command":"date"} is rendered into history as <|tool_call>call:terminal{"command":"date"}<tool_call|> rather than canonical <|tool_call>call:terminal{command:<|"|>date<|"|>}<tool_call|>. On the next turn Gemma imitates the quoted form; the parser faithfully returns a literal quote-wrapped key such as {"\"command\"":"\"ls\""}, and clients cannot find command. Retries recursively escape it.
Reproduction evidence
~/llms/logs/agentic_tool_loop_20260811-212603.json: turns 1-2 have valid arguments; turn 3 is the first completed-history turn and emits the literal quote-wrapped key/value.- The same pattern repeats in
agentic_tool_loop_20260811-202653.json. - Fresh-context controls emit valid
{command: ...}-derived JSON. - Current vllm.cpp
src/vllm/entrypoints/chat_template.cppstoresfn["arguments"] = tc.function.arguments. - Pinned vLLM converts strings to objects in
vllm/entrypoints/chat_utils.py:1915-1951. - Google/vLLM Gemma 4 canonical templates now fail closed unless historical arguments are mappings.
Expected behavior
Before rendering any chat template, vllm.cpp should mirror vLLM message post-processing:
- Decode valid JSON argument strings to JSON values.
- Normalize empty/null arguments to
{}. - Reject invalid JSON instead of teaching the model malformed history.
- Preserve already-structured arguments internally.
- Gate a complete user → assistant tool call → tool result → next-generation Gemma render, plus non-Gemma template compatibility.
This must be fixed in vllm.cpp, not in Hermes or another client.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/vllm/entrypoints/chat_template.cpp, where fn["arguments"] is currently stored, and compare the pinned vLLM handling in vllm/entrypoints/chat_utils.py:1915-1951. Trace BuildMessages() through chat-template rendering, then add coverage for a complete tool-call history with Gemma 4 and a non-Gemma template, including valid, empty/null, structured, and invalid arguments.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100