我为什么把 Agent 的核心逻辑从 LangChain 里搬出来手写

先说结论:我不是反框架派 。LangChain 是我上手 Agent 开发用得最多的框架,工具注册一个 @tool 就完事,AgentExecutor 把整个 ReAct loop 封在里面,两周的活两天能干完。这个速度优势是真实的,不是幻觉。

但去年到现在,我在项目里做了一次「反向迁移」------把 Agent 的核心循环从框架里拆出来,自己手写。这篇文章讲清楚为什么,以及边界划在哪。

不是框架不好,是它在某些阶段会变成你的负担,而那时候代价已经产生了。


一、痛点是分阶段出现的,不是一开始就有

这点很关键。很多人一上来就说「框架有坑」,但如果你在做 POC,框架几乎没有副作用。

我把项目走下来,框架的问题是在四个不同阶段逐渐浮出来的:

探索期:真的很爽。 你在做 POC,目标是跑通流程,不是优化。框架省掉大量样板代码,几十行搭起一个能用的 Agent。

第一个奇怪的 bug 出现之后:感觉变了。 Agent 在某个特定场景下输出了错误的工具参数,你开始排查。你写的代码只有五十行,但报错的 stack trace 有四十层。往下追,追到框架内部。

这时候你面临一个很尴尬的问题:你不知道问题出在哪。

  • 是你写的那五十行?
  • 是框架某个版本的逻辑变了?
  • 还是 callback 的触发时机?

你开始在 GitHub issue 里翻,或者一层层读框架源码。

打个比方:老式车出了故障,打开引擎盖,你能看见哪根管子漏油;现代豪华车出了故障,你打开引擎盖看到的是一堆看不懂的电子设备,只能去 4S 店让诊断仪扫。

框架的抽象层太多,排查问题需要穿透那些你没写、也不完全懂的层。这是实实在在的认知负担。

版本升级踩坑:又一层痛苦。 线上跑了几个月,某次例行依赖升级,框架改了接口,代码直接报错。要么回滚,要么改到兼容------可能涉及十几处修改。LangChain 早期版本升级频率很高,breaking change 是常见的。把核心业务逻辑建立在第三方框架上,线上稳定性就绑在了别人的发布节奏上。

(顺带说一个刚发生的例子:LangChain 的 AgentExecutor 已经被官方标记废弃,推荐迁移到 LangGraph。这就是典型的、无法预先规避的破坏性变更。)

规模化之后:隐性开销开始显形。 你开始关心 Agent 的调用延迟,发现某步 LLM 调用本身很快,但总耗时超预期。profile 之后发现:框架内部每次调用都在做你根本不需要的事------序列化中间结果、触发一堆 callback、记详细日志。

这些逻辑是框架为了通用性 设计进去的。对你的具体场景没用,但每次调用都在跑。高流量下,这些隐性开销会变成真实可见的延迟和成本。


二、手搓到底强在哪:就三个字,「完全掌控」

说清框架的痛,手搓的价值就好理解了。

第一,链路透明,可观测性好。 每一行代码你都知道在干什么,可以在任意位置加日志、打断点、插监控,没有黑盒。线上出问题,靠日志复现故障是最快的路------链路越清晰,定位根因越快。这在生产环境里是真实的时间和成本。

第二,精确裁剪,没有多余开销。 你只写确实需要的逻辑,不带通用性包袱。工具调用、历史维护、错误重试,每一块都按你的场景实现,没有「为了兼容其他用法」而存在的冗余。性能敏感的场景里,优化空间完全在自己手里。

第三,稳定可控,不受框架升级影响。 你自己写的接口不会突然变,没有来自外部的 breaking change。依赖只剩底层 LLM SDK,相对稳定,可以长期跑。

这不是我一个人的偏方。Anthropic 在官方的 Agent 构建指南里明确建议:不要一上来就用框架,先用最少的抽象把核心逻辑跑通。 他们的大意是------框架的抽象层会让你离底层更远,一旦出问题,调试成本比你省下的开发时间还高。

这和我的实际感受完全一致:框架帮你快速搭起来的东西,往往也是后期最难排查的东西。

还有个类比我觉得很准:框架是「租房」,装修好直接住,方便,但结构改不了,房东随时可能调整政策;手搓是「自建」,建得慢,但所有结构你都熟悉。 框架给你省了搭建时间,但你对这个「房子」的控制权始终有限。


三、同一个需求,两种写法差在哪(代码对比)

光说理论不够直观,拿最常见的场景来对比:实现一个带工具调用的 Agent loop。

框架版(LangChain):

python 复制代码
from langchain.agents import AgentExecutor, create_openai_tools_agent

agent = create_openai_tools_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)
# 整个 ReAct 循环、工具调用、消息维护,全在 executor 内部
result = executor.invoke({"input": "帮我查一下今天的天气"})

确实简洁,三四行跑起来。但你想知道「这次调用 LLM 返回了什么、工具怎么被选中的、消息列表什么顺序」时------得去读 AgentExecutor 源码,内部调用链可能十几层。

手搓版:

python 复制代码
messages = [{"role": "system", "content": system_prompt}]
messages.append({"role": "user", "content": user_input})

for i in range(max_turns):  # 手动控制最大轮次
    response = client.chat.completions.create(
        model="gpt-4", messages=messages, tools=tool_schemas
    )
    msg = response.choices[0].message
    messages.append(msg)  # 把 LLM 响应加入对话历史

    # 没有工具调用,说明 LLM 认为任务完成了
    if not msg.tool_calls:
        break

    # 有工具调用,逐个执行并把结果写回消息列表
    for tc in msg.tool_calls:
        result = execute_tool(tc.function.name, tc.function.arguments)
        messages.append({
            "role": "tool",
            "tool_call_id": tc.id,
            "content": result
        })
        # 这里可以随意加日志、监控、重试逻辑
        logger.info(f"工具 {tc.function.name} 返回: {result}")

代码量多一些,但消息列表怎么拼的、工具怎么选的、循环什么时候退出,每个细节都摆在明面上。出了问题,看这二十行就够了。

这就是「完全掌控」的具体含义------不是说框架不好,而是当你需要理解和调试每一个环节时,手搓版本给你的确定性是框架给不了的。


四、那到底什么时候用框架?给你一张判断表

这不是非此即彼。判断的关键是:项目处在什么阶段,以及对控制权的要求有多高。

用框架 手搓
POC 阶段快速验证 idea 准备上生产,稳定性是核心关切
团队刚接触 Agent 开发 流量上来了,性能和成本敏感
周边工具(文档解析、向量检索)依赖框架生态 业务逻辑高度定制,和框架通用设计偏差大
核心逻辑本身复杂度不高 需要高可观测性,链路要能随时监控回溯

折中方案:核心手写,周边借用

实践中最常见也最务实的,是介于两者之间------核心逻辑手写,周边工具性功能借用框架。

控制边界这么划:

  • 手写(Agent 的「心脏」):工具调用循环、对话历史管理、错误处理与重试、任务状态维护。这些直接决定系统行为,必须百分百理解、百分百掌控。
  • 借用(工具性周边):LangSmith 的 tracing、LlamaIndex 的文档解析、某个向量库的 Python 客户端。这些出了问题一眼就能看出来,不会造成黑盒困境,用现成的完全值得。

就像盖房子:承重墙在哪、房间怎么布局,你必须自己掌控;但门锁、插座面板、水龙头,完全可以买现成的。


五、一个判断信号,比任何规则都好用

最后说一个我自己在用的判断法。

如果你能清楚说出「框架在某个地方替我做了什么、我调的这个方法内部发生了什么」------说明你理解它,用起来有掌控感。

如果你只是调了一个方法、但完全不知道里面发生了什么------出了问题它就是不透明黑盒,这才是需要警惕的信号。

真实的项目演进轨迹通常是这样的:先用框架快速跑通、验证方向;遇到第一批线上问题后,把排查困难的关键部分换成手写;流量上来后,把性能敏感的核心逻辑全部手写;最后,框架只保留那些确实做得好的周边工具。

框架本身不是问题,「不理解就依赖」才是。


本文内容整理自我的个人知识库(Java 后端 / 大模型应用 / AI 工程化三个方向)。如果你在做 Agent 开发时也踩过类似的坑,欢迎在评论区聊聊。

相关推荐
碧海银沙音频科技研究院1 小时前
基于炬芯ATS308X的LE aduio LC3编解码算法功能实现
算法
小蒋观天下1 小时前
厂区人员徘徊游荡判别——基于时序轨迹速度的逗留行为分级算法落地
大数据·人工智能·深度学习·算法·机器学习·ai大模型
程序员清风2 小时前
堆与优先队列:从 Top K 到任务调度
java·数据结构·算法
shaibdoio2 小时前
瞬维AI技术拆解:AI获客智能体的双层漏斗算法原理
大数据·人工智能·算法
suhan2 小时前
【数据结构】四、串:串string、模式匹配BF\KMP、广义表
数据结构·后端·算法
hetao17338372 小时前
联盟场-提高组-R7 & ZYZSC-S-Round 11
c++·算法
啵啵啵鱼2 小时前
第二章 线性表
java·数据结构·算法
虾..2 小时前
计算机图形学:几何表示
算法
局部有解2 小时前
Hot100 - 三数之和
数据结构·c++·算法