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

  1. Compare the returned tools array against the request's declared tools to spot gateway mutation
  2. Confirm the target endpoint/model actually supports the Formula web-search contract
  3. Update the validation (and WEB_SEARCH_FORMULA_FUNCTION) if the upstream schema changed
  4. 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

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


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)