
1. 为什么我要从零手搓一个 Agent 内核去年年底我接手了一个内部工具链项目需求说起来很简单让一个 AI 助手能自动完成读需求文档、查代码库、改配置、跑测试、写变更记录这一整条链路。当时团队里有人提议直接用现成的编排框架我试了一圈发现一个很尴尬的问题——大部分框架把能跑通 Demo和能长期维护之间的鸿沟藏得太深了。Demo 阶段你写十几个节点就能跑一旦要加权限控制、要接内部审计、要做失败重试和状态回滚整个图就变成了一团意大利面。所以我决定从零写一个 Agent 内核。这个决定让我在接下来三个月里踩了 14 个设计上的坑也最终沉淀出一个通用内核并且意外地发现它在四个完全不同的场景里都能复用产生了复利效应。这篇文章就是把这 14 个决策和四种复利讲清楚适合正在做 Agent 开发、纠结要不要自研内核、或者已经用框架但被框架反噬的同学。我会尽量说人话把每个决策背后的为什么讲透而不是甩一堆架构图让你自己悟。先说结论性的判断Agent 开发真正的难点从来不是调用大模型而是状态管理、工具边界、失败恢复这三件事。框架帮你解决的是第一层能跑但第二层跑得稳和第三层跑得省必须靠你自己想清楚。下面我按踩坑的时间顺序把 14 个决策拆开讲。2. 十四个设计决策的逐条拆解2.1 决策一内核与业务必须彻底解耦我最初的版本把读文档这个业务逻辑直接写进了主循环里结果第二个场景要用的时候发现主循环里全是第一个场景的假设。痛定思痛我把内核抽象成三个纯粹的概念消息Message、工具Tool、状态State。内核只负责给定状态和消息决定下一步调用哪个工具至于工具内部干什么内核一概不管。这个决策的价值在后面才显现出来。当我要接入第二个场景时只需要注册新的工具集内核代码一行没改。判断标准很简单如果你删掉所有业务工具内核还能编译通过并且跑一个空循环那解耦就是成功的。2.2 决策二状态用不可变数据结构一开始我用可变字典存状态调试时经常出现这个字段什么时候被改的这种灵魂拷问。后来改成每次状态变更都返回一个新对象配合一个变更日志。代价是内存占用上去了但换来的是可回放、可对比、可快照。Agent 出问题时我能把任意一步的状态 dump 出来重放那一步这在排查为什么它突然调了个莫名其妙的工具时简直是救命稻草。2.3 决策三工具描述要当成 API 文档来写我踩过最蠢的坑是工具描述写得太随意。比如一个search工具我写的是搜索相关内容结果模型经常传一些它自己编的参数名。后来我把每个工具的描述当成给新人的 API 文档来写参数类型、取值范围、返回结构、什么情况下不该用全部写清楚。改完之后工具调用成功率肉眼可见地上升。这里有个经验描述里明确写当 X 情况时不要调用本工具比写十句本工具很好用都管用。2.4 决策四给工具加预算而不是超时超时是时间维度的限制但 Agent 真正失控往往是调用次数失控。我见过一个 Agent 在 30 秒内调了 200 次搜索工具每次都超时没触发但整体把配额烧光了。所以我在内核里给每个工具加了调用次数预算和 token 预算超预算直接熔断并返回一个明确的错误让模型知道你这条路走不通了换一条。2.5 决策五错误要分类不能一锅端最初我把所有异常都当成工具失败返回给模型结果模型分不清参数错了和服务暂时不可用重试策略完全乱套。后来我把错误分成四类参数错误不可重试、临时故障可重试、权限不足需升级、业务拒绝需换方案。每类错误返回给模型的措辞都不一样模型的处理方式也就对了。2.6 决策六记忆分层别把什么都塞进上下文我一开始图省事把所有历史消息都塞进上下文结果 token 爆炸而且模型被无关信息干扰。后来分成三层工作记忆当前任务相关、会话记忆本次对话摘要、长期记忆跨会话的事实。工作记忆全量保留会话记忆定期压缩成摘要长期记忆只在需要时检索。这个分层让上下文长度稳定在一个可控范围。2.7 决策七插件化不是目的是手段热词里插件化很火但我踩的坑是为了插件化而插件化。早期我设计了一套复杂的插件注册机制结果每个插件都要写一堆样板代码。后来我简化成一个工具就是一个函数加一份描述注册就是往列表里 append。插件化的真正价值是让新增能力不需要改内核而不是搞一套花哨的加载器。2.8 决策八编排逻辑要能看得见Agent 最让人不信任的地方是黑盒。我做了一个决策轨迹记录器把每一步的输入状态、候选工具、最终选择、理由都记下来。这个记录器后来成了团队 review Agent 行为的主要依据。你不需要可视化界面一个结构化的 JSON 日志就够了关键是每一步都要能解释。2.9 决策九并发要谨慎默认串行我一度想让 Agent 并行调用多个工具来提速结果引入了状态竞争和顺序依赖的 bug。后来改成默认串行只有明确无依赖的只读工具才允许并行。这个决策牺牲了一点速度但换来了可预测性。Agent 的可预测性比速度重要得多。2.10 决策十给模型退出的明确信号早期我的循环没有明确的终止条件模型有时候会一直再想想。后来我定义了一个finish工具模型必须显式调用它并给出最终答案循环才结束。同时设了最大步数兜底。这个决策让 Agent 的行为边界清晰了很多。2.11 决策十一提示词要版本化提示词改动对 Agent 行为的影响巨大但我一开始是直接在代码里改字符串改完就忘了之前是什么样。后来我把提示词抽成独立文件并加版本号每次改动都记录改了什么、为什么改、效果如何。这个习惯让我在行为回退时能快速定位是哪次提示词改动导致的。2.12 决策十二评测集要早建哪怕很粗糙我拖到项目中期才建评测集导致前面很多改动都是感觉变好了。后来我建了一个 50 条的小评测集覆盖典型任务和边界情况每次改动跑一遍。评测集不需要多完美能挡住明显回退就够了。这是投入产出比最高的一个决策。2.13 决策十三沙箱不是可选项Agent 会执行代码、改文件、发请求这些操作必须有边界。我给所有有副作用的工具套了一层沙箱文件操作限制在指定目录网络请求走白名单代码执行有资源限制。这个决策在后期救了我一次——一个模型生成的脚本试图遍历整个文件系统被沙箱挡住了。2.14 决策十四内核要能被单测最后一个决策是把内核逻辑和模型调用解耦用一个假的模型返回预设的工具调用序列来单测内核。这样我能在不花钱、不联网的情况下测试循环、状态管理、错误处理。这个决策让内核的稳定性上了一个台阶。3. 通用内核的四种复利3.1 复利一跨场景复用边际成本趋近于零当内核稳定后我把它用到了四个场景代码助手、文档问答、数据清洗、运维巡检。每接一个新场景工作量主要集中在写工具和调提示词内核几乎不动。这就是复利——第一次投入大后面每次复用成本极低。我算过一笔账第二个场景的开发时间只有第一个的三分之一。3.2 复利二能力沉淀工具库越用越厚因为工具是插件化的我在做第二个场景时写的工具第三个场景直接拿来用。比如读文件、搜索、执行命令这些基础工具四个场景共享。工具库像滚雪球一样变大新场景启动时能直接站在前面积累的肩膀上。3.3 复利三问题排查经验可迁移在内核上踩过的坑比如错误分类、预算控制、状态回放在四个场景里都是通用的。我排查第一个场景的经验直接用在后面三个上。这种经验的可迁移性是自研内核相对用框架最大的隐性收益——你真正理解了系统而不是被框架的黑盒牵着走。3.4 复利四评测与观测体系复用评测集和决策轨迹记录器也是内核级的四个场景共享同一套观测体系。这意味着我可以用统一的指标对比不同场景的表现也能把某个场景发现的问题快速验证是否在其他场景也存在。观测体系的复用让质量把控变得系统化而不是每个场景各搞一套。4. 实操中的关键环节与配置4.1 内核主循环的伪代码结构下面是我内核主循环的简化结构用 Python 风格伪代码表示重点是逻辑而非语法def run_agent(state, tools, max_steps20): for step in range(max_steps): # 1. 记录当前状态快照 trace.record(state) # 2. 让模型基于状态决定下一步 decision model.decide(state, tools.descriptions()) # 3. 检查预算 if budget.exceeded(decision.tool_name): state state.with_error(预算超限请换方案) continue # 4. 执行工具带沙箱 if decision.tool_name finish: return decision.answer result sandbox.execute(tools[decision.tool_name], decision.args) # 5. 更新状态不可变 state state.append(decision, result) return 达到最大步数未完成这个结构看起来简单但每一行背后都是前面 14 个决策的体现。比如trace.record对应决策八budget.exceeded对应决策四sandbox.execute对应决策十三。4.2 工具描述的模板我用的工具描述模板大致如下这个模板是踩了决策三的坑之后定下来的{ name: search_code, description: 在代码库中搜索匹配的代码片段。当需要定位某个函数或变量的定义时使用。当只是想知道文件是否存在时不要用本工具改用 list_files。, parameters: { query: {type: string, description: 搜索关键词支持正则}, path: {type: string, description: 搜索范围默认为仓库根目录} }, returns: 匹配的代码片段列表每项包含文件路径、行号、内容 }注意description里明确写了什么时候不要用这是提升调用准确率的关键。4.3 错误分类的返回措辞不同错误类型返回给模型的措辞我做了区分实测下来模型的处理方式明显更合理错误类型返回措辞示例模型预期行为参数错误参数 query 缺失请补充后重试修正参数重试临时故障服务暂时不可用可稍后重试换工具或等待权限不足当前无权限访问该路径换路径或报告业务拒绝该操作被策略拒绝请换方案放弃该路径4.4 状态快照的存储格式状态快照我用 JSON Lines 存储每行一个步骤方便追加和回放{step: 1, state_hash: a1b2, tool: read_file, args: {path: req.md}, result_summary: 读取成功1200字} {step: 2, state_hash: c3d4, tool: search_code, args: {query: config}, result_summary: 命中3处}state_hash让我能快速判断两步之间状态是否真的变了排查空转问题特别有用。5. 常见问题与排查技巧实录5.1 模型反复调用同一个工具怎么办这是最常见的失控模式。我的排查顺序是先看工具描述是不是有歧义再看错误返回是不是让模型误以为再试一次就好最后看预算是不是没设。大部分情况下把错误措辞从失败改成该路径不可行请换方案就能解决。5.2 上下文越来越长导致变慢变贵先检查记忆分层有没有做。如果做了还长看是不是工作记忆里塞了太多工具返回的原始数据。我的做法是工具返回时只保留摘要原始数据存到外部需要时再检索。这个改动让我的平均上下文长度降了 60%。5.3 换了模型后行为大变这是提示词版本化决策十一发挥作用的地方。我会用评测集跑一遍新模型对比通过率和步数。如果差异大先看是不是新模型对工具描述的敏感度不同通常微调描述就能拉回来。5.4 排查速查表现象可能原因排查动作空转不结束缺 finish 信号或预算检查终止条件和预算工具调用参数错描述不清补全参数说明和反例状态错乱可变状态被共享改不可变数据结构偶发失败并发竞争改回串行行为回退提示词改动对比版本记录5.5 几个我踩过的独家坑第一个坑是工具返回了非结构化文本模型解析起来很吃力。后来我强制所有工具返回结构化数据模型的理解准确率明显提升。第二个坑是在提示词里写了太多示例导致模型过度模仿示例而不会举一反三。示例控制在 2 到 3 个就够了。第三个坑是忘了给只读工具和写工具做区分导致模型在只读阶段就尝试改文件。给工具打上readonly标记后我能在编排层做更细的控制。6. 我对这套内核的后续扩展想法这套内核目前跑得挺稳但我还在持续打磨。接下来想做的方向有几个一是把决策轨迹做成可查询的方便按工具调用序列检索历史案例二是把评测集从 50 条扩到 200 条覆盖更多边界三是探索多 Agent 协作但前提是单 Agent 内核足够稳否则多 Agent 只会把问题放大。我个人在实际操作中的体会是Agent 开发最忌讳的就是先跑起来再说。跑起来很容易但跑起来之后你会发现所有没想清楚的设计决策都会以 bug 的形式回来找你。那 14 个决策里有至少一半是我在返工中补上的。如果你正准备从零做 Agent我的建议是先把状态管理、工具边界、失败恢复这三件事想清楚再动手写第一行代码。这比任何框架都重要。