13. 在工程实践中,为什么有时候选择「手搓」Agent,而不是直接用成熟框架?
13. 在工程实践中,为什么有时候选择「手搓」Agent,而不是直接用成熟框架?
💡 简要回答
我的感受是框架用起来快,但有几个实际痛点。第一是抽象层太多,调试的时候不知道哪步出了问题,得一层层往下扒;第二是版本升级经常有破坏性变更,线上稳定性难保证;第三是框架的通用设计往往和具体业务需求有偏差,定制起来反而更费劲。手搓的代码完全在自己掌控之内,可观测性好、出问题好排查,也更方便做性能优化。所以我现在的策略是核心逻辑手写,只在边缘功能上用框架的工具。
📝 详细解析
想象一下,你从零开始搭一个 Agent。
你需要定义工具的格式,让 LLM 能正确理解每个工具是什么、需要哪些参数;你需要解析 LLM 返回的工具调用结果;你需要在每次调用之间正确维护对话历史,不能丢消息也不能顺序错;你需要处理工具调用失败时的重试逻辑;你可能还需要接入向量数据库做知识检索……这些事情,每一个 Agent 项目都得做一遍,而且大同小异。
框架的价值就在这里:把上面这些重复工作全部封装好,你直接用,不用每次都造轮子。

LangChain 里一个 @tool 装饰器就能注册工具,AgentExecutor 把整个 ReAct loop 封装进去,还内置了 tracing、callback、记忆管理。早期上手快是真实的优势,两周的工作缩短到两天,特别是在快速验证 idea 的阶段,框架几乎没有明显的副作用。
痛点在什么时候开始出现?
框架的问题不是一开始就暴露的,而是随着项目推进,在不同阶段逐渐浮出来的。
探索期,框架真的很爽。你在做 POC,目标只是把流程跑通,几乎感受不到任何副作用。LangChain 帮你省掉了大量样板代码,几十行就搭起一个能用的 Agent,心情很好。

第一个奇怪的 bug 出现之后,感觉就变了。Agent 在某个特定场景下输出了错误的工具参数,你开始排查。代码只有五十行,但报错的 stack trace 有四十层,往下追到了框架内部。你不知道问题出在你写的那五十行里,还是框架某个版本的逻辑变化,或者是 callback 触发时机的问题。你开始在 GitHub issue 里搜,或者一层层读框架源码。
类比一下:老式车出了问题,打开引擎盖自己就能看到哪根管子漏油;现代豪华车出了问题,你打开引擎盖看到的是一堆你看不懂的电子设备,只能去 4S 店让诊断仪扫。框架的抽象层太多,排查问题需要穿透那些你没写、也不完全懂的层,这是实实在在的认知负担。
版本升级踩坑,是另一个阶段的痛苦。线上跑了几个月,某次依赖升级,LangChain 改了接口,代码直接报错。你要么回滚,要么把代码改到兼容新版本,可能涉及十几处修改。LangChain 早期版本升级频率很高,breaking change 是常见的。把核心业务逻辑建立在一个变动频繁的第三方框架上,线上稳定性就会受到这种不确定性的影响。
性能优化时发现了隐性开销,是到了规模化阶段才会碰到的问题。你开始关心 Agent 的调用延迟,发现某步 LLM 调用本身很快,但总耗时超预期。仔细 profile 之后,发现框架内部在每次调用时做了你根本不需要的事:序列化中间结果、触发一堆 callback、记录详细日志……这些逻辑是框架为了通用性设计进去的,对你的具体场景没有用,但每次调用都在跑。高流量下这些隐性开销累积起来,变成真实可见的延迟增加和费用浪费。
手搓的本质优势:完全掌控
说清楚框架的痛点之后,手搓的价值就容易理解了。它的核心优势,就是「完全掌控」三个字,体现在三个层面。

第一是链路透明、可观测性好。手搓的每一行代码你都知道在干什么,可以在任意位置加日志、打断点、插入监控,没有任何黑盒。线上出了问题,靠日志复现故障是最快的方式,链路越清晰,定位根因越快。这在生产环境里,是真实的时间和成本节省。
第二是精确裁剪、没有多余开销。你只写你确实需要的逻辑,不带任何通用性包袱。工具调用、对话历史维护、错误重试,每一块都按照你的具体场景来实现,没有为了「兼容其他用法」而存在的冗余逻辑。在性能敏感的场景里,这意味着优化空间完全在自己手里,不用绕过框架的限制来做裁剪。
第三是稳定可控、不受框架升级影响。你自己写的接口不会突然变,没有来自外部的 breaking change。依赖只有底层的 LLM SDK,相对稳定,生产环境可以长期运行,不用担心某次例行的依赖升级把线上跑坏。
有一个类比能很好地概括这个区别:框架是「租房」,装修好直接住,方便,但结构改不了,房东随时可能调整政策;手搓是「自建」,建起来慢,但所有结构都熟悉,改什么都能改,住着踏实。框架给你省了搭建时间,但你对这个「房子」的控制权始终有限。
什么时候用框架,什么时候手搓?
这不是非此即彼的选择,判断的关键是项目所处的阶段和对控制权的需求。
框架适合的时机:POC 阶段快速验证 idea,目标是跑通而不是优化;团队刚接触 Agent 开发,用框架能少踩一些基础性的坑;周边工具(文档解析、向量检索)依赖框架的生态,核心逻辑本身复杂度不高。这些场景里,框架带来的速度优势是真实的,值得用。
手搓的时机:准备上生产,稳定性成为核心关切;流量开始上来,性能和成本变得敏感;业务逻辑高度定制,和框架的通用设计偏差很大,改起来反而麻烦;团队需要高可观测性,链路要能随时监控和回溯。

折中方案:核心手写,周边借用
实践中最常见也最务实的选择,是介于两者之间的折中:核心逻辑手写,周边工具性功能借用框架。
控制边界的逻辑是这样的:工具调用的循环、对话历史的管理、错误处理和重试、任务状态的维护,这些是 Agent 的「心脏」,直接决定系统行为,必须百分百理解、百分百掌控,所以手写。而 LangSmith 的 tracing(调用链追踪)、LlamaIndex 的文档解析、某个向量库的 Python 客户端,这些是「工具性」的周边功能,出了问题一眼就能看出来,不会带来黑盒困境,用外部工具节省时间完全值得。
就像盖房子:自己设计核心结构、承重墙在哪、房间怎么布局,你必须完全掌控;但门锁、插座面板、水龙头,完全可以买现成的,不必自己从头制造每一个零件。
工程实践的清醒视角
最后有一点值得说清楚:手搓不是「比框架更好」,而是「在特定阶段有特定的价值」。
很多真实项目的演进轨迹是这样的:先用框架快速跑通,验证了方向;遇到第一批线上问题之后,开始把排查困难的关键部分替换为手写;流量上来之后,把性能敏感的核心逻辑全部手写;最后,框架只保留做得很好的周边工具。这条路走下来,既享受了早期框架的速度,又在生产阶段拿回了掌控权。

有一个判断信号可以参考:如果你能清楚说出「框架在某个地方替我做了什么、我用的这个方法内部发生了什么」,说明你理解它,用起来有掌控感;如果你只是调了一个方法但完全不知道里面发生了什么,出了问题就是一个不透明的黑盒,这才是需要警惕的信号。框架本身不是问题,「不理解就依赖」才是。
