Pull requests / #1430
#1430 serve: a tool call written <function= NAME> is the call NAME
open · @itripn · 0 commentaires · Sur GitHub
Description
### What
A tool call written `<function= NAME>` (a space after the `=`) is now the call `NAME`. The function name is stripped in the three places it is read: `parse_tool_call`, the #804 rescue's check against the declared tools, and `_finish_call`'s schema lookup.
### Why
Seen from Qwen3.8-Flash-Next IQ2_XS (v0.1.40.1, one GPU, Linux) in Zed's agent panel: in a long agent session the model wrote this at the end of its thinking, with no `</think>` before it:
```
<tool_call>
<function= capture>
<parameter=content>
...
</parameter>
</function>
</tool_call>
```
The name was read as `" capture"`, which matched no declared tool, so the #804 rescue kept the call as reasoning text (correctly, by its rules: it just never matched). The reply came back as reasoning only, with `finish_reason: "stop"` and no `tool_calls`, and the agent stopped mid-task. After the thinking the same call is delivered, but named `" capture"`, which the client does not know, and its arguments are typed without the tool's schema.
| written | inside the thinking | after the thinking |
|---|---|---|
| `<function=capture>` | call `capture` | call `capture` |
| `<function= capture>`, before | no call (reasoning) | call `" capture"`, untyped arguments |
| `<function= capture>`, after | call `capture` | call `capture` |
A name with surrounding whitespace is never meaningful, so this does not turn text into a call that was not one: the #804/#1058 conditions still decide whether a call inside the thinking is an act.
### Reproduction (no GPU)
```python
from serve.frontend import OutputParser
SCHEMA = [{"name": "capture", "parameters": {"properties": {"content": {"type": "string"}}}}]
CALL = "<tool_call>\n<function= capture>\n<parameter=content>\nhello\n</parameter>\n</function>\n</tool_call>"
for text in ("Let me save this.\n" + CALL + "\n", "Saving it.</think>\n\n" + CALL):
p = OutputParser(thinking=True, tools=SCHEMA, stream_tools=True)
evs = p.feed(text) + p.finish("stop")
print([e.call.name for e in evs if e.kind == "tool_call"])
# main: [] then [' capture']; this branch: ['capture'] both times
```
### Tests
`serve/test_tool_name_space.py`: the call inside and after the thinking, streamed and not, fed in 3-character pieces, with a string parameter `"42"` that only stays a string when the schema is found. It fails 4/4 on `main` and passes on this branch. `test_reasoning_rescue`, `test_reasoning_tools` and `test_frontend` are unchanged and pass (46 tests).
Related: #804, #1053, #1058.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Q2VjkVpcBMW9QSkWgQ1p3E
Sur le site
Liens install, modèles, releases.