近段时间,著名 AI 科学家 Andrej Karpathy 提出的氛围编程(vibe coding) 是 AI 领域的一大热门话题。简单来说,氛围编程就是鼓励开发者忘掉代码,进入开发的氛围之中。更简单地讲,就是向 LLM 提出需求,然后「全部接受」即可。
当然,这种使用 LLM 编程的方法过于简单粗暴,或许并不适合用来开发一些更加复杂和精细的项目。近日,探索和发表数据的开源工具 Datasette 的创造者、Web 应用框架 Django 创造者之一、社交会议目录 Lanyrd 的联合创始人、著名编程技术博主 Simon Willison 分享了自己使用 LLM 辅助编程的体验。文中,他分享了不少自己积累的有用实践和策略。机器之心编译了这篇博客文章,希望能给开发者读者们带来一些帮助。
Simon Willison
原文地址:simonwillison.net/2025/Mar/11...
网络上,很多开发者在讨论使用大型语言模型辅助编程,而其中不少开发者都表示挺失望的。他们经常会问自己哪里做错了 ------ 为什么有些人报告的结果如此出色,而他们自己的实验却乏善可陈?
使用 LLM 编写代码既困难又不直观。需要付出巨大的努力才能弄清楚使用它们的利弊,而且几乎没有什么指导可以帮助人们学会如何最好地使用它们。
如果有人告诉你使用 LLM 编码很容易,那么他们(可能无意中)误导了你。他们很可能偶然发现了有效的模式,但这些模式并不是每个人都能自然而然掌握的。
两年多来,LLM 代码在辅助我写代码方面表现很好。下面我将尽力将一些经验和直觉传授给你。
目录:
-
设定合理的期望
-
考虑训练截止日期
-
上下文为王
-
让它们提供选择
-
告诉它们确切要做什么
-
你必须测试它写的内容!
-
记住这只是一次对话
-
使用可以为你运行代码的工具
-
氛围编程是一种很好的学习方式
-
使用 Claude Code 的一个详细示例
-
做好让人类接管的准备
-
最大的优势是开发速度
-
使用 LLM 是对已有专业知识的放大
-
奖励:回答有关代码库的问题
设定合理的期望
不要去管围绕「AGI」的炒作 ------LLM 仍旧只是花哨的自动补全。它们所做的只是预测 token 序列 ------ 但事实证明,写代码的主要工作就是以正确的顺序将 token 串联在一起,因此只要你给它们指示正确的方向,它们就会非常有用。
如果你认为这项技术将完美地实现你的项目,而你不需要锻炼任何技能,那你就等着失望吧。
相反,你应该使用 LLM 来增强你的能力。我现在最喜欢的心理模式是将它们想象成一个过度自信的结对编程助理 ------ 它们查找东西的速度快如闪电,可以随时提供相关示例,并且可以毫无怨言地执行繁琐的任务。
过度自信很重要。它们绝对会犯错误 ------ 有时很细微,有时是大错。这些错误可能与人类错误非常不一样 ------ 如果一个人类合作者想象出了一个根本不存在的库,你会马上就不再相信他了。不要把 LLM 当人类一样看,这是一个陷阱。AI 犯的错误并不能否定它的有用性。
当与 LLM 一起工作时,你经常会发现它们无法做到的一些事情。记下这些事情 ------ 它们是有用的教训!它们也是为未来储备的宝贵例子 ------ 一个强大的新模型的标志是:它能解决以前的模型无法处理的任务。
考虑训练截止日期
任何模型都有一个关键特征:训练截止日期。也就是停止收集训练数据的日期。对于 OpenAI 的模型,这通常是 2023 年 10 月。Anthropic 和 Gemini 以及其它提供商可能有更近期的日期。
这对于编程来说非常重要,因为这个时间会影响它们知道的库。如果你使用的库自 2023 年 10 月以来发生了重大变化,OpenAI 模型将不会知道!
我从 LLM 中获得了足够的价值,现在我在选择库时会特意考虑这一点 ------ 我会尽力坚持使用具有良好稳定性且足够流行的库,这样一来,这些库的许多示例都会在其训练数据中。我喜欢应用无聊技术(boring technology)的原则 ------ 在项目的独特卖点上进行创新,在其它所有方面坚持使用经过尝试和测试的解决方案。
LLM 仍然可以帮助你处理训练数据之外的库,但你需要付出更多努力 ------ 你需要在提示词中向它们提供最新示例,说明这些库应如何使用。
这就引出了使用 LLM 时最重要的事情:
上下文为王
要想让 LLM 给出优良的结果,很大一部分工作在于都可归结为管理上下文,即当前对话的一部分文本。
此上下文不仅仅是提供给 LLM 的提示词:成功的 LLM 交互通常采用对话的形式,而其上下文包括当前对话线程中存在的来自你的每条消息和来自 LLM 的每条回复。
当你开始新的对话时,你会将该上下文重置为零。了解这一点很重要,因为对于不再有用的对话,通常的解决方法是将一切清除干净并重新开始。
一些 LLM 编程工具不仅仅适用于对话。例如,Claude Projects 允许你预先填充大量文本,包括最近的直接从 GitHub 库导入代码的功能 ------ 我经常使用这个功能。
Cursor 和 VS Code Copilot 等工具会自动包含当前编辑器会话和文件布局中的上下文,有时你可以使用 Cursor 的 @commands 等机制来提取其它文件或文档。
我主要直接使用 ChatGPT 和 Claude 网页版或应用界面的原因之一是:它能让我更容易准确地了解上下文中的内容。那些向我隐瞒上下文的 LLM 工具的效果更差。
另一个事实也可为你所用:之前的回复也是上下文的一部分。对于复杂的编程任务,可以先尝试让 LLM 编写一个更简单的版本,检查它是否有效,然后再迭代构建更复杂的实现。
我在开始新聊天时,经常会把现有代码放入到上下文中,然后再与 LLM 一起合作以某种方式来修改它。
我最喜欢的代码提示词技术之一是放入几个与我想要构建的东西相关的完整示例,然后提示 LLM 将它们用作新项目的灵感。当我在构建我的 JavaScript OCR 应用时,我详细地写了:该应用应结合 Tesseract.js 和 PDF.js------ 这两个库我过去曾使用过,我可以在提示词中提供有效示例。
让它们提供选择
我的大多数项目都是从一些开放式问题开始的:我想做的事情是否可行?我可以用哪些潜在方式来实现它?哪些选项是最好的?
我会将 LLM 用作初始研究阶段的一部分。
我会使用这样的提示:「Rust 中有哪些 HTTP 库可选?包括使用示例」或者「JavaScript 中有哪些有用的拖放库?为我构建一个展示每个库的工件」(对 Claude)。
训练截止时间在这里很重要,因为这意味着 LLM 不会建议使用较新的库。通常这样没问题 ------ 我不想要最新的,我想要最稳定的,以及存在时间足够长的,可以解决错误的东西。
如果我要使用更新的东西,我会在 LLM 世界之外自己做研究。
开始任何项目的最佳方式是使用原型来证明该项目的关键要求可以得到满足。我经常发现,LLM 可以在我坐下来使用笔记本电脑的几分钟内就让我获得那个可行的原型 ------ 有时甚至在我使用手机工作时也可以。
告诉它们确切要做什么
一旦我完成了初步研究,我就会大幅改变模式。对于生产级代码,我对 LLM 的使用更加专制:我会把它当作数字实习生,工作内容是根据我的详细指示来写代码。
这里有个最近的例子:
我可以自己写这个函数,但我需要花上十五分钟来查找所有细节,才能使代码正常工作。Claude 在 15 秒内就搞定了。
我发现,对于我在这里使用的函数签名,LLM 的响应非常好。我的工作是作为函数设计者,LLM 负责根据我的规范构建主体。
我经常还会提出跟进要求,比如「现在用 pytest 给我写测试」。再次强调,选择哪种技术完全由我决定 ------ 我希望 LLM 能帮我省去录入已经存在于我脑子里的代码的时间。
如果你对此的反应是「输入代码肯定比输入英文指令要快」,我只能告诉你,对我来说已经不是这样了。代码必须正确。而英语中存在大量捷径、奇思妙想和拼写错误,比如如果你记不住名字,你可以说「使用那个流行的 HTTP 库」。
优秀的编程 LLM 非常擅长填补空白。它们也远没有我那么懒 ------ 它们总是会记得捕捉可能的异常,添加准确的文档字符串,并用相关的类型标注代码。
你必须测试它写的内容!
有一件事是你绝对不能外包给机器的,那就是测试代码是否真的有效。
作为软件开发者,你的责任是提供能有效工作的系统。如果你还没有看到它运行,那么它就不是一个有效的系统。你需要亲手试试看。
这件事做起来可能没多少趣味,但它一直是交付良好代码的关键部分 ------ 无论有没有 LLM 参与其中。
记住这只是一次对话
如果我不喜欢 LLM 写的东西,可以马上让它重构,而它绝对不会抱怨!「将重复的代码分解成一个函数」、「使用字符串操作方法而不是正则表达式」,甚至「写更好一点!」
LLM 第一次编写的代码很少是最终的实现,但它们可以为你重新输入几十次,而不会感到沮丧或无聊。
偶尔我会从我的第一个提示中获得很好的结果 ------ 我练习得越多,结果就越频繁 ------ 但我一直做好了需要后续跟进的准备。
我一直很好奇,这是否是人们错过的关键技巧之一 ------ 糟糕的初始结果并不是失败,而是一个起点,基于此才能将模型推向你真正想要的方向。
使用可以为你运行代码的工具
现在越来越多的 LLM 编程工具能够为你运行代码。我对其中一些略微谨慎,因为错误的命令可能会造成真实的损害,所以我倾向于坚持使用在安全沙箱中运行代码的工具。我现在最喜欢的包括:
-
ChatGPT Code Interpreter,ChatGPT 可以直接在 OpenAI 管理的 Kubernetes 沙箱 VM 中编写并执行 Python 代码。这是完全安全的 ------ 它甚至无法建立出站网络连接,因此实际上可能发生的一切就是临时文件系统被破坏然后重置。
-
Claude Artifacts,Claude 可以为你构建一个完整的 HTML+JavaScript+CSS Web 应用,该应用显示在 Claude 界面中。这个 Web 应用显示在一个非常封闭的 iframe 沙箱中 ------ 这虽然极大地限制了它可以做的事情,但可以防止意外泄露你的私人 Claude 数据等问题。
-
ChatGPT Canvas 是一个较新的 ChatGPT 功能,具有与 Claude Artifacts 类似的功能。我自己还没有对此进行足够的探索。
如果你愿意更冒险一点:
-
Cursor 有一个「Agent」功能可以做到这一点,Windsurf 和越来越多的其它编辑器也是如此。我还没有花足够的时间研究这些,所以我不能提供相关推荐。
-
Aider 是这些模式的领先开源实现,是 dogfooding 的一个很好的例子 ------Aider 的最新版本有 80% 以上是由 Aider 自己编写的。aider.chat/
-
Claude Code 是 Anthropic 进入这个领域的新成员。我将在稍后详细介绍如何使用该工具。
这种在交互流程中运行代码的模式非常强大。我在选择核心 LLM 编程工具时,主要就是看它们是否可以安全地运行和迭代我的代码。
氛围编程是一种很好的学习方式
Andrej Karpathy 一个多月前创造了氛围编程(vibe coding)一词:
Andrej 认为这「对于一次性的周末项目来说还不错」。这也是探索这些模型功能的绝佳方式 ------ 而且真的很有趣。
学习 LLM 的最佳方式就是玩它们。向它们抛出荒谬的想法,然后进行氛围编程,直到它们基本可以正常工作,这是一种真正有用的方法,可以让你更快地对什么有效、什么无效形成直觉认识
早在 Andrej 给它命名之前,我就已经开始进行氛围编程了!我的 simonw/tools GitHub 存储库有 77 个 HTML+JavaScript 应用和 6 个 Python 应用,每个应用都是通过提示 LLM 构建的。我从构建这个集合中学到了很多东西,我以每周几个新原型的速度向其中添加内容:github.com/simonw/tool...
你可以在 tools.simonwillison.net 上直接试用我的大部分工具 ------ 这是这个库的 GitHub Pages 发布版本。
使用 Claude Code 的一个详细示例
在撰写本文时,我想到了 tools.simonwillison.net/colophon 页面 ------ 我想要一个可以链接到的东西,以比 GitHub 更明显的方式显示我的每个工具的提交历史记录。
我决定利用这个机会展示我的 AI 辅助编程过程。
对于这个任务,我使用了 Claude Code,因为我希望它能够直接根据我笔记本电脑上现有的工具库运行 Python 代码。
在我的会话结束时运行 /cost 命令,会向我显示以下内容:
其最初的项目从开始到结束只花了我 17 分钟多一点的时间,并且在调用 Anthropic 的 API 上仅花费了 61 美分。
在这个过程中,我会确切地告诉模型我想要构建什么。下面是我的提示词序列,完整记录在这里:gist.github.com/simonw/323e...
首先,我要求 LLM 编写一个初始脚本来收集新页面所需的数据:
我真的没有认真考虑过上面的首个提示词 ------ 它更像是我在思考初始问题时输入到机器人中的意识流。
我检查了初始结果并发现了一些问题:
然后我改变了主意 ------ 我也想要那些完整的提交消息:
像这样提供示例是获得你想要的确切内容的绝佳捷径。
请注意,我从未查看过在 gather_links.py 中编写的代码!这是纯粹的氛围编程:我正在查看它在做什么,但我把实现细节完全留给了 LLM。
这个 JSON 看起来还不错,所以我指示 LLM:
Claude 知道 GitHub URL 的工作方式,因此告诉它提交的链接并提供库名称就足够了,它猜测这些提交 URL 是 github.com/simonw/tool...
我经常发现 Claude 在网页设计方面有很好的默认品味 ------ 我只是说了「页面应该适合移动设备」,然后剩下的就交给它了。
Claude 不停地为我构建不正确的页面,所以我发出指令:
然后它自己修复了所有错误,只剩下我决定要做的两处更改:
整个项目就完成了!结果文件为 build_colophon.py,它生成的页面看起来相当不错:
还有一项任务:我需要将新的 colophon 作为我网站的一部分进行部署,但我不想将新的 colophon.html 页面签入库本身。我想要一个自定义的 GitHub Pages 构建过程。
我启动了一个全新的 Claude Code 会话,看看 Claude 是否也能做好这件事:
与第一次不同,这次我非常仔细地观察了它在做什么 ------ 我不知道如何以这种方式自定义 GitHub Pages 构建,我想学习如何做到这一点,并保持谨慎,因为它可能会产生幻觉并导致任务失败。
它告诉我它想要创建这个文件并请求我的许可:
我想这是正确的?我很喜欢这个注释「Need full history for git log in gather_links.py」------ 这是我很容易忽视的。
然后它说它想将这些文件添加到 .gitignore------ 听起来是个好主意。
它输出了对以上操作的总结:
我查看了它的成本:
因此,使用 Claude API 花费了 17 美分和 45 秒。(我分心了,因此总共花费了 10 分钟。)这里是完整记录:gist.github.com/simonw/a560...
这些代码看起来不会不可逆地破坏任何东西,因此我将其 push 到了 GitHub,然后看看会发生什么。
...... 成功了!我的 colophon 页面已上线。
有一个问题。我在 GitHub Actions 运行时观察了它,发现有些不对劲:
我原本期待的是「Test」job,但为什么有两个不同的部署?
我有一种预感,之前的默认 Jekyll 部署仍在运行,而新的部署同时运行 ------ 新脚本完成得晚并覆盖了原始脚本的结果,这纯粹是时间上的运气。
是时候暂时放下 LLM 并阅读一些文档了!
我在 GitHub Pages 的使用自定义工作流中找到了这个页面,但它没有告诉我我需要知道的内容。
出于另一个直觉,我检查了我的库的 GitHub Pages 设置界面,发现了这个选项:
我的仓库设置为「从分支部署」,所以我将其切换到「GitHub Actions」。
我手动更新了我的 README.md,以在此提交中添加指向新 Colophon 页面的链接,而这又触发了另一次 build。
这次只运行了两个 job,最终结果是正确部署的站点:
(我后来发现了另一个 bug------ 一些链接无意中在其 href= 中包含了
标签,我用另一个 11 美分的 Claude Code 会话修复了这个问题。)
之后,我还通过新增 AI 生成描述工具进一步改进了这个 colophon:simonwillison.net/2025/Mar/13...
做好让人类接管的准备
在这个例子中,我很幸运,因为它有助于说明我的最后一点:要做好接管的准备。
LLM 无法取代人类的直觉和经验。我在 GitHub Actions 上花了足够多的时间,我知道要寻找什么样的东西。在这种情况下,比起继续尝试通过提示词完成这个任务,我介入并完成项目的速度更快。
最大的优势是开发速度
我的新 colophon 页面从构思到完成、部署功能只花了我不到半个小时的时间。
我确信如果没有 LLM 的帮助,我会花更长的时间 ------ 以至于我可能根本不会费心去构建它。
也因此,我非常关心我从 LLM 中获得的生产力提升:它不是为了更快地完成工作,而是为了能够交付我根本不想花时间的项目。
我在 2023 年 3 月写过一篇文章谈到,AI 增强的开发让我对我的项目更加雄心勃勃。两年后,这种影响没有消退的迹象。
这也是加速学习新事物的好方法 ------ 今天我讲的是如何使用 Actions 自定义我的 GitHub Pages 构建,这是我将来肯定会再次使用的东西。
事实上,LLM 能让我更快地执行我的想法,这意味着我可以实现更多的想法,这意味着我可以学到更多。
使用 LLM 是对已有专业知识的放大
其他人也能以同样的方式做到这一点吗?可能不会!我在这里的提示词依赖于 25 年以上的专业编程经验,包括我之前对 GitHub Actions、GitHub Pages、GitHub 本身以及我使用的 LLM 工具的探索。
我也知道这会起作用。我花了足够多的时间使用这些工具,我相信使用从我的 Git 历史记录中提取的信息组装一个新的 HTML 页面完全在优秀 LLM 的能力范围内。
我的提示词反映了这一点 ------ 这里没有什么特别新颖的东西,所以我口述了设计,在它工作时测试了结果,偶尔推动它修复错误。
如果我试图构建一个 Linux 内核驱动程序 ------ 一个我几乎一无所知的领域 ------ 我的过程将完全不同。
奖励:回答有关代码库的问题
如果你觉得使用 LLM 为你写代码仍旧没有吸引力,那么还有另一个用例可能会让你觉得更有吸引力。
优秀的 LLM 非常擅长回答有关代码的问题。
这也是非常低风险的:最糟糕的情况是它们可能会出错。因此,你可能需要花一点时间才能弄清楚。与完全自己挖掘数千行代码相比,这仍然可能会节省你的时间。
这里的技巧是将代码提交给长上下文模型中并开始提问。我目前最喜欢的模型是 gemini-2.0-pro-exp-02-05,这是 Google Gemini 2.0 Pro 的预览版,目前可通过其 API 免费使用。
我前几天就用过这个技巧。当时我正在尝试一种对我来说很新的工具 monolith,这是一个用 Rust 编写的 CLI 工具,它可以下载网页及其所有依赖资产(CSS、图像等)并将它们捆绑到一个存档文件中。
我很好奇它是如何工作的,所以我将它克隆到了我的临时目录中并运行了以下命令:
我在这里使用我自己的 files-to-prompt 工具(去年由 Claude 3 Opus 为我构建)将库中所有文件的内容收集到一个流中。然后我将其导入我的 LLM 工具并告诉它(通过 llm-gemini 插件)使用系统提示词「architectural perspective as markdown」来调用 Gemini 2.0 Pro。
这给了我一份详细的文档,描述了该工具的工作原理 ------ 哪些源文件做什么,以及最重要的是,它使用了哪些 Rust 包。我了解到它使用了 reqwest、html5ever、markup5ever_rcdom 和 cssparser,并且根本没有评估 JavaScript,这是一个重要的限制。
我每周都会使用这个技巧几次。这是开始深入研究新代码库的好方法。