Pull requests / #525

#525 serve: rescue a tool call stranded in an unclosed thinking span

closed · @chimpera · 0 评论 · 在 GitHub 查看

Server & API

描述

Some chat templates render a tool call right after the reasoning block without ever emitting `</think>` before it. The whole call then streams out as `reasoning_content`, and a client that runs tools from the content channel ends its turn with nothing to execute — seen live as agents stopping silently mid-task (transcript: thinking-only message, `end_turn`, thinking text ends with a complete `<tool_call>` block).

The rescue is deliberately conservative: during generation the parser behaves exactly as before (no divert, nothing withheld). It keeps the reasoning text from the first `<tool_call>` opener aside; a `</think>` after the opener clears it — a valid quoted call example inside a *closed* thinking span is a mention, not an act, and is never fired as a call. Only at end of generation, in a span that never closed, is the kept tail delivered as `tool_call` events, and only when every complete block parses against the request's tool schemas. Malformed or unfinished bodies stay reasoning; a reply cut by max tokens mid-call is untouched (#530 already names that case).

Deferred rather than opt-in: a rescued call is by construction at the very end of the reply (the span never closed and generation ended), so nothing is delayed in practice — the only thing given up is `stream_tools` progressive args for rescued calls, which arrive whole at end of stream.

Tests: the live stranded shape (chunked at 1/7/10k × `stream_tools` on/off), a valid quoted example in a closed span (the review's false positive — the first version of this fix fired it as a real call), a stranded call with trailing thought, two stranded calls, a malformed mention in an unclosed span, `</think>`-before-opener, an unfinished call, and the content-channel path.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

站内延伸阅读

链到安装、模型与版本说明,便于 SEO/GEO,非官方 issue 正文。