【撕开黑盒学大模型】拒绝做 API 调包侠:如何通过手写核心循环,建立对 Agent 框架的底层认知?
拒绝做 API 调包侠:如何通过手写核心循环,建立对 Agent 框架的底层认知?
代码地址:撕开黑盒学大模型-从白盒状态机演进到工业级Agent框架
本文目标:先建立一个判断框架:工具型 Agent 的底层不是黑盒魔法,而是一个围绕状态持续推进的循环。后续文章再逐篇展开记忆、调度和框架迁移。
1. 问题与背景
很多人第一次接触 Agent,会直接从 LangChain、LangGraph、AutoGen 或各种可视化编排工具开始。这样上手很快,但也容易形成一个问题:当 Agent 在线上表现异常时,只知道“框架没有按预期调用工具”,却说不清楚状态从哪里来、工具为什么被选中、上下文为什么被污染、失败为什么没有被恢复。
这类问题在 Demo 阶段不明显。只要模型回答一次、工具跑一次,演示就看起来成立。但工程系统不一样。真实场景里会遇到:
- 模型输出格式轻微偏移,正则解析直接失败;
- 工具返回慢,串行调用把端到端延迟拉长;
- 记忆检索召回了过时信息,Agent 用错误上下文继续推理;
- 中间状态只存在进程内,服务重启后长任务无法恢复;
- 工具权限过大,Prompt 注入诱导 Agent 执行危险操作。
这些不是“再换一个框架”就能自动消失的问题。框架可以封装复杂度,但工程师需要知道它封装的是哪一层复杂度。
2. 这个专栏不反框架
先手写 Agent,不等于反对框架。相反,本专栏的目标是:先把框架隐藏起来的机制拆开,再回到框架时知道它为什么这样设计。
后续文章会沿着这个状态机继续向外展开。当前仓库中,四个 demo 对应四层能力,但第 0 篇只先说明路线,不提前消费后面几期的实现细节:
| 阶段 | 目录 | 关注点 |
|---|---|---|
| v1 | v1_react/ | ReAct 主循环、工具自省、Action 解析、Observation 回灌 |
| v2 | v2_memory/ | 短期窗口、摘要记忆、长期检索、阈值过滤、用户隔离 |
| v3 | v3_rewoo/ | Planner / Worker 解耦、DAG 依赖、异步并发、超时 Observation |
| v4 | v4_langchain/ | 手写状态图与 LangChain / LangGraph 抽象的映射 |
前三个阶段刻意不依赖真实大模型 API。这样做不是为了回避模型,而是为了让状态流转可控、可复现。本文先回答一个最基础的问题:为什么说 Agent 首先应该被理解成状态机?
3. Agent 的底层本质:状态机
无论外层包装多复杂,一个工具型 Agent 的核心通常都可以压缩成下面这个循环:
输入任务
-> 构造 Prompt / State
-> 模型输出 Thought / Action / Final
-> 如果是 Action,调用工具
-> 把 Observation 写回上下文
-> 继续下一轮
-> 如果是 Final,结束
这段循环重要的地方不在于它短,而在于它把 Agent 的关键问题都摊开了:每一轮开始时,系统手里有什么状态;模型基于什么状态做判断;工具调用改变了什么;新的结果又如何进入下一轮。
如果把它写成更工程化一点的状态对象,大概会包含这些字段:
| 状态字段 | 作用 | 出问题时的典型表现 |
|---|---|---|
task | 用户真正要解决的问题 | Agent 偏题,或者把子任务当成最终目标 |
messages / transcript | 当前对话和中间推理记录 | 上下文过长、混入无关历史、错误信息被反复引用 |
available_tools | 当前允许调用的工具集合 | 模型选择了不存在的工具,或者越权调用危险工具 |
last_action | 上一轮准备执行的工具动作 | 参数格式不稳定,解析失败 |
observation | 工具执行后的返回结果 | 工具失败没有被写回,模型以为动作成功 |
final_answer | 是否已经得到最终答案 | 循环无法停止,或者过早结束 |
也就是说,Agent 不是“模型一次性想明白所有事情”,而是不断经历下面这组状态转移:
等待用户任务
-> 组装当前状态
-> 请求模型选择下一步
-> 解析模型输出
-> 执行工具或结束任务
-> 写回观察结果
-> 再次组装状态
这个视角会改变我们调试 Agent 的方式。以前我们可能只问“模型为什么没有答对”,但状态机视角会逼着我们继续追问:
- 模型看到的上下文是不是已经被污染?
- 工具列表是不是描述不清,导致模型选错动作?
- Action 是否有稳定格式,程序能不能可靠解析?
- 工具失败后有没有被记录成 Observation?
- 循环退出条件是否明确?
- 每一步状态有没有留下 trace,方便复盘?
后续第 1 篇会用 ReAct 小引擎把这套循环写成可运行代码。第 0 篇先只保留抽象结构:
current_state = read_state()
model_output = ask_model(current_state)
next_event = parse(model_output)
if next_event is action:
observation = call_tool(next_event)
write_back(observation)
else:
finish(next_event)
这段抽象流程可以拆成三类职责:
- 读状态:
transcript代表当前上下文,模型只能基于它做判断。 - 改状态:模型输出
Action后,工具调用会生成新的observation。 - 推进状态:
Observation被追加回上下文,下一轮循环才知道刚刚发生了什么。
所以,工具型 Agent 的关键能力不是“会不会调用一个函数”,而是“能不能稳定地推进状态”。只要状态推进不可靠,外层框架再完整,系统也会在这些地方出问题:
- Action 格式漂移,程序无法解析;
- Observation 没有回灌,模型失去反馈;
- 历史上下文无限增长,重要信息被噪声淹没;
- 工具异常没有进入状态,下一轮推理建立在错误假设上;
- 缺少 trace,线上问题只能靠猜。
很多框架的复杂能力,本质上都是围绕这个状态机扩展出来的:
- 工具 Schema:让模型知道有哪些工具、每个工具需要什么参数;
- 结构化输出:让模型输出更容易被程序解析;
- 记忆管理:决定哪些历史状态进入下一轮;
- 图编排:把
while True变成节点和边; - 持久化:把进程内状态升级成可恢复状态;
- 可观测性:记录每一次模型输出、工具调用、耗时和错误。
理解这个底层循环之后,再看框架文档时就不会只是在背 API。你会自然地去判断:这个框架把状态放在哪里?状态什么时候更新?工具失败如何表达?中断后能否恢复?trace 能不能还原每一步决策?
4. 从状态机看“记忆污染”
记忆问题,本质上不是“有没有向量数据库”,而是“哪些历史状态应该进入下一轮”。很多 Agent 文章会把“接一个向量数据库”当成记忆系统的终点,这个说法容易误导读者。
向量库只是长期检索的一种存储和召回手段,不等于记忆治理。一个完整的记忆系统至少要回答四个问题:
- 最近几轮原始对话是否必须保留?
- 很早之前的上下文是否应该压缩成摘要?
- 长期记忆是否要设置相似度阈值?
- 多用户数据如何隔离和擦除?
这些问题会在后续记忆篇单独展开。放在第 0 篇里,只需要先记住一句话:记忆不是额外外挂的知识库,而是状态机的一部分。它决定下一轮模型到底看见什么,也就直接决定下一轮状态如何推进。
5. 从状态机看异步调度
调度问题也是同样的逻辑。ReAct 的优点是简单,缺点也很明显:多工具任务天然串行。
如果一个任务需要查价格、查天气、查库存,传统 ReAct 往往是:
问模型下一步 -> 调价格工具 -> 问模型下一步 -> 调天气工具 -> 问模型下一步 -> 调库存工具
每一步都可能产生模型网络等待和工具网络等待。工具之间没有依赖时,这种串行等待就是浪费。
从状态机视角看,异步调度要解决的是“哪些状态转移可以并行发生,哪些必须等待前置 Observation”。这就是后端工程基本功在 Agent 系统里的体现:依赖关系、超时、错误返回、时间线记录,比“模型很智能”更能决定系统是否稳定。
具体怎么把串行循环拆成可调度的执行图,留到后面的调度篇展开。
6. 为什么最后还要回到 LangChain / LangGraph
手写是为了理解,不是为了永远手写。
当系统进入真实交付阶段,手写框架会逐渐遇到这些问题:
- 工具 Schema 需要适配不同模型供应商;
- 状态图需要支持复杂分支;
- 中间状态需要持久化和恢复;
- 人类审批需要中断和继续执行;
- trace、评估、部署和监控需要标准化。
截至 2026-06-20,LangChain 官方文档把 tools 描述为具有明确输入输出、可被模型选择调用的函数;LangGraph 官方文档则把 LangGraph 定位为支持 durable execution、streaming、human-in-the-loop 和 persistence 的编排运行时。也就是说,框架不是替代底层理解,而是把这些能力工程化。
放回本文的主题里看,这些能力其实都在回答同一个问题:状态如何保存、如何恢复、如何观察、如何被人类介入。理解了状态机,再迁移到 LangGraph 的状态图、持久化和中断机制时,概念负担会小很多。
7. 本专栏的成功标准
这个专栏不是为了证明“我会调用某个框架”。真正的成功标准是:
- 能从代码层说明 Agent 每一步状态如何推进;
- 能解释工具调用为什么需要确定性和结构化;
- 能说明记忆系统为什么不能只靠 Top-K;
- 能用异步调度解释延迟优化;
- 能把手写模块映射到 LangChain / LangGraph 的框架抽象;
- 能划清 Demo 与生产系统的边界。
第 0 篇只建立认知框架,不提前展开后续 demo。后续每一篇会用一个最小可运行实验验证其中一层机制。
8. 总结
Agent 框架不是黑魔法。它的核心仍然是状态、工具、记忆、调度和持久化。
先手写白盒状态机,是为了把这些机制看清楚;再复用工业框架,是为了把这些机制交给经过验证的工程抽象。求职叙事中,真正有区分度的不是“我用过某个框架”,而是“我知道框架在帮我解决什么问题,也知道它解决不了什么问题”。
下一期:【撕开黑盒学大模型】大模型是如何自主调用工具的?纯 Python 实现 ReAct 引擎与函数自省注册
感谢阅读,记得点赞、关注、收藏,欢迎各位评论区交流!!!

更多推荐
所有评论(0)