zeroclaw-labs/zeroclaw · error · anyhow::Error

Invalid Azure OpenAI tool specification: unsupported tool ty

Error message

Invalid Azure OpenAI tool specification: unsupported tool type '{}', expected 'function'

What it means

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.

Source

Thrown at crates/zeroclaw-providers/src/azure_openai.rs:135

    /// `Arc`-shared with the tool registry's stored schema — serialized
    /// transparently, never deep-cloned per request
    parameters: std::sync::Arc<serde_json::Value>,
}

fn parse_native_tool_spec(value: serde_json::Value) -> anyhow::Result<NativeToolSpec> {
    let spec: NativeToolSpec = serde_json::from_value(value).map_err(|e| {
        ::zeroclaw_log::record!(
            WARN,
            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                .with_attrs(::serde_json::json!({"error": format!("{}", e)})),
            "azure_openai: invalid tool spec"
        );
        anyhow::Error::msg(format!("Invalid Azure OpenAI tool specification: {e}"))
    })?;

    if spec.kind != "function" {
        anyhow::bail!(
            "Invalid Azure OpenAI tool specification: unsupported tool type '{}', expected 'function'",
            spec.kind
        );
    }

    Ok(spec)
}

#[derive(Debug, Serialize, Deserialize)]
struct NativeToolCall {
    #[serde(skip_serializing_if = "Option::is_none")]
    id: Option<String>,
    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
    kind: Option<String>,
    function: NativeFunctionCall,
}

#[derive(Debug, Serialize, Deserialize)]

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Set every tool definition's type field to "function" with a nested function object carrying name, description, and parameters
  2. Strip or convert non-function tools (custom, code_interpreter, retrieval) before passing them to an azure provider
  3. If you need non-function tool types, route those requests to a provider family that supports them instead of azure_openai

Example fix

// before
let tool = serde_json::json!({
    "type": "custom",
    "name": "get_weather",
    "parameters": {"city": {"type": "string"}}
});

// after
let tool = serde_json::json!({
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get current weather for a city",
        "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
    }
});
Defensive patterns

Strategy: validation

Validate before calling

// Filter/normalize tools before handing them to an azure provider
let tools: Vec<serde_json::Value> = raw_tools
    .into_iter()
    .filter(|t| t.get("type").and_then(|v| v.as_str()) == Some("function"))
    .collect();
if tools.is_empty() {
    anyhow::bail!("no function-type tools to send");
}

Type guard

fn is_function_tool_spec(value: &serde_json::Value) -> bool {
    value.get("type").and_then(|t| t.as_str()) == Some("function")
        && value.get("function").is_some_and(|f| {
            f.get("name").is_some_and(|n| n.as_str().is_some_and(|s| !s.is_empty()))
        })
}

Try / catch

match provider.chat_with_tools(/* ... */).await {
    Ok(resp) => { /* ... */ }
    Err(e) if e.to_string().contains("unsupported tool type") => {
        // re-validate the tool list, convert or drop non-function tools, retry once
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/8b9fc928fb51c944. Report an issue: GitHub.