☰
AI Agent写代码不守规矩?规范注入与校验闭环实践
2026/10/15 23:28:55 网站建设 项目流程

让AI Agent写代码,最让人头疼的往往不是它写不出来,而是它写得“不像自己人写的”。前阵子让Pi Agent生成一个接口模块,功能完全正确,跑测试也没问题,但打开代码一看:函数命名全是缩写,错误处理被随手吞掉,注释写得比没有还糟糕,整个风格和现存代码库完全是两个世界。这几乎是所有把AI Agent接入一线研发的团队都会撞上的墙——AI能写、能跑,但不守规矩。

这篇文章就以Pi Agent为例,聊聊我们是怎么解决这个问题的。这里的Pi Agent是一个内部自研的编码Agent框架,核心能力是让大模型根据任务描述自动生成代码、补全接口、写测试,甚至做简单的重构。但“能生成代码”和“能生成符合团队规范的代码”之间,差着一整套工程化约束。我踩过不少坑,也试过很多方案,最后沉淀下来一套组合打法:规范注入、规则校验、失败反馈、CI闭环。这套方法适用的场景很明确:只要你的团队在用AI辅助编码,并且希望产出物能过人工评审、能合入主干,那这篇文章就值得读完。

1. AI Agent为什么总在“规范性”上翻车

1.1 大模型写代码的本质是“预测下一个Token”

很多人对AI Agent期待太高,觉得它既然能写出可运行的代码,就应该天然懂“规范”。但实际运行机制不是这样。大模型写代码,本质上是在做下一Token的概率预测:给定已有上下文,它根据海量训练语料里的统计规律,生成最可能接续的Token。训练语料里确实有大量开源项目、技术博客、Stack Overflow问答,但这些内容的风格极其分散。有的用驼峰,有的用下划线;有的喜欢把错误往上层抛,有的习惯用Result封装吞掉异常。模型的“平均风格”往往是四不像,恰好不是你们团队的标准。

所以当Pi Agent第一次生成代码时,它默认输出的风格来自它训练数据里的“最大公约数”,而不是你们仓库里的“当前规范”。如果只是偶尔生成一小段代码,手动改改还能接受。可一旦你让它批量生成十几个模块,或者让它处理一个大型重构,返工成本立刻爆炸。

1.2 “代码规范”对AI来说是隐性知识

咱们得承认一件事:大多数团队的代码规范,并没有形成机器能读的完整清单。规范散落在各种地方:WIKI文档里有一段,Code Review记录里有一些约定,老员工的脑子里还有一堆“潜规则”。很多新同学进组第一个月都在默默观察别人怎么写代码,慢慢才摸清团队风格。

这种知识形态对AI极其不友好。大模型并没有在训练时见过你们公司内部的代码仓库,也没读过你们的WIKI,更不可能感知到上次Code Review时测试同学提的“这个异常不能用catch吞掉”。它不知道规范,不是因为它笨,而是因为根本没人把规范完整、显式、结构化地告诉它。你直接在Prompt里写一句“请遵守公司代码规范”,它实际上不知道“公司代码规范”到底是什么内容,只会在输出里给你塞一个注释“// TODO: 遵守规范”,用来自欺欺人。

1.3 单独靠提示词约束,效果极不稳定

很多团队第一次尝试让AI守规矩,就是往Prompt里加约束:“请使用驼峰命名”“请遵循项目现有风格”“错误处理要完整”。一开始可能有效,尤其当任务简单时,模型会认真模仿。但只要任务复杂一点,上下文一长,它就开始顾此失彼。原因有两层:一是大模型的注意力会分散,早期指令可能在长上下文中被稀释;二是提示词里的自然语言约束本身就自带歧义,比如“错误处理要完整”到底是“所有异常都要捕获”,还是“所有异常都要往上抛”?模型只能猜,而猜就有概率出错。

这也是我后来做技术方案时反复向团队强调的一点:不要试图靠提示词当救世主,提示词只是第一道防线,真正兜底的必须是可执行的校验逻辑。AI Agent不守规矩,本质不是态度问题,而是工程上缺少强制反馈回路。

2. Pi Agent的约束与校验体系设计

2.1 三层结构:规范注入层、校验执行层、反馈修复层

Pi Agent这套方案的核心,是把“规范”从一个抽象概念拆成三层可执行的结构。每一层承担不同职责,合在一起才形成闭环。

第一层是规范注入层。把团队代码规范翻译成两部分:一部分是给大模型读的、用自然语言精简描述的关键规则;另一部分是给机器用的、结构化规则文件。模型在生成代码前,先把这两份东西加载进上下文,相当于给新人发了入职手册和操作清单。

第二层是校验执行层。Pi Agent生成代码后,不直接交付,而是先调用一系列静态检查工具和格式化工具,用脚本检查产出物是否违反规范。这一步是关键跳跃:AI说的话不承担责任,但脚本检查不通过就是过不了。校验执行层像门禁系统,规则文件怎么定义,它就不打折扣地执行。

第三层是反馈修复层。当校验失败,Pi Agent不会把错误直接丢给人,而是把校验工具的输出信息重新喂给模型,让它根据报错信息有针对性地修改代码。改完再校验,直到通过,或者达到预设的最大循环次数。这相当于让Agent自己完成“被审查—修改—再被审查”的迭代过程。

2.2 为什么必须做“规则文件”而不是“规则文档”

先说一个血泪教训。最早我们做Pi Agent的时候,直接把公司WIKI上的代码规范整段丢进Prompt,结果非常糟糕。一是WIKI文档通常两三万字,塞进上下文后Token占用太多,模型处理主任务的空间直接被压缩;二是文档里大量内容是为人类阅读写的铺垫、解释、案例图片描述,机器读起来噪声极大;三是文档里的很多规则是经验性描述,比如“代码尽量简洁”,这种话模型根本没法执行,它不知道“尽量”的量化标准是什么。

所以后来我们坚定地把规范转成了规则文件。规则文件的核心价值是:任何一条规则都必须能被机器判断真伪。比如“函数命名必须使用驼峰风格”是可判断的;“写代码要整洁”是不可判断的;“禁止在catch块里吞掉异常”是可判断的;“错误处理要完善”是不可判断的。规则文件的好处是既能让模型精准理解,又能让脚本精准执行,两边用同一套语言沟通,减小信息损耗。

2.3 强制校验必须放在“生成之后、交付之前”

有人可能会说:“我都把规则文件给模型了,也告诉它遵守了,为什么还要跑校验工具?”我理解这种想法,但现实中模型偶尔就是会“叛逆”。哪怕你把规范说得清清楚楚,生成长度一上去,前面记得、后面忘掉的情况非常常见。尤其当你让Agent一次生成多个文件时,它很可能在前两个文件里严格执行了驼峰命名,第三个文件就开始放飞自我。

所以Pi Agent的流程里,校验步骤是不可跳过的强制环节。写代码的人可以“忘了”规范,但工具链不会。把校验放在生成之后、交付之前,意味着每一份产出物都经过了客观检查,而不是依赖模型的临场发挥。这个设计跟人在CI阶段跑测试是一个道理:开发本地就算自我感觉良好,CI不过就是不过,没有讨价还价的余地。

3. 把公司规范“翻译”成机器能读的规则

3.1 规范分层:强制级、建议级、风格级

并不是所有规范都应该用机器强制卡死。如果你把每一条规范都设成error级,Agent的生成流程会频繁被打断,迭代燃尽情绪,最后产出也支离破碎。我们整理规范时做了分级:

  • 强制级规则:涉及正确性和安全性,必须机器阻断。比如不允许console.log直接上生产、不允许在catch里吞异常、不允许裸SQL拼接。
  • 建议级规则:影响可维护性,尽量机器提醒。比如函数行数不能太长、圈复杂度过高、重复代码片段。
  • 风格级规则:属于审美范畴,交格式化工具处理。比如缩进、引号、空格,这类问题直接上Prettier或等价工具自动化,不要浪费模型额外精力。

这个分级的意义是给Pi Agent设定优先级。它不必在大脑里维护几十条规则的完整细节,只需要在生成代码时重点遵守强制级,然后让工具链去检查建议级。否则把几百条规则一次性塞给模型,反而会让它在小规则上较劲、在大规则上翻车。

3.2 用规则文件描述“允许什么、禁止什么”

我们采用的做法是,把规则文件写成JSON和YAML两种格式。YAML方便人读、方便评审;JSON方便机器解析、方便程序加载。下面是一个简化的规则文件示例:

rules: - id: NO_CONSOLE_LOG type: error description: "禁止在生产代码中使用console.log" target: "js,ts" pattern: - "console.log" - "console.debug" - id: FUNCTION_NAMING type: error description: "函数命名必须使用驼峰风格" target: "js,ts" pattern: "^[a-z][a-zA-Z0-9]*$" - id: MAX_LINE_LENGTH type: warning description: "单行长度不应超过100个字符" target: "all" max: 100 - id: EXCEPTION_SWALLOW type: error description: "catch块中不允许只记录日志或置空异常" target: "js,ts" pattern: - "catch {" require: - "throw" - "return error"

你可能注意到了,有些规则用pattern可以直接查出违禁写法,有些规则描述得还不够机器化。实际操作中,正则匹配只是最原始的一层,真正复杂规范的落地要靠AST解析器。比如“检查所有函数名的命名风格”,你不能靠简单搜文本实现,因为注释里也可能出现驼峰词,反而误报。正确做法是先把代码解析成AST,然后只扫描FunctionDeclaration和VariableDeclarator节点,提取其中的函数名,再拿正则去校验。

我给一个Python实现的简化思路,配合ESLint这类工具使用更稳:

import ast import re def check_snake_case_functions(source_code: str) -> list: tree = ast.parse(source_code) problems = [] for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): name = node.name if not re.match(r"^[a-z][a-zA-Z0-9]*$", name): problems.append({ "line": node.lineno, "message": f"函数名 {name} 不符合驼峰命名规范" }) return problems

这里只是示意。不同语言要用不同的AST库,但思路完全一致:不要试图用字符串匹配去理解代码结构,要让程序真正“读懂”语法树,再去判断节点属性。这样既准确又可以给出行号,方便反馈给模型时带着位置信息。

3.3 可执行校验工具的选择与组合

规范文件是标准,真正动手检查得靠具体工具。如果你在JavaScript/TypeScript技术栈里,ESLint是首选。它天然支持自定义规则,也支持接入Prettier做风格统一。我们为Pi Agent做了两份ESLint配置,一份人工时代就在用,一份是专门给Agent用的,简化了规则数量,只保留机器判定稳定的部分。

一个典型的ESLint配置长这样:

module.exports = { root: true, parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint'], rules: { '@typescript-eslint/naming-convention': [ 'error', { selector: 'function', format: ['camelCase'], leadingUnderscore: 'allow' } ], 'no-console': 'error', 'max-len': ['warn', { code: 100, ignoreUrls: true }], 'no-empty': 'error' } };

其他语言也都有对应工具。比如Java的Checkstyle、Python的flake8/Ruff、Go的gofmt/go vet、C++的clang-format。关键不在于具体用哪个工具,而在于这些工具的退出码必须进入Pi Agent的执行流程。如果工具返回非零退出码,Agent就知道此次产出不达标,得回头修。

这里我强烈建议把“格式化”和“静态检查”拆成两步。先让工具自动格式化,再跑静态检查。否则很多风格问题会混在真实逻辑问题里,干扰模型判断。格式化自动修完,剩下的报错基本都是“硬错误”或需要人工决策的问题,反馈给模型时也更好办。

3.4 让Pi Agent真正“看到”校验结果

规则文件准备好、校验工具也接上了,接下来的问题是怎么让Pi Agent在生成代码后主动使用它们。我们在Pi Agent的推理工作流里增加了工具调用能力:Agent先有权限执行Shell命令,所以它可以自己调用ESLint、跑测试、执行格式化脚本。为了引导它正确使用工具,我们在Prompt里写了一段操作指令模板:

你是团队编码Agent。代码生成规则如下: 1. 所有函数和变量命名必须遵循项目rule.yaml中的naming规则; 2. 生产代码禁止出现console.log,如需日志请使用logger对象; 3. 生成完成后,必须依次执行: - npx eslint src/generated --exit-on-fatal-error - npm run format:check 4. 如果lint或format检查失败,请根据报错信息逐条修复,修复后重新运行检查,直到通过; 5. 最多尝试修复5次。若第5次仍未通过,请保留最后一次修改,并将错误信息汇总输出给用户。

这个模板看起来简单,但它解决了三个关键问题:第一,模型知道“做完代码之后要做什么”,流程不再停在生成那一步;第二,模型知道“什么叫通过”,有客观的退出条件;第三,模型知道“极限在哪”,不会陷入无限自修修改的僵尸循环。很多Agent实现失败,恰恰就是少了最后一条。

4. 实操过程:接入Pi Agent的完整流程

4.1 初始化配置:把历史规范导入新框架

接入Pi Agent并不是直接改个Prompt就完事,我们需要做一次“规范整治”。具体分四步走。

第一步,从WIKI、Code Review记录、老代码里提取所有规范条目,逐条讨论分类,确定它是强制级、建议级还是风格级。这一步很耗时,但没有捷径,不过这一步本身就是一次团队财富整理。

第二步,把确认过的规范写进规则文件。建议让熟悉工具链的工程师来写,因为要对具体检查工具的能力边界有判断。比如效果上是“禁止使用==”,你可以写进ESLint的eqeqeq规则,而不是在规则文件里手写正则。

第三步,在本地一个模拟仓库里把整套流程跑通。用Pi Agent生成一段已知有问题的代码,让它自检并修复,看能不能达到预期效果。这个模拟仓库要和真实仓库结构一致,包括路径、依赖、lint脚本,否则Agent学会的规矩在真实环境里不适用。

第四步,评审规则文件本身。让团队里最资深的工程师过一遍,确认没有误伤正常写法的规则。比如“函数名必须驼峰”这条,如果代码库里本来就允许某些美术、算法模块用全大写风格,就要加豁免条件。否则Agent会把合规代码也改成不合规。

4.2 Agent工作流编排:生成、自检、修复、审查

Pi Agent的完整工作流,我们编排成了四个阶段。

生成阶段,Agent读取任务描述,结合上下文找到相关文件和约定,产出第一版代码。注意这里不需要把整个代码库塞给它,只需要提供与当前任务相关的文件列表和接口定义,减少上下文噪声,降低规范稀释的概率。

自检阶段,Agent调用lint、format、甚至单元测试工具,检查产出。单元测试这一步很有用,因为很多规范性问题(比如某个函数返回值缺失)光靠静态检查发现不了,但测试一跑就暴露了。如果测试没跑通,代码风格再规范也没用。

修复阶段,Agent把自检阶段的报错信息解析成具体修改点,逐项修复。修复的顺序很关键:先修会改变程序行为的错误,再修风格问题。否则模型可能一直纠结在引号格式上,忽略了某个空指针错误。

审查阶段,Agent生成一份提交说明,列出生成文件、检查结果、修改记录。然后整个改动交给人做最终Code Review。人工审查的工作量并没有完全消失,但重点会从“吹毛求疵”转移到“看逻辑、评设计”,效率显著上去。

4.3 接入CI流水线,卡住最后一道防线

就算Pi Agent在本地跑通了全套检查,也难保有人会绕过流程,把未校验的代码直接提交。所以必须把校验结果绑定到CI流水线,让不合规的代码根本没有合入机会。我们在CI流水线里加了一个专门的stage:

stages: - generate - lint - review lint: stage: lint script: - npm run lint:check - npm run format:check only: - merge_requests

这样配置之后,哪怕开发者手动修改了Pi Agent的产出,只要改动不通过lint,MR就没法合入。这种做法其实也在反向约束开发者:如果你想绕过规范,就别用AI生成代码;既然用了AI生成代码,就让AI和工具链配合把规范守住。

还需在流水线里记录边界情况,比如Agent修复循环达到上限后的异常退出。异常退出时CI不能默默通过,必须触发告警,让负责的工程师介入。否则Agent虽然在本地自修了五次,最后推上来的代码还带一堆lint错误,CI要是给过了,前面的所有努力就白费了。

4.4 结果观测:合规率、修复率、耗时

做到这一步还不能叫完事,得量化结果。我们搭建了一个简单的看板,给Pi Agent每次生成行为记录三类指标:

  • 首次合规率:Agent生成完第一版代码,不经修复直接通过校验的比例。
  • 自修复成功率:首次校验失败后,Agent通过自我修复最终达到合规的比例。
  • 平均修复耗时:从首次失败到最终通过花费的时间。

这三个指标能帮你快速发现系统问题。如果首次合规率长期偏低,说明Prompt或规则文件的语义跟模型理解有偏差;如果自修复成功率不高,大概率是校验工具报错信息给得不够清晰,模型看不懂该改哪里;如果修复耗时太长,说明修复循环设计有问题,可能是规则过严导致模型反复踩线。

我们把这个看板提供给每个接入Pi Agent的项目组,用来判断“Agent在这块业务上到底能不能用、值不值得用”。若某类任务的首次合规率能做到80%以上,就可以把整个流程逐步放权,人只做抽审;若一直卡在50%上下,那建议还是让它做草稿生成,人工深度修改。

5. 坑与避雷:Pi Agent实践中的几个典型问题

5.1 规范规则太多导致“指令冲突”

第一次把所有规范都灌进去时,我们很快发现模型开始纠结:一条规则要求“不要使用any”,另一条规则又写着“调用第三方库时可以放宽类型检查”,模型在代码里反复横跳,生成结果反而不稳定。这是典型的规则冲突问题。

解决方案是建立优先级。在规则文件里增加priority字段,当两条规则冲突时,模型需要遵循高优先级规则。比如上面的冲突里,“不要使用any”优先级高于“放宽第三方库检查”,那模型就会安心地选择strict类型路径,而不是自己纠结。规则不在于多,而在于自洽。少而自洽的规则,比多而矛盾的一百条有效得多。

5.2 LLM对“禁止”和“必须”的理解没有你想象的强

我原以为给模型下负面指令是最容易生效的,比如“不要用console.log”。但实测发现,当代码生成长度超过几百行时,早期那些“禁止”指令经常被遗忘,模型时不时还是会输出一两个console.log。相反,“必须先把log通过logger输出”这种正面引导就稳定很多。

所以我们在规则文件里不只写禁止项,还写替代项。对每条禁止规则尽量给一个“应该怎么做”的正面指令,让模型有明确的替换路径。不要让它“不要做X”,要告诉它“应该做Y”。这个写法和给人培训很像,光说别犯错不够,还得告诉人家正确姿势。

5.3 校验工具频繁报错导致Agent陷入死循环

有一次Pi Agent在处理一个老模块时,ESLint连续报了几十个“no-explicit-any”错误,然后它开始一边修一边又引入新的错误,循环了七八次也没通过,最后时间全部消耗在无关紧要的细节上,核心功能都还没完成。原因在于那个老模块本身就大量使用了any,Agent一改,下游类型推断全乱了。

后来我们加了两个机制:一是设定修复上限,超过后强制退出并把失败报告交给人类,即使这个人类可能是开发者本人;二是按文件增量做lint,只检查Agent本次生成或修改的文件,避免被仓库里历史遗留问题拖入泥潭。你要是发现Agent总在无限循环,先看一眼是不是把整个仓库的存量问题都背到自己身上了。

5.4 人工审查成本没有下降,反而上升了

引入AI Agent早期,我们发现Code Review时间反而变长。原因很魔幻:Agent改代码时会按规则修掉所有风格问题,但也可能偷偷改掉了不该动的地方,比如把别人手写的修复逻辑又“规范化”了一遍。评审人不仅要看新逻辑,还要比对Agent误改的代码,工作量不减反增。

解决这个问题靠权限约束。Pi Agent的修改范围被严格限定在任务相关的文件里,不允许顺手修改未指定的“路过文件”。同时在提交说明里生成diff摘要,让评审人一眼看到“哪些文件是核心改动、哪些是工具自动格式化产物”,快速跳过低风险的部分。实践下来这招效果最明显,审查时间逐渐回到正常水平。

5.5 把Agent当“新人”却不给上下文,那就别怪它莽撞

最后一个问题其实最根本。我们团队有些人用Pi Agent的方式是丢给它一句“给我重构登录模块”,然后啥都不说,等它产出。结果Agent只能对着一个模块名凭空猜,连现有接口定义都拿不到。第一版代码风格倒是规范了,但一运行就知道跟现有系统接不上。

让Agent守规矩的前提是给它“足够的上下文”。我们做了一个上下文自动装配组件,根据任务描述检索相关文件,把模块入口、接口定义、依赖关系、示例代码一并打包进上下文。这让Pi Agent从“盲猜”变成了“照着团队现有代码的方式长大”。代码规范的第一个规则其实是:新代码必须和老代码保持一致的语言和结构。你连接下来要改的代码都没见过,聊规范就是空中楼阁。

这里整理了一份常见问题速查表:

现象可能原因建议处理
首次合规率低规则文件语义模糊,Prompt上下文太长精简规则,增加正面引导,缩短上下文
修复循环到上限仍未通过规则冲突或老模块历史问题拖累设置优先级,按文件增量lint
报告通过但代码质量差只做了静态检查,缺少测试验证增加单测/集成测试到校验流程
审查成本反而升高Agent修改范围失控限制修改文件范围,输出diff摘要
Agent完全无法起步任务上下文不足接入上下文装配,检索相关文件

6. 经验复盘与可复用架构

6.1 从“人管代码”到“规则管代码”

经过这段实践,我对“AI Agent守规矩”这件事有了新的理解。过去团队管代码靠的是人的纪律性:Code Review里老同学盯着,发现不对就当场指正。但有了AI Agent后,这个模式就崩了——Agent没有情感压力,也不会因为被指正而记住教训,它每次都是从零开始。想让Agent守规矩,就得把规矩沉淀成客观的、自动化的执行脚本,让人从“执法者”变成“规则制定者”。

这个理念可以扩展到不止代码规范的领域。比如生成的注释有没有包含TODO标记、有没有带着死者接口?文档生成的描述是不是一直在用空话套话?甚至AI生成的测试用例是不是只测快乐路径?只要把这些要求变成可校验的检查项,接进Agent的工作流,它就能从“生成完就完事”变成“生成完且满足标准才算完事”。

6.2 这套架构可以迁移到哪些场景

首先,最直接的迁移是代码评审助手场景。Pi Agent的规则校验层和反馈修复层完全可以独立提取,做成一个PR检查机器人:有人提交代码,机器人自动跑规范校验,把问题清单贴在评论区。这一步切走了大量重复低级的审查工作。

其次,可以迁移到文档生成场景。把“文档必须包含示例代码”“标题层级不得超过三级”“不能出现```语言块标注与实际语言不符”这类要求写成检查规则,再让Agent生成后自检。文档规范和代码规范在结构上是同一个问题。

再一个方向是代码迁移场景。比如把一个老项目从JavaScript迁移到TypeScript,迁移过程中必然涉及大量“如何保持原行为又符合新规范”的决策。过去人肉迁移很容易出现“类型是加了,但行为全变了”的问题。让Agent在迁移过程中始终保持“先比较编译产物、再检查差异”的循环,效果会好很多。

6.3 关于这套方案持续迭代的几点体会

维持这套体系运转,说难也不难,说简单也不简单。难的是规则文件需要持续维护,因为团队规范会随技术演进发生变化;简单的是只要规则文件更新,所有使用了这个规则文件的Agent和CI都会同步生效,不用担心有人漏看通知。

我的一个习惯是,每次Code Review如果出现“居然没被规则卡住”的问题,就会顺手把它记下来。攒到一定数量就沉淀成新的规则,再向规则文件里补充。这些“漏网之鱼”往往是整个系统最有价值的增量反馈,因为它们代表真实的、从实践中来的规范缺口,比坐在会议室里拍脑袋想出的规则接地气得多。

还有一点,初期即使投入比较大也不要跳过规则分级。我见过很多团队把一切的绊脚石一股脑喂给Agent,导致Agent在空格式上消耗大量精力,而真正重要的逻辑错误反而没人关心。规范不是越严越好,而是边界越清晰越好。给Agent一个少而明确、条条能被执行、执行后有反馈的规则集,比一个庞大的、互相矛盾的“规范大全”要强太多。

如果让我给刚起步的团队一个明确的入手路径,那就是:先挑出五条最让你痛恨的违规习惯,写成规则文件,接一个脚本验证,让Agent生成本次任务代码后自动跑脚本。把这五条玩透了,再慢慢往里加。不用一次把规范体系做得宏大,先让“规”能落地,再让“范”能传承。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询