用LLM构建编程Agent:Simon Willison的llm-coding-agent实验解析

当LLM库进化为Agent框架
知名开发者、Datasette创始人Simon Willison近日发布了一个颇具实验性质的项目——llm-coding-agent 0.1a0。这是他继此前一系列AI实验(他称之为"Fable"实验)之后的又一次尝试。项目的核心动机很直接:他的开源 LLM 库 已经从单纯的模型调用工具,逐步演化为具备Agent能力的框架,那么基于它构建一个类似Claude Code的编程Agent会是什么样子?
Simon的LLM库最初定位为命令行工具与Python API,用于统一调用OpenAI、Anthropic、Google等多家模型提供商的接口。这一演化路径折射出整个AI工具生态的发展逻辑:早期的LLM封装库主要解决「多模型接口统一」问题,而随着工具调用能力的成熟,这类抽象层天然成为承载Agent工作流的基础设施——因为它已经处理了认证、上下文管理、响应解析等核心复杂性,只需在其上叠加工具注册与调用循环,就能完成从「模型调用工具」到「Agent框架」的跃迁。
其演化为Agent框架的关键节点,是大模型**工具调用(Function Calling/Tool Use)**能力的成熟——OpenAI于2023年6月引入Function Calling,Anthropic随后推出Tool Use,使得模型不再只是「问答机器」,而能主动调用外部函数并基于结果继续推理,形成「感知-决策-行动」的闭环。
这一机制的本质转变值得深究。在原生工具调用出现之前,开发者主要依赖「ReAct」(Reasoning + Acting)框架——通过精心设计的提示词让模型输出结构化文本,再由外部代码手动解析并映射到函数调用,错误率高且难以标准化。原生Function Calling将这一流程内化为模型的输出格式:模型可以在推理过程中主动声明「我需要调用哪个函数、传入哪些参数」,并在获得返回值后继续推理,形成多步骤的工具调用链。这一机制能够如此自然地融入模型,部分原因在于Transformer架构对结构化输出的天然适配性——模型在预训练阶段接触了大量API文档、JSON Schema和函数签名,使其能够以极低的微调成本学会生成符合特定格式约束的函数调用请求。这将「外部世界的操作接口」纳入了模型的语义空间,使得文件系统、Shell、API等任何可编程的系统都能成为模型的「效应器」。正是这一底层能力的普及,让一个个人维护的LLM抽象库得以承载完整的Agent工作流。
值得关注的是,工具调用的标准化进程并未止步于各厂商的私有协议。Anthropic于2024年11月发布的**Model Context Protocol(MCP)**代表了这一领域的最新整合尝试——它将「模型能调用哪些工具」从各家私有实现中抽象为可互操作的开放标准接口,其设计哲学类似于微软提出的LSP(Language Server Protocol)对代码编辑器生态的整合效应:LSP在「编辑器」与「语言服务」之间插入了标准协议层,使得任意编辑器可接入任意语言的智能补全,MCP则试图在「模型」与「工具提供方」之间建立类似的标准化连接。llm-coding-agent当前采用的自定义工具集设计,与MCP的设计哲学高度一致,也暗示了此类个人实现向标准协议迁移的潜在路径——一旦工具接口标准化,Agent框架的竞争将从「工具集设计」转向「规划策略与上下文管理」的更高层次。
这个问题本身颇具代表性。随着大模型工具调用能力日益成熟,「编程Agent」已不再是大厂专利。一个成熟的LLM抽象层,加上一组精心设计的工具函数,就足以搭建出可用的自动化编码助手。Simon的这次实验,正是对这一趋势的亲身验证。
用AI构建AI:TDD驱动的自举式开发
最值得关注的一点是——这个编程Agent本身,几乎完全由AI编写完成。
Simon从他的 python-lib-template-repository 模板仓库创建了新项目,仅用两条提示词就走完了整个开发流程。
第一条提示词负责生成规格文档:
Write a spec.md for this project - it will depend on the latest "llm" alpha from PyPI and implement a Claude code style coding agent complete with tools for reading and editing files and executing commands
第二条提示词要求AI以严格的红/绿测试驱动开发(Red/Green TDD)方式,分成一系列合理的提交来构建项目:
Commit the spec, then build it using red/green TDD in a series of sensible commits (each with passing tests and updated docs) - occasionally manually test it using the OpenAI API key in your environment
红/绿TDD是测试驱动开发中最核心的节奏:先写一个必然失败的测试(红灯),再编写最少量代码使其通过(绿灯),最后重构。这一方法论由Kent Beck在《Test-Driven Development: By Example》中系统化,是极限编程(XP)的核心实践之一。
将其嵌入AI协作流程,意义尤为深远。在AI辅助编程场景中,测试的角色发生了升维——它不再只是「开发节奏的节拍器」,而成为了约束AI自主空间的「形式化规格」。这种升维源于测试作为规格语言的本质属性:自然语言提示词本质上是模糊的,同一段文字在不同上下文中可能映射到截然不同的实现;而测试用例提供了精确的输入输出约束,将需求的歧义性降至最低,是人机协作中最接近「无损传递意图」的工程手段。每一个通过的测试都是对AI输出的可重现验证,相当于将自然语言需求转化为机器可执行的断言。这与形式化方法(Formal Methods)和契约式编程(Design by Contract)的思路一脉相承:越精确的约束,越能将不确定的AI行为收束到可预期的区间。Kent Beck本人在观察AI辅助编程趋势时也指出,TDD在AI时代的价值不降反升,因为测试正在成为人机协作的「通用语言」。将TDD嵌入AI协作流程,意味着每一步AI生成的代码都必须经过可验证的测试约束,有效降低了AI幻觉(Hallucination)带来的隐性错误风险——每次提交都附带通过的测试,相当于为AI的自主发挥加了一道机械保险。
这套流程强调了三个工程实践要点:先写规格再实现、每次提交都附带通过的测试与更新的文档、以及用真实API密钥进行手动验证。这不是简单地「让AI写点代码」,而是将成熟的软件工程方法论嵌入到了AI协作流程中。整个开发过程通过Claude Code for web完成,spec、README与commit序列均开放可查阅。
值得注意的是,「用AI构建AI工具」的**递归自举(Bootstrapping)**模式并非首次出现。GitHub Copilot团队早期曾用Codex辅助优化Codex提示词,Anthropic的Constitutional AI也包含让模型评判自身输出的自我迭代机制。这种递归模式的哲学意义在于:它模糊了「工具创造者」与「工具使用者」的边界,同时也对开发者提出了更高的审阅要求——当AI能够自主扩展设计时,人类工程师的角色逐渐从「代码编写者」转变为「意图定义者与质量守门人」。
Agent工具集:编程助手的「手」与「眼」
任何编程Agent的核心,在于它能操作哪些工具。llm-coding-agent 自动实现了一套相当完整的工具集,可通过 uvx ... llm tools 查看。
编程Agent的工具集设计本质上是一种**「最小权限原则」与「可审计性」的平衡**。Claude Code、Devin、Cursor等成熟产品均采用类似的文件读写+命令执行+搜索的基础工具组合,这套组合覆盖了代码理解(读)、修改(写/改)、验证(执行)、导航(搜索/列举)四大核心操作,已被业界实践证明为编程Agent的最小可行工具集。
这一工具集的粒度选择并非显而易见。Anthropic在Claude Code的技术文档中阐述了背后的权衡:工具过细会增加模型的调用开销和上下文长度;工具过粗则损失原子性,使错误难以定位和回滚。更深层的考量在于工具粒度对模型规划能力的影响——粒度过粗的工具会将复杂的决策逻辑下沉到工具实现层,使模型失去对中间步骤的观察和调整能力;而粒度过细则会产生大量的工具调用往返,显著增加延迟和token消耗。llm-coding-agent的五元组设计——「读-写-改-执行-搜索」——代表了当前业界在这一权衡上的主流收敛点,每个工具都对应一个原子性的、在语义层面完整的操作单元。
文件读写
- read_file:读取文本文件,返回带行号的内容(类似
cat -n),支持 offset 和 limit 分页读取超大文件; - write_file:创建或覆盖文件,必要时自动创建父目录;
- edit_file:精确替换文件中的字符串,
old_string必须与文件内容完全匹配(包括空白字符)且唯一定位,并返回diff供验证。
edit_file 要求精确唯一匹配并返回diff,其设计思路直接源自版本控制系统的核心逻辑——git的unified diff格式正是基于「前后上下文行作为锚点」的精确定位机制。在大文件中,精确字符串匹配确保了Agent每次修改操作的可审计性和可预测性:就像git不允许在没有足够上下文的情况下应用patch一样,edit_file要求「唯一定位」从根本上防止了Agent在大文件中误改非目标代码段。这一设计还有一个重要的副作用:它强制Agent在执行修改前必须先通过read_file获取当前文件内容,从而形成「读-验证-改」的操作闭环,而非基于记忆中可能过时的内容直接进行盲改。这是Claude Code等成熟工具的标准做法,能有效避免误改。
这一设计背后还有一层更微妙的考量:它实际上是对**乐观并发控制(Optimistic Concurrency Control)**思想的工具化体现。在多用户数据库系统中,乐观并发控制要求每次写操作必须携带「读取时的版本戳」,若写入时版本已变更则拒绝操作——edit_file的「精确匹配old_string才执行替换」机制在单Agent场景下实现了同等的语义保障:Agent必须先「观察」文件的当前真实状态,才能基于该状态发出有意义的修改指令,这从工具接口层面消除了「基于陈旧上下文盲目写入」这一编程Agent最常见的错误模式之一。
命令执行与文件检索
- execute_command:在会话根目录运行shell命令,返回合并的stdout/stderr及退出码,带超时保护(最长600秒,超时会终止整个进程树);
- list_files:按glob模式列出文件,自动跳过隐藏目录、
node_modules、__pycache__及.gitignore覆盖的内容,最多返回200个路径; - search_files:用正则表达式搜索文件内容,返回
path:line:content格式的匹配结果。
execute_command 的超时机制值得单独展开。600秒的上限并非随意设定——它覆盖了大多数合理的编译、测试和依赖安装场景(如一个中型Rust项目的全量编译通常在5-8分钟以内),同时通过「终止整个进程树」而非仅终止父进程来防止僵尸子进程积累。这一「进程树级清理」策略对应Linux中kill(-pgid, SIGKILL)的语义,确保Agent不会因为失控的子进程(如意外触发的无限循环脚本)而耗尽宿主机资源。list_files对.gitignore规则的尊重同样是经过深思的设计选择——它使得Agent的文件视图与开发者的心智模型保持一致,避免Agent将构建产物、缓存文件误判为源代码而徒增上下文噪声。
这套工具集覆盖了读、写、改、跑、找五大操作,在安全性(超时保护、忽略规则、路径限定)上的设计也相当周到,基本复刻了主流编程Agent的能力边界。
意外之喜:一个没被要求的Python API
除了命令行工具,AI还主动实现了一个基于类的Python API,而这并不在Simon的提示词要求之内:
CodingAgent(model="gpt-5.5", root="/path", approve=True).run("Fix the failing test in tests/test_parser.py")
Simon对此表示「惊喜」。这个细节揭示了当前大模型的一个深层机制:模型在训练过程中接触了GitHub上数以百万计的开源库,这些库中「命令行工具配套Python API」的设计模式极为普遍(如Click库几乎总是同时暴露CLI和API)。模型通过这些数据习得了「一个完整的Python工具库应该提供什么接口」的隐式规范,并在生成新项目时自然地「复现」这一模式。研究者有时将此称为「训练数据的幽灵」(Ghost of Training Data)——模型并非在「理解需求后推理出最优设计」,而是在「识别出当前任务与训练数据中某类模式的相似性后进行泛化」。这一机制既能带来超预期的「最佳实践自动实现」,也可能在某些场景下引入不适合当前项目背景的设计假设——例如,一个面向特定内部工具的脚本可能并不需要完整的面向对象API,但模型会因为「训练数据中类似规模的项目通常提供此类接口」而自动添加。这种「超预期交付」既是能力的体现,也提醒开发者需要对AI的自主发挥保持审阅——并非所有「额外功能」都是预期的,审阅和筛选仍是不可替代的人工环节。
从更深的架构视角看,这个自动生成的CodingAgent类所呈现的接口设计——构造器注入依赖(model、root)、运行时传入任务(.run())、审批模式作为可选标志(approve)——实际上是**依赖注入(Dependency Injection)与命令模式(Command Pattern)**的标准组合,这两种设计模式在面向对象的Python生态中极为普遍,恰好也是AI工具库(如LangChain的Chain抽象、LlamaIndex的Pipeline)的主流设计范式。模型生成这一接口并非「发明」了什么,而是在识别到「这是一个可配置的自动化执行器」语义后,从训练数据中提取了该语义最对应的标准实现模板。这再次印证了大模型在代码生成中的本质:语义到实现模式的高维映射,而非符号逻辑推理。
生成的README还列出了实用调用配方,例如 llm code --yolo(关闭审批)、llm code --allow "pytest*" --allow "git diff*"(按模式白名单授权命令)。这种细粒度的权限控制——类似于Linux的sudo白名单机制——是编程Agent走向实际可用的关键设计,它在「自动化效率」与「人工监督」之间提供了可调节的权衡点。
实测:让GPT-5.5用SwiftUI画ASCII时钟
Simon通过一个略带刁难意味的任务测试了这个Agent。他运行 llm code --yolo 后提出需求:在 /tmp/demo 目录下用SwiftUI创建一个显示ASCII艺术时间的CLI应用。
有趣的是,GPT-5.5在推理过程中直接指出「SwiftUI并不适合做真正的CLI」——这一判断在技术上准确:SwiftUI是苹果为GUI应用设计的声明式框架,其渲染管线依赖AppKit/UIKit的视图层级体系,整个框架的设计假设是「向屏幕渲染像素」而非「向标准流输出文本」。将SwiftUI用于CLI输出,类似于用React渲染器输出纯文本——在架构上存在根本性的错配。更技术性地说,SwiftUI的视图更新机制基于@State和@ObservableObject等响应式数据流,其生命周期与main()函数的线性执行模型存在根本冲突;而CLI应用所依赖的标准输入输出流(stdin/stdout/stderr)在SwiftUI的运行时模型中根本没有对应的抽象。
这一架构错配的根源可以追溯到更底层:SwiftUI的渲染本质上依赖**CALayer(Core Animation Layer)**合成器,整个视图树的最终输出是提交给Metal图形管线的渲染指令,而非字符流。这与CLI工具所依赖的POSIX文件描述符模型(stdout本质上是fd=1的文件写入操作)处于完全不同的抽象层次——前者的「输出」是GPU显存中的帧缓冲区,后者是内核管理的字节流管道。正因如此,任何试图用SwiftUI「打印到终端」的方案都需要绕过框架的核心抽象,本质上是把SwiftUI当作项目组织工具使用而非渲染引擎,这正是模型最终可能采用的变通思路:借用Swift Package Manager的项目结构和Foundation框架的标准库,将「SwiftUI」的参与降级为依赖声明层面的符号存在。
模型随后仍然构建出了可运行的应用,可能采用了混合方案(如用Foundation框架处理核心逻辑与stdout输出,仅在项目结构层面引用SwiftUI的组织范式)。执行 swift run AsciiTime 后,程序成功输出了ASCII艺术风格的当前时间。这个案例既展示了编程Agent端到端完成任务的实际能力,也体现了模型在需求存在技术矛盾时,能给出判断并寻找变通方案——这正是区分「代码补全工具」与「真正Agent」的关键能力之一:不盲目执行,而是在执行前完成需求的可行性分析。
快速上手
项目已作为预发布版(Simon幽默地称之为「slop-alpha」)推送至PyPI,可直接运行:
uvx --prerelease=allow --with llm-coding-agent llm code
这里使用的 uvx 是Astral团队开发的uv工具链中的命令,代表了Python包管理生态的一次重要范式转移。长期以来,Python生态因pip的速度瓶颈和依赖解析算法局限性而饱受诟病——pip的依赖解析基于回溯算法,在复杂依赖图中可能出现指数级的解析时间,而其串行下载模式也使得大型项目的安装体验与现代包管理器相去甚远。uv由开发了Python linter Ruff的Astral团队用Rust重写了整个包管理栈,利用Rust的并发模型和更激进的缓存策略(包括内容寻址的全局包缓存和写时复制的虚拟环境),实现了比pip快10-100倍的安装速度。uvx作为其「临时执行」子命令,直接对标Node.js生态的npx:允许在隔离环境中直接执行PyPI包中的命令行工具,而无需手动创建虚拟环境或担心全局环境污染。
这一「临时执行」模式的技术实现值得展开:uvx在底层会为目标包创建一个隔离的虚拟环境,但通过**内容寻址缓存(Content-Addressable Cache)**机制,相同版本的包在全局缓存中只存储一份,不同虚拟环境通过硬链接(hardlink)或写时复制(CoW,在支持该特性的文件系统如btrfs/APFS上)共享物理存储。这意味着uvx的「隔离」几乎是零成本的——首次安装需要下载,但后续每次执行的环境创建时间通常在毫秒级。对比conda的环境复制(完整复制所有文件)或Docker的层级镜像(需要容器运行时),uvx的轻量隔离策略使得「为每个工具创建独立环境」从工程上变得完全可行,这对AI工具生态的快速迭代尤为重要——用户无需关心环境管理,即可安全试用llm-coding-agent这类早期实验性项目。--prerelease=allow标志对应Simon所说的「slop-alpha」预发布状态,反映了开源项目快速迭代、公开试验的文化。
结语:编程Agent「民主化」的清晰信号
Simon Willison的这次实验虽然只是0.1a0的早期版本,却传递出一个清晰信号:构建可用编程Agent的门槛正在快速下降。当底层LLM库提供了成熟的工具调用抽象,配合规范的TDD流程与精心设计的工具集,一位个人开发者用两条提示词、几个小时,就能自举出一个类Claude Code的编程助手。
更值得玩味的是「AI构建AI工具」的递归模式——它既展示了当前大模型的工程能力边界,也为我们思考未来软件开发形态提供了鲜活样本。从更宏观的视角看,这种「个人开发者用AI复现大厂产品核心功能」的趋势,与历史上开源运动复现商业软件的轨迹高度相似:技术平权正在发生,而这次的加速器是大模型本身。这一平行值得深思——开源运动的成功依赖于「代码可被任何人审阅、修改和再分发」的制度保障,而AI驱动的技术平权则依赖于「能力可通过提示词被任何人调用」的接口民主化。两者的共同底层逻辑是:当创造力的门槛足够低,创新的主体就会从少数精英机构扩散到广大个体开发者,而这种扩散历史上从未逆转。对于想深入理解编程Agent内部机制的开发者来说,这个开源项目的spec、commit历史与工具实现,都是极佳的学习材料。
核心要点
核心要点
相关推荐

AI支出鸿沟:1%企业重仓投入,多数公司仍在花"午餐钱"
基于Ramp AI Index数据,头部1%企业将AI视为刚性运营支出大举投入,而中位数企业的AI支出仅相当于午餐钱。本文解析企业AI支出分化的原因、背后的能力鸿沟,以及对中小企业的实操启示。

四足机器人Sim-to-Real Gap:仿真与现实差距的原因及解决方案
深入分析四足机器人Sim-to-Real Gap问题,从物理参数失配、传感器噪声到执行器动态,解析仿真到现实的鸿沟成因,并介绍域随机化、系统辨识等缩小差距的实用方法。

组队打Kaggle:如何寻找靠谱的竞赛队友
想在Kaggle竞赛中取得好成绩?本文详解组队打Kaggle的优势、寻找靠谱队友的渠道与方法、判断队友是否合适的标准,以及团队高效协作的关键要点,助你组建冲击奖牌的强力团队。