Hmbown/CodeWhale · error · anyhow::Error

Kimi native search requested an unexpected tool

Error message

Kimi native search requested an unexpected tool

What it means

Within the bounded Kimi native-search tool-call loop, each returned tool call must be the $web_search function. If the function name in a tool call is anything else (or missing/not a string), the client cannot execute it, so it bails rather than silently ignoring an unknown tool.

Solutions

  1. Confirm the request enables only the $web_search native search tool and no user-defined tools are leaked into the search request
  2. Log the offending function name from the response for diagnosis
  3. Update the provider adapter if Kimi renamed the tool; accept a new name in the check
  4. Retry or fall back to non-native search when the model misbehaves

Example fix

// before
if tool_call.pointer("/function/name").and_then(Value::as_str) != Some("$web_search") {
    bail!("Kimi native search requested an unexpected tool");
}
// after
let name = tool_call.pointer("/function/name").and_then(Value::as_str).unwrap_or_default();
if name != "$web_search" {
    tracing::warn!(name, "skipping unexpected Kimi native search tool");
    continue;
}
Defensive patterns

Strategy: try-catch

Type guard

fn is_web_search_call(tc: &Value) -> bool {
    tc.pointer("/function/name").and_then(Value::as_str) == Some("$web_search")
}

Try / catch

do {
    let resp = provider.search(req).await?;
    // process resp
} catch |err| if err.to_string().contains("unexpected tool") {
    eprintln!("Kimi requested a non-$web_search tool; retrying without native search");
    fallback_search(req).await
}

Prevention

When it happens

Trigger: search_builtin iterates the tool_calls array and a call's pointer /function/name is not exactly "$web_search" — i.e. the model requested a different or unnamed function during a native-search session.

Common situations: Model drift where Kimi emits a generic function call instead of the special $web_search token; misconfigured tool payload rewriting by a gateway; new Kimi model versions renaming the internal search tool.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/83520f1896051324. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/client/provider_native_search/kimi.rs:105

            .get("message")
            .and_then(Value::as_object)
            .context("Kimi web search response omitted assistant message")?;
        if choice.get("finish_reason").and_then(Value::as_str) != Some("tool_calls") {
            return Ok(parse_final_message(message));
        }

        messages.push(Value::Object(message.clone()));
        let tool_calls = message
            .get("tool_calls")
            .and_then(Value::as_array)
            .context("Kimi returned tool_calls finish reason without tool calls")?;
        if tool_calls.is_empty() {
            bail!("Kimi returned an empty native web-search tool call list");
        }
        reserve_native_search_tool_calls(&mut tool_calls_executed, tool_calls.len())?;
        for tool_call in tool_calls {
            if tool_call.pointer("/function/name").and_then(Value::as_str) != Some("$web_search") {
                bail!("Kimi native search requested an unexpected tool");
            }
            let id = tool_call
                .get("id")
                .and_then(Value::as_str)
                .context("Kimi native web-search call omitted id")?;
            let arguments = tool_call
                .pointer("/function/arguments")
                .and_then(Value::as_str)
                .context("Kimi native web-search call omitted arguments")?;
            let _: Value = serde_json::from_str(arguments)
                .context("Kimi native web-search arguments were not valid JSON")?;
            messages.push(json!({
                "role": "tool",
                "tool_call_id": id,
                "name": "$web_search",
                "content": arguments,
            }));
        }

View on GitHub (pinned to 73e0f67d83)