{"record":{"id":"8b9fc928fb51c944","repo":"zeroclaw-labs/zeroclaw","slug":"invalid-azure-openai-tool-specification-unsupport","errorCode":null,"errorMessage":"Invalid Azure OpenAI tool specification: unsupported tool type '{}', expected 'function'","messagePattern":"Invalid Azure OpenAI tool specification: unsupported tool type '(.+?)', expected 'function'","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/azure_openai.rs","lineNumber":135,"sourceCode":"    /// `Arc`-shared with the tool registry's stored schema — serialized\n    /// transparently, never deep-cloned per request\n    parameters: std::sync::Arc<serde_json::Value>,\n}\n\nfn parse_native_tool_spec(value: serde_json::Value) -> anyhow::Result<NativeToolSpec> {\n    let spec: NativeToolSpec = serde_json::from_value(value).map_err(|e| {\n        ::zeroclaw_log::record!(\n            WARN,\n            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                .with_attrs(::serde_json::json!({\"error\": format!(\"{}\", e)})),\n            \"azure_openai: invalid tool spec\"\n        );\n        anyhow::Error::msg(format!(\"Invalid Azure OpenAI tool specification: {e}\"))\n    })?;\n\n    if spec.kind != \"function\" {\n        anyhow::bail!(\n            \"Invalid Azure OpenAI tool specification: unsupported tool type '{}', expected 'function'\",\n            spec.kind\n        );\n    }\n\n    Ok(spec)\n}\n\n#[derive(Debug, Serialize, Deserialize)]\nstruct NativeToolCall {\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    id: Option<String>,\n    #[serde(rename = \"type\", skip_serializing_if = \"Option::is_none\")]\n    kind: Option<String>,\n    function: NativeFunctionCall,\n}\n\n#[derive(Debug, Serialize, Deserialize)]","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/azure_openai.rs#L117-L153","documentation":"parse_native_tool_spec validates each tool definition serialized for the Azure OpenAI chat completions API. Azure OpenAI implements only the 'function' tool type, so a spec whose JSON type field is anything else ('custom', 'code_interpreter', 'retrieval', or a typo) is rejected locally before any HTTP request is sent.","triggerScenarios":"Calling chat with native tools on an azure family provider when a tool definition's {\"type\": ...} is not the exact string \"function\" - for example a tool ported from the OpenAI Responses API ('custom' tools) or Anthropic format, or hand-written JSON with a typo.","commonSituations":"Porting tool definitions between provider SDKs; a tool registry that emits a default or empty type field; JSON tool schemas authored against newer OpenAI tool types.","solutions":["Set every tool definition's type field to \"function\" with a nested function object carrying name, description, and parameters","Strip or convert non-function tools (custom, code_interpreter, retrieval) before passing them to an azure provider","If you need non-function tool types, route those requests to a provider family that supports them instead of azure_openai"],"exampleFix":"// before\nlet tool = serde_json::json!({\n    \"type\": \"custom\",\n    \"name\": \"get_weather\",\n    \"parameters\": {\"city\": {\"type\": \"string\"}}\n});\n\n// after\nlet tool = serde_json::json!({\n    \"type\": \"function\",\n    \"function\": {\n        \"name\": \"get_weather\",\n        \"description\": \"Get current weather for a city\",\n        \"parameters\": {\"type\": \"object\", \"properties\": {\"city\": {\"type\": \"string\"}}, \"required\": [\"city\"]}\n    }\n});","handlingStrategy":"validation","validationCode":"// Filter/normalize tools before handing them to an azure provider\nlet tools: Vec<serde_json::Value> = raw_tools\n    .into_iter()\n    .filter(|t| t.get(\"type\").and_then(|v| v.as_str()) == Some(\"function\"))\n    .collect();\nif tools.is_empty() {\n    anyhow::bail!(\"no function-type tools to send\");\n}","typeGuard":"fn is_function_tool_spec(value: &serde_json::Value) -> bool {\n    value.get(\"type\").and_then(|t| t.as_str()) == Some(\"function\")\n        && value.get(\"function\").is_some_and(|f| {\n            f.get(\"name\").is_some_and(|n| n.as_str().is_some_and(|s| !s.is_empty()))\n        })\n}","tryCatchPattern":"match provider.chat_with_tools(/* ... */).await {\n    Ok(resp) => { /* ... */ }\n    Err(e) if e.to_string().contains(\"unsupported tool type\") => {\n        // re-validate the tool list, convert or drop non-function tools, retry once\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Emit tool definitions from one place with type: \"function\" fixed","Validate the tool list with the type guard before every azure chat-with-tools call","Do not port tool JSON between provider formats without mapping the type field","Add a unit test asserting every registered tool serializes with type == \"function\""],"tags":["azure-openai","tools","function-calling","validation","rust"],"backgroundTag":"unsupported-tool-type","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}