目的:搞清楚主流框架的 Agent loop 到底怎么实现的,从而判断——哪些该用现成的,哪些值得自己写。 方法:不读文档,直接读
node_modules里的源码。
一、先把问题问对
"要不要用 LangChain / LangGraph"这个问法本身有问题。它把框架当成一个整体在问用或不用,但框架其实是四个可分离的能力打包在一起:
| 能力 | 是什么 | 自建成本 |
|---|---|---|
| 1. Agent loop | 调模型 → 解析 tool_calls → 执行 → 回传 → 再调 | 几百行,一周 |
| 2. 工具协议 | schema 定义、参数校验、错误如何回传给模型 | 一两百行 |
| 3. 上下文管理 | 裁剪、压缩、防串味 | 看场景,几百行 |
| 4. 状态持久化与恢复 | checkpoint、断点续跑、人工审批、跨进程 | 数月,且容易出并发一致性 bug |
第 4 块是唯一"非用不可"的部分。 前三块自建可行且学得到东西。
二、关键发现:ReAct loop 是"图上的一条回边"
读 langchain/dist/agents/ReactAgent.js 得到的实证:
第 84 行 new StateGraph(state, { ... }) ← createAgent 内部建图
第 161 行 addNode(AGENT_NODE_NAME, ...) ← 模型节点
第 178 行 addNode(TOOLS_NODE_NAME, toolNode) ← 工具节点
第 189 行 addEdge(START, entryNode)
第 269 行 addEdge(TOOLS_NODE_NAME, toolReturnTarget) ← 这条回边就是循环
源码里搜不到 while,也搜不到 shouldContinue。 整个文件 770 行,没有一处显式循环——因为循环由 LangGraph 的图执行引擎驱动。
等价结构:
START → [模型节点] --有 tool_calls--> [工具节点] --回边--> [模型节点]
|
+--无 tool_calls--> END
这个洞察的价值:把"Agent loop 是什么"从玄学变成图论。
- 手写版:一个
while+ 一个终止条件判断 - 框架版:一张图 + 图引擎的调度与递归限制
两者语义等价,差别在工程能力(持久化、并发、可观测),不在"智能程度"。
三、由此得到的选型判据
框架的持久化能力什么时候才值它的钱?出现下面任意一条:
- 任务跨进程 / 跨天:跑三天的任务,进程重启要从第 47 步继续
- 中途需要人点头:转账、发布、发邮件前要人工审批,且审批可能几小时后
- 多 Agent 之间的流转本身需要状态管理:A 做完交 B,中间状态要能查、能回滚
- 同一套编排要跑百万次:自建调度器在并发一致性上会崩
四条都不占 → 直接手写。
用这套判据回看辩论 Agent:
- 一场辩论 ~20 次模型调用、2 分钟内跑完
- 崩了重跑成本几毛钱
- 没有跨进程、没有人工审批、没有百万次并发
结论:LangGraph 那个非用不可的能力,一行都没用上。 这是"杀鸡用牛刀"。
四、框架的代价(选型不是免费的)
实测数据(来源见文末):
| 对比项 | 结果 |
|---|---|
| 自研轻量 harness vs LangGraph,p95 延迟 | 1.2s vs 2.8s —— 图节点序列化开销 |
| Strands Harness vs LangGraph,同一 20 轮对话的 token | Strands 少用 1.82× —— 自动在稳定边界插 prompt cache 断点,LangGraph 不做 |
同一个模型,分数可以差 3 倍(OpenAI 官方 ARC-AGI-3 实验:13.3% → 38.3%,仅改两个 harness 设置)。 测出来的 Agent 能力,有一半是执行框架给的,不是模型给的。
五、我们的决策
主线手写 + LangGraph 对照。
理由:
- 手写 loop 是面试里唯一能证明"懂"的凭据。被问"Agent 卡住怎么 debug",调库的人和手写的人答出来是两个物种
- 有对照实验才能写实的文章,不写"九点认识"这种空话
- 直接回答了"为什么用/为什么不用 LangGraph"——两个方向都有答案,因为都做过
对照怎么保证公平:辩论的业务逻辑(辩手人格、反谄媚锚点、盲评裁判、检索工具)两边共用,只有 loop 层不同。
六、要手写的 loop 需要覆盖哪些点
| 能力 | 手写要做什么 |
|---|---|
| 消息协议 | 自己定义 role(system / user / assistant / tool),与框架解耦 |
| tool_calls 解析 | 模型返回的调用请求 → 校验参数 → 执行 |
| 终止条件 | 无调用 / 步数上限 / 成本上限 / 超时,任一命中即停 |
| 错误回传 | 工具抛错时,把错误转成模型能读懂的 observation,让它自己纠正 |
| 上下文裁剪 | 超出窗口时裁剪或摘要,防 token 爆炸 |
| 可观测 | 每步落 trace,支持事后回放 |
这一步做完,"要不要用框架"就不再是信仰问题,而是一个你有实测数据的问题。
七、MCP:工具协议的标准化
2026-10-04 补充。这是"工具协议"那块能力最标准的教材。
7.1 MCP 解决的是什么问题
在 MCP 出现之前,每个 Agent 要接一个外部工具(比如联网搜索),就得:
- 去读那家服务的 API 文档
- 手写 HTTP 请求、鉴权、参数校验
- 手写把返回值转成模型能读的 observation
- 把这个工具的 schema 硬编码进 system prompt
问题在于这套流程 N 个工具就要写 N 遍,而且每家的错误格式、鉴权方式、返回结构都不一样。
MCP(Model Context Protocol)把这件事标准化成了两部分:
┌─────────────┐ MCP 协议 ┌──────────────┐
│ 你的 Agent │ ← list_tools / call → │ 工具服务器 │
│ (Client) │ │ (Server) │
└─────────────┘ └──────────────┘
└─ 联网搜索
└─ 数据库
└─ 文件系统
└─ 企业内部系统
- 工具列表是动态发现的:
list_tools拿,不用硬编码 - schema 是统一的:服务端自己声明参数,客户端自动转换
- 鉴权是统一的:
Authorization: Bearer <key>一种形式
7.2 MCP 与 function calling 的关系
这是最容易搞混的地方,说清楚:
| function calling | MCP | |
|---|---|---|
| 是什么 | 模型输出"我要调这个函数"的结构化请求 | Agent 与工具提供方之间的通信协议 |
| 解决什么 | 模型 ↔ 工具的调用格式 | Agent ↔ 工具的发现与连接 |
| 谁定义 | 模型厂商的 API 规范 | 开放的跨厂商协议 |
它们是串联的,不是替代关系:
MCP list_tools → 拿到工具列表
↓ 转成 function calling schema
发给模型的 tools 参数 → 模型决定调哪个
↓ 模型返回 tool_calls
MCP call_tool → 实际执行
↓ 结果
作为 observation 回给模型
MCP 的价值就是自动完成中间那步"转 schema"——这正是手写最脏最累的部分。
7.3 我们项目里的接入点
百炼提供两个 MCP 服务,都走 HTTP Streamable:
| 服务 | 用途 |
|---|---|
联网搜索增强版 EnhancedSearch |
检索质量更高(本次选用) |
联网搜索 WebSearch WebSearch |
基础版 |
地址格式:
https://dashscope.aliyuncs.com/api/v1/mcps/EnhancedSearch/mcp
Header: Authorization: Bearer <百炼 API Key>
关键坑:Token Plan 专属 key(sk-sp-)不能调 MCP,必须用按量付费的百炼 API Key(sk-)。
实测对照(同一把 key、同一网络):
| 端点 | 结果 |
|---|---|
| Token Plan chat 端点 | 200 ✅ |
| EnhancedSearch MCP | 401 InvalidApiKey ❌ |
| WebSearch MCP | 401 InvalidApiKey ❌ |
这是两套独立的鉴权与计费体系,不是配置问题。
顺带一个容易犯的错:别把 key 的变量名按用途命名。
按量付费 key 是通用凭据——凡走 dashscope.aliyuncs.com 域名的服务(按量付费 chat、联网搜索、
其他 MCP 服务)都用它。叫 MCP_API_KEY 会让人误以为它只能干搜索这一件事。
所以本项目命名为 BAILIAN_API_KEY(按平台命名,而非按用途),与 LLM_API_KEY(通用模型凭据)区分开。
7.4 为什么这个选择对项目有价值
换成 MCP 之后,tools.ts 里有三件事值得注意:
- 工具列表动态获取——不再硬编码工具描述,服务端改了我们不用动代码
- schema 自动转换——服务端声明的参数格式直接转成 function calling,省掉手写 zod 的活
- 它是最好的对照材料——之后写手写 loop 时,工具层既要支持本地工具(zod 定义),也要支持远程 MCP(动态发现)。两种工具来源的统一,正是"工具协议"这块能力的真正考验
八、其他框架的位置(待补)
- deepagents(LangChain 官方,Anthropic 风格 harness):坐在 LangGraph 之上,loop 已被封装成
create_deep_agent,多 agent 协作更省事 - Strands(AWS):harness 层,特点是自动 prompt cache 断点
- OpenAI Agents SDK:轻量,几百行级别
共同点:loop 都是核心资产,差异在上下文管理、缓存策略、持久化。
来源
- OpenAI《How enabling two settings tripled our scores on the ARC-AGI-3 benchmark》(2026-07)
- LangChain / LangGraph v1 源码
node_modules/langchain/dist/agents/ReactAgent.js - AWS Strands vs LangGraph 实测对比(20 轮对话,同 prompt 同工具)
- 自研轻量 harness vs LangGraph 0.2.x 压测(AWS c5.2xlarge,100 并发)