
1. 为什么AI Agent写代码总是“放飞自我”1.1 一个让所有Tech Lead头疼的场景你肯定遇到过这种情况团队花了大半年时间统一了代码规范从缩进用2个空格还是4个空格到函数命名必须用驼峰还是下划线再到错误处理必须走统一的异常封装类全都白纸黑字写进了团队规范文档里。结果引入AI Agent辅助开发之后代码Review的负担不降反升——Agent生成的代码能跑但风格跟团队规范完全是两码事。我所在的团队去年开始在一个中型项目里引入Pi Agent做辅助开发项目大概有四十多个模块涉及后端服务、数据处理脚本和一部分前端组件。刚开始大家都很兴奋觉得效率要起飞了。结果第一周合并请求里Reviewer们发现Agent生成的代码存在大量风格不一致的问题有的文件用双引号有的用单引号有的函数超过两百行没有任何拆分有的直接裸抛异常完全绕过了团队统一的错误处理中间件。更离谱的是同一个Agent在不同时间生成的代码风格居然不一样今天写的工具函数用下划线命名明天同样的功能就变成了驼峰。这不是Pi Agent独有的问题几乎所有代码生成类AI Agent都有这个毛病。核心原因在于Agent的默认行为是“生成能完成功能的代码”而不是“生成符合你团队规范的代码”。它的训练数据来自开源社区的海量代码这些代码本身就风格各异所以Agent的输出天然带有随机性。你不给它明确的约束它就会在不同的风格之间“随机漫步”。1.2 让Agent守规矩的核心思路把规范变成可执行的约束很多人第一反应是“我在Prompt里写清楚不就行了”。我试过效果非常有限。你在系统提示词里写“请遵循PEP8规范”Agent确实会收敛一些但一旦任务变复杂比如让它实现一个包含多个类继承和装饰器的模块它就会开始“忘记”前面的约束。这不是Agent故意不听话而是大语言模型的工作机制决定的——上下文窗口里的信息权重会随着生成过程的推进而衰减越到后面越容易偏离初始指令。所以真正有效的思路不是“求”Agent遵守规范而是把规范变成Agent工作流中不可绕过的一环。具体来说分三层第一层是前置约束在Agent开始写代码之前就把规范“注入”到它的工作上下文中第二层是过程校验在Agent生成代码的过程中或生成后立即进行自动化检查第三层是反馈修正当检查不通过时把具体的违规信息喂回给Agent让它自我修正。这三层缺一不可只靠任何单一手段都很难稳定生效。以Pi Agent为例它本身提供了比较灵活的扩展机制支持自定义工具调用、支持在生成前后插入Hook、也支持接入外部Linter。这些能力就是我们把规范“硬约束”到Agent工作流里的抓手。下面我会从整体设计、具体配置、实操步骤和踩坑经验几个方面把整套方案拆开来讲。2. 整体方案设计与核心思路拆解2.1 三层约束架构的设计逻辑为什么是三层而不是一层这跟Agent的工作特性有关。Agent生成代码的过程可以粗略理解为理解任务→检索上下文→生成代码→输出结果。前置约束解决的是“检索上下文”阶段的问题让Agent在生成之前就“看到”规范过程校验解决的是“生成代码”阶段的问题在代码还没最终落地之前就拦截违规反馈修正解决的是“输出结果”阶段的问题让Agent有机会自己改对。如果只有前置约束Agent可能在复杂任务中“遗忘”规范如果只有过程校验每次违规都直接报错而不给Agent修正机会会导致大量无效重试如果只有反馈修正没有前置约束和过程校验Agent连自己错在哪都不知道修正就无从谈起。三层配合起来才能形成一个闭环。具体到Pi Agent的配置上前置约束主要通过系统提示词模板和项目上下文注入来实现过程校验通过自定义Lint工具和生成后Hook来实现反馈修正通过工具调用结果回传和重试策略来实现。下面逐个展开。2.2 规范的可执行化从文档到规则集团队规范文档通常是给人看的里面充满了“建议”“推荐”“尽量避免”这类模糊表述。但Agent需要的是明确的、可判定的规则。所以第一步是把规范文档“翻译”成机器可执行的规则集。举个例子团队规范里写“函数应保持简短避免过长”。这句话对人来说有指导意义但对Agent来说太模糊了。你需要把它翻译成具体的规则函数体超过80行则违规超过120行则严重违规。再比如“错误处理应统一使用项目封装的异常类”翻译成规则就是禁止直接使用raise Exception必须使用ProjectError或其子类。这个翻译过程我建议用表格来管理左边是原始规范描述右边是对应的可执行规则和检查工具。这样做的好处是当规范更新时你只需要更新表格然后同步更新Lint配置和Prompt模板即可。原始规范描述可执行规则检查工具函数应保持简短函数体不超过80行自定义Lint规则统一错误处理禁止裸抛Exception正则匹配AST检查命名风格统一函数用蛇形类用驼峰Lint内置规则导入顺序规范标准库→第三方→本地isort配置注释覆盖率公共函数必须有docstringpydocstyle这张表是整个方案的基础。没有这张表后面的Prompt注入和Lint配置就没有依据。2.3 Pi Agent的扩展点选择Pi Agent提供了几个关键的扩展点我们需要根据规范的类型选择合适的位置介入。不是所有规范都适合用同一种方式约束。系统提示词注入适合约束那些“全局性、难以用工具检查”的规范比如“代码注释使用中文”“变量命名要有业务含义”这类。这些规则很难用Lint工具精确判定但可以在Prompt里反复强调。自定义工具调用适合约束那些“需要外部工具检查”的规范比如代码格式、导入顺序、类型注解完整性。这些可以用现成的Lint工具来检查Agent在生成代码后自动调用检查工具根据返回结果决定是否重试。生成后Hook适合做“最后一道防线”比如检查生成的文件是否在允许的目录范围内、是否修改了不该修改的文件、是否引入了新的第三方依赖。这些检查放在Hook里执行不通过就直接阻断。我实际配置下来发现把Lint检查做成Agent可调用的工具是最有效的。因为Agent在生成代码后会主动调用这个工具来“自检”如果检查不通过它会根据工具返回的具体错误信息进行修正。这比在Prompt里干巴巴地写“请遵循规范”有效得多。3. 核心细节解析与实操要点3.1 系统提示词模板的写法系统提示词是Agent的“行为准则”但写法很有讲究。我试过几种不同的写法效果差异很大。最差的写法是把规范文档直接粘贴进去几千字的规范塞满上下文窗口Agent根本抓不住重点。稍好一点的写法是提炼成要点列表但Agent在复杂任务中仍然会遗忘。目前我用下来最有效的写法是分层提示词第一层是角色定义第二层是硬性约束第三层是软性建议第四层是违规示例。角色定义部分要明确告诉Agent它的身份和任务边界。比如“你是一个代码生成助手负责在指定项目目录下生成符合团队规范的代码。你的输出必须通过项目Lint检查否则会被拒绝。”硬性约束部分只放那些“违反了一定会被拦截”的规则用祈使句每条不超过一行。比如“函数体不超过80行。”“禁止使用raise Exception。”“所有公共函数必须有docstring。”这部分规则数量控制在10条以内太多了Agent记不住。软性建议部分放那些“最好遵守但不会硬性拦截”的规则比如“变量命名尽量体现业务含义”“注释使用中文”。这部分可以稍微多一些但也不要超过15条。违规示例部分非常关键但很多人会忽略。给Agent展示几个典型的违规代码片段和对应的修正版本比单纯描述规则有效得多。比如展示一个裸抛异常的代码然后展示修正后的版本Agent看了之后对规则的理解会准确很多。提示系统提示词不是越长越好。我实测下来硬性约束超过15条之后Agent的遵守率会明显下降。如果规范确实很多建议把一部分规则转移到Lint工具里做硬性检查而不是全部塞进Prompt。3.2 自定义Lint工具的接入方式Pi Agent支持注册自定义工具这是整个方案里最核心的一环。我们需要把项目的Lint检查封装成一个Agent可以调用的工具。工具的定义包括名称、描述、参数和返回值。名称要直观比如check_code_style。描述要写清楚这个工具是干什么的、什么时候调用、返回什么格式的结果。参数通常是文件路径或代码内容。返回值要结构化包含是否通过、违规列表、每条违规的具体位置和修正建议。这里有个关键细节返回给Agent的违规信息必须足够具体。如果你只返回“代码风格检查不通过”Agent根本不知道该怎么改。你需要返回类似“第23行函数process_data体长度为95行超过80行限制建议拆分为多个子函数”这样的信息。Agent拿到这种具体的反馈修正成功率会高很多。我用的Lint工具组合是flake8做基础风格检查pylint做更深入的代码质量检查isort做导入排序检查再加一个自定义的AST检查脚本做项目特有的规则检查。这些工具统一封装成一个入口脚本Agent只需要调用这一个工具即可。# lint_runner.py 简化示例 import subprocess import json def run_lint(file_path): results [] # flake8 检查 flake8_result subprocess.run( [flake8, --max-line-length100, file_path], capture_outputTrue, textTrue ) if flake8_result.stdout: results.append({ tool: flake8, issues: flake8_result.stdout.strip().split(\n) }) # 自定义AST检查 ast_issues check_ast_rules(file_path) if ast_issues: results.append({ tool: custom_ast, issues: ast_issues }) return { passed: len(results) 0, details: results }这个工具注册到Pi Agent之后Agent在生成代码后会主动调用它。如果返回passed: falseAgent会根据details里的具体信息进行修正然后再次调用工具检查直到通过或达到最大重试次数。3.3 生成后Hook的配置要点Hook是最后一道防线用来拦截那些Lint工具检查不出来但团队明确禁止的行为。比如禁止在utils/目录之外定义工具函数禁止直接修改config/目录下的配置文件禁止引入未在requirements.txt中声明的第三方库禁止生成超过500行的单个文件这些规则用Lint工具不好检查但用Hook很容易实现。Hook在Agent生成文件之后、正式写入磁盘之前执行检查不通过就阻断写入并返回错误信息。Pi Agent的Hook配置通常是在Agent的配置文件中声明。你需要指定Hook的触发时机比如before_file_write、Hook的执行命令或脚本路径、以及超时时间。Hook脚本的返回值决定是否放行返回0表示通过非0表示阻断stderr的内容会作为错误信息返回给Agent。我踩过的一个坑是Hook脚本的执行时间不能太长。如果Hook里跑了全量Lint检查每次生成文件都要等好几秒Agent的响应速度会明显下降。后来我把Hook里的检查精简到只做“快速规则”检查全量Lint交给Agent主动调用的工具去做这样既保证了拦截效果又不影响响应速度。3.4 反馈修正的重试策略Agent第一次生成代码不通过Lint检查是常态关键是给它合理的重试机会。Pi Agent支持配置最大重试次数和重试间隔我建议最大重试次数设为3次超过3次仍然不通过就放弃并报告给人工处理。重试时有一个重要技巧把上一次的违规信息作为新的上下文传给Agent。不要只是简单地让它“再试一次”而是明确告诉它“上次生成的代码有以下问题第15行函数过长、第32行使用了裸抛异常请修正这些问题后重新生成”。这样Agent的修正才有方向。另外重试时的温度参数可以适当调低。温度越低Agent的输出越确定修正时更倾向于“保守修改”而不是“重新发挥”。我一般把首次生成的温度设为0.7重试时降到0.3。4. 实操过程与核心环节实现4.1 环境准备与Pi Agent初始化先把基础环境搭好。假设你用的是Python技术栈需要安装以下工具pip install flake8 pylint isort pydocstyle然后初始化Pi Agent的配置文件。Pi Agent通常支持通过配置文件或代码方式初始化我倾向于用配置文件方便版本管理和团队共享。# pi_agent_config.yaml agent: name: code_assistant model: default temperature: 0.7 max_retries: 3 retry_temperature: 0.3 tools: - name: check_code_style description: 检查代码是否符合团队规范返回违规详情 entry: tools/lint_runner.py timeout: 30 hooks: - trigger: before_file_write entry: hooks/pre_write_check.py timeout: 5 prompt: system_template: prompts/system_prompt.md context_files: - docs/code_style_rules.md - docs/violation_examples.md这个配置文件定义了Agent的基本行为、可调用的工具、Hook的触发时机以及系统提示词的来源。团队里每个人用同一份配置保证Agent的行为一致。4.2 规范规则集的落地配置接下来把前面整理的规则集落地到具体的工具配置中。flake8的配置放在setup.cfg或.flake8文件里[flake8] max-line-length 100 max-function-length 80 exclude .git,__pycache__,venv ignore E203,W503isort的配置放在pyproject.toml里[tool.isort] profile black line_length 100 known_first_party [myproject]自定义的AST检查脚本需要单独编写主要检查那些Lint工具覆盖不到的规则。比如检查函数体长度import ast def check_function_length(file_path, max_lines80): with open(file_path, r, encodingutf-8) as f: tree ast.parse(f.read()) issues [] for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): func_length node.end_lineno - node.lineno if func_length max_lines: issues.append( f第{node.lineno}行函数{node.name} f长度为{func_length}行超过{max_lines}行限制 ) return issues这个脚本会被lint_runner.py调用检查结果统一返回给Agent。4.3 系统提示词模板的完整示例系统提示词模板是整个方案里最需要反复打磨的部分。我目前用的模板结构如下# 角色 你是项目代码生成助手负责在指定目录下生成符合团队规范的Python代码。 # 硬性约束违反将被Lint工具拦截 1. 函数体不超过80行 2. 禁止使用 raise Exception必须使用 ProjectError 或其子类 3. 所有公共函数必须有docstring 4. 导入顺序标准库 → 第三方库 → 本地模块 5. 单行不超过100字符 6. 禁止在 utils/ 目录外定义工具函数 7. 禁止直接修改 config/ 目录下的文件 8. 禁止引入未声明的第三方依赖 # 软性建议 - 变量命名体现业务含义 - 注释使用中文 - 优先使用项目已有的工具函数 - 避免过深的嵌套不超过4层 # 违规示例 错误写法 raise Exception(something wrong) 正确写法 raise ProjectError(something wrong, codeE001) # 工作流程 1. 理解任务需求 2. 生成代码 3. 调用 check_code_style 工具自检 4. 如有违规根据返回信息修正后重新自检 5. 自检通过后输出最终代码这个模板的关键在于把“工作流程”也写进去了明确告诉Agent生成代码后必须调用检查工具。实测下来写了工作流程之后Agent主动调用Lint工具的比例从不到50%提升到了90%以上。4.4 完整工作流的串联与验证所有组件配置好之后需要串联起来做一次完整的验证。我一般用一个包含多种违规场景的测试任务来验证任务在services/目录下创建一个数据处理模块包含数据读取、清洗、转换三个函数每个函数需要处理异常情况。这个任务会触发多个检查点函数长度、异常处理方式、导入顺序、docstring完整性。运行Agent后观察它的行为首次生成代码大概率会有1-2处违规Agent调用check_code_style工具拿到违规详情Agent根据违规信息修正代码再次调用工具检查直到通过Hook执行最终检查确认文件写入位置正确输出最终代码如果这个流程能跑通说明整套方案基本可用了。我实测下来简单任务单文件、逻辑简单通常1次重试就能通过复杂任务多文件、涉及继承和装饰器可能需要2-3次重试。4.5 效果度量与持续优化方案上线后需要持续跟踪效果。我建议记录以下指标指标说明目标值首次通过率Agent首次生成即通过Lint的比例60%平均重试次数通过检查所需的平均重试次数1.5人工拦截率Hook拦截的比例5%规范违规密度每千行代码的违规数2如果首次通过率低于50%说明系统提示词需要优化可能是硬性约束太多或者违规示例不够典型。如果人工拦截率超过10%说明Hook的规则可能过于严格需要放宽或者调整。我自己的经验是方案上线第一周首次通过率大概在40%左右经过两轮Prompt优化后提升到了65%以上。优化主要集中在两个方面一是把一些不常用的硬性约束转移到Lint工具里减少Prompt的规则数量二是增加了更多违规示例让Agent对规则的理解更准确。5. 常见问题与排查技巧实录5.1 Agent反复犯同一个错误怎么办这是最常见的问题。Agent在重试时反复犯同一个错误比如每次都忘记加docstring。遇到这种情况先检查系统提示词里这条规则是否足够醒目。如果规则藏在长列表中间Agent很容易忽略。我的做法是把最常违规的3条规则单独拎出来放在提示词的最前面并且用加粗标注。如果调整提示词后仍然反复违规说明这条规则可能需要用更硬性的方式约束。比如把“必须有docstring”从Prompt规则升级为Lint规则用pydocstyle做硬性检查。Agent被工具拦截几次之后通常会“记住”这条规则。还有一种情况是Agent理解了规则但“做不到”。比如要求函数不超过80行但任务本身就需要一个较长的处理逻辑。这时候需要考虑是否应该放宽规则或者引导Agent把长函数拆分成多个子函数。我一般会在提示词里加一句“如果函数逻辑较长请主动拆分为多个子函数每个子函数不超过80行。”5.2 Lint工具误报导致Agent陷入死循环Lint工具不是完美的有时候会误报。比如pylint可能对某些动态特性产生误判报出实际上不是问题的“问题”。Agent拿到误报信息后会尝试修正但修正后可能触发新的误报陷入死循环。解决方法是给Lint工具配置合理的忽略规则。在.pylintrc里把已知的误报规则关掉或者在lint_runner.py里对特定类型的告警做过滤。另外设置最大重试次数也是必要的兜底措施超过3次就放弃并报告人工处理避免无限循环消耗资源。我踩过的一个典型坑是pylint对__init__.py里的导入报“unused-import”但实际上这些导入是为了暴露公共接口。后来在配置里加了ignore__init__.py才解决。5.3 Agent生成的代码通过了Lint但不符合业务逻辑Lint只能检查代码风格和部分代码质量问题无法检查业务逻辑是否正确。Agent可能生成一段完全符合规范但业务逻辑错误的代码。这个问题没有完美的自动化解决方案但可以通过以下方式降低风险在系统提示词里要求Agent在生成代码后附上简要的逻辑说明方便人工Review时快速理解意图。在Hook里增加对关键业务函数的签名检查确保参数和返回值类型符合预期。对于核心业务模块仍然需要人工Review不能完全依赖自动化检查。5.4 不同开发者本地环境导致检查结果不一致团队协作中经常遇到的问题是同一个Agent配置在不同开发者的机器上检查结果不一样。这通常是因为Lint工具的版本不一致或者配置文件路径不对。解决方案是把所有工具版本锁定在requirements.txt里并且把配置文件放在项目根目录下统一管理。Agent的配置文件也纳入版本控制确保每个人用的都是同一份配置。如果团队规模较大可以考虑把Lint检查做成CI流水线的一部分Agent在本地生成代码后推送到CI做统一检查。5.5 常见问题速查表问题现象可能原因排查方向解决方案Agent不调用Lint工具提示词未明确要求检查系统提示词工作流程部分在提示词中明确要求生成后自检反复犯同一错误规则不够醒目检查规则在提示词中的位置把高频违规规则前置并加粗重试次数过多规则过于严格查看Lint违规详情放宽非关键规则或增加忽略配置Hook频繁拦截Hook规则太严查看Hook拦截日志调整Hook规则或改为警告检查结果不一致工具版本不一致对比各人环境锁定版本并统一配置Agent忽略docstring规则优先级低检查提示词规则顺序升级为Lint硬性检查5.6 几个我踩过的坑和对应技巧第一个坑是提示词过长导致Agent“失忆”。我一开始把整个规范文档塞进系统提示词结果Agent在生成到后半段时完全忘记了前面的约束。后来把提示词精简到只保留核心规则效果明显好转。经验是系统提示词控制在800字以内硬性规则不超过10条。第二个坑是Lint工具执行超时。pylint在大型项目上跑一次可能要十几秒Agent等不及就超时了。解决方案是只对当前生成的文件做检查不要跑全量检查。另外可以给pylint配置--jobs1避免多进程开销。第三个坑是Hook脚本权限问题。Hook脚本需要有执行权限而且在不同操作系统上的路径写法不一样。建议用Python脚本而不是Shell脚本写Hook跨平台兼容性更好。第四个坑是Agent在重试时“过度修正”。比如第一次生成只是少了一个docstringAgent在重试时把整个函数重写了反而引入了新的问题。解决方案是在重试提示里明确说“只修正以下问题不要改动其他部分”。6. 进阶优化与团队协作建议6.1 规范规则集的版本化管理团队规范不是一成不变的随着项目发展会不断更新。规则集也需要跟着版本化。我的做法是把规则集拆成三个文件rules_core.md放核心规则很少变动rules_project.md放项目特有规则随项目迭代更新rules_experimental.md放试验性规则观察效果后决定是否转正。Agent的配置文件里引用这三个文件每次规则更新只需要修改对应的文件不需要动Agent配置。这样既保证了规则的灵活性又避免了频繁修改Agent配置带来的风险。6.2 多Agent协作时的规范一致性如果团队里多个Agent分别负责不同模块的开发需要保证它们遵守同一套规范。解决方案是共享同一份系统提示词模板和Lint配置每个Agent的个性化配置只保留任务相关的部分。另外可以设置一个“规范守护Agent”定期扫描各Agent生成的代码发现规范偏差时自动提交修正建议。6.3 规范遵守情况的度量与反馈建议每周统计一次Agent的规范遵守情况把数据反馈给团队。数据好的时候可以适当放宽某些规则数据差的时候需要分析原因并调整策略。我自己的经验是规范遵守率跟提示词的质量强相关每次优化提示词后的一周内遵守率会有明显提升但随后会缓慢下降需要定期“刷新”提示词。6.4 新人上手时的注意事项新人加入团队时建议先让他们手动写一周代码熟悉团队规范后再引入Agent辅助。直接让新人用Agent容易导致他们对规范的理解停留在表面。另外新人的Agent配置应该从最严格的规则集开始等熟悉之后再逐步放宽。我在实际使用中发现Agent对规范的理解程度很大程度上取决于提示词的质量和Lint反馈的详细程度。把这两块打磨好Agent的规范遵守率可以稳定在90%以上剩下的10%通过人工Review兜底即可。这个比例比纯人工写代码时的规范违规率其实还要低一些因为人工写代码时反而容易因为赶进度而“偷懒”忽略规范。