本文档假设读者已经了解 MCP 协议本身的调用机制(initialize 握手、tools/list、tools/call、stop_reason 循环判断等),不再重复讲解协议细节。这里聚焦的是:从一次裸的模型调用,到扣子/百炼这种平台化 Agent,中间每一步是为了解决什么问题、具体是怎么实现的。
演化总览
每一步都是在解决上一步遗留的具体工程问题,不是凭空设计出来的,下面逐个拆开讲。
阶段0:裸调用 一问一答,没有工具,没有循环
↓
阶段1:接入 MCP 工具接入标准化
↓
阶段2:加上循环 从"一次调用"变成"自主决策循环"(这一步开始叫 Agent)
↓
阶段3:配置化 Agent 定义从写死在代码里,变成数据库里的一条配置记录
↓
阶段4:服务化 配置被发布成一个可远程调用的 HTTP 接口(AK/令牌鉴权)
↓
阶段5:多租户运行时 执行引擎做成无状态共享服务,不为单个 Agent 常驻资源
↓
阶段6:状态外置 会话历史、执行现场从进程内存搬到外部存储,支持中断恢复
↓
阶段7:推理层独立 大模型推理单独拆成常驻共享集群,和执行引擎解耦
阶段 0 → 1:为什么要引入 MCP
阶段 0 的问题 :一个 Controller 直接拼 messages 发给 Claude API,模型只能凭自己已有的知识回答,接触不到外部实时数据(天气、订单、知识库)。如果想让模型能查天气,最直接的做法是在代码里写死一个 get_weather() 函数,塞进 tools 参数里------能用,但每加一个新工具就要改一遍代码、重新发布。如果这个工具还想给别的项目复用,得把代码复制一份过去,工具的实现和调用逻辑散落在各个业务代码里,没有统一的接入方式。
阶段 1 怎么解决:把"工具"这个概念从业务代码里剥离出来,做成独立进程/服务(MCP Server),用标准协议(MCP)暴露给任何 Host 使用。Host 只需要实现一次 MCP Client 逻辑,就能对接市面上所有遵循 MCP 协议的工具,工具本身可以被多个项目复用,不需要每个项目各写一份。
这一步解决的是 "工具接入的标准化和复用性" 问题,跟"要不要自主循环"完全无关------阶段 1 结束时,系统依然可能是"一次调用、最多调一次工具"这种简单模式。
阶段 1 → 2:为什么要加循环,这一步开始叫 Agent
阶段 1 的问题 :调用一次工具后,谁来决定"要不要再调下一个工具""什么时候该把结果整理成文字回复用户"?如果这个判断需要人工介入(比如工程师看到 stop_reason: tool_use 后手动写代码触发下一步、手动判断循环该不该停),系统就没办法自动化处理复杂任务(比如"先查城市编码,再用编码查天气,最后总结成一句话"这种多步骤任务)。
阶段 2 怎么解决 :把"要不要继续调工具、什么时候停"这个判断逻辑写成一个自动化循环------读 stop_reason 字段,是 tool_use 就自动执行工具并把结果喂回去,是 end_turn 就自动结束返回结果,全程不需要人工介入。这个自动化循环本身,就是 Agent 和普通"单次工具调用"的分界线。
这一步解决的是 "自主性" 问题:系统从"人工按步骤驱动"变成"模型自己驱动多步骤任务"。到这里为止,Agent 已经具备完整能力了,只是它还是写死在某个具体项目代码里的------比如你们的 Copilot,这套循环逻辑是 Copilot 专属的,换一个项目就得重新实现一遍。
阶段 2 → 3:为什么要配置化,这是走向平台的分水岭
阶段 2 的问题:Copilot 这类系统的循环逻辑、system prompt、工具列表,全部是代码里的常量。如果业务方想要一个"客服 Agent",另一个业务方想要一个"数据分析 Agent",两者的模型选择、提示词、挂载的工具完全不同------按阶段 2 的做法,每换一种 Agent 都要重新写一份代码、重新部署一套服务,普通业务人员完全没法参与,只能靠工程师一个个写。
阶段 3 怎么解决:把"一个 Agent 是什么"这件事,从"写在代码里的常量"抽象成"存在数据库里的一条配置记录"------模型选哪个、system prompt 内容、关联哪些工具(对应哪些 MCP Server 或平台内置插件)、挂哪个知识库,全部字段化存进一张表。执行循环的代码只写一份(通用执行引擎),运行时现读这条配置、现组装请求,而不是把这些信息硬编码在执行代码里。
这一步是 "执行逻辑"和"Agent 定义"解耦 的关键:执行引擎变成了可以服务任意 Agent 配置的通用组件,普通用户在控制台上填表单、点几下鼠标,就能"拼"出一个新的 Agent,不需要工程师写代码、不需要重新部署服务。这正是阿里云百炼、字节扣子这类平台"零代码/低代码搭建智能体"能力的底层实现原理。
阶段 3 → 4:配置怎么变成一个可以远程调用的服务
阶段 3 的问题:一条数据库配置记录,只在平台自己的控制台里能用(比如在网页上跟它对话调试),业务方的系统没办法通过代码去调用它,没法集成进自己的产品里。
阶段 4 怎么解决------"发布"这个动作具体做了什么:
- 给这条配置记录分配一个全局唯一标识(阿里云叫 APP_ID,扣子叫 Bot_ID),本质上就是这条数据库记录的主键,对外暴露出来;
- 生成一个固定的 HTTP Endpoint 路由,比如阿里云百炼的应用调用地址是
https://dashscope.aliyuncs.com/api/v1/apps/{APP_ID}/completion,路径里的{APP_ID}就是用来在这次请求里定位是哪条配置; - 配一层鉴权机制,防止任何人拿着 APP_ID 就能白嫖调用:
- 阿里云用 DASHSCOPE_API_KEY(AK),权限归属于某个业务空间,同空间内的 AK 可以调用空间下所有已发布的应用;
- 扣子用个人访问令牌(PAT,前缀
pat_,简单场景用,权限等同你的账号)、服务端专属令牌(SAT,前缀sat_,企业版专属,用于后端常驻服务)、或者标准 OAuth2 流程(应用先用私钥签发 JWT,拿 JWT 去换短期 access token,代表某个终端用户身份,实现多用户会话隔离)。
调用方发请求时,带上 APP_ID/Bot_ID + 鉴权凭证,平台服务端收到请求后,第一步就是拿这两个信息去查:这个凭证有没有权限调用这个配置、这个配置具体内容是什么。
这一步解决的是 "可远程调用性" 问题:一条数据库配置,通过"分配 ID + 固定路由 + 鉴权"这三件事,变成了一个标准的、可以被任意外部系统集成的 HTTP 服务接口。
阶段 4 → 5:配置能被远程调用了,执行的时候资源怎么分配
阶段 4 遗留的问题:知道"调哪条配置"了,但真正执行这个 Agent 的循环逻辑,是用什么资源去跑的?如果给每个 APP_ID/Bot_ID 都分配一个专属的常驻进程或容器,等着随时被调用------问题很明显:平台上有几百万个用户创建的 Agent,其中绝大多数长期没人调用,24 小时常驻等于给几百万个空转的进程占着资源不做事,成本完全扛不住。
阶段 5 怎么解决 :不为单个 Agent 分配专属资源,而是做一套无状态、多租户共享的执行引擎服务集群 。以扣子开源的 Coze Studio 为例,它的后端是一套 Golang 微服务,采用领域驱动设计(DDD)原则构建,核心是常驻运行的 coze-server 服务------这是所有租户共用的同一份代码,不是一个 Agent 一个容器的关系。
具体执行流程:
- 请求带着 APP_ID/Bot_ID 和鉴权信息进来;
- 执行引擎的某一个(任意一个)实例接手这个请求;
- 从数据库现查这条配置记录(模型、prompt、工具列表);
- 用通用的循环逻辑(阶段 2 讲的那套 chatLoop)去执行,其中如果配置里关联了外部工具,这一步会走 MCP 协议或平台内置插件协议去调用;
- 执行完,这个实例被释放,回到资源池等待处理下一个请求(可能是另一个用户、另一个完全不同的 Agent 的请求)。
这一步解决的是 "规模化成本" 问题:只有真正发生调用的那一刻才消耗计算资源,Agent 本身闲置时不占任何执行资源,这也是为什么平台能免费、无限量地让用户创建 Agent------创建这个动作本身几乎零成本(阶段 3 已经把它变成了一条数据库记录)。
阶段 5 → 6:多轮对话、长耗时任务,状态怎么保证连续
阶段 5 遗留的问题:既然执行引擎是"任意实例都可能接手",那一个用户跟同一个 Agent 进行多轮对话时,第二轮请求万一被另一台机器上的另一个实例接手,它怎么知道第一轮聊了什么?如果历史状态存在第一个实例的内存变量里,换一台机器接手就直接断片了。
再进一步,有些任务执行到一半需要暂停------比如工作流跑到某个节点需要用户确认才能继续,这种"暂停等待、之后再恢复"的场景,靠进程内存也无法跨请求保留。
阶段 6 怎么解决 :把所有状态都外置存储,不依赖任何一个具体进程的内存:
- 对话历史 :存进数据库/Redis,用
session_id/conversation_id作为检索键。下一轮请求进来,不管被哪台机器接手,都先按这个 ID 去外部存储里查出完整历史,拼进请求里,跟前面讲过的 Claude API 无状态调用的处理方式是同一个思路,只是现在维护历史这件事从"调用方自己做"变成了"平台代为维护"。 - 长任务的执行现场快照:以 Coze 的工作流引擎为例,节点执行到需要暂停时,会把当前执行到哪个节点、所有变量的值、每个节点的状态、历史交互记录打包成一份快照(ExecutionSnapshot),通过一个外部状态存储接口(StateStore)写进持久化存储;需要恢复时,从存储里读出这份快照,重建出完整的执行上下文继续跑。配合 SSE(Server-Sent Events)做流式通信,服务端可以随时向客户端推送进度,需要时抛出"中断"事件,实现暂停-恢复的效果。
这一步解决的是 "状态和执行实例解耦" 问题:任何一台执行引擎的机器都是无状态、可替换的,真正有价值的会话数据、执行进度全部在外部存储里,这才是阶段 5 提到的"水平扩容、故障转移"能够安全实现的前提------机器可以随便宕、随便扩容,只要外部存储没丢数据,用户的对话和任务进度就不会丢。
阶段 6 → 7:模型推理这一层,为什么不能跟着一起"无状态化"
前六个阶段遗留的问题:执行引擎可以做到"用完即走、不为谁常驻",但真正做推理计算的大模型呢?模型权重动辄几十上百 GB,加载到显存需要时间,如果也跟执行引擎一样"每次请求现加载现算完就释放",每次推理前都要重新把模型权重灌进 GPU,这个延迟是用户完全无法接受的。
阶段 7 怎么解决 :把模型推理这一层,单独拆成一套持续在线、常驻显存的独立服务集群(通义千问、豆包等模型各自的推理集群),跟执行引擎解耦------执行引擎每次需要推理时,是发一个 HTTP/RPC 请求过去,不需要自己管理模型加载。这套推理集群是所有 Agent、所有租户共享复用的,靠整个平台的总请求量去做弹性扩缩容,而不是为某一个具体 Agent 单独准备。
这样整个系统里,真正"贵、需要一直开着"的只有这一层,前面几层(Agent 配置、执行引擎、状态存储)都可以做到按需伸缩、闲时几乎零成本。
总结表:每一步解决什么问题,靠什么手段
| 阶段 | 遗留问题 | 解决手段 |
|---|---|---|
| 0→1 | 工具接入没有标准,代码耦合、不可复用 | MCP 协议:Host ↔ Server 标准化通信 |
| 1→2 | 多步骤任务需要人工触发下一步 | 自动化循环(读 stop_reason 驱动),这一步开始叫 Agent |
| 2→3 | Agent 定义写死在代码里,不可配置、不可复用 | 把 Agent 定义抽象成数据库配置记录,执行引擎与定义解耦 |
| 3→4 | 配置只能在平台内部用,业务系统调不到 | 发布:分配 ID + 固定 HTTP 路由 + 鉴权(AK/PAT/OAuth) |
| 4→5 | 为每个 Agent 分配专属资源成本扛不住 | 无状态、多租户共享的执行引擎集群,现查现执行现释放 |
| 5→6 | 状态依赖单个进程内存,换机器/暂停就丢 | 状态外置到数据库/Redis,长任务做快照-恢复 |
| 6→7 | 模型推理不能像执行引擎一样按需加载 | 推理层单独拆成常驻共享集群,和执行引擎解耦 |
一句话看懂整条演化路径
MCP 解决的是"工具怎么接";循环解决的是"怎么自主执行多步任务";配置化解决的是"怎么让 Agent 可复用、可被非工程师创建";发布+鉴权解决的是"怎么被外部系统远程调用";无状态执行引擎解决的是"怎么用最低成本服务海量 Agent";状态外置解决的是"怎么保证多轮对话和长任务不因为换机器而丢失";推理层独立解决的是"真正昂贵的资源怎么共享复用"。
每一步都是对上一步在真实业务规模下暴露出的具体工程问题的针对性回应,不是设计上的巧合。