agent的流程基础-大模型上下文窗口、Function Call 与 MCP 全流程
大模型上下文窗口、Function Call 与 MCP 全流程
大模型聊天不只是“用户发送一句话,模型返回一句话”。一次完整请求通常会携带系统指令、历史消息、工具定义和工具执行结果;模型也可能不直接回答,而是先返回一个或多个工具调用,再由应用执行工具并把结果送回模型。
本文以 OpenAI 兼容的 Chat Completions 消息格式为主线,完整说明:
- 输入 token、输出 token 与上下文窗口的关系;
system、developer、user、assistant、tool等消息角色;choices、finish_reason和usage的含义;- 单个工具和多个工具的 Function Call 全流程;
- 多个工具结果如何再次提交给大模型;
- 有依赖关系的工具为什么需要分多轮调用;
- MCP 工具如何映射为模型的 Function Call;
- 流式调用、长度截断、工具异常与安全边界。
不同模型厂商的字段名称并不完全一致。本文使用的是常见 OpenAI 兼容格式,并在相关位置标注 Responses API、Anthropic 和 MCP 的差异。
1. 先看完整架构
一个支持工具调用的 AI 应用通常包含四层:
各层职责如下:
| 层 | 职责 |
|---|---|
| 用户 | 用自然语言描述目标,通常不直接构造工具调用 JSON |
| AI 应用 / Agent Host | 管理消息历史、调用模型、校验参数、执行工具、回填结果、控制循环 |
| 大模型 | 根据消息和工具定义生成普通回答,或生成结构化工具调用 |
| 工具 / MCP Server | 真正读取文件、查询数据库、调用业务接口或操作外部系统 |
关键点是:模型只负责“提出工具调用请求”,真正执行工具的是 AI 应用或 MCP Server。
2. Token 与上下文窗口
2.1 Token 是什么
模型并不是按“字数”处理文本,而是先通过 tokenizer 将内容拆成 token。一个 token 可能是:
- 一个汉字;
- 英文单词的一部分;
- 一个标点;
- 一段常见代码符号;
- JSON 中的引号、反斜杠或字段名。
因此,同样是 10 KB 内容,中文、英文、代码和经过 JSON 转义的字符串所占 token 数可能明显不同。
2.2 输入 token 包含什么
一次请求的输入 token 通常包括:
- 系统提示词;
- 开发者指令;
- 历史用户消息;
- 历史模型消息;
- 历史工具调用;
- 工具执行结果;
- 当前用户消息;
- 工具名称、描述和 JSON Schema;
- 服务商在协议层添加的其他内容。
工具定义也会占用输入上下文。工具越多、描述越长、参数 Schema 越复杂,每轮请求的固定输入成本越高。
2.3 输出 token 包含什么
输出 token 通常包括模型本轮生成的:
- 普通文本回答;
- 工具调用名称;
- 工具调用参数 JSON;
- 部分模型返回的推理内容;
- 结构化输出字段。
如果模型要调用 write_file 并把 68 KB HTML 放进 content 参数,那么整段 HTML 和 JSON 转义字符都属于模型输出。
2.4 三个相关限制
常见限制可以概括为:
输入 token + 本次输出 token <= 总上下文窗口
本次输出 token <= 模型单次输出上限
本次输出 token <= 请求指定的 max_output_tokens
所以实际可输出量近似为:
实际可输出 token = min(
请求设置的输出上限,
模型自身单次输出上限,
总上下文窗口 - 当前输入 token - 安全余量
)
例如:
总上下文窗口:128K
当前输入:30K
模型单次输出上限:16K
请求 max_output_tokens:8K
那么本轮最多输出约 8K,而不是剩余的 98K。
如果输入已经达到 127K,而总窗口只有 128K:
- 请求只预留约 1K 输出时,部分服务商可能生成不超过 1K 后结束;
- 请求仍要求 16K 输出时,服务商可能直接返回上下文超限;
- 部分客户端会先压缩或删除早期历史;
- 少数服务商会自动截断输入,但这可能造成模型遗忘早期内容。
2.5 max_output_tokens 不是目标长度
设置:
{"max_output_tokens": 1000}
表示“最多输出 1000 tokens”,不是“必须输出 1000 tokens”。
- 答案只需要 300 tokens:模型可以在约 300 tokens 时正常停止;
- 完整答案需要 2000 tokens:模型最多生成约 1000 tokens,结果可能被截断;
- 提示模型“请在 800 tokens 内回答”可以提高主动收尾概率,但不是硬保证。
不同 API 使用的字段名称不同:
| API | 常见字段 |
|---|---|
| OpenAI Responses API | max_output_tokens |
| Chat Completions | max_tokens |
| 部分新版 OpenAI 模型 | max_completion_tokens |
| Anthropic Messages API | max_tokens |
3. 消息角色
常见消息角色如下:
role |
含义 |
|---|---|
system |
应用提供的全局行为和身份约束 |
developer |
开发者指令,部分模型使用该角色代替或补充 system |
user |
用户输入 |
assistant |
模型生成的普通回答或工具调用请求 |
tool |
工具执行结果,必须通过 tool_call_id 对应模型先前的调用 |
典型消息顺序:
system
user
assistant(请求调用工具)
tool(工具结果)
assistant(最终回答)
如果模型一次调用两个工具:
system
user
assistant(包含两个 tool_calls)
tool(第一个结果)
tool(第二个结果)
assistant(综合两个结果后继续调用工具或最终回答)
4. 不使用工具时的一轮请求
4.1 请求
下面展示一个较完整的 Chat Completions 请求。实际使用时不应盲目发送所有可选字段,因为部分模型不支持 temperature、n 或 parallel_tool_calls。
{
"model": "example-model",
"messages": [
{
"role": "system",
"content": "你是一个严谨的中文技术助手。"
},
{
"role": "user",
"content": "请用一句话解释什么是上下文窗口。"
}
],
"max_tokens": 500,
"temperature": 0.2,
"top_p": 1,
"n": 1,
"stream": false,
"stop": null,
"presence_penalty": 0,
"frequency_penalty": 0,
"user": "user-001"
}
主要字段:
| 字段 | 含义 |
|---|---|
model |
模型标识 |
messages |
当前完整消息历史 |
max_tokens |
本轮最大输出 token |
temperature |
随机性;并非所有模型都支持 |
top_p |
核采样参数 |
n |
候选回答数量,通常为 1 |
stream |
是否流式返回 |
stop |
命中指定内容时停止 |
presence_penalty |
是否鼓励引入新主题 |
frequency_penalty |
是否降低重复内容 |
user |
终端用户标识;并非所有服务商都使用 |
4.2 响应
{
"id": "chatcmpl-001",
"object": "chat.completion",
"created": 1784343218,
"model": "example-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "上下文窗口是模型一次请求中能够共同处理的输入与输出 token 总容量。"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 27,
"total_tokens": 69
}
}
choices 是候选结果数组。接口允许通过 n 请求多个候选,但多数聊天应用只使用 choices[0]。
usage 常见字段:
| 字段 | 含义 |
|---|---|
prompt_tokens |
输入 token 数 |
completion_tokens |
输出 token 数 |
total_tokens |
输入与输出之和 |
prompt_tokens_details |
可选,可能细分缓存、文本和图片 token |
completion_tokens_details |
可选,可能细分文本和推理 token |
4.3 一次返回多个 choices
当请求设置 n: 2 时,服务商可能返回两个候选答案:
{
"id": "chatcmpl-choices-001",
"object": "chat.completion",
"created": 1784343219,
"model": "example-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "上下文窗口是模型单次请求可容纳的输入与输出总量。"
},
"finish_reason": "stop",
"logprobs": null
},
{
"index": 1,
"message": {
"role": "assistant",
"content": "它可以理解为模型一次推理过程中可使用的 token 总预算。"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 49,
"total_tokens": 91
}
}
聊天应用通常只选择 choices[0]。如果多个候选都包含工具调用,应用也应先选择一个候选方案,不能把所有候选中的工具调用全部执行,否则可能造成重复写文件、重复发消息、重复下单或重复扣款。
5. Function Call 的本质
Function Call,也常称 Tool Call,并不是模型直接执行函数。模型只是返回结构化意图:
{
"name": "read_file",
"arguments": {
"path": "config.yaml"
}
}
AI 应用随后负责:
- 确认工具存在;
- 解析参数 JSON;
- 按 JSON Schema 校验参数;
- 执行权限、安全和路径检查;
- 真正调用工具;
- 将结果序列化为
role: tool消息; - 再次调用模型。
6. 多工具调用完整示例
下面使用同一个任务贯穿三轮模型请求:
读取 config.yaml,同时列出当前目录中的 Markdown 文件,
然后把分析结果写入 report.md。
这里包含:
- 第一轮同时调用两个互不依赖的读取工具;
- 应用执行两个工具并把两个结果一起回填;
- 第二轮模型根据读取结果调用写文件工具;
- 应用执行写入并回填;
- 第三轮模型给出最终回答。
6.1 第一轮:把用户消息和工具定义发给模型
{
"model": "example-model",
"messages": [
{
"role": "system",
"content": "你是一个文件分析助手。需要读取或修改文件时必须使用工具。"
},
{
"role": "user",
"content": "读取 config.yaml,同时列出当前目录中的 Markdown 文件,然后把分析结果写入 report.md。"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "read_file",
"description": "读取文本文件内容。",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "要读取的文件路径。"
},
"offset": {
"type": "integer",
"description": "从第几行开始读取。",
"default": 1
},
"limit": {
"type": "integer",
"description": "最多读取多少行。",
"default": 500
}
},
"required": ["path"],
"additionalProperties": false
}
}
},
{
"type": "function",
"function": {
"name": "search_files",
"description": "按文件名模式查找文件。",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "搜索根目录。"
},
"pattern": {
"type": "string",
"description": "文件名匹配模式。"
},
"target": {
"type": "string",
"enum": ["files", "content"],
"description": "搜索文件名或文件内容。"
},
"limit": {
"type": "integer",
"description": "最多返回多少项。",
"default": 50
}
},
"required": ["path", "pattern", "target"],
"additionalProperties": false
}
}
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "将完整内容写入文件,并覆盖已有内容。",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "目标文件路径。"
},
"content": {
"type": "string",
"description": "要写入的完整文件内容。"
}
},
"required": ["path", "content"],
"additionalProperties": false
}
}
}
],
"tool_choice": "auto",
"parallel_tool_calls": true,
"max_tokens": 4096,
"temperature": 0.2,
"top_p": 1,
"n": 1,
"stream": false
}
工具相关字段:
| 字段 | 含义 |
|---|---|
tools |
当前允许模型使用的工具定义 |
tool_choice: "auto" |
模型自行决定是否调用工具 |
tool_choice: "none" |
禁止模型调用工具 |
| 指定工具 | 强制或倾向模型调用某个工具,具体格式依服务商而定 |
parallel_tool_calls |
是否允许一次响应返回多个工具调用 |
6.2 第一轮响应:模型一次命中两个工具
{
"id": "chatcmpl-002",
"object": "chat.completion",
"created": 1784343220,
"model": "example-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_read_001",
"type": "function",
"function": {
"name": "read_file",
"arguments": "{\"path\":\"config.yaml\",\"offset\":1,\"limit\":500}"
}
},
{
"id": "call_search_001",
"type": "function",
"function": {
"name": "search_files",
"arguments": "{\"path\":\".\",\"pattern\":\"*.md\",\"target\":\"files\",\"limit\":50}"
}
}
]
},
"finish_reason": "tool_calls",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 612,
"completion_tokens": 96,
"total_tokens": 708
}
}
此时:
role: assistant表明这段内容由模型生成;content: null表示本轮没有普通文本;tool_calls中包含两个独立调用;finish_reason: tool_calls表示模型等待工具结果,并不代表整个用户任务已经完成;function.arguments在 Chat Completions 中常是 JSON 字符串,应用必须再次解析。
6.3 应用执行两个工具
应用可以并行执行这两个互不依赖的工具。
第一个工具的实际调用:
{
"name": "read_file",
"arguments": {
"path": "config.yaml",
"offset": 1,
"limit": 500
}
}
第一个工具的执行结果:
{
"success": true,
"path": "config.yaml",
"content": "model:\n provider: agents\n default: Qwen3.6-Plus\n",
"total_lines": 3
}
第二个工具的实际调用:
{
"name": "search_files",
"arguments": {
"path": ".",
"pattern": "*.md",
"target": "files",
"limit": 50
}
}
第二个工具的执行结果:
{
"success": true,
"matches": [
"README.md",
"CHANGELOG.md",
"docs/部署说明.md"
],
"count": 3
}
6.4 第二轮:把两个工具结果一起回填给模型
第二次调用模型时,不能只发送工具结果。应用需要保留此前完整消息历史,并把模型原始的 assistant.tool_calls 消息以及所有对应的 tool 消息追加进去。
{
"model": "example-model",
"messages": [
{
"role": "system",
"content": "你是一个文件分析助手。需要读取或修改文件时必须使用工具。"
},
{
"role": "user",
"content": "读取 config.yaml,同时列出当前目录中的 Markdown 文件,然后把分析结果写入 report.md。"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_read_001",
"type": "function",
"function": {
"name": "read_file",
"arguments": "{\"path\":\"config.yaml\",\"offset\":1,\"limit\":500}"
}
},
{
"id": "call_search_001",
"type": "function",
"function": {
"name": "search_files",
"arguments": "{\"path\":\".\",\"pattern\":\"*.md\",\"target\":\"files\",\"limit\":50}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_read_001",
"name": "read_file",
"content": "{\"success\":true,\"path\":\"config.yaml\",\"content\":\"model:\\n provider: agents\\n default: Qwen3.6-Plus\\n\",\"total_lines\":3}"
},
{
"role": "tool",
"tool_call_id": "call_search_001",
"name": "search_files",
"content": "{\"success\":true,\"matches\":[\"README.md\",\"CHANGELOG.md\",\"docs/部署说明.md\"],\"count\":3}"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "read_file",
"description": "读取文本文件内容。",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "要读取的文件路径。"},
"offset": {"type": "integer", "description": "从第几行开始读取。", "default": 1},
"limit": {"type": "integer", "description": "最多读取多少行。", "default": 500}
},
"required": ["path"],
"additionalProperties": false
}
}
},
{
"type": "function",
"function": {
"name": "search_files",
"description": "按文件名模式查找文件。",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "搜索根目录。"},
"pattern": {"type": "string", "description": "文件名匹配模式。"},
"target": {"type": "string", "enum": ["files", "content"], "description": "搜索文件名或文件内容。"},
"limit": {"type": "integer", "description": "最多返回多少项。", "default": 50}
},
"required": ["path", "pattern", "target"],
"additionalProperties": false
}
}
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "将完整内容写入文件,并覆盖已有内容。",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "目标文件路径。"},
"content": {"type": "string", "description": "要写入的完整文件内容。"}
},
"required": ["path", "content"],
"additionalProperties": false
}
}
}
],
"tool_choice": "auto",
"parallel_tool_calls": true,
"max_tokens": 4096,
"temperature": 0.2,
"top_p": 1,
"n": 1,
"stream": false
}
注意:
- 每个
tool消息的tool_call_id必须和对应调用的id一致; - 两个工具调用必须有两个工具结果;
- 工具结果可以是 JSON 字符串,也可以是协议允许的结构化内容;
- 工具结果会成为下一轮输入 token 的一部分;
- 工具结果过大时,应用应分页、摘要或截断,而不是无限塞入上下文。
6.5 第二轮响应:模型调用写文件工具
模型已经获得配置内容和文件列表,现在可以生成报告并请求写入:
{
"id": "chatcmpl-003",
"object": "chat.completion",
"created": 1784343222,
"model": "example-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_write_001",
"type": "function",
"function": {
"name": "write_file",
"arguments": "{\"path\":\"report.md\",\"content\":\"# 分析报告\\n\\n当前模型提供商为 agents,默认模型为 Qwen3.6-Plus。\\n\\n当前目录包含 3 个 Markdown 文件:README.md、CHANGELOG.md 和 docs/部署说明.md。\\n\"}"
}
}
]
},
"finish_reason": "tool_calls",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 831,
"completion_tokens": 117,
"total_tokens": 948
}
}
应用执行:
{
"name": "write_file",
"arguments": {
"path": "report.md",
"content": "# 分析报告\n\n当前模型提供商为 agents,默认模型为 Qwen3.6-Plus。\n\n当前目录包含 3 个 Markdown 文件:README.md、CHANGELOG.md 和 docs/部署说明.md。\n"
}
}
工具结果:
{
"success": true,
"path": "report.md",
"bytes_written": 198
}
6.6 第三轮:回填写入结果并请求最终回答
{
"model": "example-model",
"messages": [
{
"role": "system",
"content": "你是一个文件分析助手。需要读取或修改文件时必须使用工具。"
},
{
"role": "user",
"content": "读取 config.yaml,同时列出当前目录中的 Markdown 文件,然后把分析结果写入 report.md。"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_read_001",
"type": "function",
"function": {
"name": "read_file",
"arguments": "{\"path\":\"config.yaml\",\"offset\":1,\"limit\":500}"
}
},
{
"id": "call_search_001",
"type": "function",
"function": {
"name": "search_files",
"arguments": "{\"path\":\".\",\"pattern\":\"*.md\",\"target\":\"files\",\"limit\":50}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_read_001",
"name": "read_file",
"content": "{\"success\":true,\"path\":\"config.yaml\",\"content\":\"model:\\n provider: agents\\n default: Qwen3.6-Plus\\n\",\"total_lines\":3}"
},
{
"role": "tool",
"tool_call_id": "call_search_001",
"name": "search_files",
"content": "{\"success\":true,\"matches\":[\"README.md\",\"CHANGELOG.md\",\"docs/部署说明.md\"],\"count\":3}"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_write_001",
"type": "function",
"function": {
"name": "write_file",
"arguments": "{\"path\":\"report.md\",\"content\":\"# 分析报告\\n\\n当前模型提供商为 agents,默认模型为 Qwen3.6-Plus。\\n\\n当前目录包含 3 个 Markdown 文件:README.md、CHANGELOG.md 和 docs/部署说明.md。\\n\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_write_001",
"name": "write_file",
"content": "{\"success\":true,\"path\":\"report.md\",\"bytes_written\":198}"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "read_file",
"description": "读取文本文件内容。",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "要读取的文件路径。"},
"offset": {"type": "integer", "description": "从第几行开始读取。", "default": 1},
"limit": {"type": "integer", "description": "最多读取多少行。", "default": 500}
},
"required": ["path"],
"additionalProperties": false
}
}
},
{
"type": "function",
"function": {
"name": "search_files",
"description": "按文件名模式查找文件。",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "搜索根目录。"},
"pattern": {"type": "string", "description": "文件名匹配模式。"},
"target": {"type": "string", "enum": ["files", "content"], "description": "搜索文件名或文件内容。"},
"limit": {"type": "integer", "description": "最多返回多少项。", "default": 50}
},
"required": ["path", "pattern", "target"],
"additionalProperties": false
}
}
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "将完整内容写入文件,并覆盖已有内容。",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "目标文件路径。"},
"content": {"type": "string", "description": "要写入的完整文件内容。"}
},
"required": ["path", "content"],
"additionalProperties": false
}
}
}
],
"tool_choice": "auto",
"parallel_tool_calls": true,
"max_tokens": 4096,
"temperature": 0.2,
"top_p": 1,
"n": 1,
"stream": false
}
6.7 第三轮响应:模型给用户最终回答
{
"id": "chatcmpl-004",
"object": "chat.completion",
"created": 1784343224,
"model": "example-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "分析完成,结果已经写入 report.md。"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 972,
"completion_tokens": 16,
"total_tokens": 988
}
}
完整循环如下:
7. 并行工具与依赖工具
7.1 可以并行的工具
如果两个调用互不依赖,模型可以在一个 tool_calls 数组中同时返回:
读取 A 文件
读取 B 文件
应用并行执行后,将两个结果一起回填,可以减少一次模型往返。
7.2 不能提前并行的工具
如果后一个调用需要前一个结果,就必须分轮:
先搜索订单号
再根据搜索结果中的 customer_id 查询客户
第一轮模型尚不知道 customer_id,无法可靠构造第二个工具的参数。正确流程是:
模型调用 search_order
→ 应用回填订单结果
→ 模型读取 customer_id
→ 模型调用 get_customer
→ 应用回填客户结果
→ 模型回答
不要让应用猜测依赖参数,也不要把模型没有明确请求的高风险操作自动串联执行。
8. finish_reason 的处理
Hermes等 Agent 框架通常会把不同供应商的结束原因归一化为以下主要类型:
| 值 | 含义 | 客户端处理 |
|---|---|---|
stop |
正常结束或命中停止条件 | 展示内容 |
tool_calls |
模型请求调用工具 | 校验并执行工具,然后继续调用模型 |
length |
达到输出或上下文相关限制 | 普通文本可续写;不完整工具参数不可执行 |
content_filter |
被安全策略终止 | 提示用户并避免执行不完整操作 |
上游原始值可能包括:
function_call
max_tokens
end_turn
tool_use
STOP
MAX_TOKENS
SAFETY
incomplete
应用层应通过 provider adapter 统一处理,而不是让所有业务代码分别适配每个厂商。
9. 工具参数为什么会被截断
工具调用 JSON 本身属于模型输出。例如:
{
"name": "write_file",
"arguments": {
"path": "index.html",
"content": "完整HTML内容"
}
}
如果完整参数需要 20K tokens,而本轮最多输出 8K,服务端可能返回已经生成的前 8K,并标记 finish_reason: length。此时参数可能变成:
{"path":"index.html","content":"<!DOCTYPE html><html>...中途停止
这不是合法 JSON,应用无法确认:
- 字符串在哪里结束;
- 转义字符是否完整;
- 后续是否还有其他参数;
- 文件内容是否完整;
- 模型是否计划继续其他工具调用。
因此不能执行不完整工具参数。普通文本可以显示部分结果或请求续写,但工具调用具有副作用,必须完整解析和校验后才能执行。
大文件更稳妥的策略是:
- 提供分块写入或追加工具;
- 控制每块大小;
- 每块使用独立、完整的工具调用;
- 最后读取文件头尾或计算哈希进行校验;
- 不依赖无限提高单次输出上限。
10. MCP 与 Function Call 的关系
MCP(Model Context Protocol)和模型 Function Call 处于不同层:
模型侧:Function Call / Tool Call
应用侧:工具注册、参数校验、权限控制和路由
MCP侧:JSON-RPC tools/list、tools/call
模型通常不知道一个工具是内置 Python 函数、HTTP 接口还是 MCP Server 提供的。对模型来说,它们都可以被转换成统一的工具 Schema。
10.1 MCP 初始化
建立连接后,MCP Client和Server会先协商协议能力。请求示例:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {}
},
"clientInfo": {
"name": "example-agent-host",
"version": "1.0.0"
}
}
}
响应示例:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {},
"prompts": {}
},
"serverInfo": {
"name": "database-mcp-server",
"version": "2.1.0"
}
}
}
随后客户端发送初始化完成通知:
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
10.2 查询 MCP 工具
MCP Client请求工具列表:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}
MCP Server返回:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "query_database",
"description": "执行只读SQL查询并返回结果。",
"inputSchema": {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "只允许SELECT语句。"
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 1000,
"default": 100
}
},
"required": ["sql"],
"additionalProperties": false
}
}
],
"nextCursor": null
}
}
10.3 将 MCP 工具转换为模型工具
AI应用将MCP的 inputSchema 转换成模型接口需要的 parameters:
{
"type": "function",
"function": {
"name": "query_database",
"description": "执行只读SQL查询并返回结果。",
"parameters": {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "只允许SELECT语句。"
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 1000,
"default": 100
}
},
"required": ["sql"],
"additionalProperties": false
}
}
}
如果不同 MCP Server存在同名工具,Host通常需要增加命名空间,例如:
sales_db__query_database
analytics_db__query_database
模型看到的是Host暴露后的唯一名称,Host保存该名称与具体 MCP Server、原始工具名之间的映射。
10.4 模型请求调用 MCP 工具
用户请求:
查询最近创建的两个用户。
发送给模型:
{
"model": "example-model",
"messages": [
{
"role": "system",
"content": "你是数据库分析助手,只能执行只读查询。"
},
{
"role": "user",
"content": "查询最近创建的两个用户。"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "query_database",
"description": "执行只读SQL查询并返回结果。",
"parameters": {
"type": "object",
"properties": {
"sql": {"type": "string", "description": "只允许SELECT语句。"},
"limit": {"type": "integer", "minimum": 1, "maximum": 1000, "default": 100}
},
"required": ["sql"],
"additionalProperties": false
}
}
}
],
"tool_choice": "auto",
"parallel_tool_calls": true,
"max_tokens": 2048,
"temperature": 0,
"stream": false
}
模型返回:
{
"id": "chatcmpl-mcp-001",
"object": "chat.completion",
"created": 1784343230,
"model": "example-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_mcp_001",
"type": "function",
"function": {
"name": "query_database",
"arguments": "{\"sql\":\"SELECT id, name, created_at FROM users ORDER BY created_at DESC LIMIT 2\",\"limit\":2}"
}
}
]
},
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 231,
"completion_tokens": 58,
"total_tokens": 289
}
}
10.5 Host通过 MCP执行工具
AI应用解析并校验模型参数后,向对应 MCP Server发送 tools/call:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "query_database",
"arguments": {
"sql": "SELECT id, name, created_at FROM users ORDER BY created_at DESC LIMIT 2",
"limit": 2
}
}
}
MCP Server返回:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"rows\":[{\"id\":102,\"name\":\"王明\",\"created_at\":\"2026-07-18T10:30:00Z\"},{\"id\":101,\"name\":\"李华\",\"created_at\":\"2026-07-18T09:15:00Z\"}],\"count\":2}"
}
],
"structuredContent": {
"rows": [
{"id": 102, "name": "王明", "created_at": "2026-07-18T10:30:00Z"},
{"id": 101, "name": "李华", "created_at": "2026-07-18T09:15:00Z"}
],
"count": 2
},
"isError": false
}
}
MCP工具结果可以包含文本、图片、音频、嵌入资源或结构化内容,具体取决于协议版本和客户端能力。Host应选择模型能够理解的表示形式,并控制结果大小。
10.6 将 MCP结果映射为模型的 tool 消息
第二轮模型请求:
{
"model": "example-model",
"messages": [
{
"role": "system",
"content": "你是数据库分析助手,只能执行只读查询。"
},
{
"role": "user",
"content": "查询最近创建的两个用户。"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_mcp_001",
"type": "function",
"function": {
"name": "query_database",
"arguments": "{\"sql\":\"SELECT id, name, created_at FROM users ORDER BY created_at DESC LIMIT 2\",\"limit\":2}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_mcp_001",
"name": "query_database",
"content": "{\"rows\":[{\"id\":102,\"name\":\"王明\",\"created_at\":\"2026-07-18T10:30:00Z\"},{\"id\":101,\"name\":\"李华\",\"created_at\":\"2026-07-18T09:15:00Z\"}],\"count\":2}"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "query_database",
"description": "执行只读SQL查询并返回结果。",
"parameters": {
"type": "object",
"properties": {
"sql": {"type": "string", "description": "只允许SELECT语句。"},
"limit": {"type": "integer", "minimum": 1, "maximum": 1000, "default": 100}
},
"required": ["sql"],
"additionalProperties": false
}
}
}
],
"tool_choice": "auto",
"parallel_tool_calls": true,
"max_tokens": 2048,
"temperature": 0,
"stream": false
}
模型最终返回:
{
"id": "chatcmpl-mcp-002",
"object": "chat.completion",
"created": 1784343232,
"model": "example-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "最近创建的两位用户是:王明(ID 102,10:30创建)和李华(ID 101,09:15创建)。"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 364,
"completion_tokens": 39,
"total_tokens": 403
}
}
MCP完整时序:
10.7 MCP不只有工具
MCP还包含其他能力:
| MCP能力 | 作用 | 是否一定经过模型工具调用 |
|---|---|---|
tools |
执行动作或查询 | 通常会映射为 Function Call |
resources |
提供文件、文档、数据库结构等上下文资源 | 不一定,Host可主动读取后加入上下文 |
prompts |
提供可复用提示模板 | 不等同于 Function Call |
sampling |
Server请求Host代为调用模型 | 调用方向与普通工具相反 |
因此,“MCP工具通常通过 Function Call触发”是正确的,但“MCP等于 Function Call”并不准确。
11. 流式返回中的工具调用
当 stream: true 时,普通文本和工具参数可能被拆成多个增量片段。示意:
data: {"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_1","function":{"name":"write_file","arguments":"{\"path\""}}]}}]}
data: {"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":":\"a.txt\",\"content\":"}}]}}]}
data: {"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"hello\"}"}}]}}]}
data: {"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}
data: [DONE]
客户端必须:
- 按
choice.index区分候选回答; - 按
tool_calls[index]或工具调用id聚合片段; - 拼接完整
function.arguments; - 等待流结束并确认
finish_reason; - 对完整参数执行 JSON 解析和 Schema 校验;
- 绝不能在参数仍处于流式生成时提前执行有副作用的工具。
12. 工具调用的安全边界
模型返回工具调用不代表应用必须执行。可靠的Agent Host至少应检查:
- 工具是否在当前会话允许列表中;
- 工具名是否来自可信注册表;
- 参数是否为合法 JSON;
- 参数是否符合 JSON Schema;
- 文件路径是否越界;
- SQL是否只读;
- 命令是否需要用户审批;
- 是否触发权限提升;
- 是否重复执行相同副作用操作;
- 工具参数是否因输出长度限制而截断;
- MCP Server是否可信、连接是否仍有效;
- 工具结果是否包含需要隐藏的密钥或个人信息。
建议将工具分为:
只读工具:搜索、读取、查询
低风险写工具:写工作区文件、创建草稿
高风险工具:删除、付款、发消息、执行系统命令、修改生产数据
高风险操作应要求明确审批,不能只凭模型生成的工具调用自动执行。
13. 常见异常与处理策略
13.1 上下文窗口超限
表现:
context_length_exceeded
maximum context length exceeded
input tokens exceed model context window
处理:压缩历史、删除无关工具结果、分页读取文件、减少工具定义,或者换用更大上下文模型。
13.2 普通文本达到输出上限
表现:
{
"message": {"role": "assistant", "content": "未完成的部分文本"},
"finish_reason": "length"
}
处理:保留已有文本,追加“从截断处继续”的消息,再发起一轮请求;最终需要去重和拼接。
13.3 工具参数达到输出上限
表现:finish_reason: length,且 function.arguments 不是完整 JSON。
处理:拒绝执行;提示模型缩小参数、分页、分块写入或改用更合适的工具。不能直接执行半截参数。
13.4 工具执行失败
工具失败也应作为 role: tool 返回模型:
{
"role": "tool",
"tool_call_id": "call_read_001",
"name": "read_file",
"content": "{\"success\":false,\"error\":\"File not found: config.yaml\",\"code\":\"ENOENT\"}"
}
模型可以根据错误改用其他路径、向用户澄清,或终止任务。不要把工具异常伪装成成功结果。
13.5 MCP调用失败
MCP协议错误可能出现在 JSON-RPC error 中:
{
"jsonrpc": "2.0",
"id": 3,
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"field": "sql"
}
}
}
工具自身的业务失败也可能通过 result.isError: true 返回。Host应区分:
- MCP传输或协议错误;
- 工具参数错误;
- 工具业务执行失败;
- 超时或连接中断;
- 用户取消。
14. Chat Completions 与 Responses API 的差异
本文示例主要使用 Chat Completions。Responses API的核心循环相同,但结构不同:
- 输入可能使用
input,而不是messages; - 输出放在
output数组中; - 工具调用可能表现为
function_callitem; - 工具结果可能使用
function_call_output; - 输出限制通常叫
max_output_tokens; - 未完成状态可能通过
status: incomplete和incomplete_details表示。
无论协议外形如何变化,核心状态机不变:
提交上下文和工具定义
→ 模型生成文本或工具调用
→ Host执行工具
→ 回填工具结果
→ 模型继续
→ 得到最终文本或终止状态
15. 实现一个Agent循环时的伪代码
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_input},
]
for iteration in range(MAX_ITERATIONS):
response = model.chat_completions.create(
model=MODEL,
messages=messages,
tools=tool_schemas,
tool_choice="auto",
parallel_tool_calls=True,
max_tokens=MAX_OUTPUT_TOKENS,
)
choice = response.choices[0]
assistant_message = choice.message
if choice.finish_reason == "length":
if assistant_message.tool_calls:
raise IncompleteToolCall("工具参数被截断,禁止执行")
messages.append(assistant_message)
messages.append({
"role": "user",
"content": "请从截断处继续,不要重复已有内容。",
})
continue
if assistant_message.tool_calls:
messages.append(assistant_message)
results = execute_validated_tool_calls(
assistant_message.tool_calls,
allow_parallel=True,
)
for call, result in results:
messages.append({
"role": "tool",
"tool_call_id": call.id,
"name": call.function.name,
"content": serialize_tool_result(result),
})
continue
return assistant_message.content
raise RuntimeError("达到最大Agent循环次数")
生产实现还需要增加:重试、超时、取消、审批、日志脱敏、上下文压缩、token预算、并发控制、幂等性、会话持久化和供应商适配。
16. 一句话总结
大模型对话的本质是一个受 token 预算约束的状态循环:应用把完整消息历史和工具定义提交给模型;模型返回普通文本或一个/多个结构化工具调用;应用执行内置工具或通过 MCP调用外部工具,再把每个结果作为 role: tool 回填;模型基于新增上下文继续工作,直到返回 finish_reason: stop、发生截断、被安全策略终止,或达到应用设置的循环上限。
更多推荐


所有评论(0)