Pull requests / #1881
#1881 serve: a tool call whose `</tool_call>` is missing ends at its own `</function>` (two calls were merged into one, or the arguments lost)
open · @LeGeRyChEeSe · 0 comentários · No GitHub
Descrição
## Title
Issue: none found (searched 2026-10-10: "missing </tool_call>", "tool_call not closed", "two tool calls merged").
Related: #210 (call_end), #211 (a call cut before its closer), #1172 (recover's batch in one wrapper, opt-in).
## Summary
**Why it matters:** when the model leaves out a call's `</tool_call>`, `call_end` falls back to the *next*
`</tool_call>` anywhere in the output. Two cases go wrong, with or without `tool_call_recovery`, streamed or not:
| model output | before | after |
|---|---|---|
| `<tool_call>` read(a.py) *(no closer)* `<tool_call>` write(b.py, "x = 1") `</tool_call>` | **one** call: `read` with `{"path": "b.py", "content": "x = 1"}` | `read(a.py)`, then `write(b.py, "x = 1")` |
| `<tool_call>` read(a.py) *(no closer)* `Done.` | `read` with `{}` (streamed) or no call | `read(a.py)`, then the content `Done.` |
The first is the worse one: an agent runs the first tool with the second tool's arguments.
**The change** (`OutputParser.feed`, the wrapped-call branch, a few lines): after the call's own `</function>` - found by
`function_end`, which walks the parameters with `param_end`, so a value that holds `</function>` or `<tool_call>` does
not count - the next non-blank text decides:
- `</tool_call>` (or the start of it, still arriving): closes the call, as before;
- `<function=` or `<parameter=` (or the start of one, still arriving): left to recover's batch (opt-in), unchanged;
- anything else (another `<tool_call>`, text): the call ends at its `</function>`, and what follows is read as usual.
Nothing changes for a call that has its closer.
## Test
- `serve/test_tool_call_unclosed.py` (new): the two cases above, closed calls unchanged, and a parameter value that
holds `</function>` and `<tool_call>`; each with `stream_tools` and `recover` on and off, fed 1, 3 and 7 characters
at a time (48 sub-tests; 24 fail without the change).
- `python -m unittest discover -s serve -t . -p "test_*.py"`: 695 tests, OK (11 skipped), the three branches together; `ToolCallRecovery` (the opt-in batch, fed 1 and 7 characters at a time) included.
No site
Links install, modelos, releases.