开头直接从技术视角切入,讲清楚这个系统的价值,然后顺着架构、选型、实现、实战、避坑的顺序展开。
1. 整体设计思路:为什么“自我编程Agent”值得自己搭一套
近两年大语言模型的应用落点越来越多地集中到 Agent 上,而“自我编程 Agent”又是 Agent 里最特别的一类:它不满足于聊天、总结、翻译,而是直接拿代码当输出,自己写函数、自己改 bug、自己跑测试,甚至能根据报错信息反推修复方案。我花了大概三周时间把这样一套系统从零搭起来,过程中踩了不少坑,也把一些“看起来可行但实际跑不通”的思路彻底推翻重来。这篇文章把最终版本的设计思路、关键代码、踩坑记录都整理出来,属于收藏级教程级别的内容。
先说清楚这系统到底能干什么:给它一个自然语言任务描述,比如“写一个函数,输入是 CSV 文件路径,输出是各列缺失值统计表”,它会自动拆解需求、生成代码、写入文件、尝试运行,如果运行报错,它还能读取错误信息自己改代码,直到跑通或者达到预设的迭代上限。整个过程不需要人工干预,开发者只需要最后检查它产出的代码和结果。
什么人适合自己搭一套?如果你已经在用代码补全类工具,但你想要的不是“补全”,而是“让模型独立完成一个小型开发任务”,那这套系统的设计思路就会对你有用。它需要的基础技能包括:熟悉大模型 API 调用方式、了解函数调用机制、有一点 Python 工程经验。难度不算高,难点全在细节设计上。
这里借用我实际调试时的一个体会:把 Agent 想成一个刚入职的实习生,它态度很好,但经验不足,容易犯错。你要做的不是替它写代码,而是给一套明确的“工作流程 + 反馈机制”——告诉它目标是什么,让它自己动手,错了就让编译器告诉它哪里错了,再让它自己改。这套系统的本质,就是把“带实习生”这件事标准化、自动化。
整套系统由四个核心模块组成:任务拆解与规划模块、代码生成模块、工具执行环境、反馈与自我修正模块。四个模块形成一个闭环,任务从进入到产出,每一步都有日志记录,方便事后回溯。
2. 核心技术选型:模型、接口、环境都要配套
2.1 模型选择的关键考量
模型是整套系统的“大脑”,选型是否合理直接决定后面工程化的工作量。我试过多款主流模型,最终固定使用具备结构化输出能力的模型,而不是纯粹靠提示词“希望它输出 JSON”。原因是自我编程 Agent 的运行链路中要频繁解析模型输出,一旦格式不稳定,解析层就得写大量容错代码,越到后期越痛苦。
选模型时重点看三个维度:一是上下文窗口大小,因为代码生成、运行日志、错误信息都要塞进对话上下文,窗口太大会导致多轮迭代后信息丢失;二是工具调用能力,也就是模型能否理解并触发“执行代码”这样的外部动作;三是输出格式遵循能力,即能否稳定输出符合 JSON Schema 的结果。
另外要关注的是模型在“中文任务描述”和“英文代码”之间的转换能力。实际测试中,有些模型中文理解很好,但生成的代码风格混乱、注释缺失;有些模型代码能力很强,但遇到模糊的中文需求会自行脑补不存在的功能。多测几种prompt风格后,我的经验是:任务描述中包含明确的输入输出示例,比单纯的文字说明效果好得多。
2.2 工具接口设计:代码执行环境怎么封装
自我编程 Agent 和普通聊天机器人最大的区别在于“工具”。聊天只需要返回文本,而编程 Agent 必须操作真实环境:写文件、跑代码、安装依赖、查看输出。这意味着你要把系统能力封装成一组工具,暴露给模型调用。我最终设计了四个基础工具:写文件工具、执行 Python 代码工具、安装依赖工具、读取文件工具。
写文件工具负责把模型生成的代码落盘;执行代码工具运行某个 Python 文件并返回 stdout、stderr 和退出码;安装依赖工具对接 pip,让模型能自行解决第三方库缺失问题;读取文件工具让模型在修改代码前先看一眼已有文件内容,避免盲目覆写。
工具接口设计的最重要原则是:返回值必须结构化、可预测。执行工具返回固定格式,比如{"exit_code": 0, "stdout": "...", "stderr": ""},这样模型才能准确理解执行结果。如果只是返回一段拼接文本,模型经常搞不清到底哪部分是错误信息,导致自我修正时答非所问。
2.3 为什么不直接用现成的开源框架
市面上有不少开源 Agent 框架,拿来即用似乎省事。但实际体验下来,框架把太多细节封装好了,出问题时你很难判断是模型的问题、提示词的问题还是框架调度的问题。自己搭一套,核心不是为了“重复造轮子”,而是为了把链路中的每一个环节都掌握在手里,后面做调试和扩展都有主动权。
自建系统的另一个好处是灵活。比如我想在特定步骤插入额外校验逻辑,或者对执行代码做超时控制,框架里改起来很难,自己写的代码里改动就是几行的事。如果你有稳定的时间和精力,我建议至少自己从零搭一个最小原型,跑通后再决定要不要引入现成框架。
3. 核心实现细节:提示词结构、状态管理和自我修正机制
3.1 消息结构设计:让模型始终知道“现在进行到哪一步”
大模型本质上是无状态的,所有“状态感知”都靠消息历史来提供。自我编程 Agent 的多轮迭代场景里,消息结构尤其关键。我采用了一种类似 ChatML 的结构:系统消息固定设置 Agent 的行为模式和工作原则;用户消息用来描述任务;工具消息用来承载代码执行结果。
系统提示词里面写清楚三件事:Agent 的角色是“专业的 Python 开发工程师”,工作原则是“必须先理解需求,再生成代码,最后通过工具执行验证”;每次修改代码前要先说明修改理由;不允许编造执行结果。
这里要特别强调系统提示词的一个细节:一定要写“如果工具返回错误,请根据错误信息修正代码并重试,而不是解释错误原因”。我最初没有这一句,模型在遇到报错时经常长篇大论分析错误原因,但完全不给修改后的代码。加了这个约束后,修正效率明显提升。
3.2 状态管理:迭代轮次、文件路径、任务目标都要跟踪
为了确保多轮交互不跑偏,我用一个简单的状态对象维护整场任务的元信息,包含:任务原始描述、当前轮次、最大迭代上限、所有生成过的文件路径列表、最近一次执行结果摘要。每一轮迭代开始前,把这些元信息作为只读参考注入到用户消息中,提醒模型当前进展。
状态对象最简单的实现是一个 Python dataclass,核心字段如下:
from dataclasses import dataclass, field from typing import List @dataclass class TaskState: task_description: str max_rounds: int = 5 current_round: int = 0 generated_files: List[str] = field(default_factory=list) last_execution_summary: str = ""每轮迭代结束时更新current_round和last_execution_summary。这个状态对象虽然简单,但它解决了 Agent 最容易出现的“迷失方向”问题——模型不会忘记自己已经改过几个文件、当前卡在哪一步。
3.3 代码生成与执行循环:反馈回路是灵魂
核心运行流程可以压缩成一句话:生成代码,执行,把执行结果返回模型,让模型判断是否完成。如果没完成,就继续循环。这里面的关键在于“让模型自己判断任务是否完成”,而不是由外部硬编码条件来决定。因为任务类型千变万化,只有模型才理解“计算出缺失值统计表”意味着什么。
我的实现方式是在每轮工具执行后,把执行结果和一段询问文本一起塞回对话:“以上是代码执行结果。请判断任务是否已完成。如果已完成,回复 DONE;如果未完成,请直接输出修改后的完整代码。”这样避免了额外的一次模型调用,也节省了 token。
代码执行循环配合超时控制很重要。某些任务会让代码进入死循环或长时间运行,我设置了单次执行超时时间为 30 秒,超时直接杀进程并返回超时提示。如果没有这个机制,一次测试可能卡住整个 Agent 流程好几分钟。
3.4 安全沙箱:千万别让 Agent 在裸环境里跑代码
让模型生成的代码直接在你的开发机执行,这是最危险的环节。大模型生成的代码可能意外删除文件、修改系统配置,甚至故意插入恶意操作。虽然大部分场景下模型不会作恶,但“意外”总是存在。我在部署时把所有代码执行都放进容器环境,宿主机和容器间只挂载一个临时工作目录。
容器化解决的不只是安全问题,还有环境一致性问题。宿主机的 Python 环境可能缺少某个包,或版本冲突,而容器里是干净的镜像,每次运行完直接销毁重建。我用的镜像基于精简版 Python 官方镜像,包含 pip,不预装任何第三方库,模型需要什么库就用安装依赖工具自行安装。
容器环境下,代码执行工具实现时需要特别注意路径映射。宿主机的工作目录和容器内的工作目录不同,日志和文件路径要标准化,否则模型执行后以为自己写入了output.csv,但实际上文件在容器里的另一个位置,外部很难找到。
4. 完整实战:从任务描述到代码自动生成与修复
4.1 实战任务设定:一个典型的数据处理脚本
为了直观展示系统的能力边界,我用一个真实任务来跑完整流程:“编写一个 Python 脚本,读取当前目录下的 sales_data.csv,该文件包含三列:日期、销售额、地区。要求按地区统计销售总额,输出为 summary.csv,按销售额降序排列。”这个任务足够有代表性:需要读文件、分组聚合、排序、写出新文件,覆盖了常见的数据处理模式。
我把这个任务作为初始用户消息传入,同时把max_rounds设置为 5,也就是允許最多循环五次生成-执行-修正的过程。执行环境是容器,工作目录挂载了包含sales_data.csv的文件夹。
4.2 第一轮迭代:模型首先生成代码
第一轮生成的代码通常是“最理所当然”的写法。模型看到需求后直接生成代码,调用写文件工具写入summary.py,然后调用执行工具运行。这个阶段的代码基本能完成“读文件并用 groupby 分组求和”的任务,但由于任务要求“按销售额降序”,第一版代码有两个常见问题:一是分组后忘记排序,二是输出列名和原文件列名不一致。
这正是这个系统最有意思的地方:模型生成的代码不是一次到位的,而是需要在执行结果的驱动下自我修正。第一轮执行后返回的 stdout 为空,exit_code 为 0,但模型判断任务未完成,原因就是缺少排序。这是模型从执行反馈中自己意识到的,而不是外部规则告诉它的。
4.3 第二轮迭代:自我修正开始发挥作用
第二轮迭代中,模型阅读了上一轮的执行摘要和文件内容,生成修改后的代码,将sort_values加进去,同时统一了列名。执行后退出码仍然是 0,模型查看输出文件内容后确认数据已正确排序,最终回复 DONE。
整个任务从开始到结束耗时约两分半钟,花费一次模型调用和一次修正调用。这是比较顺利的情况。我在测试中还遇到过完全不顺利的情况:模型生成的代码包含语法错误,执行工具返回了 traceback,模型第二轮的修复方案居然是给整个文件包上一层 try-except 并直接打印“成功”——这属于典型的“掩盖问题”,反而不是修复问题。
4.4 对代码质量的隐性检查:模型自己看得懂自己的输出
迭代结束后,我会额外要求模型做一次“自审”,描述它生成代码的逻辑结构、依赖的第三方库、运行注意事项。这一步不是为了给模型自己看,而是为了给开发者省去通读代码的时间。模型描述出来的内容如果和实际代码不符,说明生成过程有幻觉成分,需要重点检查。
从实际测试来看,自审描述和代码逻辑的一致性大约在九成左右,剩下的一成要么是模型对某些 Python 内置函数的行为理解有误,要么是它高估了自己代码的健壮性(比如没考虑空文件、列名含空格等边界情况)。这也是为什么最后的人工代码审查环节不能省。
5. 常见问题与排查技巧实录
5.1 模型“假装成功”:没有报错但结果明显不对
这是我遇到频率最高的问题。具体表现为:代码执行 exit_code 为 0,没有报错,模型满意地输出 DONE,但检查产出文件发现内容是空的或者列数不对。原因是模型在生成代码时使用了不存在的列名,Pandas 在按列名读取时会抛 KeyError,但某些写法下错误被吞掉了,或者模型把错误 try-except 掉了。
排查思路是先看生成文件的字节大小。如果目标输出文件大小为零,基本可以断定数据处理没有正确执行。修复方法是追加一条约束到系统提示词:“任务完成后,必须验证输出文件非空且结构符合预期,不得只依赖 exit_code 判断成功。”这一条加上后,类似问题少了很多。
5.2 上下文膨胀导致执行效率下降
多轮迭代后,对话历史里塞满了完整代码、运行输出、错误堆栈,token 消耗越来越大,模型调用延迟也越来越高。极端情况下,上一轮的代码内容在新一轮回复里被反复引用,token 浪费严重。
我的处理方案是引入“压缩机制”:每轮执行结束后,把完整的 stdout 和 stderr 做截断,只保留最后 1000 个字符作为摘要写回对话。代码文件本身不需要完整保留在消息里,因为模型可以通过读取文件工具随时查看。这个改动让平均 token 消耗降低了约三成,而且没有影响任务完成质量。
5.3 依赖安装失败导致反复尝试
模型在容器环境里安装第三方库,常见的问题是网络不稳定导致 pip 下载失败。模型第一轮安装失败后,会把报错信息当作代码错误来修,生成一个没有任何意义的“修复版”代码。这个坑很隐蔽,因为它不是代码逻辑问题,而是环境问题。
解决办法是在工具返回错误时加入错误类型识别标签,比如[NETWORK_ERROR]或[SYNTAX_ERROR],模型看到网络错误标签后就不会试图改代码,而是直接重新尝试安装或者换源。这个小改动让依赖问题的处理准确率提高了很多。
5.4 识别“循环修复但永远修不好”的死局
有些任务会让 Agent 陷入循环:改一个 bug,引入一个新 bug,再改新 bug,再引出另一个新 bug,直到达到迭代上限还停不下来。这类死局通常发生在需求本身和代码实现方向不匹配的情况下。
我设置了一个简单的“方向漂移检测”:如果模型连续两轮修改后的代码结构发生了大幅变化(比如从 pandas 写法突然变成了纯 Python 循环实现),就判定方向不稳定,主动将对话回退到上一轮的稳定版本,切换策略为“先生成最小实现,再逐步加功能”。绕开死局比硬碰硬修 bug 高效得多。
5.5 小技巧:用日志记录每一轮的关键决策
开发调试过程中,我养成了一个习惯:把每一轮迭代中模型的“理由叙述”单独提取出来存成日志。理由叙述虽然不影响代码执行,但它是判断模型思考质量的最直观材料。模型如果说“我改用另一种方法更稳妥”,这是正常的策略调整;模型如果说“我认为原代码可能有问题,所以加了防御性分支”,要警惕它是否在无中生有。
6. 扩展思路:这套系统未来还能怎么用
自我编程 Agent 搭好基础版本后,后面能扩展的方向很多。我最先尝试的是给它接入“测试生成”能力:任务完成后,让模型自动生成对应的单元测试并运行验证。这一步对代码质量的保障作用很大,相当于把“自我编程”升级成了“自我测试”。
另一个方向是把文件读写工具替换成 Git 操作工具,让 Agent 直接完成 commit 和 push 操作。这样它就能完整地“写代码—验证—提交”,更接近真实开发流程。但这一步要格外谨慎,建议先在私有仓库的独立分支上试用,避免污染主分支。
再有就是任务拆解的深化。目前系统处理的是单体脚本类任务,如果任务复杂度再上一个台阶,比如“实现一个包含多个模块的小型服务”,单轮规划就不够用了,需要引入任务拆分成子任务的能力,让 Agent 像一个真正的工程师一样分模块开发、独立测试、最后集成。
我在实际使用中的体会是,这套系统的上限并不完全取决于模型的能力,更取决于工程细节的设计。提示词的结构、工具的返回值格式、循环的控制策略,每一点都直接影响最终效果。第一次跑通时你可能会觉得“就这?”,但当你真正拿它去处理一批任务,观察它在失败时如何自我修正,就会意识到这已经是开发模式开始转变的信号。后续如果你也在搭类似的系统,建议从最小闭环开始,先把“生成—执行—反馈—再生成”这条路走顺,再去加更多花哨的功能。基础循环稳定了,一切才有的谈。