去年 12 月入职后,我从零构建了一个基于 LangGraph 的 Agent 系统。
当时由于本地部署环境受限,加上显存较小、模型能力有限,为了保证系统可用性,我做了不少工程层面的优化。
现在回头看,其中很多设计其实已不再必要。
随着模型能力的提升,很多曾经必须依赖工程解决的问题,已经可以交给模型本身去处理。
这篇文章主要是一次简单的架构反思:
当模型变强之后,我们是否还需要那么复杂的 Agent 系统?
第一版系统架构:复杂但合理
当时系统的整体流程大致如下:
flowchart TD
A[User Query]
B[指代消解]
C[任务拆解]
D[子任务分类]
E[Agent]
F[MCP Tools]
G[知识库 Tool]
H[输出节点]
I[最终回答]
A --> B
B --> C
C --> D
D --> E
E --> F
E --> G
F --> E
G --> E
E --> H
H --> I
整个系统可以理解为一条 多阶段流水线。
指代消解
第一步是处理用户问题中的指代关系。
例如:
它的运行情况如何?
如果不结合上下文,系统其实无法理解 “它” 指代什么。
所以需要先通过模型进行 指代消解(Coreference Resolution),将问题补全为完整语义。
任务拆解
第二步是对复杂问题进行拆解。
例如:
今天电站的运行情况如何,如果出错如何解决?
这句话其实包含两个任务:
- 查询电站运行情况(工具查询)
- 查询解决方案(知识库检索)
因此需要进行 任务拆解。
子任务分类
拆解后的子任务会被分类为三类:
- 工具调用
- 知识库查询
- 其他
其中:
- 其他任务会直接过滤
- 工具和知识库任务进入 Agent 执行
Agent 执行
在当时的架构中,子任务分类之后,会进入一个 Agent。
这个 Agent 内部包含两类能力:
- MCP Server Tools
- 知识库 Tool(RAG)
知识库查询本质上也被视为一个 Tool,通过 MCP Host 挂载 Server 工具,最终组成 Tools 集合。
Agent 会根据子任务选择调用哪个 Tool。
为了避免上下文丢失,我们采用 串行执行子任务:上一个问题 + 答案会被拼接进上下文中,再传递给 Agent 处理下一个子任务。
输出节点
当所有子任务执行完后:
- 将子任务结果组装为 Dict
- 交给输出节点
- 由模型生成最终回答
一些工程优化
为了让系统在本地模型环境下稳定运行,还做了很多优化:
- Message 只存储 用户输入 + 最终输出
- 控制上下文 Token
- 知识库查询包含
- Query Rewrite
- Rerank 排序
这套系统在当时确实 非常精细。
但现在回头看,也确实 非常复杂。
为什么当时会设计这么复杂?
答案其实很简单:模型能力太弱。
当时本地模型存在很多限制:
- 上下文窗口较小
- 推理能力有限
- Tool Calling 不稳定
所以很多事情必须通过工程解决,例如:
- 任务拆解
- 上下文管理
- 流程编排
工程复杂度,本质上是在 弥补模型能力的不足。
一个新的思考
后来我注意到两个东西:
- Agent Skills
- OpenClaw
Skills 的出现让 Agent 能力可以模块化组织。
而 OpenClaw 的架构则非常简单,本质上可以理解为:
MCP + Agent + Skills
但它却能够完成很多复杂任务。
这让我开始重新思考一个问题:系统是否被我们过度工程化了?
从这些现象可以看到,我们正在逐渐改变一个默认假设:
过去我们习惯认为模型能力有限,所以很多问题必须通过复杂工程解决。
但现在越来越多案例证明:模型本身已经可以解决很多问题。
与其处处限制模型,不如先让模型尝试解决,再在必要的地方补充工程能力。
模型能力的变化
真正让我下决心重构架构的,是 Qwen3.5 的发布。
我原本项目使用的是 Qwen3 14B AWQ,但实际测试发现 Qwen3.5 4B 性能已经超过。
更关键的是它的上下文长度:
Context Length: 262,144
Extensible up to 1,010,000 tokens
此外还有:
- 更稳定的 Tool Calling
- 更强的推理能力
- 多模态支持
这让我意识到一件事:随着模型能力增强,很多过去的工程优化可能已不再必要。
第二版系统架构:极简 Agent
于是我重新设计了整个系统。
flowchart TD
A[User Query]
B[Main Agent]
C[Sub Agent]
D[Tools]
E[Result]
A --> B
B --> C
C --> D
D --> C
C --> B
B --> E
整体流程:
User Query → Agent → Tool / SubAgent → Result
新旧架构对比
flowchart LR
subgraph 旧架构
A1[指代消解]
A2[任务拆解]
A3[子任务分类]
A4[Agent + Tools]
A5[输出节点]
end
subgraph 新架构
B1[Agent]
B2[Tools / SubAgent]
B3[Result]
end
A1 --> A2 --> A3 --> A4 --> A5
B1 --> B2 --> B3
新架构将多个前置处理节点合并为一个统一的 Agent,大幅简化了流程。
新架构的三个关键设计
Tool 并发执行
传统 ReAct Agent 通常是 串行调用工具。
但在很多实际业务中,多个工具之间 没有强依赖关系。
因此新的架构支持 并发 Tool 调用,可以显著降低整体延迟。
Sub-Agent 隔离
为了避免工具返回的大量数据污染主 Agent 上下文,我引入了 Sub-Agent。
Sub-Agent 的作用是:
- 执行复杂任务
- 处理工具返回的数据
- 只返回精简结果(通过调用 LLM 来压缩)
这样可以避免上下文膨胀。
异步消息压缩
即使模型上下文已经很长,但保持上下文 信噪比 仍然非常重要。
我的设计是:
- 对话结束后 异步运行
- 当上下文达到阈值触发
- 重排 checkpoint + PG 数据
- 使用小模型进行 消息压缩
写在最后
回头看整个过程,其实是一个很有意思的变化。
过去我们习惯:用工程弥补模型能力。
而现在可能需要慢慢转变为:先相信模型,再补工程能力。
随着模型能力提升,很多复杂系统其实可以变得非常简单。
如果你对 Agent 架构、工具并发或者消息压缩有其他想法,欢迎一起交流。