{"record":{"id":"11327ee360339d77","repo":"Hmbown/CodeWhale","slug":"kimi-web-search-formula-returned-an-unexpected-tool","errorCode":null,"errorMessage":"Kimi web-search Formula returned an unexpected tool declaration","messagePattern":"Kimi web-search Formula returned an unexpected tool declaration","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/client/provider_native_search/kimi.rs","lineNumber":238,"sourceCode":"        bail!(\n            \"Kimi native web search exceeded the {MAX_NATIVE_SEARCH_TOOL_CALLS}-call safety limit\"\n        );\n    }\n    *executed = total;\n    Ok(())\n}\n\nfn formula_web_search_tools(payload: &Value) -> Result<Value> {\n    let tools = payload\n        .get(\"tools\")\n        .and_then(Value::as_array)\n        .context(\"Kimi web-search Formula omitted tools\")?;\n    if tools.len() != 1\n        || tools[0].get(\"type\").and_then(Value::as_str) != Some(\"function\")\n        || tools[0].pointer(\"/function/name\").and_then(Value::as_str)\n            != Some(WEB_SEARCH_FORMULA_FUNCTION)\n    {\n        bail!(\"Kimi web-search Formula returned an unexpected tool declaration\");\n    }\n    Ok(Value::Array(tools.clone()))\n}\n\nfn formula_fiber_result(payload: &Value) -> Result<&str> {\n    if payload.get(\"status\").and_then(Value::as_str) != Some(\"succeeded\") {\n        bail!(\"Kimi web-search Formula fiber did not succeed\");\n    }\n    payload\n        .pointer(\"/context/output\")\n        .or_else(|| payload.pointer(\"/context/encrypted_output\"))\n        .and_then(Value::as_str)\n        .map(str::trim)\n        .filter(|result| !result.is_empty())\n        .context(\"Kimi web-search Formula fiber omitted its result\")\n}\n\nfn builtin_search_tools() -> Value {","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/client/provider_native_search/kimi.rs#L220-L256","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nif tools.len() != 1 || tools[0].get(\"type\")... != Some(WEB_SEARCH_FORMULA_FUNCTION) {\n    bail!(\"Kimi web-search Formula returned an unexpected tool declaration\");\n}\n// after: find the known tool instead of requiring exactly one\nlet found = tools.iter().any(|t| {\n    t.get(\"type\").and_then(Value::as_str) == Some(\"function\")\n        && t.pointer(\"/function/name\").and_then(Value::as_str) == Some(WEB_SEARCH_FORMULA_FUNCTION)\n});\nif !found {\n    bail!(\"Kimi web-search Formula returned an unexpected tool declaration\");\n}","handlingStrategy":"validation","validationCode":"// client-side check mirroring the contract before sending\nlet tools = body[\"tools\"].as_array()?;\nassert_eq!(tools.len(), 1);\nassert_eq!(tools[0][\"type\"], \"function\");\nassert_eq!(tools[0][\"function\"][\"name\"], WEB_SEARCH_FORMULA_FUNCTION);","typeGuard":"fn formula_tools_ok(tools: &Value) -> bool {\n    tools.as_array().map_or(false, |t| t.len() == 1\n        && t[0].get(\"type\").and_then(Value::as_str) == Some(\"function\")\n        && t[0].pointer(\"/function/name\").and_then(Value::as_str) == Some(WEB_SEARCH_FORMULA_FUNCTION))\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["kimi","formula","schema-validation","tool-declaration"],"backgroundTag":"schema-validation-failed","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"}