Hmbown/CodeWhale · error
Kimi web-search Formula returned an unexpected tool…
Error message
Kimi web-search Formula returned an unexpected tool declaration
What it means
Before running the Kimi Formula flow, the client sends a tools declaration and then echoes back the tools returned in the response, validating there is exactly one tool, of type 'function', named WEB_SEARCH_FORMULA_FUNCTION. Any other shape — zero, multiple, or differently named/typed tools — violates the single-tool Formula contract, so it bails.
Solutions
- Compare the returned tools array against the request's declared tools to spot gateway mutation
- Confirm the target endpoint/model actually supports the Formula web-search contract
- Update the validation (and WEB_SEARCH_FORMULA_FUNCTION) if the upstream schema changed
- Fall back to the built-in native search path if Formula is unavailable
Example fix
// before
if tools.len() != 1 || tools[0].get("type")... != Some(WEB_SEARCH_FORMULA_FUNCTION) {
bail!("Kimi web-search Formula returned an unexpected tool declaration");
}
// after: find the known tool instead of requiring exactly one
let found = tools.iter().any(|t| {
t.get("type").and_then(Value::as_str) == Some("function")
&& t.pointer("/function/name").and_then(Value::as_str) == Some(WEB_SEARCH_FORMULA_FUNCTION)
});
if !found {
bail!("Kimi web-search Formula returned an unexpected tool declaration");
} Defensive patterns
Strategy: validation
Validate before calling
// client-side check mirroring the contract before sending let tools = body["tools"].as_array()?; assert_eq!(tools.len(), 1); assert_eq!(tools[0]["type"], "function"); assert_eq!(tools[0]["function"]["name"], WEB_SEARCH_FORMULA_FUNCTION);
Type guard
fn formula_tools_ok(tools: &Value) -> bool {
tools.as_array().map_or(false, |t| t.len() == 1
&& t[0].get("type").and_then(Value::as_str) == Some("function")
&& t[0].pointer("/function/name").and_then(Value::as_str) == Some(WEB_SEARCH_FORMULA_FUNCTION))
} Prevention
- Use endpoints known to echo the Formula contract exactly
- Diff returned tools against declared tools to catch gateway mutation
- Keep Formula routing to the dedicated Formula-capable endpoint/model
When it happens
Trigger: formula_web_search_tools inspects the response 'tools' array and it either has length != 1, the first tool's type is not 'function', or its /function/name is not WEB_SEARCH_FORMULA_FUNCTION (the earlier .context covers a wholly missing tools field).
Common situations: Kimi API response schema change; a gateway injecting or reordering tools; sending the Formula request to an endpoint that echoes the client's tool catalog differently; non-Formula model on the Formula path.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Kimi Formula web search exceeded the bounded tool-call loop
- Kimi Formula web search requested an unexpected tool
- Kimi web-search Formula fiber did not succeed
- Codewhale metadata event was not a terminal receipt
- Invalid pet world checkpoint.
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/11327ee360339d77.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/client/provider_native_search/kimi.rs:238
bail!(
"Kimi native web search exceeded the {MAX_NATIVE_SEARCH_TOOL_CALLS}-call safety limit"
);
}
*executed = total;
Ok(())
}
fn formula_web_search_tools(payload: &Value) -> Result<Value> {
let tools = payload
.get("tools")
.and_then(Value::as_array)
.context("Kimi web-search Formula omitted tools")?;
if tools.len() != 1
|| tools[0].get("type").and_then(Value::as_str) != Some("function")
|| tools[0].pointer("/function/name").and_then(Value::as_str)
!= Some(WEB_SEARCH_FORMULA_FUNCTION)
{
bail!("Kimi web-search Formula returned an unexpected tool declaration");
}
Ok(Value::Array(tools.clone()))
}
fn formula_fiber_result(payload: &Value) -> Result<&str> {
if payload.get("status").and_then(Value::as_str) != Some("succeeded") {
bail!("Kimi web-search Formula fiber did not succeed");
}
payload
.pointer("/context/output")
.or_else(|| payload.pointer("/context/encrypted_output"))
.and_then(Value::as_str)
.map(str::trim)
.filter(|result| !result.is_empty())
.context("Kimi web-search Formula fiber omitted its result")
}
fn builtin_search_tools() -> Value {View on GitHub (pinned to 73e0f67d83)