Kimi API tool calling lets a model select a function and return its arguments as JSON. Your application—not the model—must validate those arguments, run the function, attach the result to the conversation and ask Kimi for the final answer.
We tested that selection step with kimi-k3 on eight frozen synthetic cases. K3 returned the exact expected tool and arguments in 8/8 single-pass calls. The test used no real inventory, payment, calendar, file or currency system, so it does not prove that a production integration is safe or reliable.
Tested versus documented: The 8/8 result below is independently observed. The multi-turn implementation flow, schema rules and message ordering are checked against Kimi’s current official documentation. We did not run a new API call for this guide, and the downloadable examples were tested offline only.
The short implementation pattern
- Describe each permitted function in the request’s
toolsarray using the documented JSON Schema structure. - Send the user message and tools to
POST /v1/chat/completions. - If the response has
finish_reason: "tool_calls", keep the complete assistant message. - Parse every
function.argumentsstring as JSON, then validate the tool name, fields, types and values before execution. - Execute only an allowlisted server-side handler with the minimum required permission.
- Add one
role: "tool"message per call, preserving the matchingtool_call_id. - Send the expanded message history back to Kimi and stop only when the model returns a normal answer or your application reaches a strict round limit.
Kimi’s official tool-calling guide documents this six-stage model-to-application loop. Its Tool Use reference describes the accepted function structure and schema constraints.
What tool calling does—and does not do
| Stage | Kimi’s role | Your application’s role |
|---|---|---|
| Tool definition | Reads names, descriptions and parameter schemas | Exposes only approved tools with narrow schemas |
| Tool selection | Chooses zero, one or multiple tools | Decides whether the choice is allowed |
| Arguments | Returns serialized JSON in function.arguments | Parses and validates the JSON as untrusted input |
| Execution | Does not run your local or third-party function | Runs the handler with authorization, timeouts and audit controls |
| Tool result | Reads the returned result | Matches it to the original tool_call_id |
| Final response | Produces the user-facing answer | Enforces loop, cost and side-effect limits |
This page covers developer-defined function tools. Kimi’s hosted Formula tools, such as official web search, have a separate discovery, execution and billing flow described in the official tools guide.
Define a narrow Kimi tool
This is one of the exact synthetic schemas used in our pilot:
{
"type": "function",
"function": {
"name": "calculate_shipping",
"description": "Calculate synthetic shipping for a postal code, package weight and priority flag.",
"parameters": {
"type": "object",
"properties": {
"postal_code": { "type": "string" },
"weight_kg": { "type": "number" },
"priority": { "type": "boolean" }
},
"required": ["postal_code", "weight_kg", "priority"],
"additionalProperties": false
}
}
}
The current Tool Use reference says function.strict defaults to true when omitted and requires a supported MFJS/JSON Schema subset. Our frozen definitions omitted the explicit strict field, so they relied on that documented default. We still checked the returned arguments independently rather than treating schema enforcement as an application security boundary.
Good tool definitions are deliberately small:
- use a unique, descriptive function name;
- state when the tool should be selected;
- require every field needed for safe execution;
- use exact data types and enums or patterns where they materially reduce ambiguity;
- reject unexpected fields with
additionalProperties: falsewhere supported; and - split read and write operations so a low-risk lookup cannot silently become a mutation.
Python: validate before dispatch
Kimi documents OpenAI-compatible Chat Completions and the SDK base URL https://api.moonshot.ai/v1. The full downloadable Python example requires an explicit --live flag, caps tool rounds and dispatches only synthetic local handlers.
The critical section is the trust boundary:
choice = response.choices[0]
if choice.finish_reason == "tool_calls":
messages.append(choice.message.model_dump(exclude_none=True))
for tool_call in choice.message.tool_calls:
name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
validate_arguments(name, arguments, tool_schemas)
result = SYNTHETIC_HANDLERS[name](arguments)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": name,
"content": json.dumps(result),
})
Never index a handler map with an unchecked model-supplied name. validate_arguments in the kit confirms the allowlist, required fields, extra fields, types, patterns and minimum values before the handler is called.
Node.js: preserve the assistant message
The Node.js example uses the same control flow with the raw HTTP endpoint. It deliberately pushes the complete assistant message before any tool result:
const choice = completion.choices[0];
if (choice.finish_reason === "tool_calls") {
messages.push(choice.message);
for (const toolCall of choice.message.tool_calls) {
const name = toolCall.function.name;
const args = JSON.parse(toolCall.function.arguments);
validateArguments(name, args, toolDefinitions);
const result = executeSyntheticTool(name, args);
messages.push({
role: "tool",
tool_call_id: toolCall.id,
name,
content: JSON.stringify(result),
});
}
}
Kimi’s guide specifically warns that omitting the returned assistant message can produce tool_call_id not found. It also requires every returned call to have a matching role: "tool" message with the correct ID.
Our K3 tool-selection result
We froze six non-operative function definitions and eight prompts before the first paid response. Each case was a separate streamed request to the official Global Kimi Open Platform using kimi-k3, reasoning_effort: high, a 2,048-token completion cap, concurrency one and zero corrective retries.
| Case | Prompt boundary | Expected and observed tool | Exact arguments | Result |
|---|---|---|---|---|
| TC01 | Inventory lookup | inventory_lookup | {"sku":"ORB-17"} | Pass |
| TC02 | Standard shipping | calculate_shipping | {"postal_code":"02139","weight_kg":2.5,"priority":false} | Pass |
| TC03 | Dated review | schedule_review | {"project_id":"ALTA-42","date":"2032-09-17"} | Pass |
| TC04 | Exact filename | find_document | {"filename":"field-notes-v3.md"} | Pass |
| TC05 | Integer quantity | add_line_item | {"sku":"NOVA-8","quantity":4} | Pass |
| TC06 | Currency fields | convert_currency | {"amount":125.5,"currency_from":"USD","currency_to":"EUR"} | Pass |
| TC07 | Case-sensitive SKU | inventory_lookup | {"sku":"teal-9x"} | Pass |
| TC08 | Priority and UK postcode | calculate_shipping | {"postal_code":"SW1A 1AA","weight_kg":0.75,"priority":true} | Pass |
Every case met all six frozen checks: exactly one tool call, exact tool name, valid JSON, exact values, exact types and no extra arguments. Across the eight calls, the API reported 5,001 prompt tokens, 2,560 cached tokens and 915 completion tokens. The usage-derived cost assigned to this track was $0.021816 at the dated rates used by the parent K3 pilot.
This track was part of our broader Kimi K3 API pilot, not a new run for this article.
What 8/8 does not establish
The result is useful but narrow:
- each case ran once, so it does not measure repeatability or failure rate;
- all functions and data were synthetic and non-operative;
- prompts requested exactly one tool, so parallel or dependent multi-tool execution was not scored;
- the pilot scored selection and arguments, not a second-round final answer;
- no malformed tool result, timeout, rate-limit or adversarial prompt was injected;
- no production authorization, idempotency or rollback system was tested; and
- results from one model, account, region and date should not be generalized to every Kimi model or later release.
An 8/8 single pass supports the statement “K3 matched these eight frozen cases once.” It does not support “Kimi tool calling is 100% reliable.”
Streaming: assemble arguments by call index
Our pilot used streaming. The TC01 response delivered the tool name first and split {"sku":"ORB-17"} across several later deltas. Parsing each fragment as complete JSON would have failed.
Kimi’s streaming documentation says the initial tool-call chunk carries the ID and function name, later chunks carry argument fragments, and multiple calls are separated by index. A streaming implementation should therefore:
- maintain one accumulator per choice and tool-call index;
- concatenate argument fragments in arrival order;
- wait for the completed call before parsing its JSON;
- retain each tool-call ID and name; and
- never execute a partially assembled argument object.
The downloadable examples use non-streaming responses for a smaller, auditable implementation. The evidence sample shows the assembled output from the real streamed call.
Safe execution controls
Before a model-selected function can affect an external system, add controls outside the model:
- Allowlist: reject any unknown function name.
- Schema validation: reject missing, extra or incorrectly typed fields.
- Authorization: check the user’s permission for the exact resource and action.
- Confirmation: require explicit confirmation for payments, deletion, publishing or account changes.
- Idempotency: attach a stable operation key so retries cannot duplicate a write.
- Limits: cap rounds, calls, arguments, execution time and total API spend.
- Isolation: run parsers and code tools in a constrained environment.
- Audit: record request IDs and sanitized metadata, not secrets or unnecessary personal data.
- Failure handling: return a bounded error object to the model; do not retry a side effect blindly.
Treat model-generated arguments exactly like untrusted user input. A valid JSON object can still request an unauthorized or harmful action.
Download the reproducibility kit
Download the Kimi API Tool Calling Kit. It contains:
- the six frozen function definitions and eight test cases;
- complete Python and Node.js examples with explicit live-run gates;
- offline unit tests and an evidence validator;
- a sanitized TC01 request/response example;
- the derived 8/8 case ledger with usage, cost and timing fields; and
- a file manifest plus SHA-256 checksums.
Package SHA-256: 8F06B74DE14B29E618A500EC5FA86EA9E92C6A3245779CA2FCA6BEC76586A882 Package size: 19,453 bytes
The ZIP contains no API key, authorization header, raw account balance or private response headers. Running either live example can incur API cost; offline tests do not contact Kimi.
Run the offline checks
After extracting the package from its own root directory:
python -m unittest python/test_kimi_tool_calling.py
node --test node/test-kimi-tool-calling.mjs
node validator/validate-bundle.mjs
The Python and Node tests validate the frozen schemas, cases, allowlist and type rejection without network access. The bundle validator also rechecks the sanitized evidence, manifest and checksums.
Common implementation failures
| Symptom | Likely cause | Corrective check |
|---|---|---|
tool_call_id not found | The assistant tool-call message was omitted or an ID does not match | Append the complete assistant message, then one correctly matched tool result per call |
| JSON parse error while streaming | An argument fragment was parsed before all chunks arrived | Accumulate by tool-call index, then parse once complete |
| Unknown function executed | The model-supplied name was dispatched directly | Reject names outside a fixed server-side allowlist |
| Extra or wrong-type fields reach a handler | JSON syntax was checked but the schema was not | Validate required, additional and typed properties locally |
| Duplicate write after retry | A transient failure retriggered a side effect | Use idempotency keys and separate selection retries from execution retries |
| Endless tool loop | No application-level stop condition | Set a strict maximum number of model and tool rounds |
Pre-production checklist
- [ ] API key stored only in a server-side secret manager
- [ ] Tool names and JSON Schemas versioned and reviewed
- [ ] Model-selected names checked against an allowlist
- [ ] Arguments validated locally before dispatch
- [ ] User and resource authorization checked after validation
- [ ] Destructive or billable actions require confirmation
- [ ] Every tool call has a matching result and
tool_call_id - [ ] Round, time, concurrency and spend limits configured
- [ ] Side-effecting operations use idempotency and audit records
- [ ] Logs exclude API keys and unnecessary sensitive content
- [ ] Failure, timeout and malformed-result cases tested
- [ ] Repeatability measured before publishing a reliability claim
Verification record
- Official documentation rechecked: August 5, 2026
- Independent source run:
kimi-k3-pilot-20260805-v1.1 - Tool-selection calls: 8 completed, 0 failed, 0 retried
- Independently scored result: 8/8 exact single-pass cases
- New API calls for this guide: 0
- Downloadable examples: offline-tested; live end-to-end execution not run
- WordPress status: Published; no tags and no featured image
Corrections are welcome through Sources & Corrections. For endpoint setup and key handling, start with our Kimi API guide.
