从Demo到生产:Agent核心技术全景解析——MCP、Skill、Function Calling与大规模工具治理

本文面向有一定LLM开发基础的工程师,从底层交互原理到生产级架构设计,完整拆解Agent生态的核心组件、交互逻辑与大规模落地难点。全文贯穿「LLM是推理引擎,Agent是调度框架」的核心认知,所有知识点均对应工业界落地实践。


阅读指南

  • 新手开发者:建议从第1-3章按顺序阅读,建立完整的认知框架
  • 有经验的Agent开发者:可直接跳转至第4章「生产级难题:大规模工具治理」,这是从Demo到生产的核心门槛
  • 架构师:重点关注第4章的并发调度与返洪治理体系,以及各方案的ROI权衡

术语表(统一认知)

术语 定义
LLM 大语言模型,仅负责文本/结构化输出推理,无执行能力
Agent 运行在LLM之上的调度框架,负责工具调用、流程控制、结果回填
Tool 可被调用的能力单元抽象,分为本地Tool和MCP Tool
MCP Model Context Protocol,Anthropic推出的工具远程调用标准协议
Skill Prompt层的场景化剧本,用于约束LLM的行为流程
Function Calling LLM调用外部工具的标准机制(OpenAI命名),Anthropic称为Tool Use
Handoff 多Agent架构中,父Agent将任务委托给子Agent的机制
Context Window LLM单次推理可处理的最大Token长度限制

第一章:基础认知——LLM与Agent的边界(核心中的核心)

绝大多数Agent开发者的第一个误区是:认为Agent的能力是LLM自带的。事实上,二者是完全分离的:

LLM是纯推理引擎,Agent是调度框架,二者通过Function Calling接口通信

LLM本身没有任何执行能力,它既不知道什么是文件、什么是数据库,也不知道如何调用外部API。它能接触到的只有两个结构化输入:

  1. messages:对话历史,包含system(系统提示)、user(用户输入)、assistant(LLM历史输出)、tool(工具返回结果)四种角色
  2. tools:工具列表,每个工具包含name(名称)、description(功能描述)、parameters(参数JSON Schema)

除此之外,LLM对外部环境一无所知——它不知道工具是本地函数还是远程服务,不知道Skill的存在,甚至不知道自己是运行在Agent框架中。

1.1 单轮Agent-LLM交互全流程(裸API版)

我们用最原始的OpenAI API调用,还原一次完整的工具调用流程,这也是所有Agent框架的底层逻辑:

Step 1:Agent接收用户请求,组装输入

用户提问:北京天气咋样,顺便查订单888
Agent框架完成三件事:

  • 将系统提示(含Agent身份、全局规则)和Skill内容拼入system消息
  • 将本地Tool和MCP Tool统一注册到tools数组
  • 将用户问题拼入user消息

最终发给LLM的请求如下(简化版):

{
  "model": "gpt-4o",
  "messages": [
    {
      "role": "system",
      "content": "你是智能助手。\n\n[Skill: 天气查询] 用户问天气时必须调用get_weather工具\n[Skill: 订单查询] 用户问订单时必须调用query_order工具"
    },
    {
      "role": "user",
      "content": "北京天气咋样,顺便查订单888"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "查询指定城市天气,需传入city参数",
        "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}
      }
    },
    {
      "type": "function",
      "function": {
        "name": "query_order",
        "description": "查询订单详情,需传入order_id参数",
        "parameters": {"type": "object", "properties": {"order_id": {"type": "string"}}}
      }
    }
  ],
  "tool_choice": "auto"
}
Step 2:LLM推理,输出工具调用请求

LLM根据system中的Skill规则和tools中的描述,判断需要调用两个工具,输出结构化tool_calls(而非自然语言):

{
  "choices": [{
    "finish_reason": "tool_calls",
    "message": {
      "role": "assistant",
      "tool_calls": [
        {
          "id": "call_weather_001",
          "type": "function",
          "function": {"name": "get_weather", "arguments": "{\"city\": \"北京\"}"}
        },
        {
          "id": "call_order_001",
          "type": "function",
          "function": {"name": "query_order", "arguments": "{\"order_id\": \"888\"}"}
        }
      ]
    }
  }]
}

⭐ 注意:此时LLM的生成过程已经终止,它在等待Agent框架执行工具并返回结果,不会直接回答用户问题。

Step 3:Agent执行工具,回填结果

Agent框架拦截tool_calls,根据工具名称路由到对应的执行器:

  • get_weather是本地Tool,直接调用Python函数get_weather("北京"),返回15℃, 晴
  • query_order是MCP Tool,通过MCP Client向远程MCP Server发送JSON-RPC请求,返回已发货,顺丰SF123

框架将结果包装为tool角色的消息,必须携带对应的tool_call_id(LLM通过这个ID匹配调用请求和返回结果),追加到messages中:

{
  "role": "tool",
  "tool_call_id": "call_weather_001",
  "content": "15℃, 晴"
}
{
  "role": "tool",
  "tool_call_id": "call_order_001",
  "content": "已发货,顺丰SF123"
}
Step 4:LLM二次推理,生成最终回答

Agent将更新后的messages再次发给LLM,此时LLM已经拿到了工具返回结果,finish_reason变为stop,输出自然语言回答:

北京今天15℃晴,体感舒适。您的订单888已发货,由顺丰承运,运单号SF123,可通过顺丰小程序追踪物流。

至此,一次完整的Agent-LLM交互闭环结束。这个流程会循环执行,直到LLM不再输出tool_calls,这就是所谓的Agent Loop


第二章:核心组件辨析——Skill、MCP、Tool的三层关系

这三个概念是Agent生态中最容易混淆的部分,我们从「LLM视角」和「Agent框架视角」两个维度拆解:

⭐ 三者处于完全不同的层级:Tool是能力抽象层,MCP是协议传输层,Skill是Prompt编排层

2.1 ⭐Tool:能力单元的抽象

Tool是所有可被调用的能力的统称,是连接LLM和外部世界的桥梁。无论工具的实现方式如何,在LLM视角中都是tools数组中的一个条目:

  • 本地Tool:运行在Agent框架进程内的函数,比如read_filebash,框架直接调用,无网络开销
  • MCP Tool:运行在远程MCP Server上的工具,框架通过MCP协议转发调用请求,支持跨进程、跨机器部署

LLM完全无法区分这两类Tool的差异,它只根据description判断是否需要调用,根据parameters填充参数。

2.2 ⭐MCP:工具的远程调用标准协议

在MCP出现之前,每个Agent框架都需要为每个外部工具编写适配代码,工具开发者也需要为不同框架开发不同SDK,形成了严重的生态碎片。
MCP解决了这个问题:它定义了统一的JSON-RPC协议,规定了工具发现(tools/list)、工具调用(tools/call)、进度通知(notifications/progress)等标准方法。只要工具实现了MCP Server,任何兼容MCP的Agent框架都可以直接调用,无需额外适配。
⭐ MCP的核心价值是「解耦」:工具开发者不需要关心调用方的框架,Agent框架不需要关心工具的实现细节,二者通过标准协议通信。

2.3 ⭐Skill:Prompt层的场景化剧本

Skill是Agent框架层的概念,本质是一段增强Prompt + 触发规则 + 步骤约束,用于解决两个问题:

  1. LLM选Tool不准:当注册的工具过多时,LLM容易选错工具,Skill可以通过明确的规则引导LLM决策
  2. 复杂流程难以维护:多步骤任务(比如代码Review需要先diff、再lint、最后生成报告),如果完全依赖LLM自主决策,稳定性极差,Skill可以将流程固化

Skill不会被LLM识别为一个独立的Tool,而是被Agent框架拼入system消息中,作为LLM推理的上下文。例如一个code-review.skill的内容可能如下:

# Code Review Skill
## 触发条件
用户提及「review」「代码审查」「检查代码」
## 执行步骤
1. 调用`git_diff`工具获取最近一次提交的变更
2. 调用`lint`工具检查代码规范问题
3. 结合上述结果,按以下模板输出报告:
   - 变更概述
   - 规范问题(如有)
   - 性能风险(如有)
   - 改进建议
## 约束
- 禁止修改用户代码
- 报告必须使用中文,避免技术黑话

⭐ Skill的本质是「给LLM的干活说明书」,它不改变LLM的调用能力,只改变LLM的决策逻辑。

2.4 三者的关联与映射

组件 LLM视角 Agent框架视角 作用
Skill system消息中的一段文本 Prompt编排模块,负责注入和触发 约束LLM的决策流程
MCP tools数组中的普通条目 工具注册模块,负责拉取MCP Server的工具列表 标准化远程工具调用
Tool tools数组中的条目 工具执行模块,负责路由和执行 提供具体的能力单元

第三章:多Agent架构——从单兵到军团

当任务复杂度提升,单个Agent会出现上下文膨胀、职责混乱、成本失控等问题,此时需要引入多Agent架构。首先要明确一个核心区别:

子Agent是独立的LLM会话实例,Skill是同一会话下的Prompt切换

3.1 多Agent的四种经典拓扑

1. 监督者模式(最常用)
        监督者Agent
       /    |    \
   订单Agent 退款Agent 物流Agent
  • 只有监督者Agent直接与用户交互,负责意图识别和任务分发
  • 子Agent只负责单一领域的任务,完成后将结果返回给监督者
  • 子Agent之间不直接通信,所有交互通过监督者中转
  • 适用场景:客服分流、工单分类等职责清晰的场景
  • 代表框架:OpenAI Agents SDK的handoffs机制
2. 分层模式
监督者
├── 订单Agent
│     ├── 查询Agent
│     └── 退款Agent
└── 技术Agent
      ├── 代码Review Agent
      └── 测试Agent
  • 监督者之下有多层子Agent,每层只负责下一层的任务分发
  • 适合业务逻辑本身有层级结构的场景,比如企业内部的部门-小组架构
  • 优势是职责边界清晰,缺点是层级过深会导致延迟增加
3. 网状模式(群聊模式)
代码Agent ↔ 审查Agent ↔ 测试Agent
   ↑                   ↑
   └───── 共享消息池 ───┘
  • 没有固定的监督者,多个Agent在同一个共享消息池中轮流发言
  • 每轮由一个Agent发言,其他Agent可以看到全部历史消息,下一个发言者由规则或专门的「发言选择器」决定
  • 适用场景:头脑风暴、代码评审等多角色协作场景
  • 代表框架:AutoGen的GroupChat
4. 共享黑板模式
AgentA  AgentB  AgentC
   \      |      /
    └─ 共享State ─┘
  • 没有直接的Agent间通信,所有Agent读写同一份共享State
  • 框架的调度器决定每轮由哪个Agent执行,执行完成后更新State
  • 优势是状态可追溯、支持断点恢复,缺点是调度逻辑复杂
  • 代表框架:LangGraph的StateGraph

3.2 Agent间的交互方式

1. Handoff(委托)

监督者Agent的LLM输出transfer_to_xxx的结构化指令,Agent框架拦截后,创建新的子Agent实例,将原任务的摘要传递给子Agent,子Agent执行完成后返回结果给监督者。
⭐ 核心是上下文隔离:子Agent看不到监督者的全部历史消息,只接收必要的任务信息,避免上下文膨胀。

2. Tool伪装(轻量多Agent)

将子Agent封装为一个Tool,注册到主Agent的tools列表中。主Agent的LLM将子Agent视为普通Tool调用,框架收到调用请求后,创建子Agent实例执行任务,返回结果作为Tool Result。
这种方式的优势是实现简单,缺点是不支持子Agent反向调用主Agent,适合轻量级的职责拆分。

3. 共享State

Agent之间通过读写共享State传递信息,每个Agent只关注State中自己需要的字段,执行完成后更新对应字段。这种方式适合长流程、多状态的任务,比如科研助理、复杂数据处理 pipeline。


第四章:生产级难题——大规模工具治理(核心干货)

前面的内容都是Demo级的基础,当工具数量从几个增长到几百个,单次工具调用返回结果从几百字增长到几万字时,所有Demo级的方案都会失效。本章内容是工业界落地的核心经验。

4.1 ⭐并发调度:从一把梭到精细化控制

LLM一次吐出20个tool_calls是常见现象,但无脑并发(asyncio.gather)会带来灾难性的问题:依赖冲突、资源耗尽、成本飙升。大厂的解决方案是分层调度

1. 依赖解析:构建DAG(有向无环图)

框架首先对tool_calls做依赖分析,区分三类工具:

  • 只读工具read_filegrepquery_db等,不修改外部状态,可安全并发
  • 变更工具write_fileeditsend_email等,修改外部状态,必须串行执行,避免竞态条件
  • 依赖工具:比如read_file(A)的输出是write_file(B)的输入,必须按顺序执行

例如,24个tool_calls中,18个是只读工具,6个是变更工具,框架会将18个只读工具分成2批并发执行,6个变更工具串行执行,相比全串行效率提升8倍,相比全并发避免了数据冲突。
⭐ 关键优化:减少LLM Round-Trip次数。每一次LLM调用都需要将全部上下文传给LLM,分批处理的核心价值不是提高工具执行的并发度,而是减少LLM调用的次数,从而降低成本和延迟。

2. Tool Necessity Score(TNS):过滤低价值调用

当注册的工具过多时,LLM容易出现「工具滥用」的问题——明明不需要调用工具,却频繁发起调用。大厂会为每个工具计算TNS分数,基于三个维度:

  • 历史调用成功率:成功率低于30%的工具,TNS分数降低
  • 响应延迟:平均响应时间超过10s的工具,TNS分数降低
  • 用户反馈:用户标记为「无用调用」的工具,TNS分数降低

当LLM发起TNS分数低于阈值的工具调用时,框架会直接拦截,并返回「工具暂时不可用」的模拟结果,避免无效调用。

3. 超时、重试、熔断三件套

生产环境的工具调用必须具备容错能力,大厂普遍采用三态生命周期管理:

PENDING → EXECUTING → FINALIZED
                ↓
              FAILED / TIMEOUT
  • 超时控制:不同类型的工具设置不同的超时时间,用户可见的工具(如查订单)超时设为3s,内部辅助工具设为30s,日志类工具设为60s
  • 重试策略:只读工具支持2次重试,变更工具不支持重试(避免重复修改状态)
  • 熔断机制:当某个工具的连续失败率超过10%时,自动熔断5分钟,期间所有对该工具的调用直接返回失败,避免级联故障
4. 异步解耦:避免Agent Loop阻塞

如果工具执行时间长达30s,Agent Loop会被阻塞30s,严重影响用户体验。大厂的解决方案是将工具执行和Agent Loop解耦:

  1. Agent收到LLM的tool_calls后,将调用请求写入WAL(预写日志),发送到消息队列(如Kafka)
  2. 专门的Tool Executor集群异步执行工具调用,执行完成后将结果写入Redis
  3. Agent Loop定期检查Redis中是否有对应结果,有则回填并继续循环,无则返回「任务处理中」的临时回复给用户

这种方式下,Agent不会被长时间运行的工具阻塞,支持高并发场景。

4.2 ⭐返洪治理:从烧Token到精准控流

用户之前提出的「先summary不是浪费Token吗?」是一个非常典型的误区。事实上,不控制工具返回结果的规模,会带来更严重的成本和质量问题:

  • 超过Context Window上限,直接导致API调用失败,前面所有的输入成本全部白费
  • 即使没超过上限,过长的上下文会导致LLM推理速度下降、幻觉概率上升,后续对话质量大幅降低

大厂的解决方案是四层防御体系,从根源上避免长结果进入Agent Loop:

第1层:源头拦截——MCP Server侧分页

⭐ 这是最有效的一层防御,在工具返回结果之前就把规模控制住。MCP协议明确要求,对于返回大量数据的工具(如SQL查询、日志拉取),必须支持游标分页:

{
  "summary": {"total": 428, "failed_count": 12},
  "top_items": [
    {"id": "INC-1842", "status": "open", "created_at": "2026-01-01"}
  ],
  "next_cursor": "eyJpZCI6MTg0Mn0="
}
  • 第一层只返回统计摘要和Top N条核心数据,总数据量控制在1000Token以内
  • LLM根据摘要判断是否需要更多数据,如需则携带next_cursor发起第二次调用,获取下一批数据
  • 这种方式下,80%的请求都不需要获取全量数据,从源头避免了返洪问题
第2层:单条截断——Micro-Compact

对于无法通过分页控制规模的工具(如读取单个大文件),框架会在工具返回后、回填LLM之前做截断:

  • 单条Tool Result超过10000字符时,保留首尾各2000字符,中间插入[...内容过长已截断...]
  • ⭐ 关键规则:错误结果不截断。如果工具返回的是错误信息,必须保留完整内容,否则LLM无法诊断错误原因
  • 代表实现:OpenAgent SDK的micro_compact_messages函数,阿里AgentScope的tool_result_limit配置
第3层:上下文压缩——Auto-Compact

即使单条Tool Result控制住了,随着对话轮次增加,整个messages的长度也会逐渐逼近Context Window上限。框架会定期触发Auto-Compact:

  • 当上下文使用率达到70%时,触发压缩
  • 将历史对话(除了最近的20%内容)发送给LLM,要求压缩为一条摘要,替换原有的历史消息
  • 保留最近N轮对话的完整内容,避免信息断层
  • 代表实现:Google ADK的EventsCompactionConfig,设置compaction_interval=3(每3轮压缩一次),overlap_size=1(保留最近1轮完整内容)
第4层:超大卸载——Offload到磁盘

对于超过1MB的超大型结果(如50MB的日志文件),即使截断也无法满足要求,此时框架会将结果卸载到本地磁盘,只将文件路径回填给LLM:

{
  "role": "tool",
  "tool_call_id": "call_log_001",
  "content": "日志文件过大,已保存至/path/to/log.txt,可使用read_file工具指定offset和limit读取特定片段"
}

LLM后续如果需要查看具体内容,可以调用read_file工具,指定偏移量和长度,按需读取,避免一次性加载全部内容。

4.3 成本账:为什么Summary是值得的?

回到用户之前的问题:「先summary不是浪费Token吗?」
我们假设一个场景:Context Window剩余50k Token,工具返回结果50k Token,后续还有10轮对话。

  • 不Summary的成本
    • 直接塞入50k Token,导致后续每轮对话都需要额外处理这50k Token,10轮对话额外消耗500k Token
    • 上下文过长导致LLM推理速度下降50%,用户体验变差
    • 极端情况下超过Context Window上限,任务失败,前面所有成本全部白费
  • Summary的成本
    • 额外消耗50.5k Token(50k输入+0.5k输出),约0.26美元
    • 后续对话只需处理0.5k Token的摘要,速度和质量都有保障

显然,Summary的成本远低于不Summary的隐性成本,ROI极高。这也是为什么所有生产级Agent都必须内置上下文压缩能力的原因。


第五章:生产级最佳实践清单

最后,我们将前面的知识点浓缩为可落地的实践清单,方便读者直接应用到项目中:

Skill编写规范

  1. 开头明确「触发条件」,帮助框架精准召回,避免误触发
  2. 显式声明「覆盖规则」,比如「本场景忽略全局简洁回答要求,输出详细报告」,避免和Agent自带Prompt冲突
  3. 只写流程约束,不要在Skill中重复定义Tool的Schema,避免和MCP Server的定义不一致
  4. 优先使用按需召回模式,避免将所有Skill全量注入System Prompt,节省Token

MCP Server设计规范

  1. 所有返回大量数据的工具必须支持游标分页,默认返回摘要+Top N结果
  2. 工具描述必须清晰准确,避免歧义,LLM完全依赖描述选择工具
  3. 实现notifications/progress接口,长时间运行的任务定期发送进度通知,避免Agent误认为任务超时
  4. 错误返回必须包含明确的错误码和错误信息,方便LLM诊断问题

Agent调度配置建议

  1. 只读工具并发数不超过10,变更工具必须串行执行
  2. 设置Tool Necessity Score阈值,拦截低价值调用
  3. 为不同类型的工具设置差异化超时时间,变更工具禁用重试
  4. 开启Auto-Compact,设置触发阈值为70%,保留最近20%的上下文

成本控制技巧

  1. 子Agent使用小模型(如GPT-4o-mini),只有主Agent使用大模型,降低整体成本
  2. 非关键工具调用使用低优先级队列,错峰执行
  3. 定期分析工具调用日志,下线低使用率、低成功率的工具
  4. 对高频、稳定的工具调用结果做缓存,避免重复调用

结语

Agent开发的核心矛盾,是在LLM的「灵活性」和系统的「可控性」之间寻找平衡。Function Calling给了LLM调用外部世界的能力,Skill给了LLM遵循流程的能力,MCP给了工具标准化的能力,而并发调度和返洪治理则是让这些能力在生产环境中稳定运行的保障。
从Demo到生产,差的不是某一个神奇的技巧,而是一整套体系化的设计和工程实现。希望本文能帮助你在Agent开发的道路上少走弯路,真正把Agent的能力落地到业务场景中。


附录:推荐阅读

  1. Anthropic MCP官方文档:https://modelcontextprotocol.io/
  2. OpenAI Agents SDK文档:https://platform.openai.com/docs/agents
  3. LangGraph State Management:https://langchain-ai.github.io/langgraph/concepts/low_level/
  4. 阿里AgentScope 2.0技术白皮书:https://modelscope.cn/docs/AgentScope/intro
Logo

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

更多推荐