ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI Agent开发实战:为什么定位与修复必须拆分为两个任务

AI Agent开发实战:为什么定位与修复必须拆分为两个任务 1. 为什么“定位”和“修复”不能塞进同一个任务里1.1 一个真实场景引出的问题去年我在做一个内部代码维护助手的时候踩过一个很典型的坑。当时的需求很简单给一个报错堆栈让 AI 找出问题并改掉。我一开始的做法是把“找 bug”和“改 bug”写进同一个提示词里让模型一次性输出“问题在哪、怎么改、改完的代码是什么”。结果跑了几十次之后发现成功率低得离谱——要么是定位错了地方改了一堆无关代码要么是定位对了但改的时候又把别的地方改坏了。后来我把这个任务拆成两步第一步只让模型输出“问题出在哪个文件、哪一行、根因是什么”第二步拿着第一步的结论去生成补丁。成功率一下子从三成左右提到了七成以上。这个经历让我意识到定位和修复本质上是两种不同的认知任务硬塞在一起模型会互相干扰。这篇内容就是围绕这个现象展开的。我会从任务本质、上下文管理、Agent 架构、实操拆解几个角度把“为什么要拆”讲透同时给出可以直接参考的拆分方案和提示词模板。不管你是刚接触 AI Agent 开发还是已经在做多 AI 协作的工程化落地应该都能从中拿到一些能直接用的东西。1.2 定位和修复到底差在哪先把这个核心问题说清楚。定位Localization的目标是在给定的代码库、日志、报错信息里找到“问题发生在哪里”以及“为什么会发生”。它的输出是一个判断是一段解释是“病灶的位置和成因”。修复Repair的目标是在已知问题位置和成因的前提下生成一段能通过验证的代码变更。它的输出是一个动作是一段 diff是“手术方案”。这两件事对模型能力的要求完全不同。定位更依赖理解、检索、推理能力模型需要在大量信息里做筛选和判断容错率相对高——定位偏一点后面还能纠正。修复更依赖生成、约束、验证能力模型需要在明确的边界内产出精确的代码容错率极低——改错一个字符整个补丁就废了。打个比方定位像是医生做诊断修复像是医生做手术。你不会让一个医生一边做 CT 一边开刀因为这两件事需要的注意力、工具、心态都不一样。AI 也一样。1.3 混在一起会触发哪些具体问题我把实际遇到过的失败模式整理了一下大概有这么几类失败模式具体表现根本原因定位漂移模型直接跳到“怎么改”跳过了“为什么”生成任务的目标压过了理解任务的目标过度修改定位对了但顺手改了无关代码修复阶段缺少明确的边界约束幻觉补丁改的代码引用了不存在的函数或变量模型在没完全理解上下文时就动手上下文污染前面的错误定位结论影响了后面的修复两个任务的中间状态混在同一个上下文里无法回溯改错了不知道是哪一步出的问题定位和修复的中间结果没有分离这些问题的共同点是当模型同时承担“判断”和“生成”两个目标时它会在两者之间做妥协而妥协的结果往往是两边都做不好。2. 拆分背后的核心原理认知负荷与上下文管理2.1 认知负荷理论在 AI 任务上的映射认知负荷这个概念原本是教育心理学里的说的是人的工作记忆容量有限同时处理太多信息就会过载。这个逻辑放到大模型身上同样成立只不过“工作记忆”变成了上下文窗口和注意力分配。当你让模型同时做定位和修复时它需要在同一个推理过程里维护两套目标一套是“我要找对地方”一套是“我要写对代码”。这两套目标会争夺模型的注意力资源。实测下来模型往往会偏向生成任务因为生成任务的输出更“显眼”——它要吐出代码而定位任务的输出只是一段分析文字容易被忽略。拆开之后每个阶段只有一个明确目标。定位阶段的提示词里全是“找、分析、判断”这类词修复阶段的提示词里全是“改、生成、约束”这类词。模型不需要在两者之间切换注意力集中度明显提升。2.2 上下文窗口的“干净度”问题还有一个很实际的原因上下文污染。假设你把定位和修复放在一个对话里。模型先输出了一段定位分析里面可能包含一些猜测性的内容比如“我怀疑是这里的问题”。然后它基于这个猜测去修复。如果猜测是错的但修复代码看起来又挺像那么回事你就很难判断到底是定位错了还是修复错了。拆成两个任务之后定位阶段的输出是一个结构化的结论比如{ file: src/utils/parser.js, line: 142, root_cause: 正则表达式未处理空字符串输入, confidence: 0.85 }修复阶段只接收这个结论不接收定位过程中的所有中间推理。这样上下文是干净的修复阶段不会被定位阶段的猜测干扰。而且如果修复失败你可以单独回去检查定位结论对不对排查路径非常清晰。2.3 两个任务的验证方式完全不同定位的验证方式是人工确认或交叉验证——你可以让另一个模型或者人来判断“这个定位对不对”。修复的验证方式是自动化测试——跑一遍测试用例过了就是过了没过就是没过。这两种验证方式对任务设计的要求不一样。定位任务需要输出可解释的推理过程方便人工审查修复任务需要输出可执行的代码变更方便自动验证。如果混在一起你既拿不到干净的推理过程也拿不到干净的代码变更。提示在实际工程里定位阶段的输出建议强制结构化JSON 或固定格式这样后续无论是人工审查还是自动流转都方便。修复阶段的输出建议直接给 diff 或完整文件不要给“修改建议”这种模糊的东西。3. Agent 架构下的拆分方案设计3.1 两种主流拆分模式在 Agent 开发里拆分定位和修复有两种常见模式串行模式一个 Locator Agent 负责定位输出结构化结论一个 Fixer Agent 负责修复接收结论并生成补丁。两个 Agent 之间通过一个明确的数据结构传递信息。并行模式多个 Locator Agent 从不同角度定位比如一个看堆栈、一个看代码变更历史、一个看测试失败信息然后把定位结果汇总再交给 Fixer Agent。串行模式适合大多数场景实现简单调试方便。并行模式适合复杂 bug比如涉及多个模块的连锁问题但工程复杂度高需要处理结果冲突和置信度合并。我个人的建议是先从串行模式做起跑通了再考虑并行。很多团队一上来就搞多 Agent 协作结果定位结果对不齐反而更难调试。3.2 Locator Agent 的设计要点Locator Agent 的核心任务是“缩小范围”。它的输入通常包括报错信息或异常堆栈相关代码文件或代码库索引最近的代码变更记录测试失败信息它的输出应该是一个排序后的候选列表而不是一个单一结论。因为定位本身是有不确定性的给修复阶段多个候选让修复阶段自己判断往往比强行给一个结论更好。一个实际可用的 Locator 提示词结构大概是这样你是一个代码问题定位助手。你的任务是在给定代码库中找到问题所在位置和根本原因。 输入 - 报错信息{error_message} - 相关文件{relevant_files} - 最近变更{recent_changes} 要求 1. 列出最多 3 个可能的出错位置按可能性从高到低排序 2. 对每个位置说明判断依据 3. 不要生成任何修复代码 4. 输出格式为 JSON 输出格式 { candidates: [ { file: 文件路径, line: 行号, reason: 判断依据, confidence: 0.0-1.0 } ] }注意最后一条要求“不要生成任何修复代码”。这是为了防止模型“手痒”直接跳到修复。实测下来如果不加这条约束大概有 20% 到 30% 的情况模型会直接开始改代码。3.3 Fixer Agent 的设计要点Fixer Agent 的核心任务是“在约束内生成补丁”。它的输入是 Locator 的输出加上原始代码输出是代码变更。关键约束有几个只改定位结论指向的位置不要顺手改别的地方保持代码风格一致不要引入新的依赖生成可验证的变更最好是 diff 格式如果定位结论明显不对要拒绝修复并说明原因最后一条特别重要。实际跑的时候Locator 有时候会给出错误的定位如果 Fixer 盲目去修就会产生“看起来改了但没改对”的补丁。让 Fixer 有拒绝权可以过滤掉一部分错误定位。3.4 两个 Agent 之间的数据契约拆分方案能不能跑通很大程度上取决于两个 Agent 之间的数据契约设计得好不好。我踩过的坑是一开始让 Locator 输出自然语言Fixer 去解析自然语言结果解析经常出错。后来改成强制 JSON 之后稳定性好了很多。数据契约大概包含这些字段字段类型说明filestring问题所在文件路径line_startnumber起始行号line_endnumber结束行号root_causestring根本原因描述confidencenumber置信度 0-1evidencearray判断依据列表suggested_scopestring建议修改范围这个契约的好处是Locator 必须把“为什么这么判断”说清楚Fixer 拿到的是一个明确的范围不会乱改。4. 实操拆解从报错到补丁的完整流程4.1 准备阶段构建代码库索引在跑定位之前需要先让模型能“看到”代码。有两种做法全量塞入把整个代码库塞进上下文。适合小项目代码量在几千行以内。缺点是上下文占用大而且模型容易在大量代码里迷失。检索增强先对代码库做索引根据报错信息检索相关文件只把相关部分塞进上下文。适合中大型项目。实现方式可以用简单的关键词匹配也可以用向量检索。我一般用混合方案先用报错信息里的文件名、函数名做关键词匹配拿到一批候选文件再用向量检索补充一批语义相关的文件最后合并去重控制在上下文窗口的 60% 以内。4.2 定位阶段让模型输出结构化结论定位阶段的提示词需要包含几个关键要素角色设定明确告诉模型它是定位助手不是修复助手输入信息报错信息、相关代码、变更记录输出格式强制 JSON包含候选列表约束条件不生成修复代码按可能性排序实际跑的时候我会让 Locator 输出 3 个候选然后人工或者用另一个模型做一次交叉验证。如果 Top1 的置信度超过 0.8直接进入修复如果低于 0.5就补充更多上下文重新定位。这里有个经验定位阶段的温度参数建议调低比如 0.1 到 0.3。定位需要的是准确和稳定不需要创造性。修复阶段可以稍微高一点0.2 到 0.5给模型一点灵活性。4.3 修复阶段在约束内生成补丁修复阶段的提示词结构你是一个代码修复助手。你的任务是根据定位结论生成代码补丁。 定位结论 {locator_output} 原始代码 {original_code} 要求 1. 只修改定位结论指向的位置 2. 保持代码风格一致 3. 输出 unified diff 格式 4. 如果定位结论明显错误输出 {status: rejected, reason: ...} 5. 不要引入新的依赖 输出格式 { status: fixed | rejected, diff: unified diff 内容, explanation: 修改说明 }跑完修复之后一定要跑测试。测试通过才算修复成功。如果测试失败把失败信息反馈给 Fixer让它重新生成。一般重试 2 到 3 次还不行就退回定位阶段重新定位。4.4 验证阶段自动化测试与人工复核验证分两层自动验证跑单元测试、集成测试。这是硬指标过了就是过了。人工复核对于自动验证通过但改动较大的补丁建议人工看一眼。特别是涉及核心逻辑的修改自动化测试不一定能覆盖所有边界情况。我一般会设置一个阈值如果 diff 行数超过 50 行或者涉及的文件超过 3 个就触发人工复核。小改动直接自动合并。4.5 一个完整的实操示例假设报错信息是TypeError: Cannot read property length of undefined at parseInput (src/utils/parser.js:142:23) at handleRequest (src/api/handler.js:56:12)第一步定位把报错信息、parser.js 和 handler.js 的相关代码、最近的 git 变更记录一起塞给 Locator。Locator 输出{ candidates: [ { file: src/utils/parser.js, line_start: 140, line_end: 145, root_cause: parseInput 函数未对输入参数做空值检查当 input 为 undefined 时访问 input.length 报错, confidence: 0.92, evidence: [报错堆栈直接指向 parser.js:142, 该行代码为 input.length, 最近变更记录显示该函数上周刚修改过] } ] }第二步修复把定位结论和 parser.js 的原始代码给 Fixer。Fixer 输出--- a/src/utils/parser.js b/src/utils/parser.js -139,7 139,10 function parseInput(input) { if (input undefined || input null) { return []; } const tokens []; for (let i 0; i input.length; i) { tokens.push(input[i]); } return tokens; }第三步验证跑测试通过。合并。这个流程跑下来从报错到补丁大概需要 30 秒到 2 分钟取决于代码库大小和模型响应速度。比人工排查快很多而且定位结论是可追溯的出了问题能查到是哪一步出的错。5. 常见问题与排查技巧实录5.1 定位不准怎么办定位不准是最常见的问题。排查思路检查上下文是否足够是不是相关文件没塞进去报错信息是不是太模糊检查提示词是否有歧义是不是让模型同时做了太多事检查温度参数是不是太高了导致输出不稳定增加候选数量从 3 个增加到 5 个给修复阶段更多选择引入交叉验证用另一个模型独立定位一次对比结果我遇到过一次定位一直不准的情况最后发现是代码库索引没更新模型看到的是旧版本的代码。所以索引更新这个环节一定要自动化不能靠手动。5.2 修复阶段改坏了别的地方这个问题通常是约束不够明确导致的。解决办法在提示词里明确“只改定位结论指向的位置”输出 diff 而不是完整文件减少误改范围修复后跑全量测试不只是跑相关测试设置 diff 行数阈值超过就人工复核还有一个技巧让 Fixer 在生成 diff 之前先输出一个“修改计划”说明它打算改哪些地方、为什么。这样你可以在它动手之前就发现范围不对。5.3 两个 Agent 之间传递信息丢失这个问题一般出在数据契约设计上。排查清单问题可能原因解决办法Fixer 说“定位信息不完整”Locator 输出格式不对强制 JSON schema 校验Fixer 改错了位置行号偏移用代码片段而不是行号定位Fixer 拒绝修复置信度太低补充上下文重新定位定位结论和修复补丁对不上中间有格式转换错误加一层格式校验行号偏移是个很隐蔽的坑。因为代码库在变Locator 看到的行号和 Fixer 看到的行号可能不一致。解决办法是用代码片段做定位锚点而不是纯行号。比如 Locator 输出“问题在 parseInput 函数的 for 循环那一行”Fixer 根据这个描述去定位比纯行号可靠。5.4 成本和时间开销拆成两个任务意味着两次模型调用成本大概翻倍。但实际算下来因为成功率提升了总体成本反而可能更低。我做过一个粗略的对比不拆分的情况下平均需要 3.2 次尝试才能修好一个 bug拆分之后平均 1.4 次。虽然单次成本翻倍但总成本降低了大概 12%。而且时间开销上因为减少了无效重试整体耗时也短了。如果成本敏感可以考虑用便宜模型做定位贵模型做修复。定位任务对模型能力要求相对低一些用中等模型就能跑得不错。5.5 什么时候不适合拆分拆分不是万能的。以下几种情况可能不适合极简单的 bug比如拼写错误、明显的语法错误直接修就行拆分反而增加开销上下文极小的场景代码量很小模型一眼就能看全拆分意义不大实时性要求极高的场景两次调用带来的延迟不可接受我一般的判断标准是如果人工修这个 bug 需要先看几分钟代码才能动手那就值得拆分。如果一眼就能看出问题直接修。6. 一些实操心得和后续扩展方向6.1 提示词里的“禁止项”比“要求项”更重要这是我踩了很多坑之后总结出来的。在 Locator 的提示词里写“不要生成修复代码”比写“请仔细分析问题”更有效。因为模型天生倾向于“完成任务”如果你不明确禁止它就会顺手把修复也做了。同理在 Fixer 的提示词里写“不要修改定位范围之外的代码”比写“请谨慎修改”更有效。明确的禁止项能显著降低模型的越界行为。6.2 中间结果要落盘定位结论和修复补丁都要存下来。一方面方便回溯另一方面这些数据可以用来做后续的模型微调或者提示词优化。我一般会把每次运行的输入、定位结论、修复补丁、测试结果存成一个 JSON 文件按时间戳命名。跑多了之后你会发现哪些类型的 bug 定位容易出错哪些类型的修复容易失败然后针对性地优化提示词。6.3 后续可以扩展的方向这个拆分方案跑通之后有几个自然的扩展方向多 Locator 并行针对复杂 bug用多个 Locator 从不同角度定位然后合并结果。比如一个看堆栈一个看变更历史一个看测试覆盖。Fixer 自验证让 Fixer 在生成补丁之后自己先跑一遍逻辑推演检查有没有明显的逻辑错误再输出最终补丁。定位结果缓存相似的报错信息可以复用之前的定位结论减少重复调用。人工反馈闭环人工复核的结果反馈回系统用来优化定位和修复的提示词。这些扩展不需要一次性全做可以先把基础的串行模式跑稳再逐步加。6.4 一个容易被忽略的细节错误信息的预处理报错信息里往往包含大量噪音比如时间戳、进程 ID、无关的堆栈帧。在塞给 Locator 之前建议做一次预处理提取关键信息错误类型、错误消息、最顶层的业务代码堆栈帧。这个预处理步骤看起来不起眼但对定位准确率影响很大。我实测过预处理之后定位准确率大概能提升 15% 到 20%。因为模型不会被噪音干扰能更聚焦在真正的问题上。预处理可以用简单的正则规则也可以用一个小模型来做。规则方式更可控推荐先用规则跑起来不够用了再上模型。6.5 关于模型选择的一点经验定位和修复对模型的要求不一样。定位更看重长上下文理解和指令遵循能力修复更看重代码生成和格式约束能力。我试过几种组合目前比较稳定的是定位用上下文窗口大、指令遵循好的模型修复用代码能力强、输出格式稳定的模型。不一定要用同一个模型。如果预算有限定位可以用中等模型修复用强模型。因为定位错了可以重试修复错了代价更大。注意不管用什么模型提示词里的输出格式约束一定要严格。我见过太多次因为模型输出格式不对导致整个流程卡住的情况。建议在解析输出之前加一层格式校验格式不对就重试不要硬解析。6.6 最后分享一个小技巧如果你刚开始做这个拆分不知道提示词怎么写可以先手动模拟一遍自己先做定位把定位结论写下来然后只拿着这个结论去做修复。感受一下“只有定位结论”的情况下修复需要哪些信息。然后把这些信息补进 Fixer 的输入里。这个手动模拟的过程能帮你快速找到数据契约里缺了哪些字段。我一开始就是靠这个方法发现“需要把原始代码片段一起传给 Fixer”的因为光有行号Fixer 找不到具体位置。另外定位阶段的输出里evidence 字段特别有用。它不仅是给人工看的也可以作为 Fixer 的参考。如果 evidence 里提到了某个具体的变量名或函数名Fixer 在修复时就能更准确地定位到代码位置。这个拆分方案我用了大半年从最初的单 Agent 硬塞到现在的双 Agent 串行中间踩了不少坑但整体方向是对的。核心就一句话让每个 Agent 只做一件事把中间结果结构化把验证自动化。剩下的就是不断调提示词和补上下文了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进