上个季度我接手了一个跑了五年的营销活动服务代码是Group买的人换过三拨文档停留在三年前。第一次让AI帮忙改促销规则我给它贴了一屏上下文它回了我一段能编译但逻辑明显错的代码第二次我把完整的告警规则甩进对话框它倒是给出了一个很“现代”的重构方案差点把线上还在用的老接口拆了。当时我脑子里冒出一个词散装AI——每个需求都开一个新对话每次都要重新灌上下文每个脚本自成一体没有流程、没有版本、没有复用就像抽屉里什么工具都有但真要干活时永远找不到配套的那把螺丝刀。后来我转向SKILL编排把那些零零散散的AI用法整理成可复用、可组合、可约束的技能块再通过编排串成一条受控的工作流对存量代码做了一次“微创手术”。这次实践让我觉得方向对了。这篇文章就把我的完整思路、文件写法、编排方式和踩坑记录整理出来给同样被“散装AI”折磨的同路人一个参考。1. “散装AI”为什么治不了存量代码1.1 症状我从“散装”现场看到的五个问题先说诊断。我给“散装AI”下的定义是用得很频繁但每用一次都是从零开始AI的理解、产出和流程都不可控。具体到我那个营销项目症状非常典型。第一是上下文反复投喂。同一个服务的代码结构我每周都要给AI重新解释一遍因为每次对话都是新的模型不记得上次的结论。一份代码解读文档被我在聊天窗口里复制粘贴了至少二十遍效率损耗极其明显。第二是产出格式飘忽不定。今天让它输出接口清单它给你一段Markdown表格明天让它输出同一份清单它改给你一个JSON。后续脚本接不住我得手动处理。AI很强但每次都在“即兴发挥”这在使用上是一个很麻烦的问题。第三是行为边界模糊。你只是让它“看一下这几个Controller有没有幂等问题”它顺手就把你的路由注解给改了。没有边界就没有安全感这在存量代码上尤其致命——老系统最怕的就是“顺手改进”。第四是流程完全不可复现。上回处理某个告警AI一顿操作解决了但这套方法没有沉淀。同样的告警一个月后再次出现一切从零开始还要冒着操作差异的新风险。第五是没法多人协作。团队里每个人都在用自己的姿势用AI你写的提示词我不一定能看懂我的流程你也没法复用。散装不等于丰富散装等于失控。1.2 病根你给AI的只是“回答”不是“技能”把五个症状归拢起来根子其实是一个问题我们默认AI给的是“回答”而不是“技能”。“回答”是一次性的用完就没了“技能”是可重复执行的标准动作有输入、有输出、有步骤、有约束可以被挂载到不同场景里反复调用。打个比方你问一个厨师“这道菜怎么炒”他给你完整说一遍这叫回答。你拿到一张标准菜谱上面写了食材用量、火候档位、下锅顺序、出锅标准以及什么情况加什么补救措施这才叫技能。回答听了就忘技能可以传给别人、可以批量复用、可以验收。而SKILL就是让AI从“回答问题”切换到“执行技能”的关键载体。它不是一段复杂的提示词而是一个结构化的操作说明包——里面写清楚AI在这个任务里应该看什么、做什么、按什么顺序做、绝对不能做什么、最后以什么格式交差。有了这层结构化封装AI的输出就从“随机应变”变成了“照章办事”。后面我会具体演示一个SKILL长什么样但先记住这个判断散装AI不是AI不行而是我们没给它配技能包。存量代码不需要更大的模型需要更规范的流程。2. SKILL到底是什么一次说清它和提示词、Agent的分工2.1 一个最小SKILL的结构长什么样我最早接触SKILL这个概念时也觉得它不过是“把提示词换了个文件夹”。但实际写起来才发现差别在结构。一份提示词是一段话一个SKILL是一个模块。一个最小的SKILL文件通常包含下面这些字段name: scan_missing_idempotency description: 扫描指定服务的Controller方法找出缺少幂等保护的写操作只读不改代码。 triggers: - 用户要求检查接口幂等性 - 输入中包含 controller 路径列表 params: - name: service_path type: string required: true - name: controller_paths type: array required: true steps: - action: list_methods filter: only: [POST, PUT, PATCH] method_name_include: [order, create, pay, update] - action: llm_review prompt: | 对每个写接口判断是否满足以下任一条件 1. 使用幂等键头处理防重 2. 数据库唯一索引兜底 3. 方法本身天然幂等 输出JSON数组字段为 method, path, risk_level, reason, suggestion。 - action: write_report output_path: reports/idempotency_report.json outputs: - reports/idempotency_report.json safety: - 该SKILL只做读操作禁止修改任何源码你把这个文件喂给AI或挂在Agent框架里它就明确知道自己叫什么、什么场景会用到它、需要谁来提供什么参数、先做什么后做什么、输出文件放哪、红线是什么。这里有几个点我特别想强调。description不是摆设它是SKILL被正确调用的入口。很多Agent框架靠语义检索匹配技能description写得含糊AI就会在错误场景里选错技能。steps要写动作级的顺序而不是写一段长篇大论。每个步骤最好有一个可验证的产物比如中间JSON、临时报告。safety是存量代码场景里的命根子。宁可多写几条“禁止”也别等AI自由发挥后给你惊喜。2.2 为什么SKILL比“写在聊天框里的提示词”更可靠从我个人的体感来说SKILL相对普通提示词的提升可以分成四层。第一层是可复用。提示词属于某个对话SKILL属于某个任务类型。同一个SKILL可以在新项目里反复挂载不用重新调教。第二层是可测试。提示词的好坏要靠“聊出来”SKILL的行为可以用给定输入跑一遍检查输出是否符合规范。我在实践里会给每个SKILL配一个最小样例输入跑一次就知道有没有写坏。第三层是可约束。提示词只能表达“我建议你这么做”SKILL的结构化字段可以表达“你必须按这个顺序做”“你只能改这几个文件”。这在代码生成和自动修复里区别巨大——存量代码经不起“建议”。第四层是可组合。单个SKILL只干一件小手术多个SKILL通过编排组成一条流水线完成一次复杂的大改造。这一点是普通提示词完全给不了的。2.3 SKILL、Agent、Workflow有什么关系热词里经常出现“agent框架与编排”“skill和agent的区别”“workflow编排”我在这里用一句话把关系捋清楚SKILL是“会做某件事的标准动作”Agent是“会自己判断什么时候调用这些动作的调度员”Workflow是“提前规定好动作顺序的流水线”。Agent的优点是灵活但它会“自作主张”在存量代码改造这种低容错场景里全放给Agent我是睡不着的。所以我实际采用的策略是把SKILL作为最小执行单元外面套Workflow编排把顺序和断点、审批都写死只在关键节点上让Agent做有限决策。说白了手术台上主刀的方案和步骤是预先定好的助手可以在范围内递刀但不能自己决定多切一刀。3. 用SKILL给存量代码做“微创手术”手术方案怎么设计3.1 术前检查先画影响面地图存量代码改造最忌讳上来就动手。老系统里模块之间藕断丝连你以为只改一个Controller结果它偷偷被五个定时任务反射调用。所以在设计任何SKILL之前我都是用AI先做“术前检查”输出一张影响面地图。实际做法是让AI基于代码库生成四张清单目标接口的调用链谁调用了它它调用了谁依赖的文件清单涉及哪些Service、Mapper、配置类风险标记有没有反射调用、AOP切面、分布式定时任务数据变更点是否涉及数据库表结构、缓存键、消息队列Topic这一步我通常也封装成一个SKILL叫map_impact_area。它的输出不是给人看的长文而是一个结构化的JSON后续的改造SKILL可以直接引用这个JSON来限定自己的工作范围。影响面地图的价值在于它把“微创”变成了可验证的标准——如果后续生成的代码改动波及了地图之外的任何文件那这个改动就应该被拦截而不是被合并。3.2 切口设计把一个改造拆成多个SKILL有了地图之后下一步是设计“切口”。微创手术的关键是小切口、少损伤对应到代码改造里就是不要做一个“干所有事的大SKILL”而是把改造拆成多个单一职责的小SKILL每个SKILL只完成一个不可再分的动作并且有明确的输入和输出。以我这次幂等改造为例整体目标是为老订单服务的写接口补上幂等能力我没有写一个大SKILL让它“完成改造”而是拆成了五个scan_missing_idempotency扫描风险写接口输出风险报告generate_idempotent_schema基于风险报告生成改造设计输出每个接口的改法建议patch_controller严格按设计文件给指定Controller加幂等校验逻辑update_dependencies更新pom文件和工具类引用但不改业务代码run_regression_tests执行指定的回归测试用例输出测试报告每个SKILL的改动手脚都很小小到出了问题我可以直接看它一个文件就定位到根因。这就是“微创”的工程含义单点改动小、边界清晰、便于回滚。切口设计的另一个原则是控制依赖方向扫描SKILL不修改任何东西生成设计SKILL不修改代码只有最终的patch_controller才真正动刀。职责分得越清AI出错时越容易定位。3.3 缝合与规范输出约束和护栏手术切得好不算完缝合也决定恢复质量。对应到SKILL编排就是输出的规范化约束。我惯用的做法是在每个改造类SKILL的safety和输出规范里强制要求三样东西必须给出精确diff不允许只说“我改好了”必须列出本次改动的全部文件清单且清单不能超出影响面地图的范围必须标记回滚点说明改前状态和可回滚方式这个约束我在SKILL里写成硬性steps而不是放在提示词里“恳求”。比如patch_controller的输出强制包含{ changed_files: [OrderController.java], diff_files: [patches/001_order_controller.diff], rollback_point: git_commit_ab12cd34, risk_notes: 新增幂等键校验老客户端未传幂等键时将返回400 }护栏的价值是防止AI“带节奏”。没有护栏时AI特别喜欢顺手帮你重构代码风格、升级过期写法看起来很贴心但在存量系统里这就是事故隐患。我在所有SKILL的safety里都默认写一条不对与本次任务无关的代码做任何改动哪怕是明显的代码异味也只记录不处理。3.4 止血措施干跑模式与保留区最后补一个我觉得非常重要的设计SKILL必须支持干跑模式。手术不能上来就切得先在模型上走一遍。干跑就是在真实代码上计算但不提交任何写入。比如patch_controller干跑时AI会生成diff文件和回滚点但不会直接修改工作区我审查diff没问题后再切换到执行模式。这个设计让我敢让AI“摸着代码”又不至于被它“碰坏代码”。有条件的话我还会让SKILL输出到独立分支或至少在git stash里留一个自动保存点。这不是多余动作存量代码的容错率低多一道止血就少一次通宵。4. 编排让多个SKILL像流水线一样配合4.1 串联流水线把改造步骤排好顺序单个SKILL解决的是“一件事做得规范”编排解决的是“一系列事衔接得顺畅”。我这一次实践用的编排思路就是先把改造步骤排成一个串行流水线每个环节的产出作为下一个环节的输入。当时的编排文件长这样name: idempotency_mini_surgery version: 1.0 stages: - id: map_impact skill: map_impact_area input: service_path: order-service output: reports/impact_map.json - id: scan skill: scan_missing_idempotency input: impact_map: reports/impact_map.json output: reports/idempotency_report.json - id: design skill: generate_idempotent_schema input: report: reports/idempotency_report.json output: designs/idempotent_schema.json - id: dryrun_patch skill: patch_controller mode: dry_run input: design: designs/idempotent_schema.json output: patches/preview_diff - id: review skill: human_review input: diff: patches/preview_diff - id: apply_patch skill: patch_controller mode: apply input: design: designs/idempotent_schema.json - id: test skill: run_regression_tests input: test_suite: order-service-regression safe_rollback: checkpoint_before: stage_apply_patch这段配置看起来简单但它解决了一个我在实际使用中很头疼的问题AI在各个任务之间传递信息时用的是自然语言容易失真。而编排文件强制每个环节落盘一个JSON文件下一个环节只读取结构化的上环节产物不依赖对话里的上下文。信息流稳定整个流程就可追踪。4.2 并联分诊不同类型任务走不同分支不是所有改造都适合一条流水线走到黑有的是多任务并行这个时候编排要给SKILL搭分支。我举个例子幂等报告扫描出来有新增类接口和老接口两种情况新增类接口可以直接加幂等键校验而老接口要考虑兼容旧客户端两者不能用一个SKILL流程处理。这种场景我就在编排里做“分诊”用一个判断SKILL先给每个接口打标然后分到不同的处理分支分支A完全兼容直接走patch_controller加校验分支B需要兼容旧客户端先走add_compat_layer做好兼容切换再走patch_controller并联分诊在代码库大、改造面广时尤其有用。它能避免一个保守接口被“激进方案”误伤也能避免一个全新接口被“兼容方案”拖慢。每一类改动边界清楚了整个改造的风险也就分散了。4.3 人在环给AI加审批节点这是所有编排里最重要的一环强制人工审批。我不能不承认AI写的代码在大方向上是靠谱的但存量系统里总有那些没写进文档的“隐规则”——某个看似无用的字段被别处硬编码读取某个方法名带legacy后缀意味着不能动。这些规则无法全部靠提示词传达只能靠人来把关。所以我在编排里设计了断点机制AI执行完幂等报告生成后进入人工审查环节我确认风险报告无误后才放行到设计阶段dry_run生成diff后再停一次等我确认。也就是整个流水线中有至少两个human_in_the_loop检查点。这个“人在环”设计可能不如全自动Agent酷但它把事故率压到了最低。存量代码改造慢就是快稳就是快。5. 实操复盘给老订单服务做幂等改造5.1 背景与目标这次实操的对象是营销活动里的订单服务Spring Boot单体跑了五年日订单量不大但都是真实交易。问题很具体部分创建订单和支付回调接口没有幂等保护网络抖动或者客户端重试时会出现重复订单和重复入账。目标不是重构是补充幂等能力还得保证三个月后交接时下一任维护者能看懂改了什么、为什么这么改。我定下三个验收标准所有写接口有幂等键校验老客户端不传幂等键时走兜底逻辑而不是直接报错整套改动不影响现有告警。这个目标就决定了它是一次不折不扣的“微创手术”而不是另起炉灶。5.2 我设计的三层SKILL清单基于目标我把SKILL分成三层来设计。第一层是诊断层负责只看不动map_impact_area和scan_missing_idempotency。前者输出影响面地图后者输出风险接口报告。这一层我要求严格只读任何写操作都会被safety规则拦截。第二层是设计层负责把诊断结果翻译成可执行的改造方案generate_idempotent_schema。它会针对每个风险接口给出一条改造建议并拆解成小步骤让后续执行SKILL按图施工不再自行判断“该怎么办”。第三层是执行层负责真正动刀patch_controller、update_dependencies、run_regression_tests。执行层每个SKILL都要求输出改动文件清单、diff文件和回滚点并且patch类SKILL默认先干跑。这三层设计的核心思想是诊断、设计、执行分离。AI在每一层都只干自己的活儿不越权不顺手不自由发挥。5.3 编排与执行过程我把刚才那套串联流水线配置手动跑了起来。因为工具链还没完全自动化我用了自己写的一个轻量Python runner来读取YAML编排逐级调用SKILL对应的提示词模块并在断点处停下来等我确认。执行过程的记录大概是这样第一步map_impact_area扫描order-service生成影响面地图覆盖了我预期内的Controller、Service和Mapper还额外标记出一个我没注意到的MQ消费者这个信息在后面的改造中避免了一次遗漏。这一步让我感觉到术前检查不是形式主义是真能查出隐藏风险。第二步scan_missing_idempotency跑了所有POST、PUT、PATCH方法识别出12个风险接口。我人工复核了一下其中11个确实需要处理1个是误报方法本身天然幂等整体精确度可以接受。第三步generate_idempotent_schema根据报告生成改造设计把12个接口分成“直接加幂等键校验”和“需要兼容兜底”两类。我在审批节点做了一次调整把一个接口从兼容类挪到直接类因为通过调用链分析发现它的老客户端已经全部升级。第四步patch_controller先干跑生成diff我审查后发现它对一个接口的幂等校验放错了注解位置改回来重新干跑通过后进入执行模式。第五步run_regression_tests跑了既有回归用例全部通过同时check_diff_scope验证所有改动文件都在影响面地图范围内。整个流程耗时一个下午比我想象得快但关键不在于快在于每一步都有据可查。5.4 结果与复盘改造上线后观察了两周没有出现重复订单告警也没有老客户端报障。更重要的是我把整套SKILL文件、编排配置和干跑diff都提交到了仓库的ai-skills目录下下一次类似改造可以直接复用。复盘的时候我自己总结了三个做得好和两个不足。做得好的诊断设计执行分离让每个环节的AI行为都可验证人工审批节点虽然拖时间但真的拦住了问题干跑模式让diff审查变得轻松。不足的SKILL的参数校验还不够严格有一个SKILL在缺少参数时没有直接报错而是生成了空报告后续要补上编排还没有和CI/CD深度打通目前还是半自动。不过对我来说这次实践最大的收获是让团队真正开始“用工程的方式使用AI”而不是继续散装地聊。6. 常见问题与排查技巧实录6.1 问题速查表实践过程中我踩过不少坑整理成一张速查表给准备上手的人参考现象可能原因排查思路SKILL被错误触发description写得太泛和别的SKILL重叠收紧description加入具体触发条件和服务名AI不按steps执行自由发挥steps写得像建议而不是指令把每个step写成可验证动作并配置强制输出JSON改动了范围外文件safety约束缺失或太弱在SKILL里硬性加入allowed_files白名单并在编排后加check_diff_scope环节输出格式不稳定只在提示词里规定了格式没有结构化字段把输出格式声明为steps里的强制动作并让AI先生成JSON再转Markdown中间数据丢失每个SKILL的上下文相互隔离前一个环节的输出只存在于对话里强制每个SKILL将产物落盘为文件后续SKILL只从文件读取干跑和真实执行结果不一致干跑模式可能跳过写入动作导致的逻辑差异干跑和apply共用同一套核心逻辑只在最后提交动作上做分支老接口兼容问题被忽略诊断SKILL只看接口签名没看历史调用方加入historian_reviewSKILL专门扫描存量调用方做兼容评估这个表不是标准答案但它是真实踩坑的记录。遇到问题先从这七个方向排查能省不少时间。6.2 几个值得记住的避坑技巧除了上面的排查表我还想单独说几个从实践中沉淀下来的技巧这些技巧在官方文档里通常不会写。第一个技巧是“小步输入的探测法”。在写patch_controller这类SKILL时不要一开始就丢给它整个服务路径先给一个单文件路径跑一遍确认行为的稳定再逐步扩大到多文件、多模块。因为SKILL是结构化的如果基础行为就不稳定外层编排再严谨也是给不稳定的地基盖楼。第二个技巧是“让AI先给结论再给细节”。在生成幂等改造设计时如果直接让AI输出“改法”它常常会给出冗长的方案说明。我在SKILL里做了调整强制它先输出一个决策表接口名、风险等级、是否兼容、推荐方案、工作估计。决策表是结构化的审查起来一目了然而不是沉浸在一大段文字里自己提取关键信息。第三个技巧是“编排里加一道diff范围校验”。前面提到check_diff_scope这个SKILL是我强烈推荐加的。它的职责只有一个把实际产生的diff文件清单和影响面地图比对如果出现地图外的文件立即终止流程并标记风险。这道环节看起来多此一举但它在AI“带节奏”时能把损失控制在最小。第四个技巧是“为每个SKILL准备退化场景”。我在最初使用SKILL时遇到最头痛的问题就是AI在理想输入下表现很好一旦输入缺失参数或格式不对就会生成奇怪的结果。后来我在每个SKILL里增加一个fallback策略当参数无法解析时要么直接向用户要明确参数要么输出一个标记“信息不足”的空模板而不是猜一个路径继续往下走。不要小看这一步它会救你很多次。第五个技巧是“SKILL的版本管理”。SKILL本身是代码应该走版本管理。我一开始把SKILL放到一个独立目录里命名带上版本号改版后不覆盖旧版而是生成新版本。有回退需求时直接切到旧版本的SKILL文件。这个习惯让我敢于迭代SKILL而不怕把以前的好版本改坏了。写在最后的一点经验从“散装AI”到SKILL编排最大的改变不在于用了什么高端框架而在于我重新定位了AI在代码改造中的角色它不再是那个“什么都能聊的聊天对象”而是一个“内化了操作规程的执行单元”。这个认知转变对我影响很大过去我担心AI失控现在我担心的是我的操作规范不够清晰。如果你也想在自己的项目里尝试这套思路我的建议是不要一开始就搭一个很完备的技能库。先选择一个让你头疼的具体存量改造场景写两到三个SKILL用串联流水线串起来跑通一次。等熟悉了SKILL的原子化拆分和编排节奏再逐步扩大范围。你要的从来不是酷炫的全自动而是可控、可查、可回滚的“微创”。最后再分享一个小技巧把这些SKILL当作团队资产而不只是个人工具。每次改造完把SKILL文件和复盘记录提交到仓库团队里其他人也能用。当AI的使用从“个人即兴发挥”变成“团队标准操作”散装AI的日子才真的算过去了。