资讯动态

大模型驱动的自我编程Agent系统:从代码生成到自动验证的完整闭环

发布时间:2026/10/20 4:44:08 来源:尧图企业网站定制
1. 我为什么花三个月搭一个“自己写代码”的Agent系统先聊点实际的。去年年底我接了个很头疼的需求一个项目里有大量的重复性改代码工作——换个数据源、调整接口字段、修补固定的错误模式。团队里几个同事每天把时间耗在“复制粘贴再微调”上既枯燥又容易出错。我一直琢磨能不能让大语言模型直接读代码、直接改代码、然后自己跑测试验证这个想法推着我花了好几个月搭了一套基于大语言模型的自我编程Agent系统今天把整个思路、踩过的坑和核心实现方案整理出来。这篇文章适合几类人看一是想用大模型做代码自动生成但不知道怎么落地的开发者二是已经接触过Agent概念、想搞清楚“自我编程”到底怎么闭环的人三是纯粹对大模型应用边界感兴趣的爱好者。我会从系统整体设计讲到Prompt工程、代码执行沙箱、测试反馈循环、记忆机制再到最后放进真实项目里跑的体验。先说结论一个大语言模型本身不会“编程”它只能生成文本。但当你把“生成代码—执行代码—观察结果—反思修正”这个循环交给Agent去自动运转再配上一套足够可靠的工具链它就真的能像初级工程师一样干活了。这个系统最核心的不是模型选得多强而是你如何设计那一层层反馈回路。2. 自我编程Agent的完整工作循环从“生成”到“自我修正”2.1 拿“写一段排序代码”当第一个测试用例我先用一个极简例子把整个循环讲明白。假设目标任务是写一个Python函数输入一串未排序的数字返回升序排序后的列表。第一轮Agent调用大模型生成初始代码def sort_numbers(nums): return sorted(nums)这步很简单但真正的关键在后面——系统不会直接把这段代码当作最终结果而是把它放进沙箱环境里执行测试# 测试代码 assert sort_numbers([3, 1, 2]) [1, 2, 3] assert sort_numbers([]) [] assert sort_numbers([5, 5, 5]) [5, 5, 5]如果测试全部通过Agent直接返回结果整个循环结束。这也是自我编程系统里最理想的一条路径一次生成、一次通过。但实际项目中这种情况占比可能不到40%。2.2 构造一个会失败的场景看看Agent如何自救为了演示“自我修正”的完整链路我故意让初始模型猜错需求。假设任务变成写一个函数输入一个字符串把其中所有字母变成大写但数字保持不变。模型第一轮很可能给出这样的代码def process_string(s): return s.upper()明显错了它把数字也处理了。这段代码会被送进沙箱执行测试用例是assert process_string(abc123) ABC123执行结果肯定是失败。此时Agent会捕获错误信息或断言失败详情连同初始代码、测试结果、最新测试日志一起重新发给大模型要求它“根据测试失败信息修改代码”。第二轮模型就可能给出def process_string(s): return .join(c.upper() if c.isalpha() else c for c in s)再跑测试通过了。整个“生成—执行—反馈—再生成”的闭环构成了自我编程Agent的骨架。2.3 四段式工作流拆解把这个闭环拆开核心环节就是四段任务解析、代码生成、执行验证、经验沉淀。任务解析把人类语言描述变成机器可理解的约束条件。比如“把字母大写但数字不变”这句话需要被转换成若干条可验证的测试用例这一步直接决定了Agent能不能知道自己“做对了”。代码生成是调用大模型产出候选实现输入通常包含系统提示词、任务描述、参考代码片段、历史失败记录。执行验证是整个系统最像“工程”的部分——在隔离环境里运行Agent生成的代码采集运行结果、报错信息、性能数据。经验沉淀则把每一轮成功或失败的过程整理下来存进记忆库下一次遇到相似任务时可以调出来当参考。这个循环跑得顺不顺很大程度上取决于后面几个环节的工程实现而不仅仅是模型能力。3. 模型选型与运行环境本地模型还是API调用各有各的账3.1 不同量级下怎么挑模型自我编程Agent对模型的要求比单纯聊天高不少——它需要较强的指令遵循能力、上下文理解能力和代码生成稳定性。我分别试过本地部署的7B、13B、70B级别模型也试过商用API。先给个直观结论如果任务只是简单函数生成、代码补全7B量级的模型已经能用一旦涉及多文件修改、跨模块调试、长上下文推理至少得13B起步70B级别在复杂任务上明显更稳。但模型越大部署成本、推理延迟、上下文处理开销都会指数级上升。我实际测试过一个典型场景让Agent修改某个项目里的数据清洗模块需求是“把列名从snake_case改成camelCase同时保留两个特殊字段不动”。7B模型经常只改一半就停13B模型偶尔漏掉边界条件70B模型基本能一次完成。但70B模型一次完整调用的成本和时间大概是13B的4到6倍。3.2 本地推理框架的取舍如果你倾向本地部署有几个推理框架可以选。早期我用的是一套比较通用的本地推理方案后来发现不同框架之间的差异还是挺大的。以性能优先的流式输出场景为例我最终选定的方案是把模型量化到4-bit用支持多线程推理的框架跑单次生成延迟能压到可接受范围。这里分享一个经验不要让整段代码一次性生成而是开启流式输出边生成边解析遇到明显的语法错误可以提前中断节省大量等待时间。如果是API调用路线优势是模型能力强、不用管部署劣势是有调用成本、有网络延迟而且在企业内网环境里还可能涉及数据传输合规问题。我当时因为项目数据敏感最终选了本地部署方案。3.3 一个足够轻量的运行拓扑我最终使用的运行拓扑非常朴素一台24G显存的工作站跑量化后的13B模型外加一套Python虚拟环境当沙箱。工作站上跑一个调度服务接收任务、调用模型、执行代码、收集结果全部走本地回环不访问外网。很多朋友问我要不要上K8s、要不要搞分布式推理我的回答是当你还在验证Agent能不能工作时单机方案已经足够。先把循环跑通再考虑规模。4. 沙箱执行层隔绝不等于随便跑跑安全与保真是两回事4.1 为什么代码必须在沙箱里跑大模型生成的代码是不可信的——它可能带恶意行为也可能因为逻辑错误把宿主机搞崩。所以执行层必须做隔离。但沙箱不是把代码扔进虚拟机就完事它还承担两个关键职责一是给Agent提供足够的执行环境让它能正常运行各种依赖二是把执行过程中的所有信息原样反馈给模型包括stdout、stderr、返回值、异常堆栈。我用的是Python的subprocess加操作系统级隔离配合资源限制命令控制CPU和内存使用。每个生成任务都会新建一个临时目录代码在这个目录里运行运行结束后整个目录直接销毁避免文件残留影响下一次任务。4.2 超时、内存和网络三大约束自我编程Agent跑起来以后你会发现它非常容易跑飞。常见情况包括生成的代码出现死循环、一次性申请过多内存、偷偷访问外网。所以执行层的三项硬约束不能少超时控制每个任务设30秒硬超时超时直接终止进程并把“执行超时”作为反馈信息传回模型。内存限制限制进程最大内存为512MB超出即被系统杀掉。网络禁用在命名空间层面关闭网络访问确保生成的代码不能对外通信。这三条约束执行以后系统稳定性有了质的提升。尤其超时控制第一次加上的时候就因为某个死循环代码救了整个任务队列。4.3 错误反馈的“翻译”环节沙箱执行完以后关键一步是把原始报错翻译成模型能理解的反馈文本。比如下面这段原始错误Traceback (most recent call last): File solution.py, line 3, in module process_string(abc123) TypeError: unsupported operand type(s) for : int and str不能直接原样塞给模型了事。更好的做法是追加一层格式化执行失败。 目标: 把字符串中的字母大写数字保持原样。 你的代码在运行 process_string(abc123) 时抛出了 TypeError。 错误类型为: 类型错误 (int 和 str 不能相加)。 请检查你在字符串拼接时是否遗漏了类型转换。模型看到这种经过“翻译”的提示修正效率会高很多。这个环节是最容易被忽视、却对迭代轮次影响最大的地方。5. 测试生成与用例覆盖让Agent摸黑找到“正确”的边界5.1 测试用例从哪来让模型自己出题自我编程系统里有一个值得认真设计的环节测试用例生成。你不能只靠人类写死几条固定断言否则Agent一遇到没覆盖到的场景就可能“作弊”。比如任务是“数字排序”模型只实现了冒泡排序且写错了边界但测试用例全是正序输入它就蒙混过关了。我的做法是让大模型先生成一批测试用例再人为注入一些边界条件和随机输入。系统里维护一个测试增强模块自动给原始测试集加入空输入、单元素输入、重复元素输入、超大数值、Unicode字符等边界情况。拿排序任务举例最终执行的测试集可能长这样assert sort_numbers([]) [] assert sort_numbers([1]) [1] assert sort_numbers([3, 3, 3]) [3, 3, 3] assert sort_numbers([-1, 10, 2]) [-1, 2, 10] assert sort_numbers([10**9, -10**9]) [-10**9, 10**9]5.2 当测试不充分时怎么发现“假通过”判断“测试是否覆盖到位”本身是个难题。我在早期构建时踩过一个典型坑模型生成了一段只对特定输入成立的“假实现”比如通过检查第一个元素是否等于3来返回固定结果而测试集恰好没有覆盖其他输入于是所有测试都通过了。后来我给系统加了一层“变异测试”思路人为对代码做少量破坏性修改比如把改成、把改成-然后重新跑一遍测试集。如果破坏后的代码仍然通过说明测试集太弱需要补充用例。这个机制虽然增加了一些计算开销但能显著减少“假通过”的情况。5.3 测试代码本身的版本管理测试用例是Agent系统里少数由“人类最终把关”的部分。我的建议是测试代码单独建版本管理每次Agent迭代失败信息时测试集版本快照也要记录下来。这样当你回滚Agent的行为时能知道它当时是被哪一组测试“教”出来的。6. 让Agent“记住”前车之鉴经验库与失败模式库6.1 一次失败不能永远失败但也不能重复踩坑如果每一轮任务都从零开始让大模型生成代码它大概率还是会犯同样的错。我搭的时候发现一个特别明显的问题同一个类型错误这个任务犯了修好了下个任务遇到类似场景它能犯一个几乎一模一样的错。这说明模型本身没有跨任务记忆你必须在系统层面替它记。我因此在Agent里维护了一个“失败模式经验库”。每当任务经过多轮修正最终成功时系统会把整个修正链整理成结构化记录包括原始任务类型、初始错误代码段、报错关键信息、最终成功修复方式。结构化格式像这样{ task_type: data_transform, initial_error: TypeError: unsupported operand type for : int and str, root_cause: attempting to concatenate string with int, fix_strategy: explicitly cast numeric values to str before concatenation, example_patch: return c.upper() if c.isalpha() else c }6.2 两种记忆模式长期倾向库和短期任务上下文长期倾向库存的是跨任务的“偏好经验”比如“这个项目里统一使用单引号”“不要修改公共接口的签名”。短期任务上下文则只保留当前任务内部的历史轮次信息比如“上一轮已经改过第12行这一轮不要再重复修改”。我没有用复杂的向量数据库来管理这些记忆而是先用轻量的结构化存储加关键词索引。当新任务进来时根据任务描述中的关键短语、函数名、报错类型把最相关的若干条历史经验取出来拼进Prompt。实验下来这个机制能把平均迭代轮数从5.2轮降到2.8轮提升相当明显。6.3 经验库也不是越多越好这里要提醒一句经验库内容太杂会反向干扰模型。我曾经把几十条完全不相关的修复经验全部拼进Prompt结果模型反而混乱生成了更差的代码。后来做了筛选策略每次最多取3条最相关经验而且要对这3条做去重和排序。测试下来效果最稳定。7. 复杂任务实操让Agent改一个CSV数据清洗模块7.1 任务描述与初始代码前面讲了不少原理接下来用一个稍微接近真实工程的案例把整个系统串起来看。假设有一个CSV数据清洗模块原始代码如下import pandas as pd def clean_csv(input_path, output_path): df pd.read_csv(input_path) df df.dropna() df[price] df[price].apply(lambda x: float(x.replace($, ))) df.to_csv(output_path, indexFalse)现在任务要求增加一列total_price计算公式为price * quantity同时把price列里的货币符号去掉以后再转浮点如果quantity列中存在空值默认填充为1。我把这段代码和任务描述一起交给Agent第一轮它生成的修改版代码大致是这样import pandas as pd def clean_csv(input_path, output_path): df pd.read_csv(input_path) df df.dropna() df[price] df[price].apply(lambda x: float(x.replace($, ))) df[quantity] df[quantity].fillna(1) df[total_price] df[price] * df[quantity] df.to_csv(output_path, indexFalse)乍一看好像都做了但有一个问题dropna()被放在填充quantity之前如果原始数据里quantity为空这一整行会被直接删掉后面的填充逻辑永远不会生效。这个顺序问题很典型模型第一轮通常发现不了。7.2 测试暴露逻辑顺序问题系统生成的测试用例之一专门构造了quantity为空的数据集# 构造临时测试数据 data name,price,quantity\napple,$2.00,\nbanana,$1.50,3\n期望结果是apple这一行的quantity被填充为1total_price2.0banana的total_price4.5。但Agent当前代码执行后apple这一行直接被dropna删除测试断言失败。失败信息被采集后经过翻译模块变成这样测试失败: 预期 apple 行的 total_price 为 2.0实际该行在输出中不存在。 可能原因: 在填充 quantity 之前执行了 dropna导致空值行被提前删除。 建议: 调整操作顺序先填充空值再执行缺失值处理。7.3 第二轮修正与联合验证Agent收到反馈后第二轮生成的代码把执行顺序调整了def clean_csv(input_path, output_path): df pd.read_csv(input_path) df[quantity] df[quantity].fillna(1) df df.dropna() df[price] df[price].apply(lambda x: float(x.replace($, ))) df[total_price] df[price] * df[quantity] df.to_csv(output_path, indexFalse)这轮测试全部通过并且额外用例覆盖了“quantity全部为空”“quantity存在文本脏数据”等情况都通过了。整个任务一共迭代了两轮耗时大约40秒。这是在我本地13B模型量化条件下跑出来的成绩换成更强的API模型可能第一轮就能过。8. 调度与任务管理单任务很简单多个任务同时跑才是地狱8.1 串行跑还是并发跑当任务数量多起来以后调度策略就成了核心问题。我开始时很天真直接让所有任务并发跑觉得反正沙箱互相隔离。结果显存直接被模型推理占满执行队列互相拖慢整体吞吐量反而更低。后来做了两层控制第一层是模型推理的并发数本地单卡最多同时跑2个生成任务第二层是执行沙箱的并发数小幅放开到4。这样保证模型推理引擎稳定同时代码执行不会被卡成瓶颈。8.2 优先级与任务队列真实项目里任务不是一样重要的。我设计了一个带优先级的任务队列高优先级任务比如线上热修复插队执行普通任务按FIFO处理低优先级任务只在系统空闲时运行。这个调度逻辑不复杂但实际使用中效果非常明显团队再也不用为了抢资源互相等了。8.3 超时重试与最大迭代轮数调度层还需要一个“终止条件”。我设了两道闸单轮代码执行超时30秒就终止并进入重试单个任务的迭代轮数上限为5轮超过5轮还没通过测试就直接放弃把半成品和失败原因记录下来留给人工处理。刚开始我把迭代上限设成10轮想着给Agent更多机会。实际跑下来发现超过5轮还没解决的问题往往不是模型笨而是任务描述本身有歧义或测试用例有问题继续迭代纯属浪费资源。9. Prompt设计为什么我不用“你是一个编程专家”这种开场9.1 系统提示词的四个层级Prompt不是写一段“你是一个编程专家”就完事。我在Agent里把Prompt拆成四个层级角色边界层、任务描述层、经验参考层、输出约束层。角色边界层负责限定模型的行为范围比如“你是一名Python开发工程师只输出代码和简短说明不要闲聊”。任务描述层把人类的需求变成结构化指令包括输入、输出、约束条件、已知的成功标准。经验参考层放前面提到的历史相关记录。输出约束层规定代码格式、注释风格、变量命名规范。举例说明最终传给模型的Prompt结构大致是[角色] 你是一名Python开发工程师负责修复用户提供的代码。只输出修改后的完整代码。 [任务] 修改 clean_csv 函数实现以下目标 1. 新增 total_price 列值为 price * quantity 2. 去除 price 列中的货币符号 3. quantity 为空时默认填充为 1 4. 保持原函数签名不变 [参考经验] - 同类任务失败原因: dropna 执行顺序错误导致空值行被提前删除 - 修复建议: 先填充空值再处理缺失值 [输出要求] - 只输出Python代码不包含解释 - 保留原函数所有导入语句 - 禁止修改函数签名9.2 动态更新Prompt中的失败轮次除了静态模板每轮迭代我都把上一轮运行结果追加进Prompt。这样模型能看到自己刚才生成的代码、执行后的报错、以及测试断言的详细对比相当于站在“上帝视角”审视自己的错误。有一点值得注意不要只贴报错堆栈要贴“期望值vs实际值”的对比。因为很多代码逻辑错误并不会抛异常而是返回了不该返回的值。告诉模型“预期是A你返回了B”比告诉它“抛了一个异常”更容易修正。9.3 温度参数要确定性不要创造力写代码这件事和写诗不一样我们需要的是稳定、可预期、符合规范的结果。我测试过不同温度参数的效果温度设为0的时候同一个任务多次生成的结果几乎一致Bug率最低温度调到0.4以上代码会开始出现一些“花活”比如用不同的方式实现同一个功能但Bug率明显上升。最终我固定把代码生成阶段的温度设为0.1调试解释阶段的温度设为0.3。前者保证代码稳定后者允许模型对问题做一些发散分析帮助定位根因。10. 实测数据与调优记录到底能省多少时间Bug率如何10.1 两条对比线人工改码 vs Agent改码我挑了两个典型任务做了对比测试。第一组是前面提到的CSV清洗模块修改第二组是一个更复杂的“多文件重构”任务——把项目里三个工具模块的日志输出方式全部从标准输出改成JSON格式并且要求兼容老的日志开关。人工组一个熟手工程师处理第一组任务大约用时8分钟第二组用了25分钟。Agent组第一组两轮迭代共40秒第二组总共迭代了7轮耗时约6分钟。看起来Agent在两类任务上都更快但第二组的6分钟背后隐藏着一个事实——最后一步“合并代码到主分支”还是人工完成的因为Agent生成的代码虽然逻辑正确但没有保持原有的日志接口不变需要人工做适配修正。10.2 失败模式的统计我跑了近200个不同类型的编程任务统计下来结果如下任务类型一次通过率平均迭代轮数最终成功率单函数实现52%1.491%数据清洗类38%2.186%多文件修改19%3.874%调试修复类41%1.983%这个表可以看出一个清晰的趋势任务涉及的文件越多一次通过率越低最终成功率也越低。单函数任务对Agent最友好多文件改动则需要更强的全局上下文理解能力。10.3 影响成功率的关键因素排名按我的实际观察影响最终成功率最重要的因素依次是任务描述的精确程度。描述里只写“优化一下”的任务Agent基本都会跑偏写清楚输入输出约束和期望结果的任务成功率能提高30个百分点。测试用例的覆盖度。测试弱Agent就能“蒙混过关”测试强它才会认真处理边界条件。历史经验的命中情况。如果经验库里刚好有一条极其相似的修复记录迭代轮数能直接减半。模型的指令遵循能力。模型能力当然是基线但它的重要性反而排在任务描述和测试用例之后这一点和很多人直觉相反。11. 经常踩的坑与改进方向11.1 大模型“自作主张”改接口这是我在多文件任务里遇到最多的坑。Agent在修改一个函数时经常顺手把另一个函数的参数名也改了因为它觉得这样“更合理”。最终导致调用方全部报错。我的解决方案是在系统提示词里明确“禁止修改任何函数签名和对外接口”同时在执行验证环节加一步“接口一致性检查”——自动比对修改前后所有公共函数的签名发现变化直接拦截。11.2 上下文窗口塞满导致“失忆”任务一多历史记录很容易把上下文窗口占满。模型到后面就像“失忆”了一样忘记最开始的约束条件。我后来做了上下文压缩策略把完整的代码历史压缩成“改动摘要”只保留最近两轮的完整代码更早的代码只保留关键差异说明。11.3 测试通过但业务不对测试用例是Agent的“标准答案”但标准答案本身可能就写错了。所以我保留了人工审核环节尤其是数据变化、资金计算、权限逻辑这类高风险任务Agent生成的代码合并前必须人工Code Review。把Agent当成“可以无限重试的初级工程师”而不是“无限智能的高级工程师”是我这段时间最大的体会。12. 后续还能怎么玩从“改代码”到“部署验证”再到“需求闭环”12.1 跟CI/CD流水线打通我在考虑下一步把Agent系统直接接入CI/CD流水线。当开发提交代码后Agent自动检查代码变更、生成测试用例、执行回归测试如果发现问题就直接生成修复补丁提交。这相当于把“自我编程”从离线任务变成在线质量守护。12.2 多Agent协作拆解任务再合并复杂任务交给单个Agent往往力不从心但拆成多个Agent协作就有意思了一个Agent负责理解需求并拆解任务另一个Agent负责实现功能A第三个负责功能B最后汇总Agent做集成测试。这会产生很多工程细节比如子Agent之间的通信协议、任务拆分粒度、合并冲突处理都值得单独深入研究。12.3 跟专用小模型搭配我还想试一个混合方案主干逻辑用大模型但错误分类、代码格式化、接口一致性检查这类重复性工作训练几个专用小模型来处理。这样可以降低整体成本也能减少大模型在琐碎环节的时延。这条路我还在继续走目前只能说方向让人觉得靠谱。后续要是有新的落地结果我会再专门写一篇分享。最后再分享一个经验不要把“代码生成”当成目标要把“自动验证”当成核心。你给Agent编织的反馈回路越扎实它展现出的智能就越惊人。反过来如果只追求一次生成就完美那跟调Prompt没什么区别离“自我编程”还差得远。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑