Proposal: structured streaming tool-call support for SSE parser#965
Draft
mtdphn wants to merge 1 commit into
Draft
Proposal: structured streaming tool-call support for SSE parser#965mtdphn wants to merge 1 commit into
mtdphn wants to merge 1 commit into
Conversation
Adds a design proposal for accumulating delta.tool_calls by index in the SSE parser, returning structured ToolCall[] through StreamChatResult instead of converting to XML. Companion to the native-tools-in-streaming fix (PR nullclaw#964). Requires the root.zig fix to be merged first.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR: Structured streaming tool-call support for SSE parser (companion to root fix)
Problem
The root fix (
agent/root.zignative-tools-in-streaming) enablestools[]+tool_choice: "auto"in streaming requests. For servers that leave model-emitted XML indelta.content, this is sufficient — the agent parses<tool_call>from the accumulated text.However, vLLM deployments with
--enable-auto-tool-choice --tool-call-parser(vLLM docs) intercept the model's XML output, strip it fromdelta.content, and re-emit it as structureddelta.tool_callsin the SSE stream. The current SSE parser (src/providers/sse.zig) only readsdelta.contentanddelta.reasoning*— it ignoresdelta.tool_calls, producingNoResponseContentwhen the server-side parser is active.Design
MultiToolCallAccumulatoraccumulatesdelta.tool_callsbyindexfield (supporting multiple parallel tool calls per the OpenAI streaming spec). Returns structuredToolCall[]throughStreamChatResult— no XML conversion, no user-visible markup.Changes
src/providers/root.zig— 1 field addedAdd
tool_callstoStreamChatResult:Update
emitChatResponseAsStreamto stealresponse.tool_callsbeforefreeStreamUnusedChatResponseFieldsfrees them.src/providers/sse.zig— ~200 lines addedNew types and functions:
MultiToolCallAccumulator— array ofDisassembledToolCallkeyed byindex. Supports N parallel tool calls.ensureIndex()— finds or creates a slot for a given indexintoOwnedToolCalls()— converts to[]ToolCallwith stable fallback IDs (call_{index})extractSseDataPayload()— stripsdata:SSE framing, returns JSON payloadextractToolCallDelta()— parsesdelta.tool_callsfrom a JSON payload, accumulates intoMultiToolCallAccumulatorisToolCallFinishReason()— returnstrueif the chunk hasfinish_reason: "tool_calls"(no dangling pointer)finalizeStreamResultWithToolCalls()— wrapper called from all 4 return sites incurlStream()to attach accumulated tool callsChanges to
curlStream():sse_dataviaextractSseDataPayload()beforeparseSseLine()— runs tool-call extraction on EVERY chunk, not only.skipbranches (catches chunks with bothdelta.contentanddelta.tool_calls)return finalizeStreamResult(...)withreturn finalizeStreamResultWithToolCalls(...)— covers normal completion, wait failure recovery, nonzero exit recovery, and abnormal termination recoveryRemoved (not needed):
formatToolCallXml— no XML conversionflushToolCalls— no user-visible markupextractFinishReason— replaced byisToolCallFinishReasonboolsrc/agent/root.zig— 1 lineAt line 2293, wire streaming tool calls through:
Testing
Add SSE fixture tests for:
delta.tool_callsacross multiple chunks (single call)indexvalues (0 and 1)delta.contentanddelta.tool_calls\ncontaining tool-call dataPrior art
delta.tool_callswith index, id, function name, and chunked argumentsdelta.tool_calls