{"record":{"id":"d042fdd162143cd4","repo":"siyuan-note/siyuan","slug":"streamed-function-call-is-missing-from-the-termina","errorCode":null,"errorMessage":"streamed function call is missing from the terminal response","messagePattern":"streamed function call is missing from the terminal response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/openai_completion.go","lineNumber":505,"sourceCode":"func (stream *OpenAICompletionStream) queueResponseTerminal(response openai.CreateResponseResponse) error {\n\tif err := responseResultError(response); err != nil {\n\t\treturn err\n\t}\n\tterminalToolCalls := map[int]struct{}{}\n\tfor index, raw := range response.Output {\n\t\titem, ok := responseOutputItem(raw)\n\t\tif !ok || item.Type != \"function_call\" {\n\t\t\tcontinue\n\t\t}\n\t\tif item.Status == \"incomplete\" || item.Status == \"in_progress\" ||\n\t\t\t(response.Status == openai.ResponseStatusIncomplete && item.Status != \"completed\") {\n\t\t\treturn errors.New(\"response ended with an incomplete function call\")\n\t\t}\n\t\tterminalToolCalls[index] = struct{}{}\n\t}\n\tfor index := range stream.responseToolCalls {\n\t\tif _, ok := terminalToolCalls[index]; !ok {\n\t\t\treturn errors.New(\"streamed function call is missing from the terminal response\")\n\t\t}\n\t}\n\toutput, err := MarshalOpenAIResponseOutput(response.Output)\n\tif err != nil {\n\t\treturn err\n\t}\n\tstream.responseOutput = output\n\n\tfor index, raw := range response.Output {\n\t\titem, ok := responseOutputItem(raw)\n\t\tif !ok || item.Type != \"function_call\" {\n\t\t\tcontinue\n\t\t}\n\t\tcurrent := stream.responseToolCalls[index]\n\t\tif current.ID != \"\" && item.CallID != \"\" && current.ID != item.CallID {\n\t\t\treturn errors.New(\"streamed function call ID does not match the terminal response\")\n\t\t}\n\t\tif current.Function.Name != \"\" && item.Name != \"\" && current.Function.Name != item.Name {","sourceCodeStart":487,"sourceCodeEnd":523,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/util/openai_completion.go#L487-L523","documentation":"The adapter accumulates function_call deltas keyed by output index in stream.responseToolCalls. The terminal response's Output array must contain function_call items at exactly those indices. A streamed index with no terminal counterpart means the deltas and the final response disagree about which calls exist, so the adapter aborts rather than emit a tool call whose final state is unknown.","triggerScenarios":"Providers that reorder or drop items between delta emission and final assembly; a message item inserted into the terminal output shifting indices; gateways merging or regenerating events from multiple upstream attempts.","commonSituations":"OpenAI-compatible gateways with index-handling bugs on parallel tool calls; mid-stream provider failover changing assembly behavior.","solutions":["Retry the request — assembly races are often transient on gateways.","Reproduce against the official endpoint; if clean there, upgrade or replace the gateway.","Use non-streaming requests (CreateOpenAICompletion) for tool-heavy conversations on that provider.","Capture an SSE trace and report it to the gateway maintainers."],"exampleFix":"// before: streaming tool calls through a flaky gateway\n{\"apiBase\": \"https://my-gateway/v1\", \"protocol\": \"openai-responses\", \"stream\": true}\n\n// after: same gateway, terminal output as the single source of truth\n{\"apiBase\": \"https://my-gateway/v1\", \"protocol\": \"openai-responses\", \"stream\": false}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"On mismatch errors, treat the whole stream as unusable — never execute tool calls assembled from deltas — and rerun the step non-streaming so the terminal output is the single source of truth.","preventionTips":["Prefer fully compliant endpoints when tool calling matters","Keep gateways current","Retain SSE traces when certifying a new provider","Design callers to rerun a step rather than salvage a partial stream"],"tags":["streaming","tool-calls","openai","responses-api","consistency"],"backgroundTag":"stream-state-mismatch","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}