Skip to content

工具调用让 Kimi 模型从"能说"变成"能做":开发者定义工具 JSON Schema,模型决定调用哪个工具并生成参数,开发者执行工具返回结果,模型基于结果给出最终回答。本文以联网搜索为例演示完整循环。

使用 Kimi API 完成工具调用(tool_calls)

工具调用的核心流程

1. 定义工具(JSON Schema 格式)
2. 传入 tools 参数,发送请求
3. 模型返回 finish_reason="tool_calls"
4. 解析 tool_calls,执行对应函数
5. 将执行结果以 role="tool" 传回
6. 模型根据结果给出最终回答

与 OpenAI 对比:Kimi API 工具调用格式与 OpenAI 完全兼容,但不支持已废弃的 functions 参数,且 tool_choice 不支持 required


定义工具

使用 JSON Schema 描述工具能力:

python
tools = [
    {
        "type": "function",
        "function": {
            "name": "search",
            "description": "通过搜索引擎搜索互联网内容。当用户提问需要最新信息或你的知识无法回答时调用。",
            "parameters": {
                "type": "object",
                "required": ["query"],
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "用户想要搜索的内容"
                    }
                }
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "crawl",
            "description": "获取指定 URL 网页的内容。",
            "parameters": {
                "type": "object",
                "required": ["url"],
                "properties": {
                    "url": {
                        "type": "string",
                        "description": "目标网页地址"
                    }
                }
            }
        }
    }
]

完整 Agent 循环(Python)

python
import json
from openai import OpenAI

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

def search(query: str) -> str:
    # 实际实现:调用搜索 API(如 Tavily、Brave Search)
    return f"搜索结果:关于'{query}'的相关内容..."

def crawl(url: str) -> str:
    # 实际实现:抓取网页内容
    return f"网页内容:{url} 的正文..."

def run_agent(user_message: str) -> str:
    messages = [
        {"role": "system", "content": "你是一个能联网查询最新信息的助手。"},
        {"role": "user", "content": user_message}
    ]

    while True:
        completion = client.chat.completions.create(
            model="kimi-k2.6",
            messages=messages,
            tools=tools,
        )
        choice = completion.choices[0]

        # 无工具调用,直接返回
        if choice.finish_reason != "tool_calls":
            return choice.message.content

        # 将模型的工具调用消息加入历史
        messages.append(choice.message)

        # 执行所有工具调用
        for tool_call in choice.message.tool_calls:
            args = json.loads(tool_call.function.arguments)

            if tool_call.function.name == "search":
                result = search(**args)
            elif tool_call.function.name == "crawl":
                result = crawl(**args)
            else:
                result = "未知工具"

            # 将工具结果传回
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": result,
            })

answer = run_agent("今天 AI 领域有什么重要新闻?")
print(answer)

TypeScript 示例

typescript
import OpenAI from "openai";

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

async function runAgent(userMessage: string): Promise<string> {
  const messages: OpenAI.ChatCompletionMessageParam[] = [
    { role: "system", content: "你是一个能联网查询最新信息的助手。" },
    { role: "user", content: userMessage },
  ];

  while (true) {
    const completion = await client.chat.completions.create({
      model: "kimi-k2.6",
      messages,
      tools,
    });

    const choice = completion.choices[0];

    if (choice.finish_reason !== "tool_calls") {
      return choice.message.content ?? "";
    }

    messages.push(choice.message);

    for (const toolCall of choice.message.tool_calls ?? []) {
      const args = JSON.parse(toolCall.function.arguments);
      let result: string;

      if (toolCall.function.name === "search") {
        result = `搜索结果:${args.query}...`; // 替换为真实搜索
      } else {
        result = "未知工具";
      }

      messages.push({
        role: "tool",
        tool_call_id: toolCall.id,
        content: result,
      });
    }
  }
}

Thinking 模式下的注意事项

启用 thinking 模式(kimi-k2.6 默认开启)时:

  • tool_choice 只能是 "auto""none"
  • 每轮工具调用后,assistant 消息中的 reasoning_content 必须原样保留并传入下一轮

常见问题

Q: 模型一定会调用工具吗?

A: 不一定,模型会根据上下文判断是否需要工具。如需强制调用,可在 system prompt 中明确指示,Kimi API 目前不支持 tool_choice="required"

Q: 可以同时定义多少个工具?

A: 最多 128 个工具。

Q: 工具调用支持并行吗?

A: 支持,模型可以在一次响应中返回多个 tool_calls,每个有独立的 tool_call_id,需要逐个执行并分别回传结果。

Kimi API 的工具调用(tool_calls)让模型从"说话"变成"做事"。通过注册工具定义,模型可以决定何时调用哪个工具、传什么参数,开发者执行工具后将结果回传,模型再给出最终回复。本文覆盖完整循环流程。

使用 Kimi API 完成工具调用(tool_calls)

什么是 tool_calls

tool_calls 给予了 Kimi 模型执行具体动作的能力。模型能通过 tool_calls 帮你搜索互联网、查询数据库、甚至操作智能家居。

tool_callsfunction_call 进化而来,是其超集。由于 OpenAI 已将 function_call 标记为废弃,Kimi API 不再支持 function_call,请统一使用 tool_calls

一次完整的工具调用流程:

  1. 用 JSON Schema 定义工具
  2. 通过 tools 参数提交给模型
  3. 模型决定调用哪个工具(或不调用)
  4. 模型以 JSON 格式输出调用参数
  5. 开发者执行工具,获取结果
  6. 将结果回传给模型,模型给出最终回复

关键理解:模型本身不执行工具,它只输出"应该调用什么、传什么参数"。实际执行由开发者完成。

工具定义

以联网搜索为例,定义两个工具:search(搜索)和 crawl(抓取网页):

typescript
import OpenAI from "openai";

const tools: OpenAI.Chat.ChatCompletionTool[] = [
  {
    type: "function",
    function: {
      name: "search",
      description: `通过搜索引擎搜索互联网上的内容。
        当你的知识无法回答用户提出的问题,或用户请求联网搜索时,调用此工具。
        搜索结果包含网站标题、地址(URL)和简介。`,
      parameters: {
        type: "object",
        required: ["query"],
        properties: {
          query: {
            type: "string",
            description: "用户搜索的内容,从对话上下文中提取。",
          },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "crawl",
      description: "根据网站地址(URL)获取网页内容。",
      parameters: {
        type: "object",
        required: ["url"],
        properties: {
          url: {
            type: "string",
            description: "需要获取内容的网站地址(URL)。",
          },
        },
      },
    },
  },
];

完整调用循环

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

async function executeTool(name: string, args: Record<string, string>): Promise<string> {
  if (name === "search") {
    // 替换为真实的搜索实现
    return JSON.stringify([{ title: "示例结果", url: "https://example.com", snippet: "..." }]);
  }
  if (name === "crawl") {
    // 替换为真实的网页抓取实现
    return `网页内容:${args.url} 的文字内容...`;
  }
  return "工具不存在";
}

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

// 使用 while 循环处理多轮工具调用
while (true) {
  const completion = await client.chat.completions.create({
    model: "kimi-k2.6",
    messages,
    tools,
  });

  const choice = completion.choices[0];
  messages.push(choice.message); // 必须把 assistant 消息加回去

  if (choice.finish_reason === "stop") {
    // 模型完成回复,退出循环
    console.log(choice.message.content);
    break;
  }

  if (choice.finish_reason === "tool_calls" && choice.message.tool_calls) {
    // 执行模型请求的所有工具
    for (const toolCall of choice.message.tool_calls) {
      const args = JSON.parse(toolCall.function.arguments);
      const result = await executeTool(toolCall.function.name, args);

      messages.push({
        role: "tool",
        tool_call_id: toolCall.id, // 必须匹配对应的 tool_call id
        content: result,
      });
    }
  }
}

messages 结构说明

工具调用的 messages 顺序必须正确:

system: 系统提示
user: 用户问题
assistant: tool_call(name=search, arguments={query: "..."})  ← 模型返回
tool: search_result(tool_call_id=...)                         ← 工具执行结果
assistant: tool_call(name=crawl, arguments={url: "..."})      ← 模型继续调用
tool: crawl_content(tool_call_id=...)
assistant: 最终回复(finish_reason=stop)

常见错误:如果出现 tool_call_id not found 错误,检查是否把完整的 assistant 消息(含 tool_calls 字段)加回了 messages。不能只加 content,要加整个 message 对象。

流式输出下的工具调用

流式模式下,工具调用的判断方式有所不同:

typescript
const stream = await client.chat.completions.create({
  model: "kimi-k2.6",
  messages,
  tools,
  stream: true,
});

let toolCallChunks: Record<number, { id: string; name: string; arguments: string }> = {};

for await (const chunk of stream) {
  const delta = chunk.choices[0]?.delta;
  
  if (delta?.tool_calls) {
    for (const tc of delta.tool_calls) {
      const idx = tc.index ?? 0;
      if (!toolCallChunks[idx]) {
        toolCallChunks[idx] = { id: tc.id ?? "", name: tc.function?.name ?? "", arguments: "" };
      }
      toolCallChunks[idx].arguments += tc.function?.arguments ?? "";
    }
  }
}

流式输出注意事项:

  • delta.tool_calls 是否存在判断是否有工具调用(不要等 finish_reason
  • delta.content 先输出,delta.tool_calls 后输出
  • 第一个数据块包含 tool_call.idfunction.name,后续块只追加 arguments
  • 多个并行工具调用时,用 index 字段区分

token 消耗说明

tools 参数中的内容同样计入 token 消耗。工具定义越详细,每次请求的 token 越多。精简 description 可以降低成本。

常见问题

Q: 模型一定会调用工具吗?

A: 不一定。模型会根据上下文自主决定是否调用工具。如果你确定需要调用,可以在 prompt 中明确说明(如"请联网搜索"),或使用 tool_choice: "required" 强制调用。

Q: 可以同时注册多个工具吗?

A: 可以,tools 是数组,可以一次性提交多个工具定义。模型也可以在一次回复中选择多个工具并行调用。

Q: 工具调用的结果格式有要求吗?

A: role: "tool" 消息的 content 是字符串,通常传 JSON 字符串或纯文本。建议用结构化 JSON,方便模型解析。