{"record":{"id":"eb5f7a2af7eaaca7","repo":"Billionmail/BillionMail","slug":"failed-to-unmarshal-tool-call-arguments-v-s","errorCode":null,"errorMessage":"failed to unmarshal tool call arguments: %v, %s","messagePattern":"failed to unmarshal tool call arguments: (.+?), (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/askai/openai.go","lineNumber":687,"sourceCode":"\t\t\t\t\to.ThinkEnd = false\n\t\t\t\t\to.IsThinkContent = false // Reset thinking content state after processing\n\t\t\t\t\tif o.IsThinking {\n\t\t\t\t\t\to.IsThinking = false // Reset thinking state after processing\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\to.WriteEvent(request, messageBody, isText, false) // Write event to the response\n\t\t}\n\t}\n\n\tif len(toolCallMap) > 0 {\n\t\t// If there are tool calls, write them to the response\n\t\tfor _, toolCall := range toolCallMap {\n\t\t\tif toolCall.Function.Name == TOOL_NAME_HTTP_REQUEST {\n\t\t\t\tvar params WebSearchParams\n\t\t\t\tif err := json.Unmarshal([]byte(toolCall.Function.Arguments), &params); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to unmarshal tool call arguments: %v, %s\", err, toolCall.Function.Arguments)\n\t\t\t\t}\n\n\t\t\t\to.WriteEvent(request, \"\\n\\n<tool>Requesting URL: \"+params.Url, isText, false)\n\t\t\t\t// Call the HTTP request tool with the URL from the tool call\n\t\t\t\tresponseContent := o.HttpRequestTool(params.Url)\n\t\t\t\to.WriteEvent(request, \" complete</tool>\\n\\n\", isText, false)\n\n\t\t\t\tassistantMessage := openai.ChatCompletionMessage{\n\t\t\t\t\tRole:    openai.ChatMessageRoleAssistant,\n\t\t\t\t\tContent: \"\",\n\t\t\t\t\tToolCalls: []openai.ToolCall{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:       toolCall.ID,\n\t\t\t\t\t\t\tType:     toolCall.Type,\n\t\t\t\t\t\t\tFunction: toolCall.Function, // Use the function from the tool call\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}","sourceCodeStart":669,"sourceCodeEnd":705,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/askai/openai.go#L669-L705","documentation":"During streamed chat completion, tool-call argument chunks are accumulated and, for the http_request tool, JSON-unmarshalled into WebSearchParams. If the model produced malformed/incomplete JSON arguments, json.Unmarshal fails and the error (plus the raw arguments) is wrapped and returned, aborting the stream.","triggerScenarios":"CreateChatCompletionStream receives a tool call named http_request whose Function.Arguments is truncated, invalid JSON (e.g. unescaped quotes in a URL), or empty; typically when the stream ends mid-tool-call or the model hallucinates bad JSON.","commonSituations":"Max-tokens cut off a tool call mid-arguments; low-quality/small models emitting malformed JSON; arguments containing characters that break JSON encoding; a provider change altering argument chunking.","solutions":["Validate/repair toolCall.Function.Arguments before unmarshalling (e.g. json.Valid check, accumulate chunks until complete)","Use json.Unmarshal on a buffered full-arguments string rather than partial chunks; only parse once all delta chunks arrive","Log the raw arguments (already included in the error) to confirm truncation and adjust max_tokens or prompt","Fall back to skipping the bad tool call and continuing the stream instead of failing the whole request"],"exampleFix":"// before\nif err := json.Unmarshal([]byte(toolCall.Function.Arguments), &params); err != nil {\n    return fmt.Errorf(\"failed to unmarshal tool call arguments: %v, %s\", err, toolCall.Function.Arguments)\n}\n// after\nif !json.Valid([]byte(toolCall.Function.Arguments)) {\n    o.WriteEvent(request, \"\\n\\n<tool>Skipping malformed tool call</tool>\\n\\n\", isText, false)\n    continue\n}\nif err := json.Unmarshal([]byte(toolCall.Function.Arguments), &params); err != nil {\n    continue\n}","handlingStrategy":"validation","validationCode":"args := toolCall.Function.Arguments\nif !json.Valid([]byte(args)) {\n    // skip or buffer more chunks before invoking the tool path\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &params); err != nil {\n    log.Warnf(\"bad tool args: %v raw=%s\", err, toolCall.Function.Arguments)\n    continue // skip malformed tool call, keep stream alive\n}","preventionTips":["Only unmarshal once all argument delta chunks are accumulated","Set generous max_tokens so tool calls aren't truncated mid-JSON","Use models known to emit well-formed tool-call JSON; prefer native function-calling APIs","Log raw arguments on failure to detect systematic model/provider issues"],"tags":["go","json","llm","streaming","tool-calls"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}