{"record":{"id":"83520f1896051324","repo":"Hmbown/CodeWhale","slug":"kimi-native-search-requested-an-unexpected-tool","errorCode":null,"errorMessage":"Kimi native search requested an unexpected tool","messagePattern":"Kimi native search requested an unexpected tool","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/client/provider_native_search/kimi.rs","lineNumber":105,"sourceCode":"            .get(\"message\")\n            .and_then(Value::as_object)\n            .context(\"Kimi web search response omitted assistant message\")?;\n        if choice.get(\"finish_reason\").and_then(Value::as_str) != Some(\"tool_calls\") {\n            return Ok(parse_final_message(message));\n        }\n\n        messages.push(Value::Object(message.clone()));\n        let tool_calls = message\n            .get(\"tool_calls\")\n            .and_then(Value::as_array)\n            .context(\"Kimi returned tool_calls finish reason without tool calls\")?;\n        if tool_calls.is_empty() {\n            bail!(\"Kimi returned an empty native web-search tool call list\");\n        }\n        reserve_native_search_tool_calls(&mut tool_calls_executed, tool_calls.len())?;\n        for tool_call in tool_calls {\n            if tool_call.pointer(\"/function/name\").and_then(Value::as_str) != Some(\"$web_search\") {\n                bail!(\"Kimi native search requested an unexpected tool\");\n            }\n            let id = tool_call\n                .get(\"id\")\n                .and_then(Value::as_str)\n                .context(\"Kimi native web-search call omitted id\")?;\n            let arguments = tool_call\n                .pointer(\"/function/arguments\")\n                .and_then(Value::as_str)\n                .context(\"Kimi native web-search call omitted arguments\")?;\n            let _: Value = serde_json::from_str(arguments)\n                .context(\"Kimi native web-search arguments were not valid JSON\")?;\n            messages.push(json!({\n                \"role\": \"tool\",\n                \"tool_call_id\": id,\n                \"name\": \"$web_search\",\n                \"content\": arguments,\n            }));\n        }","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/client/provider_native_search/kimi.rs#L87-L123","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nif tool_call.pointer(\"/function/name\").and_then(Value::as_str) != Some(\"$web_search\") {\n    bail!(\"Kimi native search requested an unexpected tool\");\n}\n// after\nlet name = tool_call.pointer(\"/function/name\").and_then(Value::as_str).unwrap_or_default();\nif name != \"$web_search\" {\n    tracing::warn!(name, \"skipping unexpected Kimi native search tool\");\n    continue;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn is_web_search_call(tc: &Value) -> bool {\n    tc.pointer(\"/function/name\").and_then(Value::as_str) == Some(\"$web_search\")\n}","tryCatchPattern":"do {\n    let resp = provider.search(req).await?;\n    // process resp\n} catch |err| if err.to_string().contains(\"unexpected tool\") {\n    eprintln!(\"Kimi requested a non-$web_search tool; retrying without native search\");\n    fallback_search(req).await\n}","preventionTips":["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"],"tags":["kimi","web-search","tool-calls","contract-violation"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}