fix(SERVE-CHAT-TEMPLATE): a malformed historical tool-call argument answers 400 and makes the conversation unrecoverable
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 423
- Forks
- 53
- Avg merge
- 20h 26m
- Merged PRs (30d)
- 310
Description
Row: SERVE-CHAT-TEMPLATE (.agents/engine-matrix.md:219), whose local-anchor column names src/vllm/entrypoints/chat_template.cpp.
A note on the row, because the obvious candidate does not resolve. .agents/specs/tool-call-history-arguments.md:5 names SERVE-TOOL-HISTORY-ARGS, tracked by #526. That ID is not a matrix row: 0 hits in .agents/engine-matrix.md and .agents/roadmap_v1.md, against a positive control of SERVE-CHAT-TEMPLATE returning 1 in the same scope and probe form. So this issue names the row that owns the edited file, not the ID the spec invented.
Found by wave PORTQ-7 (#2717) re-deriving PORT-NOW entry [254], upstream 161ffd37d2 vllm#48922. Nothing was executed.
The divergence
A malformed arguments string on a historical assistant tool call makes this tree answer HTTP 400. Upstream now coerces it to {} with a warning and continues.
There is exactly one site, and it is enumerated rather than sampled: DecodeHistoricalToolArguments (src/vllm/entrypoints/chat_template.cpp:35), called from exactly one place, BuildMessages (:90). grep -rn DecodeHistoricalToolArguments src include tests returns those two lines and nothing else.
Its behaviour today:
- empty ->
{}(:38) — matches upstream; - JSON
null->{}— matches upstream; - parse failure ->
throw ChatTemplateError(:45-49) — upstream warns and coerces; - any other valid JSON returned as-is (
:50-51) — so"[]","42"and"true"reach minja as an array, a number and a bool. Upstream coerces every non-object to{}with a warning.
The throw is client-facing: api_server.cpp:361-378 maps ChatTemplateError to BadRequestError. Two handlers reach the site — handle_chat_completions (api_server.cpp:306) and handle_tokenize's chat form, routed through chat_->prompt_fn() at api_server.cpp:816-822. The C-ABI fallback prompt builder does not decode arguments at all (src/capi/chat_prompt.cpp).
Upstream's rationale is the part worth carrying: a malformed argument lives in conversation history, so failing on it fails every subsequent turn and makes the conversation unrecoverable.
This reverses a committed decision, and a reviewer must be told so
.agents/specs/tool-call-history-arguments.md states the fail-closed choice four times — :28 ("malformed JSON raises instead of entering the prompt"), :61, :88, and :119 ("fail closed; do not coerce"). tests/vllm/entrypoints/test_chat_template.cpp:868-881 pins the throw under the name "malformed historical tool arguments fail closed".
So the fix amends that spec and inverts an existing test. A fresh reviewer must not read that inversion as an assertion being deleted — it is the assertion changing sides, because upstream has since converged on the opposite, with a stated reason this project did not have when it chose.
The commit is the whole distance
The pre-commit shape existed at 5559679229 and was ported faithfully. Not a pre-pin hole. git show e126687a9a:vllm/entrypoints/chat_utils.py still carries both warnings at :2082 and :2098; the two later in-range touches of that file (555ea65e8c, and acd0af90e8 which is entry [256] of this queue) narrow neither.
One adjacent divergence, best fixed in the same change
Upstream explicitly passes a dict-valued arguments through. This tree parses arguments into a std::string (src/vllm/entrypoints/openai/protocol.cpp:487, struct at include/vllm/entrypoints/openai/protocol.h:152-155), so a dict makes nlohmann raise type_error.302, which escapes into the from_json catch and answers 400 (api_server.cpp:325-329). Same user-visible symptom, different mechanism, same family.
Size
~20-25 product lines in chat_template.cpp, plus a warning sink — that file has no logger today and would need std::cerr or the request logger. ~60-80 test lines: invert the one existing case and add the non-object cases upstream parametrises. ~6 spec lines in tool-call-history-arguments.md.
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 with DecodeHistoricalToolArguments and BuildMessages in src/vllm/entrypoints/chat_template.cpp, then inspect the protocol argument types in src/vllm/entrypoints/openai/protocol.cpp and .h. Read the existing case in tests/vllm/entrypoints/test_chat_template.cpp:868-881 and the related spec. Done means malformed or non-object historical arguments no longer produce a 400, the adjacent dict-valued case is handled, and the tests and spec reflect the new behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend-api-design, testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100