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
- Confirm the request enables only the $web_search native search tool and no user-defined tools are leaked into the search request
- Log the offending function name from the response for diagnosis
- Update the provider adapter if Kimi renamed the tool; accept a new name in the check
- 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
- Do not inject user-defined tools into native-search requests
- Track Kimi API changelogs for tool-name changes
- Log the offending function name before failing
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
- Kimi Formula web search requested an unexpected tool
- Kimi Formula web search exceeded the bounded tool-call loop
- Kimi native web search exceeded the bounded tool-call loop
- Kimi native web search exceeded the
- Kimi returned an empty native web-search tool call list
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)