最近我们团队在啃一个内部项目,名字很直白:AI Debug Engineer。目标是让Agent拿到一份报错信息、一堆日志、一个编译失败的仓库时,能像一位有三年经验的工程师一样定位问题、给出修复建议,甚至自动跑测试验证。项目推进到第二周,我得出了一个在团队里挨了不少骂的结论:别再往 Skill 里塞一切了。
第一版实现非常“诚实”:我们把所有调试知识揉进了一个超级 Skill。错误码字典、Linux 命令、Git 操作、Python 调试技巧、日志分析规范、主流框架的常见坑位,全部写进一个文件,自信地认为“知识越多,Agent越聪明”。结果上线第一轮就翻了车。不是不智能,而是整个上下文窗口里充满了噪声。用户问一句“这个SQL为什么那么慢”,它先把find、grep、awk的用法解释了一遍。我们才开始意识到,Skill 不是知识库的容器,而是动作的触发器。
这篇文章把这次重构过程展开聊聊:什么该放Skill、什么该交给Agent、调试类任务怎么拆解、错误排查怎么闭环。如果你也在写Agent的插件或技能,或者正纠结“skill和agent的区别”,再或者被网上各种“好用的skill”冲昏了头脑,这篇应该能帮你少走几个弯。
1. 从“终极Skill”到组合式Debug Agent:为什么要重新思考
1.1 第一版:把整个调试宇宙塞进一个Skill
刚开始做AI Debug Engineer的时候,我们的出发点是“消灭重复劳动”。于是第一版长这样:一个叫 debug_master 的 Skill,里面塞了十几类内容。写代码的时候大家都很兴奋,觉得这是一个能让模型自己查错、自己修、自己验证的万能插件。文件结构大致是这样的:
debug_master/ prompt.md knowledge/ error_codes.md linux_commands.md git_cheatsheet.md python_debug_patterns.md web_framework_pitfalls.md database_troubleshooting.md tools/ run_shell.py read_log.py apply_patch.py看起来模块化,但在模型侧这些内容最终都会合并成一个超大上下文。更可怕的是,我们还在 prompt.md 里写了一长串“专业工程师”的persona、指令、输出格式、禁止事项。每次调用,模型都要“读”完一大堆资料再开始回答。一个简单的问题,它得从“读文档”开始,速度和准确率自然都崩了。后来我把这个设计发给一个做过多年AI平台的同行看,他回了一句:“你把图书馆塞进一把螺丝刀,还想用它修灯泡。”当时我不服气,后来发现他说得太准了。
1.2 为什么Skill里的东西越多,模型反而越笨
这个现象一度让我很困惑:大模型不是参数越多越聪明吗?给它的上下文多,按理说知识更丰富。但实际情况是,大模型的注意力机制天然更关注与当前输入相关的片段。你把100个错误码塞进知识库,模型在回答“MySQL锁等待超时”时,会同时考虑“Python缩进错误”和“Nginx 502”的信息,这些无关内容就是噪声。更关键的是,Skill给模型的不是“资料”,而是“行为指令”。一个真正优秀的技能,应该让模型知道:现在进入什么场景、可以调用哪些工具、输出应当符合什么格式。至于“错误码怎么写”这类静态知识,更适合建模成可检索片段,而不是让模型死记硬背。我们把它全塞进去,等于让一个工程师背着整个图书馆去修一个灯泡,每一步都要在书堆里翻找。
这个问题在调试场景里会被放大,因为调试本身是目标驱动的推理过程。模型每次只能调用有限的上下文去做因果分析。如果它脑子里塞满了“哪种命令怎么用”,自然就没有余量去思考“为什么会报这个错”。所以我们最后的结论是:Skill里的信息越多,模型的推理能力就越容易被稀释。这不是玄学,是Transformer注意力机制带来的现实约束。
1.3 重新定义分工:Agent负责决策,Skill负责动作
重构的核心是把职责拆开:Agent是一个有目标、会拆解、会回忆上次结论的动态执行体;Skill则是那个执行体手里的“专业扳手”。Debug这件事,本质上是“观察—假设—验证”的循环,而不是“从知识库搜出答案”。前者属于Agent的规划能力,后者才属于Skill的工具能力。
所以“skill和agent的区别”这个问题就有了答案:Agent是主体,Skill是主体可调用的插件。你不可能让Skill自己决定“先去查日志还是先跑测试”,那是Agent的事。如果你把决策和动作都塞进Skill,相当于把整个机器人装进一把螺丝刀里,还要这把螺丝刀自己判断该修哪里。这不是能力变强,是耦合变重。我们重构后的第一行设计原则就写在团队文档的顶部:Agent动脑,Skill动手,谁也别越界。
2. 核心设计原则:哪些内容该留在Skill里,哪些不该
2.1 Skill的正确定位:原子动作与静态知识查询
重构后我们给Skill下了个非常朴素的定义:技能 = 在特定输入下,完成单一可复用动作的模块。对于AI Debug Engineer,真正值得做成Skill的不是“会修所有bug”,而是下面几类:
- 信息采集类:解析traceback、提取日志切片、读取测试报告、查看Git提交记录;
- 静态知识类:错误码速查、框架版本兼容性表、语言规范速查;
- 动作执行类:运行单元测试、执行linter、打补丁、回滚变更。
这些动作有一个共同点:输入输出明确,不需要大模型做高难度推理。比如“给我这段traceback的第一行和文件名”,规则就能做,做成Skill又快又稳。而那些需要推理的部分——判断哪个假设最值得验证、比较两条日志的时间先后、决定下一步操作——全部留给Agent主流程。我们在设计时还加了一条硬约束:如果某个Skill希望增加“根据上下文智能判断”之类的描述,那基本就说明它还不够原子,应该继续拆。
| 维度 | 塞满一切的坏Skill | 原子化的好Skill |
|---|---|---|
| 定位 | 知识大全 | 单一动作 |
| 输入 | 隐式场景 | 明确参数 |
| 上下文占用 | 每次全量加载 | 按需加载 |
| 维护成本 | 高 | 低 |
| 链接性问题 | 改一处全局乱 | 互不影响 |
| 模型执行效果 | 意图混淆、上下文爆炸 | 清晰可靠 |
2.2 Debug Engineer的核心循环:观察—假设—验证
调试和写业务代码有一个本质区别:业务代码是先定需求再实现;调试是结果已存在,但原因未知,需要在不确定性中做决策。所以AI Debug Engineer不能设计成一次问答,而应该设计成一个循环。我们最后定下来的核心循环有五个状态:
- READY:等待接收任务;
- OBSERVED:从报错、日志、测试失败中收集事实;
- HYPOTHESIZED:针对现象提出至少一个可验证的假设;
- VERIFIED:执行某个工具或命令,验证假设是否成立;
- RESOLVED或ABORT:成立则修复并回归,不成立则回到第2步或第3步。
这个循环本身放在Agent的planner里,而不是放在任何一个Skill里。每个状态会触发不同的Skill:OBSERVED阶段调用日志解析,HYPOTHESIZED阶段调用错误码查询,VERIFIED阶段调用测试执行。Skill和状态一一映射,模型永远知道自己现在在干嘛,上下文也不会被无关内容污染。这里最需要注意的一点是:不要试图用一条巨大的系统提示词去描述所有状态转移。状态机的逻辑应该写在代码里,或者写成非常精简的配置文件,让Agent在每一步只看到当前状态对应的信息。
2.3 动线清晰的轻量编排:让大模型自己拆问题
定好循环后,我们给Agent写了非常薄的调度prompt,核心只有几十行,不再包含任何专业知识。它负责做三件事:识别用户请求属于哪个阶段、挑选一个候选Skill、汇总已有事实。这里用了一个比较讨巧的约束——禁止Agent直接输出修复建议,除非它已经填完DebugContext中的“当前事实”和“已验证假设”字段。以下是精简后的context结构示例,实际上调用时用JSON传给模型,模型先填充再继续规划:
{ "task": "用户报告的报错信息或调试目标", "observed_facts": [], "hypotheses": [], "verifications": [], "current_state": "READY", "selected_skill": null, "final_answer": null }这个结构让Agent每一步都有据可依。我们在实测中发现,只要把“当前状态”和“已做验证”写清楚,即使模型偶尔选错了Skill,下一步它也能根据context纠偏。这比把所有可能性写进prompt要管用得多。顺带说一句,网上很多人在问“skill怎么写”,我的建议是:先不要急着写内容,把你希望这个Skill解决的问题抽象成一个“输入→输出”的函数签名,再开始填细节。你连输入输出都说不清楚,那这个Skill大概率会成为下一个上下文炸弹。
2.4 别再混淆:Agent、Skill、Memory、MCP到底啥关系
热词里大家都在问“agent和skill的区别”“springai skill”“codex skill”,本质是同一件事的各个面。我也见过有人把Skill叫插件,把Memory叫记忆,把MCP叫工具协议,最后开会时发现大家说的不是同一个东西。这里给一个我的简化理解:
- Agent:能感知环境、制定目标、调用工具、保存记忆的执行体;
- Skill:Agent可复用的能力单元,是“会做什么”;
- Memory:Agent运行过程中的状态信息,是“记住了什么”;
- MCP:让外部工具变成标准化可调用资源的协议,是“怎么接通”。
有人会把Skill做成包含大段persona的文件,这也可以,但请记住:persona和决策逻辑属于Agent,工具定义和局部知识属于Skill,长期状态存在Memory。混在一起,就会回到一开始那个“debug_master”的坑:改一处逻辑,整个技能全废。网上搜“codex skill”“opencode skill”会看到很多人把调试技巧写成插件,但如果仔细看,那些真正好用的Skill大多只做一件事:解析、搜索、格式化、运行,而不是“取代Agent去思考”。你的Agent架构越清晰,你越能分辨哪些Skill值得装。
3. 实操过程:从满身赘肉到清晰可控的重构记录
3.1 第一步:盘点现有Skill,拆分“角色能力”和“领域知识”
动手重构的第一步不是删代码,而是先盘点。我们花了半天时间把原来的debug_master拆成了一张能力清单,给每个条目打标签:是“行为”,还是“知识”。比如“知道MySQL常见的三个死锁场景”属于知识,可以做成查询型Skill;“执行一条SQL去查看锁状态”属于动作,做成工具型Skill。两者都不该塞进Agent主体。拆完之后,原来一个8万字符的Skill变成了下面这几个小型技能模块:
| 模块名称 | 类型 | 输入 | 输出 |
|---|---|---|---|
| traceback_parser | 动作 | 报错文本 | 文件、行号、异常链 |
| log_slicer | 动作 | 日志路径、时间范围 | 关键日志片段 |
| error_code_lookup | 知识 | 错误码前缀 | 常见原因列表 |
| test_runner | 动作 | 测试命令 | 通过/失败、失败摘要 |
| patch_applier | 动作 | diff内容 | 应用结果、回滚信息 |
这里要特别强调:我们不追求“一套Skill全平台通用”。每个Agent应用场景不同,Skill的切分粒度自然也不同。我们也没有刻意使用某个复杂的插件框架,就是一个普通的工具集合。每个Skill都维护独立的输入输出定义、版本号、测试样例,后续接入OpenCode这类支持Skill的编辑器插件时,也能直接迁移。结构清不清晰,取决于你能不能给每个Skill写出一句话说明,而不是取决于文件夹多不多。
3.2 第二步:让Agent先“观察”,再“胡说八道”
重构后我们发现,AI Debug Engineer最大的提升来自于一个很小的改动:强制Agent在给结论前先填写DebugContext的observed_facts,并只允许使用这些事实做推理。以前它拿到一个报错,会直接脑补一个最常见的修复方案。现在我们会先调用traceback_parser,把异常类型、触发文件、行号抽出来;再调log_slicer,把最近两分钟内相关进程的日志切出来。这些结果全部写到observed_facts里,模型再看到的是“结构化事实”,而不是一整段原始日志。
这一步相当于给调试过程立了个规矩:先找证据,再下判断。模型即使不太聪明,只要证据链完整,也能得出可靠结论。反之,如果证据不足,模型会返回“当前事实不足,需要补充XX信息”,而不是硬编一个答案。我们曾经在测试集里故意给了一条不完整的报错,比如只给一个“Segmentation fault”没有其他信息。重构前的模型会给出五种可能原因,每一句都像正确的废话;重构后的Agent会说“当前信息不足,需要确认触发命令、核心转储文件、复现步骤”。这个变化让我非常惊喜,因为它意味着Agent开始懂得“不知道”也是一种有效状态。
3.3 第三步:把常见调试策略写成“作业指导书”,而不是塞进Skill
调试策略是最容易让人手痒想写进Skill的内容。比如“遇到CPU高占用先看top、再看线程栈、再看GC日志”,这套流程很成熟,但它是方法论,不是技能。我们在项目里把它叫做“作业指导书”,本质是一个Markdown格式的过程文档,只在Agent处于特定状态时才会被注入。我们为不同场景准备了独立指导书:Python异常、Java内存溢出、SQL慢查询、前端白屏。每个指导书只包含“按顺序要做的检查”和“每项检查应该调用哪个Skill”。
注意,指导书本身不是Skill,它更像Agent思维链的流程图。这样做的最大好处是:你改一种场景的调试流程,完全不碰其他场景;而如果把它写进一个巨型Skill,改一处就得重新测试全局。举一个实际例子:我们最初在debug_master里写了一条“遇到JSON解析错误就检查文件编码”,后来发现这个建议在Web场景下不适用,因为还有可能是分层解析失败。但因为所有知识都在一个Skill里,我们没法只改这一条而不影响其他知识块。现在,这条建议只在“Python异常处理指导书”里,且只影响traceback_parser和test_runner两个模块,改动成本从“重新写一遍文件”变成了“改两行Markdown”。
3.4 第四步:打通验证闭环,防止Agent自嗨
最后一个关键操作是“强制验证”。重构前,模型经常给出修复方案但从不运行;重构后,我们给Skill体系加了一条规则:任何修复补丁必须经过test_runner验证,只有测试通过才允许输出最终结果。具体做法是:Agent生成diff后,不等用户确认,先交给patch_applier应用到一个临时分支,再调用test_runner跑相关测试。如果测试失败,当前的假设标记为失效,自动回到HYPOTHESIZED状态。这里建议你在真实项目里设置好安全边界,比如只允许在容器或沙箱环境中自动执行命令,避免直接操作生产环境。下面是我们用来向Agent描述这个闭环的一段配置摘要,重点不是完整代码,而是层次关系:
pipeline: - skill: traceback_parser on_success: log_slicer - skill: log_slicer on_success: hypothesis_form - skill: test_runner on_success: resolve on_failure: hypothesis_form - skill: patch_applier on_success: test_runner这段配置的巧妙之处在于,它让Skill之间的依赖关系变成显式的,Agent只需要顺着流程走。即使是不太懂底层调试细节的小模型,也能按这条流水线完成任务。你会发现,整个设计里没有一个环节依赖“模型在宽松指令下自觉做得更好”。我们把能固化的东西全部固化,把需要推理的空间留给真正值得推理的部分,比如假设排序、线索关联。这个边界一旦定住,后续加新场景就只是一条新流水线的事。
4. 避坑指南与问题排查实录
4.1 常见问题速查表
写这篇文章时,我们整理了AI Agent在调试场景里最常遇到的几个问题,也附上了我们实践的解法。
| 症状 | 根本原因 | 我们的解法 |
|---|---|---|
| Skill一调用就上下文爆炸 | 知识全部塞进一个Skill | 拆成原子Skill,按需加载 |
| 该修A却跑去查B | 意图路由失败 | Agent先填DebugContext,再选Skill |
| 给了方案但不验证 | 缺少验证闭环 | 强制test_runner校验 |
| 修改Skill后其他场景受影响 | 单Skill覆盖过多 | 每类场景独立作业指导书 |
| 模型反复调用同一个工具 | 没有状态机控制循环 | READY到VERIFIED的状态流转 |
| 日志太长,模型看不完 | 直接把原始日志当输入 | 用log_slicer切成摘要片段 |
这张表基本就是我们踩坑的缩影。你会发现大多数问题的根源,都不是模型能力不够,而是我们给模型的“舞台”太乱了。Skill设计得好不好,决定了Agent是站上舞台自由发挥,还是被困在一个塞满杂物的仓库里找不到路。
4.2 一个完整的复盘:从“答案错误”到“证据链修复”
重构完成后,我们用一批真实报错做了回归测试。其中有个案例很典型:Python项目运行时报“json.decoder.JSONDecodeError: Unexpected UTF-8 BOM”。第一版超级Skill给了一堆通用建议,从“检查JSON格式”到“增加异常处理”,完全没有命中。重构后的Agent走了一遍标准流程:
- traceback_parser抽取异常类型、文件位置;
- log_slicer拿到报错前几行日志,发现文件头有非法字符;
- hypothesis_form提出“文件可能是UTF-8 with BOM,但解析用的模式不认BOM”的假设;
- test_runner跑了一个快速脚本验证读取前3字节是否符合BOM规则;
- 结论是打开文件时增加encoding='utf-8-sig’即可修复,随后自动运行测试通过。
这个例子让我直观感受到,Debug Agent的本质不是搜索引擎,而是一名严谨的工程师。它必须靠证据链说话。把决策流程还给它,把工具和知识做成可插拔的Skill,才是正确的打开方式。从工程回报率来看,这一个案例就抵得上我们最初写那个8万字符Skill所花的时间,因为前者让Agent从“赌徒”变成了“侦探”。
4.3 再补充一点:如何避免“Skill依赖症”
现在网上有很多“好用的skill”仓库,很多开发者看到什么技能都想装。我的建议是:先判断这个Skill解决的是单一问题,还是想包揽一类问题。单一问题优先装,包揽一类问题大概率是个坑。另外,Skill也不是越原子越好,太碎了会让Agent编排成本变高。我们的经验是,粒度控制在“一次调用能完成一个可被验证的动作”是最舒服的。比如“读取测试报告”是一个独立动作,但“测试报告+日志+错误码综合分析”就应该拆成三个Skill,由Agent自己决定是否都调用。
这次重构之后,我给团队的约定很简单:每次你想往现有Skill里增加新场景时,先停一下,问自己三个问题。它需要新的输入吗?它会因为旧逻辑而跑错吗?它是不是应该成为一个独立Skill?如果三个答案里有任何一个“是”,那就新建吧。别怕Skill多,怕的是每个Skill都在塞一堆无关内容。Agent开发里的最大成本从来不是写代码,而是想清楚边界。Skill不应该是一个收纳盒,而应该是一把扳手。把决策留给人或Agent主体,把能力留在Skill里,调试这件事才会真正自动起来。