Heidi Labs
KarpathyAI 权威大佬文章约 13 分钟5,045 字

用氛围编程打造 MenuGen

我经常在餐厅坐下,翻看菜单,然后感到……有点懵。Pâté 又是什么?Tagine 是什么?Cavatappi……那是意面,对吧?Sweetbread 听起来很好吃(我特别爱吃甜食)。有时候真的会完全失控。“Confit tubers folded with matured curd and finished with a beurre noisette infusion。”好吧,所以……这到底是什么?我这辈子花了太多时间在谷歌上搜食物的图片,所以当最近要参加一个氛围编程(vibe coding)黑客松时,我知道这是终于做出那个我一直想要、却哪里都找不到的应用的绝佳机会。它来了,我把它叫做……🥁🥁🥁……MenuGen:

Screenshot 2025-04-26 at 1

MenuGen 非常简单。你拍一张菜单的照片,它就会为所有菜单项生成图片。它把菜单可视化。显然,它不会和那家特定餐厅实际端给你的菜一模一样,但能给你一个基本概念:这些菜里有些是沙拉,这是鱼,这是汤,等等。我个人使用时觉得它太有用了,所以在黑客松之后(我在 localhost 上做出了第一个可用版本)又继续氛围编程了一阵,把它部署上线,加上身份验证、支付,基本把它做成了一个真正的产品。所以它就在这里,下次外出就餐时试试吧 :): menugen.app!

MenuGen 是我第一个端到端氛围编程的应用。我(一个喜欢折腾、但几乎没有实际 Web 开发经验的人)从零开始,一路做到一个真正可用的产品:人们可以注册、付费、从中获得实用价值,而我也能赚取诚实可观的 10% 加价。这挺酷的。但除了应用的实用价值,MenuGen 对我也很有意思,因为它是对氛围编程应用以及今天这件事有多可行的一次探索。因此,我没有直接写任何代码;100% 的代码都是由 Cursor+Claude 写的,我基本上并不真正了解 MenuGen 在我习惯的传统意义上如何运作。所以现在项目“完成”了(意思是第一个版本似乎能跑),我想写这篇短文,记录我的体验——对于一个非 Web 开发者来说,今天氛围编程一个 Web 应用是什么样子。

首先,本地版本。 这是氛围编程中相对常见的体验,应用在本机运行的第一个原型只花了很少时间。我用了 Cursor + Claude 3.7,给了它应用描述,它很快就写好了所有 React 前端组件,排出一个漂亮的网页,有流畅的彩色字体、小小的 CSS 动画、响应式设计等等,唯独没有真正的后端功能。看到一个全新的网站这么快成形,是一种强烈的钩子。我感觉自己完成了 80%,但(伏笔……)其实更接近 20%。

OpenAI API。 大概从这里开始,麻烦来了。我需要调用 OpenAI API,从图片中对菜单项做 OCR。我得拿到 OpenAI API 密钥。我得在略显复杂的菜单里周旋,它会问我关于“项目”和详细权限的问题。Claude 一直幻觉出已弃用的 API、模型名称以及最近全都变了的输入/输出约定,这很令人困惑,但在我来回粘贴文档一段时间后,它把这些都解决了。单个 API 调用跑通后,我立刻遇到了严重的 API 速率限制,每 10 分钟只能发出几个查询。

Replicate API。 接下来,我需要根据描述生成图片。我注册了一个新的 Replicate API 密钥,很快又遇到了类似的问题。我的查询不工作,因为 LLM 的知识已经过时;此外,这一次连官方文档也因为 API 的近期变化而有点过时——现在它不再直接返回 JSON,而是返回某种我和 Claude 都不理解的 Streaming 对象。然后我又遇到了 API 速率限制,导致调试应用很困难。后来有人告诉我,这些是这类服务用来缓解欺诈的常见保护措施,但它们也让新的合法账户更难上手。我听说 Replicate 正在转向另一种方式,你需要预先购买额度,这或许对以后有帮助。

Vercel 部署。 至少到这时,应用在本地已经能跑,所以我挺开心。是时候部署基础的第一版了。注册 Vercel、添加项目、配置、指向我的 GitHub 仓库、推送到 master、看着新的 Deployment 构建……然后报错。日志显示了一些 lint 错误,原因是未使用的变量之类的常见问题,但很难理解或调试,因为一切在本地都正常,只在 Vercel 构建时出错,所以我通过向 master 推送假的调试提交来强制重新部署,从而调试问题。修好这些问题后,网站仍然拒绝工作。我问 Claude。我问 ChatGPT。我查阅文档。我在谷歌上到处搜。1 小时后,我终于意识到自己犯的愚蠢错误——我的 .env.local 文件保存了 OpenAI 和 Replicate 的 API 密钥,但这个文件(正确地!)属于 .gitignore 的一部分,不会被推送到 git,因此你必须手动进入 Vercel 项目设置,找到正确的位置,手动添加环境密钥。我算是相对快地理解了问题,但我能想象一个有抱负的氛围编程者会在这里卡上一阵。部署终于成功后,Vercel 高兴地提供了一个 URL。这又让我吃了一惊,因为我的项目是一个私有 git 仓库,还没准备好见光。我没想到 Vercel 会把你那个未完成项目的 !私有! 仓库,就这样自动部署到一个完全公开、容易猜到的 URL 上,哈哈。

Clerk 身份验证。 Claude 建议我们使用 Clerk 做身份验证,我就照做了。注册 Clerk、配置项目、获取 API 密钥。这时 Claude 幻觉出了大约 1000 行看起来是已弃用 Clerk API 的代码。我不得不来回粘贴大量文档,才逐渐让事情走通。接下来,到目前为止,Clerk 一直运行在“Development”部署中。要转到“Production”部署,还有更多关卡要过。Clerk 要求你把应用托管在自己拥有的自定义域名上。menugen.vercel.com 不行。所以我不得不购买域名 menugen.app。然后我得把域名接入我的 Vercel 项目。然后我得修改 DNS 记录。然后我得选择一个 OAuth 提供商,比如我选了 Google。但为此又是一场配置冒险。我得启用一个“SSO 连接”。我得转到 Google Cloud Console,创建一个新项目,并添加一个新的 OAuth 凭据。我还得在这里等待一段审批流程。然后我得在 Vercel、Clerk 和 Google 所有嵌套设置之间来回折腾一阵,才能正确接通。大概在这里我想过放弃这个项目,但第二天早上醒来时感觉好多了。

Stripe 支付。 接下来我想加入支付,让人们可以购买额度。这意味着又一个网站、又一个账户、更多文档、更多密钥。我把后端选为“Next.js”,把“入门”文档里的第一段代码复制粘贴到我的应用里……报错。后来我才意识到,当你选择 Next.js 时,Stripe 给你的是 JavaScript 代码,而我的应用是用 TypeScript 构建的,所以每次粘贴一段代码,Cursor 都会因为 linter 错误而不高兴,但在我让 Claude“修复错误”几次,并威胁要换用 ChatGPT 之后,Claude 随着时间推移把东西补好了。然后回到 Stripe 仪表板,我们创建一个 Product,创建一个 Price,找到 price 密钥(不是 product 密钥!),把所有这些密钥复制粘贴到各处。大约在这里,我发现 Claude 用了一个非常糟糕的方法,把成功的 Stripe 支付与用户额度对应起来(它试图匹配电子邮件地址,但用户在 Stripe 结账时可能提供的邮箱,未必是他们注册时使用的 Google 账户邮箱,因此用户可能实际上拿不到购买的额度)。我向 Claude 指出这一点,它立刻道歉,并通过在请求元数据中传递唯一用户 ID 正确地重写了代码。它感谢我指出问题,并告诉我它将来会正确处理,我知道这只是煤气灯效应(gaslighting)。但由于我们的快速测试能跑通,只需再点几下,就能把部署从 Development 升级到 Production,现在重新创建一个 Product、重新创建一个 Price、重新复制所有密钥和 ID,在本地和 Vercel 设置中各来一遍……然后就成功了 :)

数据库?工作队列? 到目前为止,所有处理都是“即时”完成的——只有请求和结果,当场返回,没有任何缓存、保存等。所以结果是短暂的,如果响应耗时太长(例如菜单太长、项目太多,或者 API 延迟太高),请求就可能超时并中断。如果你刷新页面,一切也会消失。正确的做法是有一个数据库,在其中登记并跟踪工作,客户端只需在状态就绪时显示最新状态。我意识到我得从 Marketplace 连接一个数据库,比如 Supabase PostgreSQL(尽管 Claude 向我推销使用 Vercel KV,而我知道它实际上已经弃用)。然后我们还需要一些队列服务,比如 Upstash 之类的,来运行实际的处理。这意味着更多服务、更多登录、更多 API 密钥、更多配置、更多文档、更多痛苦。这太让人受不了了。留作未来工作。

TLDR。 氛围编程 MenuGen 作为本地演示是一段令人兴奋、充满乐趣的冒险,但作为一个部署上线的真实应用,却是一场有点痛苦的苦旅。构建现代应用有点像组装宜家家具。有所有这些服务、文档、API 密钥、配置、开发/生产部署、团队和安全功能、速率限制、定价层级……与此同时,LLM 对一切的知识都略有陈旧,当你仔细观察时,它们会犯下微妙但关键的设计错误,有时还会在解决方案上产生幻觉或对你进行煤气灯操纵。但对我来说最有趣的部分是,我甚至没有在代码编辑器本身上花太多工夫。我大部分时间都花在浏览器里,在标签页和设置之间移动,配置并把一个怪物粘合起来。所有这些工作和状态甚至无法被 LLM 访问或操作——照这样下去,我们怎么可能在 2027 年实现社会自动化?

展望未来。 作为对当今在几乎没有 Web 开发背景的情况下氛围编程一个应用是什么感觉的探索,我心中剩下的,是同等分量的惊叹(这确实可能,而且比以往能做到的要容易、快速得多!)和一点对“本可以如何”的挫败感。当然,部分痛苦在于,这些基础设施没有一个是真正为这种使用方式设计的。其预期目标受众是生活在 LLM 之前世界的专业 Web 开发者团队,而不是独自原型开发应用的氛围编程者。以下是一些能让 MenuGen 这类超简单应用更容易创建的想法:

  • 某些应用开发平台可以自带全部“电池”。某种看起来与 Vercel Marketplace 相反的东西。某种有主见、具体、预配置好每个人都想要的全部基础能力的东西:域名、托管、身份验证、支付、数据库、服务器函数。如果某个服务能让这些变得简单并开箱即用,那可能会非常棒。
  • 所有这些服务都可以变得对 LLM 更友好。你告诉用户的一切基本上会立刻被复制粘贴给 LLM,所以你不妨直接和 LLM 对话。你的服务可以有一个 CLI 工具。后端可以用 curl 命令来配置。文档可以是 Markdown。所有这些对 LLM 来说,都是人体工学上友好得多的界面和抽象。不要和开发者说话。不要让开发者去访问、查看或点击。指导并赋能他们的 LLM。
  • 对于我的下一个应用,我正在考虑采用基础 HTML/CSS/JS + Python 后端(FastAPI + Fly.io 风格之类的?),比“现代 Web 开发”的 serverless 多重宇宙简单得多。像 MenuGen(或类似应用)这样的简单应用,在那个范式下可能会显著更容易。
  • 最后,MenuGen 很可能根本不应该是一个功能齐全的应用。“应用”只是调用一次 GPT 对菜单做 OCR,然后对结果做一个 for 循环,为每个项目生成图片并漂亮地呈现给用户。这几乎听起来就像一个简单的自定义 GPT(用 OpenAI 早先发布的原始 GPT“应用商店”术语来说)。MenuGen 能不能只是一个提示词?LLM 能不能不返回文本,而是返回一个简单网页来展示结果,类似 Artifacts 那样?许多其他应用是不是也可以长这样?我能不能把它作为应用发布到商店,并以同样方式赚取加价?

目前,我很高兴自己把第一个超级定制的应用氛围编程到了终点——它是真实的,解决了我长久以来的一个需求,而且可以和朋友们分享。感谢我用来构建它的所有上述服务。原则上,如果其他人也喜欢它,它可以完全被动地赚一些钱——这就是 @levelsio 的梦想。归根结底,今天氛围编程完整的 Web 应用还有点混乱,对于任何真正重要的事情来说都不是好主意。但其中已有明显的伟大迹象,我认为这个行业只需要一点时间来适应 LLM 的新世界。我个人非常兴奋地看到应用开发的门槛降到接近于零,任何人都能像制作一条 TikTok 一样轻松地构建和发布应用。这类超定制自动化,可能会成为人类创造力的一片美丽新画布。

配套推文(以及“评论区”)在我的 X @karpathy 上。