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

# 使用 Kimi API 的联网搜索功能

<Note>
  在 `kimi-k3` 上使用联网搜索，推荐使用 Formula API 官方工具通道（OpenAI 协议，标准 `function` tool），详见[如何在 Kimi API 中使用官方工具](/docs/guide/use-official-tools)。
</Note>

`$web_search`（`builtin_function` 类型）是 Kimi 内置的联网搜索工具函数，基于工具调用 `tool_calls` 用法实现：模型只负责生成搜索参数，搜索本身由 Kimi 大模型定义并执行。当你不想自行实现搜索引擎调用、网页抓取与内容清洗时，声明这个内置工具即可获得开箱即用的联网搜索能力。

它的基本用法和流程与普通的工具调用 `tool_calls` 相同——定义工具、通过 `tools` 提交、模型生成参数、回传执行结果、模型给出回复，完整流程见[使用 Kimi API 完成工具调用](/docs/guide/use-kimi-api-to-complete-tool-calls)；本页只标注 `$web_search` 与普通 `function` 之间的差别。

## 声明 `$web_search`

与普通的 `tool` 不同，`$web_search` 不需要提供具体的参数说明，只需在 `tools` 中声明 `type` 和 `function.name` 即可成功注册：

```python theme={null}
tools = [
	{
		"type": "builtin_function",  # <-- 我们使用 builtin_function 来表示 Kimi 内置工具，也用于区分普通 function
		"function": {
			"name": "$web_search",
		},
	},
]
```

**`$web_search` 以美元符号 `$` 作为前缀，这是我们约定的表示 Kimi 内置函数的一种表达方式**（在普通的 `function` 定义中，不允许出现美元符号 `$`），后续如果有其他 Kimi 内置函数，也将以美元符号 `$` 作为前缀。

**`$web_search` 可直接配合模型推理行为使用**：`kimi-k3` 始终进行推理；`kimi-k2.6` 也可在思考开启状态下正常执行联网搜索。

`$web_search` 可以与其他普通的 `function` 共存：在同一个 `tools` 声明中，可以自由组合 `type=builtin_function` 和 `type=function` 的工具。

## 执行联网搜索

使用 `$web_search` 时，基本流程与普通的 `function` 并无区别，开发者甚至可以不用修改原先执行工具调用 `tool_calls` 的代码。以下示例演示完整流程：声明 `$web_search`、发起提问、循环处理 `tool_calls` 直到模型给出最终回复——其中 `search_impl` 只是把模型生成的参数原样返回：

<Note>
  本页示例默认使用最新模型 `kimi-k3`。K3 使用请求顶层 `reasoning_effort` 配置推理强度（支持 `"low"` / `"high"` / `"max"`，默认 `"max"`）。换用 `kimi-k2.6`、`kimi-k2.5` 等其他模型时，只需替换 `model` 字段，但各模型的参数配置存在差异，详见[模型参数参考](/docs/api/models-overview)。
</Note>

<Tabs>
  <Tab title="python">
    ```python theme={null}
    from typing import *

    import os
    import json

    from openai import OpenAI
    from openai.types.chat.chat_completion import Choice

    client = OpenAI(
        base_url="https://api.moonshot.cn/v1",
        api_key=os.environ.get("MOONSHOT_API_KEY"),
    )

    # search 工具的具体实现，这里我们只需要返回参数即可
    def search_impl(arguments: Dict[str, Any]) -> Any:
        """
        在使用 Moonshot AI 提供的 search 工具的场合，只需要原封不动返回 arguments 即可，
        不需要额外的处理逻辑。

        但如果你想使用其他模型，并保留联网搜索的功能，那你只需要修改这里的实现（例如调用搜索
        和获取网页内容等），函数签名不变，依然是 work 的。

        这最大程度保证了兼容性，允许你在不同的模型间切换，并且不需要对代码有破坏性的修改。
        """
        return arguments


    def chat(messages) -> Choice:
        completion = client.chat.completions.create(
            model="kimi-k3",
            messages=messages,
            max_tokens=32768,
            tools=[
                {
                    "type": "builtin_function",  # <-- 使用 builtin_function 声明 $web_search 函数，请在每次请求都完整地带上 tools 声明
                    "function": {
                        "name": "$web_search",
                    },
                }
            ]
        )
        return completion.choices[0]


    def main():
        messages = [
            {"role": "system", "content": "你是 Kimi。"},
        ]

        # 初始提问
        messages.append({
            "role": "user",
            "content": "请搜索 Moonshot AI Context Caching 技术，并告诉我它是什么。"
        })

        finish_reason = None
        while finish_reason is None or finish_reason == "tool_calls":
            choice = chat(messages)
            finish_reason = choice.finish_reason
            if finish_reason == "tool_calls":  # <-- 判断当前返回内容是否包含 tool_calls
                messages.append(choice.message)  # <-- 我们将 Kimi 大模型返回给我们的 assistant 消息也添加到上下文中，以便于下次请求时 Kimi 大模型能理解我们的诉求
                for tool_call in choice.message.tool_calls:  # <-- tool_calls 可能是多个，因此我们使用循环逐个执行
                    tool_call_name = tool_call.function.name
                    tool_call_arguments = json.loads(tool_call.function.arguments)  # <-- arguments 是序列化后的 JSON Object，我们需要使用 json.loads 反序列化一下
                    if tool_call_name == "$web_search":
                        tool_result = search_impl(tool_call_arguments)
                    else:
                        tool_result = f"Error: unable to find tool by name '{tool_call_name}'"

                    # 使用函数执行结果构造一个 role=tool 的 message，以此来向模型展示工具调用的结果；
                    # 注意，我们需要在 message 中提供 tool_call_id 和 name 字段，以便 Kimi 大模型
                    # 能正确匹配到对应的 tool_call。
                    messages.append({
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "name": tool_call_name,
                        "content": json.dumps(tool_result),  # <-- 我们约定使用字符串格式向 Kimi 大模型提交工具调用结果，因此在这里使用 json.dumps 将执行结果序列化成字符串
                    })

        print(choice.message.content)  # <-- 在这里，我们才将模型生成的回复返回给用户


    if __name__ == '__main__':
        main()
    ```
  </Tab>

  <Tab title="node.js">
    ```js theme={null}
    const openai = require('openai'); // 需要安装 openai 库

    const client = new openai.OpenAI({
        apiKey: process.env.MOONSHOT_API_KEY,
        baseURL: "https://api.moonshot.cn/v1",
    });

    const tools = [
        {
            "type": "builtin_function",
            "function": {
                "name": "$web_search",
            },
        }
    ];

    function search_impl(args) {
        return args
    }

    const messages = [
        { "role": "system", "content": "你是 Kimi，由 Moonshot AI 提供的人工智能助手，你更擅长中文和英文的对话。你会为用户提供安全，有帮助，准确的回答。同时，你会拒绝一切涉及恐怖主义，种族歧视，黄色暴力等问题的回答。Moonshot AI 为专有名词，不可翻译成其他语言。" },
        { "role": "user", "content": "请搜索2024年10月8日的中國A股指数是多少?" }  // 在提问中要求 Kimi 大模型联网搜索
    ];

    let finishReason = null;

    async function main() {
        while (finishReason === null || finishReason === "tool_calls") {
            const completion = await client.chat.completions.create({
                model: "kimi-k3",
                messages: messages,
                tools: tools  // <-- 我们通过 tools 参数，将定义好的 tools 提交给 Kimi 大模型
            });
            const choice = completion.choices[0];
            console.log(choice);
            finishReason = choice.finish_reason;
            console.log(finishReason);
            if (finishReason === "tool_calls") { // <-- 判断当前返回内容是否包含 tool_calls
                messages.push(choice.message); // <-- 我们将 Kimi 大模型返回给我们的 assistant 消息也添加到上下文中，以便于下次请求时 Kimi 大模型能理解我们的诉求
                for (const toolCall of choice.message.tool_calls) { // <-- tool_calls 可能是多个，因此我们使用循环逐个执行
                    const tool_call_name = toolCall.function.name;
                    const tool_call_arguments = JSON.parse(toolCall.function.arguments); // <-- arguments 是序列化后的 JSON Object，我们需要使用 JSON.parse 反序列化一下
                    let tool_result;
                    if (tool_call_name == "$web_search") {
                      tool_result = search_impl(tool_call_arguments)
                    } else {
                      tool_result = 'no tool found'
                    }


                    // 使用函数执行结果构造一个 role=tool 的 message，以此来向模型展示工具调用的结果；
                    // 注意，我们需要在 message 中提供 tool_call_id 和 name 字段，以便 Kimi 大模型
                    // 能正确匹配到对应的 tool_call。
                    console.log("toolCall.id");
                    console.log(toolCall.id);
                    console.log("tool_call_name");
                    console.log(tool_call_name);
                    console.log("tool_result");
                    console.log(tool_result);
                    messages.push({
                        "role": "tool",
                        "tool_call_id": toolCall.id,
                        "name": tool_call_name,
                        "content": JSON.stringify(tool_result), // <-- 我们约定使用字符串格式向 Kimi 大模型提交工具调用结果，因此在这里使用 JSON.stringify 将执行结果序列化成字符串
                    });
                }
            }
            console.log(choice.message.content); // <-- 在这里，我们才将模型生成的回复返回给用户
        }

    }

    main();
    ```
  </Tab>
</Tabs>

为什么 `search_impl` 不需要任何搜索、解析、获取网页内容的逻辑？正如 `builtin_function` 的名称所示，`$web_search` 是 Kimi 大模型内置的函数，由 Kimi 大模型定义，也由 Kimi 大模型执行：

1. 当 Kimi 大模型生成了 `finish_reason=tool_calls` 的响应时，表明它已经意识到需要执行 `$web_search`，并且已经做好执行 `$web_search` 的一切准备工作；
2. Kimi 大模型会将执行函数所必须的参数以 `tool_call.function.arguments` 的形式返回给调用方，但这些参数并不由调用方执行，调用方只需要将 `tool_call.function.arguments` 原封不动地提交给 Kimi 大模型，即可由 Kimi 大模型执行对应的联网搜索流程；
3. 当你将 `tool_call.function.arguments` 使用 `role=tool` 的 `message` 提交时，Kimi 大模型随即开始执行联网搜索流程，并根据搜索和阅读结果生成可供用户阅读的消息，即 `finish_reason=stop` 的 `message`。

## 切换到自行实现的联网搜索

联网搜索功能旨在不破坏原有 API 和 SDK 兼容性的前提下，提供一种可靠性高的大模型联网搜索解决方案，完全兼容 Kimi 大模型原有的工具调用 `tool_calls` 特性。 **当你想从 Kimi 提供的联网搜索功能切换到自己实现的联网搜索功能时，只需要简单两步改动即可在不破坏代码整体结构的情况下完成：**

1. 将 `$web_search` 的 `tool` 定义修改成你自己实现的 `tool` 定义（包括 `name`、`description` 等），这可能需要在 `tool.function` 中添加额外的说明信息以告知模型具体需要生成哪些参数，你可以在 `parameters` 字段中添加任意你需要的参数信息；
2. 修改 `search_impl` 函数的实现：使用 Kimi 提供的 `$web_search` 时，只需原封不动返回入参 `arguments`；使用自己的联网搜索服务时，则需要完整实现 `search` 和 `crawl` 功能——调用搜索引擎接口（或自行实现内容搜索）获取 URL 和摘要、按 URL 抓取网页内容（不同网站可能需要应用不同的读取规则）、将网页内容清洗整理成 Markdown 等模型便于识别的格式，并处理无搜索结果、网页内容获取失败等错误和异常情况。

完成上述步骤后，你就成功完成了从 Kimi 提供的联网搜索功能，迁移到自己实现的联网搜索功能的所有事项。

## 统计联网搜索的 Token 消耗

使用 `$web_search` 时，搜索结果同样会被计入提示词所占用的 Tokens（即 `prompt_tokens`）。通常情况下，联网搜索的结果包含的内容众多，最终产生的 Tokens 消耗也会更多；为了避免在不知情的情况下消耗大量 Tokens，模型在生成 `$web_search` 的参数 `arguments` 时，会在其中的 `usage` 对象下额外添加一个 `total_tokens` 字段（读取路径为 `arguments.usage.total_tokens`），用于告知调用方本次搜索内容总共占用的 Tokens 数量，这些 Tokens 将会在完成整个联网搜索流程时计入 `prompt_tokens`。

以下示例演示如何读取搜索结果占用的 `total_tokens`，以及整轮对话的 Tokens 消耗：

```python theme={null}
from typing import *

import os
import json

from openai import OpenAI
from openai.types.chat.chat_completion import Choice


client = OpenAI(
    base_url="https://api.moonshot.cn/v1",
    api_key=os.environ.get("MOONSHOT_API_KEY"),
)


# search 工具的具体实现，这里我们只需要返回参数即可
def search_impl(arguments: Dict[str, Any]) -> Any:
    """
    在使用 Moonshot AI 提供的 search 工具的场合，只需要原封不动返回 arguments 即可，
    不需要额外的处理逻辑。

    但如果你想使用其他模型，并保留联网搜索的功能，那你只需要修改这里的实现（例如调用搜索
    和获取网页内容等），函数签名不变，依然是 work 的。

    这最大程度保证了兼容性，允许你在不同的模型间切换，并且不需要对代码有破坏性的修改。
    """
    return arguments


def chat(messages) -> Choice:
    completion = client.chat.completions.create(
        model="kimi-k3",
        messages=messages,
        max_tokens=32768,
        tools=[
            {
                "type": "builtin_function",
                "function": {
                    "name": "$web_search",
                },
            }
        ]
    )
    usage = completion.usage
    choice = completion.choices[0]

    # =========================================================================
    # 通过判断 finish_reason = stop，我们将完成联网搜索流程后，消耗的 Tokens 打印出来
    if choice.finish_reason == "stop":
        print(f"chat_prompt_tokens:          {usage.prompt_tokens}")
        print(f"chat_completion_tokens:      {usage.completion_tokens}")
        print(f"chat_total_tokens:           {usage.total_tokens}")
    # =========================================================================

    return choice


def main():
    messages = [
        {"role": "system", "content": "你是 Kimi。"},
    ]

    # 初始提问
    messages.append({
        "role": "user",
        "content": "请搜索 Moonshot AI Context Caching 技术，并告诉我它是什么。"
    })

    finish_reason = None
    while finish_reason is None or finish_reason == "tool_calls":
        choice = chat(messages)
        finish_reason = choice.finish_reason
        if finish_reason == "tool_calls":
            # 追加完整 assistant message，原样保留 reasoning_content 和 tool_calls。
            messages.append(choice.message)
            for tool_call in choice.message.tool_calls:
                tool_call_name = tool_call.function.name
                tool_call_arguments = json.loads(
                    tool_call.function.arguments)
                if tool_call_name == "$web_search":

    				# ===================================================================
                    # 我们将联网搜索过程中，由联网搜索结果产生的 Tokens 打印出来
                    search_content_total_tokens = tool_call_arguments.get("usage", {}).get("total_tokens")
                    print(f"search_content_total_tokens: {search_content_total_tokens}")
    				# ===================================================================

                    tool_result = search_impl(tool_call_arguments)
                else:
                    tool_result = f"Error: unable to find tool by name '{tool_call_name}'"

                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "name": tool_call_name,
                    "content": json.dumps(tool_result),
                })

    print(choice.message.content)


if __name__ == '__main__':
    main()

```

执行上述代码，获得如下返回结果：

```shell theme={null}
search_content_total_tokens: 13046  # <-- 代表由于触发了联网搜索动作，产生的联网搜索结果占用的 Tokens 数
chat_prompt_tokens:          13212  # <-- 代表包含了联网搜索结果的输入 Tokens 数量
chat_completion_tokens:      295    # <-- 代表 Kimi 大模型根据联网搜索结果生成的 Tokens 数量
chat_total_tokens:           13507  # <-- 代表包含了联网搜索流程的请求消耗的总 Tokens 数量

# 此处省略 Kimi 大模型根据联网搜索结果生成的内容
```

## 关于模型大小的选择

启用联网搜索后，搜索结果会显著增加上下文长度。为避免触发 `Input token length too long`，建议选择上下文窗口更大的 `kimi-k3`（1M token 上下文）：

```python theme={null}
def chat(messages) -> Choice:
    completion = client.chat.completions.create(
        model="kimi-k3",
        messages=messages,
        tools=[
            {
                "type": "builtin_function",  # <-- 使用 builtin_function 声明 $web_search 函数，请在每次请求都完整地带上 tools 声明
                "function": {
                    "name": "$web_search",
                },
            }
        ]
    )
    return completion.choices[0]
```

## 联网搜索计费

除了 Tokens 消耗外，我们还会对每次联网搜索收取一次调用费用，详情请见[计费](/docs/pricing/tools)。
