[BUG] $web_search tool-result echo always fails with 400 tokenization failed (identical request works on kimi-k2.6)

Summary

Following the documented web-search flow ( Use Kimi API's Internet Search Functionality - Kimi API Platform ) — declare the $web_search builtin_function tool, then echo the tool call’s arguments back verbatim in a role: "tool" message — always fails on kimi-k3 with:

HTTP 400 {"error":{"message":"Invalid request: tokenization failed","type":"invalid_request_error"}}

The byte-identical request works correctly on kimi-k2.6: the search results are injected server-side (round-2 prompt_tokens jumps from ~60 to ~8,700) and the model answers with live web data. On kimi-k3 the second request never succeeds, so web search is unusable on k3.

Note: the docs specifically recommend kimi-k3 for web search because of its 1M context window.

Reproduction (Node 18+, no dependencies)

const KEY = process.env.MOONSHOT_API_KEY;
const TOOLS = [{ type: "builtin_function", function: { name: "$web_search" } }];

async function call(model, messages) {
  const res = await fetch("https://api.moonshot.ai/v1/chat/completions", {
    method: "POST",
    headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ model, messages, max_tokens: 8192, tools: TOOLS }),
  });
  return { status: res.status, body: await res.json() };
}

for (const model of ["kimi-k2.6", "kimi-k3"]) {
  const userMsg = { role: "user", content: "What is today's date and the weather in Camden, NJ? Use web search." };
  const r1 = await call(model, [userMsg]);
  const msg = r1.body.choices[0].message;          // finish_reason: "tool_calls"
  const tc = msg.tool_calls[0];                    // type: "builtin_function", args: {"search_result":{"search_id":...},"usage":{...}}
  const r2 = await call(model, [
    userMsg,
    { role: "assistant", content: msg.content ?? "", tool_calls: msg.tool_calls },
    { role: "tool", tool_call_id: tc.id, name: tc.function.name, content: tc.function.arguments },
  ]);
  console.log(model, "round2:", r2.status, r2.body?.error?.message ?? r2.body.usage);
}

Observed output (2026-07-23):

kimi-k2.6 round2: 200 { prompt_tokens: 8695, completion_tokens: 2017, ... }   <- search results injected, correct live answer
kimi-k3   round2: 400 Invalid request: tokenization failed

What was ruled out

All of the following round-2 variants on kimi-k3 also return 400 tokenization failed:

  • assistant message with content: "" vs. content field omitted entirely

  • tool message with and without the name field

  • tool message content = full arguments string vs. only the search_result object vs. empty string

  • index added to the echoed tool_call

  • tools array omitted from the round-2 request

The only variant that returns 200 is rewriting the echoed tool_call’s type: "builtin_function" to type: "function" — but then the search results are not injected (round-2 prompt_tokens stays ~250 and the model reports the tool result is empty), so it is not a workaround.

Round-1 always succeeds on both models (tool call emitted, search executed and billed — arguments.usage.total_tokens reports ~2,700–8,600 tokens).

Environment

  • Endpoint: https://api.moonshot.ai/v1/chat/completions

  • Models: kimi-k3 (fails), kimi-k2.6 (works)

  • Date observed: 2026-07-23, reproduced consistently across ~8 attempts and 3 different queries

  • Failure is immediate (~1s), suggesting request validation/tokenization of the echoed builtin_function history rather than a search-expansion timeout