拒绝做 API 调包侠:如何通过手写核心循环,建立对 Agent 框架的底层认知?

代码地址:撕开黑盒学大模型-从白盒状态机演进到工业级Agent框架

本文目标:先建立一个判断框架:工具型 Agent 的底层不是黑盒魔法,而是一个围绕状态持续推进的循环。后续文章再逐篇展开记忆、调度和框架迁移。

1. 问题与背景

很多人第一次接触 Agent,会直接从 LangChain、LangGraph、AutoGen 或各种可视化编排工具开始。这样上手很快,但也容易形成一个问题:当 Agent 在线上表现异常时,只知道“框架没有按预期调用工具”,却说不清楚状态从哪里来、工具为什么被选中、上下文为什么被污染、失败为什么没有被恢复。

这类问题在 Demo 阶段不明显。只要模型回答一次、工具跑一次,演示就看起来成立。但工程系统不一样。真实场景里会遇到:

  • 模型输出格式轻微偏移,正则解析直接失败;
  • 工具返回慢,串行调用把端到端延迟拉长;
  • 记忆检索召回了过时信息,Agent 用错误上下文继续推理;
  • 中间状态只存在进程内,服务重启后长任务无法恢复;
  • 工具权限过大,Prompt 注入诱导 Agent 执行危险操作。

这些不是“再换一个框架”就能自动消失的问题。框架可以封装复杂度,但工程师需要知道它封装的是哪一层复杂度。

2. 这个专栏不反框架

先手写 Agent,不等于反对框架。相反,本专栏的目标是:先把框架隐藏起来的机制拆开,再回到框架时知道它为什么这样设计。

后续文章会沿着这个状态机继续向外展开。当前仓库中,四个 demo 对应四层能力,但第 0 篇只先说明路线,不提前消费后面几期的实现细节:

阶段目录关注点
v1v1_react/ReAct 主循环、工具自省、Action 解析、Observation 回灌
v2v2_memory/短期窗口、摘要记忆、长期检索、阈值过滤、用户隔离
v3v3_rewoo/Planner / Worker 解耦、DAG 依赖、异步并发、超时 Observation
v4v4_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)

这段抽象流程可以拆成三类职责:

  1. 读状态transcript 代表当前上下文,模型只能基于它做判断。
  2. 改状态:模型输出 Action 后,工具调用会生成新的 observation
  3. 推进状态Observation 被追加回上下文,下一轮循环才知道刚刚发生了什么。

所以,工具型 Agent 的关键能力不是“会不会调用一个函数”,而是“能不能稳定地推进状态”。只要状态推进不可靠,外层框架再完整,系统也会在这些地方出问题:

  • Action 格式漂移,程序无法解析;
  • Observation 没有回灌,模型失去反馈;
  • 历史上下文无限增长,重要信息被噪声淹没;
  • 工具异常没有进入状态,下一轮推理建立在错误假设上;
  • 缺少 trace,线上问题只能靠猜。

很多框架的复杂能力,本质上都是围绕这个状态机扩展出来的:

  • 工具 Schema:让模型知道有哪些工具、每个工具需要什么参数;
  • 结构化输出:让模型输出更容易被程序解析;
  • 记忆管理:决定哪些历史状态进入下一轮;
  • 图编排:把 while True 变成节点和边;
  • 持久化:把进程内状态升级成可恢复状态;
  • 可观测性:记录每一次模型输出、工具调用、耗时和错误。

理解这个底层循环之后,再看框架文档时就不会只是在背 API。你会自然地去判断:这个框架把状态放在哪里?状态什么时候更新?工具失败如何表达?中断后能否恢复?trace 能不能还原每一步决策?

4. 从状态机看“记忆污染”

记忆问题,本质上不是“有没有向量数据库”,而是“哪些历史状态应该进入下一轮”。很多 Agent 文章会把“接一个向量数据库”当成记忆系统的终点,这个说法容易误导读者。

向量库只是长期检索的一种存储和召回手段,不等于记忆治理。一个完整的记忆系统至少要回答四个问题:

  1. 最近几轮原始对话是否必须保留?
  2. 很早之前的上下文是否应该压缩成摘要?
  3. 长期记忆是否要设置相似度阈值?
  4. 多用户数据如何隔离和擦除?

这些问题会在后续记忆篇单独展开。放在第 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 引擎与函数自省注册
感谢阅读,记得点赞、关注、收藏,欢迎各位评论区交流!!!
在这里插入图片描述

更多推荐