☰
13_不用框架怎么实现一个Agent
2026/10/10 2:47:59 网站建设 项目流程

13 · 不用框架怎么实现一个 Agent

我失业在家做求职作品集,第四周的任务是「做一个 Agent」。

打开教程,前十篇里有九篇告诉你:用 LangChain / LangGraph / LlamaIndex,三行代码接上,完事。

我没用。不是洁癖,是三个很具体的理由。这篇文章写我怎么手写的,以及——更重要的——什么时候你还是该用框架。


一、为什么不用:三个理由

1. 我要证明的是「我会做」,不是「我会调」

简历上写「熟练使用 LangChain」,面试官心里想的是「那换个人也能」。

手写一遍,我能说清楚每一步为什么这么写:为什么解析要做三态、为什么熔断设 2 次而不是 1 次、为什么观察要截断。这些是判断,不是配置。

框架把判断藏起来了 —— 你调create_react_agent(),它跑起来了,但你不知道它在哪一步做了什么取舍。

2. 框架的版本和抽象,会吃掉我的调试时间

LangChain 那套抽象(chain / runnable / agent executor)本身就要学。而我只有六周,其中一周要做 Agent + MCP + 可观测。

我要的是「Agent 的原理」,不是「某个框架的 API」。这两件事花的时间差不多,但前者可迁移,后者可能下个版本就变了。

3. 最实际的一条:不用框架,可观测才好做

这条是我做完才想明白的。

框架的 Agent 跑起来,你想看每一步发生了什么,得先学会它的回调体系、它的 trace 格式。而我自己写的循环,事件是我自己发的——想在哪埋点就在哪埋点,格式我自己定。

后来我接 trace 的时候,Agent 代码改了 0 行。就这一条,省下的时间够我再写两个模块。


二、怎么实现:一个纯文本协议的 ReAct

核心决定:完全不传tools/tool_choice给模型。

# app/react.pyrun_react(...)# 不传 tools,不传 tool_choice

模型用纯文本表达意图:

思考:我需要先查一下天气。 行动:get_weather 参数:{"city": "北京"}

我的代码负责:解析 → 执行 → 把结果回灌 → 让模型继续。

为什么不走原生 function calling?因为它依赖服务端支持。如果我降级到某家免费模型、而它恰好不支持,整个 Agent 就废了。纯文本协议是最差的模型也能跑的那条路。

代价是:格式全靠约定,模型随时可能不守约。所以有了下面这些设计。

设计一:解析做三态,不是二值

{"kind":"action"}# 要调工具{"kind":"final"}# 给出最终答案{"kind":"unknown"}# 没解析出有效标记

unknown这一态是关键。二值解析遇到格式不对,只能抛异常或者当 final——前者让整个任务崩掉,后者会让用户收到一个莫名其妙的答案。

我的处理:unknown当成一次观察结果回灌给模型,附一段格式示范,让它自己改。

设计二:给两次机会,然后熔断

MAX_UNKNOWN_IN_A_ROW=2# app/react.py:76

设 2 而不是 1:给模型一次「照着格式重来」的机会。设 1 太苛刻(一次口误就崩),设 3 太浪费(改不对的格式改三次也改不对)。

设计三:三个终止出口,且绝不编造

出口 1 模型给出「最终答案」 —— 正常结束 出口 2 步数到 max_steps(默认 6) —— 安全结束,由我控制 出口 3 连续 2 次格式不明 —— 安全结束

后两个返回ok=False+空答案。

这点我想了很久。熔断的目标是「不烧钱 + 不骗人」,不是「一定要有个答案」。让模型在不知道的情况下硬编一个,比空手而归糟糕得多。

设计四:观察回灌要截断,而且要说明截断了

OBSERVATION_MAX_CHARS=1500# app/react.py:79

截断会丢信息,所以必须标注:「已截断,原长 N 字。如需完整结果请缩小查询范围」。

不标注会怎样?模型会以为它看到的就是全部,然后基于残缺信息下结论。告诉模型它的视野有边界,它才有可能自己缩小查询范围。


三、踩的坑:一个差点让我误判的 bug

做完之后我接了可观测(把事件流翻成 span 树,画瀑布图)。跑起来一看:

step 1: 651ms step 2: 640ms step 3: 620ms step 4: 590ms ---------------- 合计: 244% ← 比整次任务还长一倍多

四步加起来 244%,我第一反应是「系统变慢了」。

实际是:我的 ReAct 事件词汇里有step_start,没有step_end。

没有结束信号,每一步就只能靠下一个step_start来推断结束;最后一步更是要等到根 span 兜底关闭才结束。于是每一步的耗时都变成了「从它开始到全程结束」。

尺子坏了,我却在怀疑被测的东西。

修法:下一个step_start/final/stop到来时,关闭上一步。修完我加了条测试专门钉死这个行为——这类 bug 特别容易在改事件词汇时复发。

这个坑给我的教训跟第三周一模一样:你没法优化一个你测不准的东西。先修尺子。


四、框架我装了,但主代码不用

项目里装着 LangGraph,但主代码不用。

我把它做成了对照脚本:

python scripts/langgraph_compare.py --with-langgraph

这样我既保住了「未依赖框架」这个卖点,又能说清楚两种实现差异在哪——而且我知道 LangGraph 的 checkpointer 适合什么场景。

「我评估过,然后选择了不用」和「我不会」,在面试官耳朵里是两回事。


五、什么时候你还是该用框架

诚实说,下面这些情况别手写:

场景为什么
生产环境,要长期维护手写意味着所有边界情况你自己扛。框架踩过的坑比你多
需要复杂的分支 / 循环 / 并行图LangGraph 的状态图 + 条件边是真好用,手写状态机是自虐
需要持久化、断点续跑checkpointer 这类基础设施,别自己造
团队协作框架是公共词汇。你手写的协议,只有你自己看得懂

我自己这个项目的结论也一样:生产更该用 workflow(确定性流程),agent 只作展示和面试素材。

Anthropic 那篇《Building Effective Agents》给了三个「不该用 agent」的判据,我照着对了一遍:

  • 路径可预测 → 用 workflow
  • 一次调用能完成 → 直接调 LLM,别套 agent
  • 错误成本高且不可恢复 → workflow + 人工检查点

根本原因就一句:agent 的错误会累积(errors compound)。十步里每步 95% 正确,端到端只剩 60%。


六、一句话

手写一遍 Agent,最大的收获不是那个 Agent,是我知道它在哪一步会出错。

这份清单我写进了docs/Agent失败模式与兜底方案.md,十二类,每类都有真实触发场景和兜底手段。

框架能帮你跑起来,但不会告诉你它会怎么坏。


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询