> ## Documentation Index
> Fetch the complete documentation index at: https://platform.kimi.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Use Official Tools in Kimi API

Kimi Open Platform offers a set of official tools that you can **freely** integrate into your own applications (official tools are currently free for a limited time; when the tool load reaches capacity limits, temporary rate limiting measures may be applied). This page lists the available official tools and shows how to call and execute them through the Kimi API.

<Warning>
  The web search (`web_search`) is currently being updated. We do not recommend using this functionality in the near term. This documentation is outdated; please follow subsequent content updates.
</Warning>

## Choose an official tool to use

The following table lists the currently available official tools:

| Tool Name       | Tool Description                                                                                                                  |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `convert`       | Unit conversion tool, supporting length, mass, volume, temperature, area, time, energy, pressure, speed, and currency conversions |
| `web-search`    | Real-time information and internet search tool. For pricing and availability details, see [Web Search Price](/docs/pricing/tools)      |
| `rethink`       | Intelligent reasoning tool                                                                                                        |
| `random-choice` | Random selection tool                                                                                                             |
| `mew`           | Random cat meowing and blessing tool                                                                                              |
| `memory`        | Memory storage and retrieval system tool, supporting persistent storage of conversation history and user preferences              |
| `excel`         | Excel and CSV file analysis tool                                                                                                  |
| `date`          | Date and time processing tool                                                                                                     |
| `base64`        | Base64 encoding and decoding tool                                                                                                 |
| `fetch`         | URL content extraction Markdown formatting tool                                                                                   |
| `quickjs`       | Quick JS engine security execution JavaScript code tool                                                                           |
| `code_runner`   | Python code execution tool                                                                                                        |

## Full example: call the `web_search` official tool

The following Python example uses the `web_search` official tool to show how to call official tools through the Kimi API. You can also interactively experience the capabilities of Kimi models and tools in the [Kimi Development Workbench](https://platform.kimi.ai/playground).

The example defaults to `moonshot/web-search:latest`; you can also add any of the following formula URIs to the example to try other official tools: `moonshot/convert:latest`, `moonshot/web-search:latest`, `moonshot/rethink:latest`, `moonshot/random-choice:latest`, `moonshot/mew:latest`, `moonshot/memory:latest`, `moonshot/excel:latest`, `moonshot/date:latest`, `moonshot/base64:latest`, `moonshot/fetch:latest`, `moonshot/quickjs:latest`, `moonshot/code_runner:latest`

<Note>
  The examples on this page use the latest model `kimi-k3` by default. K3 configures reasoning effort with the top-level `reasoning_effort` request field (supports `"low"` / `"high"` / `"max"`, default `"max"`). To use another model such as `kimi-k2.6` or `kimi-k2.5`, just replace the `model` field — parameter configurations differ across models. See the [Model Parameter Reference](/docs/api/models-overview).
</Note>

```python theme={null}
# Formula Chat Client - OpenAI chat with official tools
# Uses MOONSHOT_BASE_URL and MOONSHOT_API_KEY for OpenAI client

import os
import json
import asyncio
import argparse
import httpx
from openai import AsyncOpenAI


class FormulaChatClient:
    def __init__(self, moonshot_base_url: str, api_key: str):
        self.openai = AsyncOpenAI(base_url=moonshot_base_url, api_key=api_key)
        self.httpx = httpx.AsyncClient(
            base_url=moonshot_base_url,
            headers={"Authorization": f"Bearer {api_key}"},
            timeout=30.0,
        )
        self.model = "kimi-k3"

    async def get_tools(self, formula_uri: str):
        response = await self.httpx.get(f"/formulas/{formula_uri}/tools")
        return response.json().get("tools", [])

    async def call_tool(self, formula_uri: str, function: str, args: dict):
        response = await self.httpx.post(
            f"/formulas/{formula_uri}/fibers",
            json={"name": function, "arguments": json.dumps(args)},
        )
        fiber = response.json()

        if fiber.get("status", "") == "succeeded":
            return fiber["context"].get("output") or fiber["context"].get(
                "encrypted_output"
            )

        if "error" in fiber:
            return f"Error: {fiber['error']}"
        if "error" in fiber.get("context", {}):
            return f"Error: {fiber['context']['error']}"
        if "output" in fiber.get("context", {}):
            return f"Error: {fiber['context']['output']}"
        return "Error: Unknown error"

    async def handle_response(self, response, messages, all_tools, tool_to_uri):
        message = response.choices[0].message
        messages.append(message)
        if not message.tool_calls:
            print(f"\nAI Response: {message.content}")
            return

        print(f"\nAI decided to use {len(message.tool_calls)} tool(s):")

        for call in message.tool_calls:
            func_name = call.function.name
            args = json.loads(call.function.arguments)

            print(f"\nCalling tool: {func_name}")
            print(f"Arguments: {json.dumps(args, ensure_ascii=False, indent=2)}")

            uri = tool_to_uri.get(func_name)
            if not uri:
                raise ValueError(f"No URI found for tool {func_name}")

            result = await self.call_tool(uri, func_name, args)
            if len(result) > 100:
                print(f"Tool result: {result[:100]}...")  # limit the output length
            else:
                print(f"Tool result: {result}")

            messages.append(
                {"role": "tool", "tool_call_id": call.id, "content": result}
            )

        next_response = await self.openai.chat.completions.create(
            model=self.model, messages=messages, tools=all_tools
        )
        await self.handle_response(next_response, messages, all_tools, tool_to_uri)

    async def chat(self, question, messages, all_tools, tool_to_uri):
        messages.append({"role": "user", "content": question})
        response = await self.openai.chat.completions.create(
            model=self.model, messages=messages, tools=all_tools
        )
        await self.handle_response(response, messages, all_tools, tool_to_uri)

    async def close(self):
        await self.httpx.aclose()


def normalize_formula_uri(uri: str) -> str:
    """Normalize formula URI with default namespace and tag"""
    if "/" not in uri:
        uri = f"moonshot/{uri}"
    if ":" not in uri:
        uri = f"{uri}:latest"
    return uri


async def main():
    parser = argparse.ArgumentParser(description="Chat with formula tools")
    parser.add_argument(
        "--formula",
        action="append",
        default=["moonshot/web-search:latest"],
        help="Formula URIs",
    )
    parser.add_argument("--question", help="Question to ask")

    args = parser.parse_args()

    # Process and deduplicate formula URIs
    raw_formulas = args.formula or ["moonshot/web-search:latest"]
    normalized_formulas = [normalize_formula_uri(uri) for uri in raw_formulas]
    unique_formulas = list(
        dict.fromkeys(normalized_formulas)
    )  # Preserve order while deduping

    print(f"Initialized formulas: {unique_formulas}")

    moonshot_base_url = os.getenv("MOONSHOT_BASE_URL", "https://api.moonshot.ai/v1")
    api_key = os.getenv("MOONSHOT_API_KEY")


    if not api_key:
        print("MOONSHOT_API_KEY required")
        return

    client = FormulaChatClient(moonshot_base_url, api_key)

    # Load and validate tools
    print("\nLoading tools from all formulas...")
    all_tools = []
    function_names = set()
    tool_to_uri = {}  # inverted index to the tool name

    for uri in unique_formulas:
        tools = await client.get_tools(uri)
        print(f"\nTools from {uri}:")

        for tool in tools:
            func = tool.get("function", None)
            if not func:
                print(f"Skipping tool using type: {tool.get('type', 'unknown')}")
                continue
            func_name = func.get("name")
            assert func_name, f"Tool missing name: {tool}"
            assert (
                func_name not in tool_to_uri
            ), f"ERROR: Tool '{func_name}' conflicts between {tool_to_uri.get(func_name)} and {uri}"

            if func_name in function_names:
                print(
                    f"ERROR: Duplicate function name '{func_name}' found across formulas"
                )
                print(f"Function {func_name} already exists in another formula")
                await client.close()
                return

            function_names.add(func_name)
            all_tools.append(tool)
            tool_to_uri[func_name] = uri
            print(f"  - {func_name}: {func.get('description', 'N/A')}")

    print(f"\nTotal unique tools loaded: {len(all_tools)}")
    if not all_tools:
        print("Warning: No tools found in any formula")
        return

    try:
        messages = [
            {
                "role": "system",
                "content": "You are Kimi, an AI assistant provided by Moonshot AI. You are proficient in Chinese and English conversations. You provide users with safe, helpful, and accurate answers. You will reject any questions involving terrorism, racism, or explicit content. Moonshot AI is a proper noun and should not be translated.",
            }
        ]
        if args.question:
            print(f"\nUser: {args.question}")
            await client.chat(args.question, messages, all_tools, tool_to_uri)
        else:
            print("Chat mode (type 'q' to quit)")
            while True:
                question = input("\nQ: ").strip()
                if question.lower() == "q":
                    break
                if question:
                    await client.chat(question, messages, all_tools, tool_to_uri)

    finally:
        await client.close()


if __name__ == "__main__":
    asyncio.run(main())
```

## Understand the Formula concept

Before calling official tools, you need to understand Formula: it is a lightweight script engine collection that transforms Python scripts into "instant computing power that can be triggered by AI with one click" — developers only need to focus on writing code, while the platform handles startup, scheduling, isolation, billing, and recycling.

Formulas are called through semantic URIs (such as `moonshot/web-search:latest`). Each formula contains a declaration (telling the AI what it can do) and an implementation (Python code), and the platform automatically handles all underlying details (startup, isolation, recycling, etc.), making tools easy to share and reuse in the community. You can experience and debug these tools in Kimi Playground, or call them through the API in your applications.

## Call a Formula directly to run a tool

A formula URI generally consists of 3 parts, for example `moonshot/web-search:latest`: `web-search` is its `name`; the namespace currently only supports `moonshot`; and `latest` is the default tag.

For example, to call web search, you can send an HTTP request like this:

```bash theme={null}
export FORMULA_URI="moonshot/web-search:latest"
export MOONSHOT_BASE_URL="https://api.moonshot.ai/v1"

curl -X POST ${MOONSHOT_BASE_URL}/formulas/${FORMULA_URI}/fibers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MOONSHOT_API_KEY" \
-d '{
  "name": "web_search",
  "arguments": "{\"query\": \"Please look up the latest news about Moonshot AI.\"}"
}'
```

`web-search` was set as protected when created, so its result appears in the `context.encrypted_output` field, in a format similar to `----MOONSHOT ENCRYPTED BEGIN----... ----MOONSHOT ENCRYPTED END----`; this content can be passed directly into the tool call.

## Integrate official tools with Chat Completions

As shown in [Is 3214567 a prime number? An example of Tool Calls](/docs/api/tool-use), when using official tools with Chat Completions, there are several key points you need to align between the Formula API and the model.

### Fetch the tool definition and append it to the `tools` field

Given a formula URI (for example `moonshot/web-search:latest`), append it directly to the URL to request the tool declarations:

```bash theme={null}
curl ${MOONSHOT_BASE_URL}/formulas/${FORMULA_URI}/tools \
    -H "Authorization: Bearer $MOONSHOT_API_KEY"
```

Sample output:

```json theme={null}
{
  "object": "list",
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "web_search",
        "description": "Search the web for information",
        "parameters": {
          "type": "object",
          "properties": {
            "query": {
              "description": "What to search for",
              "type": "string"
            }
          },
          "required": [ "query" ]
        }
      }
    }
  ]
}
```

Take the `tools` field from the response (always an array of dicts) and append it to your request's `tools` list — the platform guarantees this list is API-compatible.

Note that:

* If `type=function`, you need to ensure `function.name` is unique within a single API request, otherwise the chat completion request will be considered invalid and immediately returned with a 400 error (`invalid_request_error`, with a message like `function name get_weather is duplicated`);
* If you use multiple formulas at the same time, you need to maintain your own `function.name` -> `formula_uri` mapping for future reference.

### Handle the tool call returned by the model

If the chat completion returns `finish_reason=tool_calls`, the model has triggered a tool call, and the response looks like this:

```json theme={null}
{
  "id": "chatcmpl-1234567890",
  "object": "chat.completion",
  "choices": [
    {
      "message": {
        "role": "assistant",
        "tool_calls": [
          {
            "id": "web_search:0",
            "type": "function",
            "function": {
              "name": "web_search",
              "arguments": "{\"query\": \"What is the RGB value of sky blue?\" }"
            }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ]
}
```

From `choices[0].message.tool_calls[0].function.name` you can tell that `web_search` needs to be called, and the `formula_uri` corresponding to `web_search` is `moonshot/web-search:latest`. Copy `choices[0].message.tool_calls[0].function` from the response in full as the body, and send a request to `${MOONSHOT_BASE_URL}/formulas/${FORMULA_URI}/fibers`.

Note that although the `function.arguments` output by the model is valid JSON in content, it is still an encoded string in format — you don't need to escape it; just use it directly as the body of the call.

### Handle the Fiber result and continue the conversation

A Fiber is a "process snapshot" of a specific execution, containing logs, Tracing, and resource usage, which is convenient for debugging and auditing. The `status` of the POST result may be `succeeded` or various types of errors; when it succeeds, the result looks like this:

```json theme={null}
{
  "id": "fiber-f43p7sby7ny111houyq1",
  "object": "fiber",
  "created_at": 1753440997,
  "lambda_id": "lambda-f3w8y6qcoqgi11h8q7ui",
  "status": "succeeded",
  "context": {
    "input": "{\"name\":\"web_search\",\"arguments\":\"{\\\"query\\\": \\\"What is the RGB value of sky blue?\\\" }\"}",
    "encrypted_output": "----MOONSHOT ENCRYPTED BEGIN----+nf6...DSM=----MOONSHOT ENCRYPTED END----"
  },
  "formula": "moonshot/web-search:latest",
  "organization_id": "staff",
  "project_id": "proj-88a5894a985646b5902b70909748ba16"
}
```

Search tools may return `encrypted_output`, while in general the result is `output` — this output is your input for the next round. When continuing the request, arrange the messages as follows:

```javascript theme={null}
messages = [
  /* other messages */
  { /* the return content of the previous round of the model */
    "role": "assistant",
    "tool_calls": [
      {
        "id": "web_search:0",
        "type": "function",
        "function": {
          "name": "web_search",
          "arguments": "{\"query\": \"What is the RGB value of sky blue?\" }"
        }
      }
    ]
  },
  { /* the information you need to supplement */
    "role": "tool",
    "tool_call_id": "web_search:0",  /* note that the id here needs to be aligned with the id in the previous tool_calls[] */
    "content": "----MOONSHOT ENCRYPTED BEGIN----+nf6...DSM=----MOONSHOT ENCRYPTED END----"
  }
]
```

The model can then continue with further reasoning.

## Notes

* The model may return more than one `tool_calls`; you must return results for all `tool_calls` for the model to continue, otherwise the request will be considered invalid and rejected;
* If the assistant message has `tool_calls`, the next messages must be exactly the same `role=tool` messages as the `tool_calls`, and `tool_call_id` must be aligned one-to-one with the previous `tool_calls.id`:
  * If there are multiple `tool_calls`, the order is not sensitive;
  * The ids of the `tool_calls` output by the model are always unique, and the ids in the `role=tool` messages must also be aligned with them;
  * The uniqueness requirement is only local to the `tool_calls`-response in this round, not for the entire conversation or globally.
