← 技术专栏
阅读模式

Agent Harness 拆解:框架到底替你做了什么

把 LangGraph 的 createAgent 读到源码层,拆出循环、工具协议、上下文管理与状态持久化四种能力,再判断哪些值得手写,哪些交给框架。

文章目录 13 节

目的:搞清楚主流框架的 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 得到的实证:

text
第 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 的图执行引擎驱动。

等价结构:

text
START → [模型节点] --有 tool_calls--> [工具节点] --回边--> [模型节点]
             |
             +--无 tool_calls--> END

这个洞察的价值:把"Agent loop 是什么"从玄学变成图论。

  • 手写版:一个 while + 一个终止条件判断
  • 框架版:一张图 + 图引擎的调度与递归限制

两者语义等价,差别在工程能力(持久化、并发、可观测),不在"智能程度"。

三、由此得到的选型判据

框架的持久化能力什么时候才值它的钱?出现下面任意一条:

  1. 任务跨进程 / 跨天:跑三天的任务,进程重启要从第 47 步继续
  2. 中途需要人点头:转账、发布、发邮件前要人工审批,且审批可能几小时后
  3. 多 Agent 之间的流转本身需要状态管理:A 做完交 B,中间状态要能查、能回滚
  4. 同一套编排要跑百万次:自建调度器在并发一致性上会崩

四条都不占 → 直接手写。

用这套判据回看辩论 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 对照。

理由:

  1. 手写 loop 是面试里唯一能证明"懂"的凭据。被问"Agent 卡住怎么 debug",调库的人和手写的人答出来是两个物种
  2. 有对照实验才能写实的文章,不写"九点认识"这种空话
  3. 直接回答了"为什么用/为什么不用 LangGraph"——两个方向都有答案,因为都做过

对照怎么保证公平:辩论的业务逻辑(辩手人格、反谄媚锚点、盲评裁判、检索工具)两边共用,只有 loop 层不同。

六、要手写的 loop 需要覆盖哪些点

能力 手写要做什么
消息协议 自己定义 role(system / user / assistant / tool),与框架解耦
tool_calls 解析 模型返回的调用请求 → 校验参数 → 执行
终止条件 无调用 / 步数上限 / 成本上限 / 超时,任一命中即停
错误回传 工具抛错时,把错误转成模型能读懂的 observation,让它自己纠正
上下文裁剪 超出窗口时裁剪或摘要,防 token 爆炸
可观测 每步落 trace,支持事后回放

这一步做完,"要不要用框架"就不再是信仰问题,而是一个你有实测数据的问题。

七、MCP:工具协议的标准化

2026-10-04 补充。这是"工具协议"那块能力最标准的教材。

7.1 MCP 解决的是什么问题

在 MCP 出现之前,每个 Agent 要接一个外部工具(比如联网搜索),就得:

  1. 去读那家服务的 API 文档
  2. 手写 HTTP 请求、鉴权、参数校验
  3. 手写把返回值转成模型能读的 observation
  4. 把这个工具的 schema 硬编码进 system prompt

问题在于这套流程 N 个工具就要写 N 遍,而且每家的错误格式、鉴权方式、返回结构都不一样。

MCP(Model Context Protocol)把这件事标准化成了两部分:

text
┌─────────────┐        MCP 协议         ┌──────────────┐
│ 你的 Agent  │  ← list_tools / call → │  工具服务器   │
│  (Client)   │                        │  (Server)    │
└─────────────┘                        └──────────────┘
                                            └─ 联网搜索
                                            └─ 数据库
                                            └─ 文件系统
                                            └─ 企业内部系统
  • 工具列表是动态发现的:list_tools 拿,不用硬编码
  • schema 是统一的:服务端自己声明参数,客户端自动转换
  • 鉴权是统一的:Authorization: Bearer <key> 一种形式

7.2 MCP 与 function calling 的关系

这是最容易搞混的地方,说清楚:

function calling MCP
是什么 模型输出"我要调这个函数"的结构化请求 Agent 与工具提供方之间的通信协议
解决什么 模型 ↔ 工具的调用格式 Agent ↔ 工具的发现与连接
谁定义 模型厂商的 API 规范 开放的跨厂商协议

它们是串联的,不是替代关系:

text
MCP list_tools  →  拿到工具列表
                    ↓ 转成 function calling schema
发给模型的 tools 参数  →  模型决定调哪个
                    ↓ 模型返回 tool_calls
MCP call_tool   →  实际执行
                    ↓ 结果
作为 observation 回给模型

MCP 的价值就是自动完成中间那步"转 schema"——这正是手写最脏最累的部分。

7.3 我们项目里的接入点

百炼提供两个 MCP 服务,都走 HTTP Streamable:

服务 用途
联网搜索增强版 EnhancedSearch 检索质量更高(本次选用)
联网搜索 WebSearch WebSearch 基础版

地址格式:

text
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 里有三件事值得注意:

  1. 工具列表动态获取——不再硬编码工具描述,服务端改了我们不用动代码
  2. schema 自动转换——服务端声明的参数格式直接转成 function calling,省掉手写 zod 的活
  3. 它是最好的对照材料——之后写手写 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 并发)
· · ·

写到这里,留待实践继续检验。

把文章放回实践里多 Agent 对抗辩论系统 ↗