Kimi API 提供 $web_search 内置工具,不需要自己实现搜索抓取逻辑,模型会处理搜索结果并生成回答。使用时需将 type 设置为 builtin_function,且必须禁用 thinking 模式。本文提供完整使用示例。

使用 Kimi API 的联网搜索($web_search)

与自定义搜索工具的区别

特性 $web_search 内置工具 自定义 search 工具
实现复杂度 只需注册,无需实现 需要自己调用搜索 API
搜索质量 Kimi 官方搜索能力 取决于你选择的搜索服务
费用 按搜索次数额外计费 取决于搜索服务费用
thinking 兼容性 必须禁用 thinking 兼容 thinking 模式

基础用法

import json
import os
from openai import OpenAI

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

tools = [
    {
        "type": "builtin_function",   # 注意:是 builtin_function,不是 function
        "function": {"name": "$web_search"}  # 固定名称,无需参数说明
    }
]

messages = [
    {"role": "system", "content": "你是一个能联网查询最新信息的助手。"},
    {"role": "user", "content": "今天 OpenAI 有什么新闻?"}
]

# 使用 $web_search 时必须禁用 thinking
while True:
    response = client.chat.completions.create(
        model="kimi-k2.6",
        messages=messages,
        tools=tools,
        extra_body={"thinking": {"type": "disabled"}},  # 必须禁用
    )
    choice = response.choices[0]

    if choice.finish_reason != "tool_calls":
        print(choice.message.content)
        break

    messages.append(choice.message)

    for tool_call in choice.message.tool_calls:
        # 关键:原样返回 arguments,不做任何处理
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": tool_call.function.arguments,  # 原样返回!
        })

混合使用内置和自定义工具

可以同时注册 builtin_function 和普通 function

tools = [
    {
        "type": "builtin_function",
        "function": {"name": "$web_search"}
    },
    {
        "type": "function",
        "function": {
            "name": "get_local_data",
            "description": "查询本地数据库中的信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string"}
                },
                "required": ["query"]
            }
        }
    }
]

执行时,按 tool_call.function.name 判断调用哪个工具:

if tool_call.function.name == "$web_search":
    # 原样返回 arguments
    result = tool_call.function.arguments
elif tool_call.function.name == "get_local_data":
    # 执行自定义逻辑
    args = json.loads(tool_call.function.arguments)
    result = query_local_db(args["query"])

Token 消耗统计

$web_search 会产生额外的 token 消耗(搜索结果内容):

# 响应中包含搜索相关的 token 统计
print(response.usage)
# search_content_total_tokens: 搜索结果消耗的 token
# chat_total_tokens: 对话本身消耗的 token

具体定价见联网搜索价格说明


常见问题

Q: 为什么使用 $web_search 必须禁用 thinking?

A: 官方限制,内置联网搜索工具与 thinking 模式不兼容。如需 thinking + 联网,可以自行实现搜索工具(调用 Tavily 等 API),此时不受此限制。

Q: $web_search 的搜索结果质量如何?

A: 使用 Kimi 官方联网能力,中文搜索效果较好。如需精确控制搜索源或需要更高质量的搜索,建议自行实现工具调用搜索 API。

Q: 执行 $web_search 工具时为什么要"原样返回 arguments"?

A: 这是 $web_search 的特殊设计,Kimi 服务端识别到 arguments 原样返回后,会自动执行搜索并将结果注入上下文,开发者不需要(也无法)自己做搜索。

Kimi API 的 $web_search 是内置联网搜索工具,通过 type: "builtin_function" 注册,执行时将 arguments 原样返回给 API 即可,由 Kimi 负责实际搜索和网页阅读。注意:使用时必须禁用 thinking 模式,并推荐使用 kimi-k2.6 模型。

使用 Kimi API 的联网搜索功能

两种联网搜索方式的对比

方式 工作量 适合场景
自定义 tool_calls 需要自己实现搜索 + 网页抓取 需要精确控制搜索源、使用特定搜索引擎
$web_search 内置工具 开箱即用,几乎零配置 快速接入,不需要自己维护搜索基础设施

注册 $web_search

$web_search 使用特殊的 type: "builtin_function" 声明,不需要提供参数说明:

const tools = [
  {
    type: "builtin_function", // 区别于普通 "function"
    function: {
      name: "$web_search", // 以 $ 开头代表内置工具
    },
  },
];

注意:使用 $web_search 时必须通过 thinking: { type: "disabled" } 禁用思考模式。

完整调用示例

import OpenAI from "openai";

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

async function chat(messages: any[]) {
  return await (client as any).chat.completions.create({
    model: "kimi-k2.6", // 推荐用 kimi-k2.6,上下文更大,更适合处理搜索结果
    messages,
    max_tokens: 32768,
    thinking: { type: "disabled" }, // $web_search 必须禁用 thinking
    tools: [
      {
        type: "builtin_function",
        function: { name: "$web_search" },
      },
    ],
  });
}

const messages: any[] = [
  { role: "system", content: "你是 Kimi,由 Moonshot AI 提供的人工智能助手。" },
  { role: "user", content: "请搜索 Context Caching 技术,告诉我它是什么。" },
];

let finishReason = null;
while (finishReason === null || finishReason === "tool_calls") {
  const completion = await chat(messages);
  const choice = completion.choices[0];
  finishReason = choice.finish_reason;

  if (finishReason === "tool_calls") {
    messages.push(choice.message);
    
    for (const toolCall of choice.message.tool_calls) {
      const args = JSON.parse(toolCall.function.arguments);
      
      // 关键:$web_search 只需要原样返回 arguments,不需要实际执行搜索
      messages.push({
        role: "tool",
        tool_call_id: toolCall.id,
        name: toolCall.function.name,
        content: JSON.stringify(args), // 原样返回,Kimi 自己执行搜索
      });
    }
  }
}

console.log(messages.at(-1).content);
import os
import json
from openai import OpenAI

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

def search_impl(arguments):
    # $web_search 只需要原样返回参数,Kimi 负责实际搜索
    return arguments

messages = [
    {"role": "system", "content": "你是 Kimi,由 Moonshot AI 提供的人工智能助手。"},
    {"role": "user", "content": "请搜索 Context Caching 技术,告诉我它是什么。"},
]

finish_reason = None
while finish_reason is None or finish_reason == "tool_calls":
    completion = client.chat.completions.create(
        model="kimi-k2.6",
        messages=messages,
        max_tokens=32768,
        extra_body={"thinking": {"type": "disabled"}},
        tools=[{"type": "builtin_function", "function": {"name": "$web_search"}}],
    )
    choice = completion.choices[0]
    finish_reason = choice.finish_reason
    
    if finish_reason == "tool_calls":
        messages.append(choice.message)
        for tool_call in choice.message.tool_calls:
            args = json.loads(tool_call.function.arguments)
            tool_result = search_impl(args)
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "name": tool_call.function.name,
                "content": json.dumps(tool_result),
            })

print(choice.message.content)

内置工具运作原理

$web_search服务端执行的内置工具:

  1. 模型返回 finish_reason: "tool_calls",表示需要搜索
  2. 你把 arguments 原样回传给 API(不需要实际搜索)
  3. Kimi 服务端接收到后,自动执行搜索 + 网页阅读
  4. 搜索结果注入到下一次请求的上下文中
  5. 模型基于搜索结果生成最终回复

Token 消耗监控

搜索结果会大幅增加 token 消耗,可以从 arguments 中提前获知:

for tool_call in choice.message.tool_calls:
    args = json.loads(tool_call.function.arguments)
    
    # usage.total_tokens 表示本次搜索结果预计占用的 token 数
    search_tokens = args.get("usage", {}).get("total_tokens", 0)
    print(f"本次搜索预计消耗 {search_tokens} tokens")

典型消耗示例:

search_content_total_tokens: 13046  ← 搜索结果占用的 token
chat_prompt_tokens:          13212  ← 包含搜索结果的总输入 token
chat_completion_tokens:      295    ← 模型回复消耗
chat_total_tokens:           13507  ← 总消耗

注意事项

  • 使用 $web_search必须用 kimi-k2.6,以应对搜索结果带来的大量 token 输入
  • 联网搜索除 token 费用外,还会额外计算调用次数费用,详见联网搜索计费
  • $web_search 可以和普通 function 混合放在同一个 tools 数组中
  • 从自定义搜索迁移到 $web_search:只需改 tools 定义 + search_impl 函数,其他代码不变

常见问题

Q: 为什么 $web_search 执行时要原样返回 arguments?

A: 因为实际搜索是由 Kimi 服务端执行的,arguments 实际上是搜索的"任务令牌"——把它原样传回去,Kimi 才知道要执行哪次搜索。你不需要、也不能替换成自己的搜索结果。

Q: 可以和自定义工具一起用吗?

A: 可以。type: "builtin_function"type: "function" 可以在同一个 tools 数组中共存。

Q: 搜索结果不准怎么办?

A: 可以在 user 消息里更明确地描述搜索需求,或者切换到自定义 tool_calls 方式,自己接入特定的搜索引擎(如 Bing API、Tavily 等)。