大模型上下文窗口、Function Call 与 MCP 全流程

大模型聊天不只是“用户发送一句话,模型返回一句话”。一次完整请求通常会携带系统指令、历史消息、工具定义和工具执行结果;模型也可能不直接回答,而是先返回一个或多个工具调用,再由应用执行工具并把结果送回模型。

本文以 OpenAI 兼容的 Chat Completions 消息格式为主线,完整说明:

  • 输入 token、输出 token 与上下文窗口的关系;
  • systemdeveloperuserassistanttool 等消息角色;
  • choicesfinish_reasonusage 的含义;
  • 单个工具和多个工具的 Function Call 全流程;
  • 多个工具结果如何再次提交给大模型;
  • 有依赖关系的工具为什么需要分多轮调用;
  • MCP 工具如何映射为模型的 Function Call;
  • 流式调用、长度截断、工具异常与安全边界。

不同模型厂商的字段名称并不完全一致。本文使用的是常见 OpenAI 兼容格式,并在相关位置标注 Responses API、Anthropic 和 MCP 的差异。

1. 先看完整架构

一个支持工具调用的 AI 应用通常包含四层:

用户

AI 应用 / Agent Host

大模型 API

本地或内置工具

MCP Client

MCP Server

各层职责如下:

职责
用户 用自然语言描述目标,通常不直接构造工具调用 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 通常包括:

  1. 系统提示词;
  2. 开发者指令;
  3. 历史用户消息;
  4. 历史模型消息;
  5. 历史工具调用;
  6. 工具执行结果;
  7. 当前用户消息;
  8. 工具名称、描述和 JSON Schema;
  9. 服务商在协议层添加的其他内容。

工具定义也会占用输入上下文。工具越多、描述越长、参数 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 请求。实际使用时不应盲目发送所有可选字段,因为部分模型不支持 temperaturenparallel_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 应用随后负责:

  1. 确认工具存在;
  2. 解析参数 JSON;
  3. 按 JSON Schema 校验参数;
  4. 执行权限、安全和路径检查;
  5. 真正调用工具;
  6. 将结果序列化为 role: tool 消息;
  7. 再次调用模型。

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
  }
}

完整循环如下:

write_file search_files read_file 大模型 AI应用 用户 write_file search_files read_file 大模型 AI应用 用户 par [并行执行] 提交文件分析任务 messages + tools tool_calls[read_file, search_files] 读取 config.yaml 文件内容 查找 *.md 文件列表 原消息 + assistant.tool_calls + 两条tool结果 tool_calls[write_file] 写入 report.md 写入成功 完整历史 + 写入结果 最终文本回答 展示完成信息

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完整时序:

MCP Server MCP Client 大模型 Agent Host 用户 MCP Server MCP Client 大模型 Agent Host 用户 建立连接 initialize capabilities + serverInfo notifications/initialized tools/list MCP工具Schema 注册模型工具 查询最近两个用户 messages + 转换后的tools tool_calls[query_database] 路由工具名与参数 tools/call MCP CallToolResult 规范化工具结果 assistant.tool_calls + role=tool 最终回答 展示结果

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]

客户端必须:

  1. choice.index 区分候选回答;
  2. tool_calls[index] 或工具调用 id 聚合片段;
  3. 拼接完整 function.arguments
  4. 等待流结束并确认 finish_reason
  5. 对完整参数执行 JSON 解析和 Schema 校验;
  6. 绝不能在参数仍处于流式生成时提前执行有副作用的工具。

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_call item;
  • 工具结果可能使用 function_call_output
  • 输出限制通常叫 max_output_tokens
  • 未完成状态可能通过 status: incompleteincomplete_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、发生截断、被安全策略终止,或达到应用设置的循环上限。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐