{"record":{"id":"851421067ce6c7b2","repo":"aaif-goose/goose","slug":"responses-function-call-output-missing-call-id-and","errorCode":null,"errorMessage":"Responses function_call output missing call_id and id","messagePattern":"Responses function_call output missing call_id and id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-provider-types/src/formats/openai_responses.rs","lineNumber":850,"sourceCode":"                                id,\n                                Ok(CallToolRequestParams::new(strip_unicode_tags(name))\n                                    .with_arguments(object(sanitize_tool_arguments(\n                                        input.clone(),\n                                    )?))),\n                            ));\n                        }\n                    }\n                }\n            }\n            ResponseOutputItem::FunctionCall {\n                id,\n                call_id,\n                name,\n                arguments,\n                ..\n            } => {\n                let request_id = call_id.clone().or_else(|| id.clone()).ok_or_else(|| {\n                    anyhow!(\"Responses function_call output missing call_id and id\")\n                })?;\n                let request_id = sanitize_tool_request_id(&request_id, &mut tool_request_ids)?;\n                let parsed_args = parse_tool_arguments(arguments)?;\n\n                content.push(MessageContentBlock::tool_request(\n                    request_id,\n                    Ok(CallToolRequestParams::new(strip_unicode_tags(name))\n                        .with_arguments(object(parsed_args))),\n                ));\n            }\n        }\n    }\n\n    let mut message = Message::new(Role::Assistant, chrono::Utc::now().timestamp(), content);\n\n    message = message.with_id(response.id.clone());\n\n    Ok(message)","sourceCodeStart":832,"sourceCodeEnd":868,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-provider-types/src/formats/openai_responses.rs#L832-L868","documentation":"Goose converts a non-streaming OpenAI Responses API payload (ResponsesApiResponse) into its internal Message format via responses_api_to_message. For every output item of type function_call it needs an identifier to correlate the tool request with the later tool result: it prefers call_id and falls back to id. When the model/gateway returns a function_call item where both call_id and id are null or absent, the whole response conversion aborts with this error.","triggerScenarios":"A provider configured with the openaiResponses format returns a tool call: response.output contains {\"type\":\"function_call\",\"name\":...,\"arguments\":...} with neither call_id nor id. Typical with OpenAI Responses-compatible gateways (LiteLLM, vLLM, Azure proxies) or hand-built JSON fixtures that omit these fields; responses_api_to_message is the non-streaming path, so this fires on a completed request, not a stream chunk.","commonSituations":"Switching a provider from chat completions to the Responses API schema of a third-party gateway that drops call_id; upstream API schema drift after an OpenAI or proxy version bump; replaying recorded responses where the id fields were stripped; unit tests with hand-written response fixtures missing the ids.","solutions":["Log/capture the raw Responses API JSON and confirm the function_call item really lacks both call_id and id","If a gateway/proxy (LiteLLM, vLLM, custom) sits in front, upgrade or configure it to forward call_id for function_call output items","Test the same request against the official OpenAI Responses API; if it works there, the bug is in the intermediary, not goose","If you control the payload (fixtures/tests), add a unique call_id (or id) to every function_call item","Last resort for unfixable upstreams: patch this match arm to synthesize a fallback id (e.g. format!(\"call_{n}\") from tool_request_ids.len()) instead of failing the entire response"],"exampleFix":"// before\nlet request_id = call_id.clone().or_else(|| id.clone()).ok_or_else(|| {\n    anyhow!(\"Responses function_call output missing call_id and id\")\n})?;\n\n// after - degrade gracefully instead of dropping the whole response\nlet request_id = call_id.clone().or_else(|| id.clone()).unwrap_or_else(|| {\n    tracing::warn!(\"function_call output missing call_id and id; synthesizing fallback\");\n    format!(\"call_{}\", tool_request_ids.len())\n});","handlingStrategy":"validation","validationCode":"// before calling responses_api_to_message\nlet bad = response\n    .output\n    .iter()\n    .filter(|item| matches!(item, ResponseOutputItem::FunctionCall { id: None, call_id: None, .. }))\n    .count();\nif bad > 0 {\n    anyhow::bail!(\"refusing to convert: {bad} function_call item(s) without call_id/id\");\n}","typeGuard":"fn function_call_has_identifier(item: &ResponseOutputItem) -> bool {\n    match item {\n        ResponseOutputItem::FunctionCall { id, call_id, .. } => id.is_some() || call_id.is_some(),\n        _ => true,\n    }\n}","tryCatchPattern":"match responses_api_to_message(&response) {\n    Ok(msg) => msg,\n    Err(err) if err.to_string().contains(\"missing call_id and id\") => {\n        tracing::warn!(\"dropping response with unidentifiable tool calls: {err}\");\n        Message::assistant()\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Prefer the official OpenAI Responses endpoint or a gateway verified to forward call_id on function_call items","Log raw Responses payloads (debug level) so id-stripping intermediaries are identifiable immediately","Keep recorded fixtures faithful: never strip call_id/id when anonymizing transcripts","Add a unit test asserting every function_call fixture carries an identifier"],"tags":["rust","openai-responses","tool-calls","parsing","provider"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}