2025-07-12 08:00:03
原文: https://lucumr.pocoo.org/2025/7/3/tools/
作者: Armin Ronacher
译者: Gemini 2.5 Pro
如果你在 Twitter 上关注我,你就会知道我最近不太看好 MCP (模型上下文协议)。不是说我讨厌这个想法,只是我发现它并不像宣传的那样好用。在我看来,MCP 有两个主要缺陷:
做一个简单的实验就能说明问题:试着用 GitHub MCP 完成一个 GitHub 任务,然后用 gh
CLI 工具再做一遍。你几乎肯定会发现,后者对上下文的利用效率高得多,你也能更快地达到目的。
我想回应一下我收到的关于我这个立场的一些反馈。我在 Agentic Coding (智能体编程) 的背景下对 MCP 做了广泛的评估,因为在这里它的局限性最容易被观察到。一种反馈是,MCP 对于通用的代码生成可能意义不大,因为模型在这方面已经做得很好了,但它对于终端用户的应用却很有意义,比如,在一家金融公司自动化处理某个特定领域的任务。另一种反馈是,我需要着眼于未来,在未来,模型将能够使用更多的工具,处理更复杂的任务。
我目前的看法是,我的数据表明,现在的 MCP 永远都会比写代码更难用,主要是因为它依赖于推理。如果你看看现在为了支持更多工具而提出的方法,这些方案都包含一个过滤层。你把所有的工具都传给一个 LLM,让它根据当前的任务进行筛选。到目前为止,还没有人提出更好的方法。
我相信这一点很可能同样成立——即使对于非编程的、特定领域的任务,你也不应该使用当前形式的 MCP——主要原因在于,即便是在那些场景下,代码生成也依然是更好的选择,因为它具有可组合性。
思考这个问题的方式是,当你没有 AI 时,作为一名软件工程师,你解决问题的首选工具就是代码。对于非软件工程师来说,代码可能遥不可及。人们手动完成的许多任务,其实都可以通过软件自动化。挑战在于找到人来写这个软件。如果你在一个小众领域工作,自己又不是程序员,你可能不会拿起编程书去学写代码,也可能找不到一个愿意为你解决特定问题而提供定制软件的开发者。是的,也许你的任务需要一些推理,但许多人确实一直都需要这些定制软件。
我们常说“用一个 shell 脚本取代自己”是有原因的,因为这件事已经发生了很久。有了 LLM 和编程,想法就变成了用 LLM 来取代自己,而不是用 shell 脚本。但你会遇到三个问题:成本、速度和普适的可靠性。所有这些问题都是我们在考虑使用工具或 MCP 之前就需要处理的。我们需要想办法确保我们的自动化任务在规模化时能够正确工作。
自动化的关键在于自动化那些会反复发生的事情。你不会去自动化一个一次性的、永远不会重现的变更。你会开始自动化那些机器能真正给你带来生产力提升的事情,因为你只需要做一两次,弄清楚如何让它工作,然后让机器重复一千次。对于这种重复,有非常充分的理由支持始终使用代码。这是因为,如果我们指示机器用推理来完成它,它可能行得通,特别是对于小任务,但这需要验证,而验证花费的时间几乎和一开始自己动手做一样多。让 LLM 为你计算,某种程度上可行,但更好的办法是让 LLM 写 Python 代码来进行计算。为什么?首先,你可以审查的是公式,而不是计算结果。我们可以自己写,也可以用 LLM 作为裁判,来判断方法是否正确。我们基本不需要验证 Python 的计算是否正确,你可以信赖它。所以,通过选择代码生成来解决任务,我们离能够自己验证和确认过程更近了一步,而不是寄希望于 LLM 推理正确。
这显然远不止于计算。以这个博客为例。我最近把整个博客从 reStructuredText 转换成了 Markdown。我把这个转换任务拖了很久,部分原因是我有点懒。但也是因为,当我懒到考虑用 LLM 来做这件事时,我根本不相信它能在转换过程中不出任何问题。我担心如果它用尽了上下文,可能会开始胡编乱造文本,或者稍微改变措辞。就是说,我太担心那些细微的退化问题了。
我最终还是用了 LLM,但我让它用一种不同的方式来完成这个转换:通过代码。
然后我让这个过程循环跑起来。我没有提供所有的文章,而是从 10 篇开始,直到差异变得很小,然后才让它处理全部。它大概跑了 30 分钟左右,等我回来时,发现结果已经处于一个相当可以接受的状态了。
这次转换的关键,并不在于 LLM 有能力完成它,而在于我最终真正信任这个过程,因为我可以审查它的方法。不仅如此,我还试着问另一个 LLM 对这个 LLM 写的代码和变更有什么看法。这让我对整个过程不会丢失数据有了高得多的信心。我感觉这样做是对的。它感觉像一个在根本上是正确的机械化过程,我能够观察它并进行抽查。最坏的情况下,出现的退化也只是轻微的 Markdown 语法错误,但文本本身不会被损坏。
这里的另一个关键是,因为推理的量是相对恒定的,所以这个过程中推理的成本只与迭代次数和样本大小成正比,而与我想要转换的文档总数无关。最终,我让它一直处理所有文档,但处理 15 个文档和 150 个文档的工作量或多或少是相同的,因为最后基于 LLM 的分析步骤并没有增加太多需要审查的东西(它已经跳过了文件中所有微小的差异)。
说了这么多,其实就是想说,这整个转换过程都是通过代码完成的。这是一个流水线(pipeline):从人类输入开始,生成代码,然后用 LLM 作为裁判进行迭代。你可以将这个转换模式应用到任何一个通用任务上。
举个例子,你可能会用到的一个 MCP 就是 Playwright。我发现很难在所有情况下都用代码来取代 Playwright,因为你本质上是在远程控制浏览器。你给它的任务,很大程度上涉及到读取页面、理解页面内容,然后点击下一个按钮。在这种场景下,你很难在每一步都消除推理。
但是,如果你已经知道页面是什么——例如,你正在操作你自己开发的应用——那你其实可以告诉它去写一个 Playwright Python 脚本并运行它。这个脚本可以按顺序执行许多步骤,而不需要任何推理。我注意到这种方法快得多,而且因为它理解你的代码,所以通常仍然能产生正确的结果。它不需要实时地导航、读取页面内容、寻找按钮或按下输入。相反,它会写一个单独的 Python 脚本,一次性自动化整个过程,相比之下,需要的上下文非常少。
这个过程是可重复的。一旦脚本写好了,我可以执行它 100、200 甚至 300 次,而不需要任何额外的推理。这是 MCP 通常无法提供的一个巨大优势。让一个 LLM 理解通用的、抽象的 MCP 工具调用是极其困难的。我多希望能把一个 MCP 客户端直接嵌入到 shell 脚本里,让我可以通过代码生成来高效地运行远程 MCP 服务,但实际上这非常难,因为这些工具在设计时就没有考虑到基于非推理的自动化。
还有,讽刺的是:我是个人,不是 MCP 客户端。我可以运行和调试一个脚本,但我甚至不知道如何可靠地进行 MCP 调用。这总像一场赌博,而且极难调试。我喜欢用 Claude Code 在生成代码时生成的一些小工具。其中一些,我已经让它转化成了我开发流程中的长期补充。
我不知道。但这是一个有趣的时刻,我们可以思考一下,为了让有目的的智能体编程(agentic coding)中的代码生成变得更好,我们到底能做些什么。奇怪的是,MCP 在能用的时候其实相当不错。但它目前的形式感觉太像一个死胡同,无法扩展,尤其无法扩展到规模化的自动化,因为它太依赖推理了。
所以,或许我们需要寻找一种更好的抽象,来融合 MCP 的优点和代码生成的优点。为此,我们可能需要构建更好的沙箱,并且开始研究如何以一种允许智能体对推理进行某种扇出/扇入(fan out / fan in)的方式来暴露 API。实际上,我们希望尽可能多地在生成的代码中完成工作,然后在批量执行代码后,利用 LLM 的魔力来评判我们做了什么。
我也可以想象,以一种能为 LLM 提供足够上下文,让它能用人类语言向非程序员解释脚本在做什么的方式来进行代码生成,这可能会非常有趣。这或许能让那些自己不是开发者的用户也能使用这些工作流。
无论如何,我只能鼓励大家绕过 MCP,去探索其他的可能性。如果你赋予 LLM 编写代码的能力,它们能做的远不止于此。
这里有一些你可能想读的文章或想看的视频:
2025-07-12 08:00:02
原文: https://lucumr.pocoo.org/2025/6/12/agentic-coding/
作者: Armin Ronacher
译者: Gemini 2.5 Pro
最近,分享 agentic coding 经验的人如雨后春笋般涌现。在我最近发表了关于这个话题的两篇 文章后,收到了不少关于我个人实践的提问。所以,我在这里就献丑了。
简单来说,我主要用的是 Claude Code,订阅了每月 100 美元较便宜的 Max 套餐 1。这么做效果不错,原因有几点:
我的大致工作流是:给 agent(实际上拥有完全权限)分配一个任务,然后等它完成。除非是小任务,我很少打断它。因此,IDE 的作用——以及 IDE 中 AI 的作用——被大大削弱了;我主要用它做最终编辑。这种方法甚至让我重新用起了 Vim,一个没有 AI 集成的编辑器。
一个提醒:我估计这篇博客会很快过时。这个领域的创新速度太疯狂了;一个月前还成立的道理,今天可能就不再适用了。因此,我将专注于那些我认为具有持久生命力的概念。
如果你想看我用它来为一个开源库做贡献的一小段过程,我这里有一段录像可供观看。
我禁用了所有权限检查。基本上就是运行 claude --dangerously-skip-permissions
。更具体地说,我设置了一个叫 claude-yolo
的别名。你可以说这不负责任,也确实有风险,但你可以通过把开发环境移到 docker 里来管理这些风险。不过,我得说,如果你能稍微看着它点,就算不用 docker,效果也出奇地好。当然,你的情况可能不同(YMMV)。
MCP。这是一个你无法回避的术语。它基本上是一个标准化协议,用来让 agent 访问更多工具。老实说:目前我很少用它,但确实在用。我之所以很少用,是因为 Claude Code 本身就很擅长直接运行常规工具。所以对我来说,只有当我想让 Claude 访问一些它觉得不好用的东西时,才需要 MCP。一个很好的例子是用于浏览器自动化的 playwright-mcp。我用它是因为我还没找到更好的替代品。但比如说,当我想让 agent 捣鼓我的数据库时,我就让它用它能找到的任何工具。在我的例子里,它喜欢用 psql
,这已经足够好了。
总的来说,我只在备选方案太不可靠时才开始使用 MCP。因为 MCP 服务器本身有时也不是特别可靠,它们是又一个可能出错的东西。我力求保持简单。我的自定义工具就是些普通脚本,它直接运行就行。
我评估了 agent 在我工作负载下使用不同语言时的表现,如果你可以选择语言,我强烈推荐新后端项目使用 Go。有几个因素非常有利于 Go:
cargo test
的调用语法而失败。而在 Go 中,测试运行直接且增量,极大地增强了 agentic 工作流。它不需要去搞清楚该运行哪些测试,Go 会自己搞定。作为对比,我最初选择的 Python 常常带来巨大挑战。Agent 很难处理 Python 的“魔法”(比如:Pytest 的 fixture 注入)或复杂的运行时问题(比如:使用 async 时错误的事件循环),经常生成错误代码,连 agentic 循环本身都难以解决。Python 还有实际的性能问题。我不是说它写的代码慢,我是说 agent 循环本身很慢。这是因为 agent 喜欢生成进程和测试脚本,而解释器启动和初始化整个应用可能需要相当长的时间。
在前端,我选择了 tailwind、react、tanstack 的 query 和 router,以及 vite。我对此不是特别满意,但发现它比其他选择要好。Tailwind 和 vite 很棒,没什么可抱怨的。Tanstack 基于文件的路由让我不太爽。部分原因是它喜欢在文件名中使用美元符号,而这真的很容易迷惑 agent。例如,它常常试图编辑 $param.tsx
,结果却因为被 shell 的变量插值搞混了,而去编辑 .tsx
文件。这是个小问题,但非常烦人。
无论用什么语言,高效的工具都至关重要。关键原则如下:
举个例子:我把关键工具都放进一个 Makefile
里。一个 make dev
命令通过一个进程管理器来启动我的服务。和我通常的做法相比,我做了两个关键改动:
下面是一个能很好地说明这一点的 agentic 交互示例:
⏺ Great! The formatting looks good. Now let me run the development server to test the dynamic onboarding:
⏺ Bash(make dev)
⎿ Error: error: services are already running make: *** [dev] Error 1
⏺ The development server is already running. Let me check the logs to see the frontend URL:
⏺ Bash(make tail-log)
⎿ 01:06:55 backend | Starting server on localhost:8080
01:07:39 backend | [Running: sh -c 'cd backend && go run cmd/server/main.go serve']
01:07:39 backend | Starting server on localhost:8080
08:45:34 backend | "GET http://localhost:8080/api/users/me HTTP/1.1" from 127.0.0.1:61804 - 200
39.252666ms
… +96 lines (ctrl+r to expand)
⏺ Perfect! I can see the frontend is running on http://localhost:3000/ and the latest changes
have been hot-reloaded. Let me test the dynamic onboarding functionality using playwright:
总的来说,日志记录超级重要。例如,我的应用目前有一个注册登录流程,会给用户发送一封邮件。在调试模式下(agent 运行的模式),邮件内容会直接记录到标准输出。这至关重要!它使得 agent 能够用一个远程控制的浏览器,无需额外帮助就能完成整个登录流程。agent 知道邮件会被记录下来,这是因为它收到了一个 CLAUDE.md
指令,它会自动查阅日志以获取需要点击的链接。
这算是一个工具吗?在我看来,算的。
Agentic coding 的低效主要源于推理成本和次优的工具使用。我再重申一遍:快速、清晰的工具响应至关重要。我们还没谈到的是,有些工具是“涌现的”,由 agent 自己临时编写。快速的编译和执行能极大地提升 agent 的生产力。那么我们该如何帮助它呢?
通过正确的指令,AI 必须能够通过遵循现有惯例,非常迅速地创建一个新工具。这是必要的,因为你希望 AI 编写一些新代码并运行它。如果一个工具运行需要 3 毫秒,而另一个工具需要编译 5 秒,然后再花一分钟启动、连接数据库和 Kafka,并输出 100 行无意义的日志,那么工作流的质量和速度将有天壤之别。
如果你的东西确实很慢,可以考虑 vibe-coding 一个守护进程,你可以动态地加载东西进去。举个例子,Sentry 重载代码和重启都太慢了。为了在那上面试验一些 agentic coding,我的变通方法是写一个模块,监视一个文件系统位置,然后导入并执行放在那里的所有 Python 模块,再将输出写入一个它能查看的日志里。这不完美,但对 agent 在应用上下文中评估一些基础代码非常有帮助。
平衡日志的冗余度至关重要:信息丰富而又简洁的日志可以优化 token 使用和推理速度,避免不必要的成本和速率限制。如果你找不到这个平衡点,就提供一些简单的开关让 AI 来控制。
在理想的设置中,有用的日志输出是 agent 编写代码的自然副产品。从第一次代码生成就获得可观察性,要远胜于先写代码,运行失败,然后再回到调试循环中添加调试信息。
你真正需要的是稳定的生态系统。LLM 很擅长 Go,也喜欢用 Flask,因为这些都是变化很少的相当稳定的生态系统。你的代码库也是同理。AI 在写代码时喜欢留下各种各样的“面包屑”,这些东西以后可能会造成混淆。例如,我见过 agent 留下有用的注释,解释它为什么选择这条路而不是另一条。如果你随随便便让 AI 升级库,导致其中一些决策不再合理,那么你可能会发现 AI 还在继续沿用一个已经过时的模式。
理论上,这对 agent 和人类来说应该是一样的,但现实是,agent 让升级变得如此“廉价”,以至于人们很想就让 AI 去做,然后看看测试是否还能通过。我发现这根本不是一条成功的路。对待升级要比以前更加保守。
同样地,在使用 AI 时,我强烈倾向于生成更多代码,而不是使用更多依赖。我之前写过为什么你应该自己写代码,但随着我越来越多地使用 agentic coding,我对此愈发深信不疑。
在 agentic 的上下文中,简单的代码远胜于复杂的代码。我最近刚写过关于丑陋代码的文章,我认为在 agent 的背景下,这篇文章值得重读。让 agent 去做“能奏效的最蠢的事”。
Agent 单个运行速度并不算特别快,但并行化可以提升整体效率。要想办法管理好共享状态,比如文件系统、数据库或 Redis 实例,这样你才能同时运行多个 agent。要么避免共享状态,要么找到一种能快速将东西分割开来的方法。
你最初的共享状态就是文件系统,再 checkout 一份代码就行。但说实话,我还没有一个绝佳的解决方案。目前有一些不错的初步尝试。例如,一个值得关注的工具是 container-use。它是一个 MCP 服务器,指示 Claude 或其他 agent 完全在 Docker 中运行它们的实验。
然后还有像 Cursor 的后台 agent 和 Codex 这样的工具,它们正在把整套东西都搬到 CI 里,这会很有趣。目前,这套方案对我来说还行不通,但一个月后再看看吧。
Agentic coding 改变了重构的优先级。Agent 能有效地处理任务,直到项目复杂性超过某个可管理的阈值。这里的“太大”指的是它必须考虑的东西的总量。举个例子,你可以用 vibe code 的方式把前端拼凑一段时间,但最终你会达到一个临界点,你必须告诉它去创建一个组件库。为什么?因为如果 tailwind 的 class 乱七八糟地分布在 50 个文件里,你会发现很难让 AI 在不引入重大回归的情况下进行重新设计或提取组件。
一个 agentic 工作流会鼓励你在恰当的时机进行良好的代码维护和重构。你不想做得太早,也绝不想做得太晚。
Agentic coding 正在飞速发展,我今天的工作流明天可能就会大不相同。但有一点是明确的:将 agent 集成到你的开发流程中可以释放巨大的生产力。我鼓励你继续试验。工具和技术会不断演进,但核心原则——简单、稳定、可观察性和智能并行化——将依然至关重要。
最终,目标不仅仅是利用 agent 更快地写代码,而是写出更好、更可维护、更有弹性的代码。即使是今天,代码也已经和几个月前那种糟糕的浆糊完全不同了。保持适应性,编码愉快!
2025-07-12 08:00:01
原文: https://www.dbreunig.com/2025/06/26/how-to-fix-your-context.html
作者: DB Reunig
译者: Gemini 2.5 Pro
继我们之前的文章《长 Context 如何失效》之后,我们来探讨一下如何减轻甚至完全避免这些故障。
但在开始之前,我们先简单回顾一下长 Context 可能出现的几种故障:
这里所有的问题都关乎信息管理。Context 中的一切都会影响模型的响应。这又回到了那句古老的编程格言:“垃圾进,垃圾出”。幸运的是,我们有很多方法来处理上述问题。
检索增强生成 (Retrieval-Augmented Generation, RAG) 是指选择性地添加相关信息,以帮助 LLM 生成更好的响应。
关于 RAG 的文章已经太多了,我们今天不打算深入探讨,只想说一句:它还远未过时。
每当有模型提高 Context 窗口的上限,新一轮“RAG 已死”的辩论就会出现。最近一次是 Llama 4 Scout 带着 1000 万 token 的窗口横空出世。面对这么大的容量,你真的很难不产生“去他的,把所有东西都扔进去”然后收工的想法。
但是,正如我们上次所说:如果你把 Context 当成一个杂物抽屉,那么里面的杂物就会影响你的响应。如果你想了解更多,这里有一个看起来很棒的新课程。
Tool Loadout 是指只选择相关的工具定义添加到你的 Context 中。
“Loadout” 是一个游戏术语,指的是你在进入一个关卡、比赛或回合前选择的技能、武器和装备的特定组合。通常,你的 Loadout 是根据具体情况量身定制的——角色、关卡、团队其他成员的配置,以及你自己的技能。
在这里,我们借用这个词来描述为特定任务选择最相关的工具。
也许选择工具最简单的方法就是对你的工具描述应用 RAG。这正是 Tiantian Gan 和 Qiyao Sun 在他们的论文《RAG MCP》中所做的。通过将工具描述存储在向量数据库中,他们能够根据输入的 Prompt 选择最相关的工具。
在对 DeepSeek-v3 进行 Prompting 时,该团队发现,当你拥有超过 30 个工具时,选择正确的工具变得至关重要。超过 30 个,工具的描述开始重叠,造成混淆。一旦超过 100 个工具,模型几乎肯定会测试失败。而使用 RAG 技术来选择少于 30 个工具,不仅大大缩短了 Prompt,还使工具选择的准确性提高了多达 3 倍。
对于较小的模型,问题在远未达到 30 个工具时就开始了。我们在上一篇文章中提到的一篇论文《Less is More》,证明了 Llama 3.1 8b 在给定 46 个工具时无法通过一个基准测试,但在只给 19 个工具时却能成功。问题在于 Context Confusion (上下文混淆),而并非 Context 窗口的限制。
为了解决这个问题,《Less is More》背后的团队开发了一种使用 LLM 驱动的工具推荐器来动态选择工具的方法。他们引导 LLM 去推理“它‘认为’回答用户查询需要多少个以及哪种类型的工具”。然后对这个输出进行语义搜索(又是工具 RAG),以确定最终的 Loadout。他们在伯克利函数调用排行榜上测试了这种方法,发现 Llama 3.1 8b 的性能提升了 44%。
《Less is More》的论文还指出了小 Context 的另外两个好处:降低功耗和提高速度。这在边缘设备(即在你的手机或个人电脑上,而非专用服务器上运行 LLM)上是至关重要的指标。即使他们的动态工具选择方法未能改善模型的结果,节省的能源和速度的提升也值得这样做,分别节省了 18% 和 77%。
幸运的是,大多数 Agent 的应用场景都比较小,只需要少数几个精心挑选的工具。但如果功能范围或集成数量需要扩展,请务必考虑你的 Loadout。
Context Quarantine 是指将不同的 Context 隔离在各自专用的线程中,每个线程由一个或多个 LLM 单独使用。
当我们的 Context 不过长且不包含不相关内容时,我们会得到更好的结果。实现这一点的一个方法是将我们的任务分解成更小的、隔离的工作——每个工作都有自己的 Context。
这种策略有许多例子,但 Anthropic 详细介绍其多 Agent 研究系统的博文是一个通俗易懂的案例。他们写道:
搜索的本质是压缩:从浩瀚的语料库中提炼洞见。子 Agent 通过并行运行在各自的 Context 窗口中来促进压缩,它们同时探索问题的不同方面,然后为主研究 Agent 浓缩最重要的 Token。每个子 Agent 还提供了关注点分离——独特的工具、Prompt 和探索路径——这减少了路径依赖,并实现了彻底、独立的调查。
研究工作天然适合这种设计模式。当给定一个问题时,可以识别出几个子问题或探索领域,并使用多个 Agent 分别进行 Prompting。这不仅加快了信息收集和提炼的速度(如果计算资源充足),还能防止每个 Context 累积过多信息或与特定 Prompt 不相关的信息,从而提供更高质量的结果:
我们的内部评估表明,多 Agent 研究系统尤其擅长处理需要同时进行多个独立方向探索的广度优先查询。我们发现,一个以 Claude Opus 4 为主 Agent、Claude Sonnet 4 为子 Agent 的多 Agent 系统,在我们的内部研究评估中,性能比单 Agent 的 Claude Opus 4 高出 90.2%。例如,当被要求找出信息技术标准普尔 500 指数中所有公司的董事会成员时,多 Agent 系统通过将任务分解给子 Agent 找到了正确答案,而单 Agent 系统则因缓慢的顺序搜索而未能找到答案。
这种方法也有助于 Tool Loadout,因为 Agent 设计者可以创建几种具有自己专用 Loadout 和工具使用说明的 Agent 原型。
因此,对于 Agent 构建者来说,挑战在于找到那些可以拆分成独立线程的隔离任务。需要多个 Agent 共享 Context 的问题并不特别适合这种策略。
如果你的 Agent 领域适合并行处理,一定要阅读 Anthropic 的整篇文章。写得非常棒。
Context Pruning 是指从 Context 中移除不相关或不需要的信息。
Agent 在调用工具和组合文档时会不断累积 Context。有时,我们有必要停下来评估一下已经组合的内容,并剔除其中的糟粕。你可以把这个任务交给你的主 LLM,也可以设计一个单独的、由 LLM 驱动的工具来审查和编辑 Context。或者你也可以选择一个更适合修剪任务的专用工具。
Context Pruning 有着(相对)悠久的历史,因为在 ChatGPT 出现之前,Context 长度在自然语言处理(NLP)领域是一个更棘手的瓶颈。目前的一种修剪方法,Provence,正是在这一历史基础上发展的,它是一个“用于问答的高效且稳健的 Context 修剪器”。
Provence 速度快、准确、使用简单,而且相对较小——只有 1.75 GB。你只需几行代码就可以调用它,像这样:
from transformers import AutoModel
provence = AutoModel.from_pretrained("naver/provence-reranker-debertav3-v1", trust_remote_code=True)
# 读入加州阿拉米达维基百科条目的 markdown 版本
with open('alameda_wiki.md', 'r', encoding='utf-8') as f:
alameda_wiki = f.read()
# 根据一个问题来修剪文章
question = '我有哪些离开阿拉米达的交通方式?'
provence_output = provence.process(question, alameda_wiki)
Provence 对文章进行了编辑,削减了 95% 的内容,只给我留下了这个相关的子集。它做得非常完美。
你可以使用 Provence 或类似的功能来精简文档或整个 Context。此外,这种模式也充分说明,你应该以字典或其他形式维护一个结构化的 1 Context 版本,并在每次调用 LLM 之前从中组合出一个编译好的字符串。这种结构在修剪时会非常方便,可以确保主要指令和目标被保留,而文档或历史记录部分则可以被修剪或总结。
Context Summarization 是指将累积的 Context 浓缩成一个简短的摘要。
Context Summarization 最初是作为处理较小 Context 窗口的工具出现的。当你的聊天会话快要超出最大 Context 长度时,系统会生成一个摘要,然后开启一个新线程。聊天机器人用户以前会在 ChatGPT 或 Claude 中手动操作,要求机器人生成一个简短的回顾,然后粘贴到新的会话中。
然而,随着 Context 窗口的增大,Agent 构建者发现,总结的好处不仅仅是保持在总 Context 限制之内。随着 Context 的增长,它会变得分散注意力,导致模型减少对其训练期间所学知识的依赖。我们称之为 Context Distraction。玩《精灵宝可梦》的 Gemini Agent 背后的团队发现,任何超过 10 万 Token 的内容都会触发这种行为:
虽然 Gemini 2.5 Pro 支持 100 万+ Token 的 Context,但如何为 Agent 有效利用它是一个新的研究前沿。在这种 Agent 设置中,我们观察到,当 Context 显著增长超过 10 万 Token 时,Agent 倾向于重复其庞大历史记录中的动作,而不是构思新的计划。这个现象,虽然只是个例,但它突显了用于检索的长 Context 和用于多步生成式推理的长 Context 之间的重要区别。
总结你的 Context 很容易,但要为任何特定的 Agent 做到完美却很难。知道应该保留哪些信息,并将其详细告知一个由 LLM 驱动的压缩步骤,这对 Agent 构建者来说至关重要。值得将这个功能作为一个独立的、由 LLM 驱动的阶段或应用来开发,这样你就可以收集评估数据,从而直接为这个任务提供信息和优化。
Context Offloading 是指将信息存储在 LLM 的 Context 之外,通常通过一个工具来存储和管理数据。
这可能是我最喜欢的策略,只因为它太简单了,以至于你不敢相信它会奏效。
同样,Anthropic 对这项技术有一篇很好的文章,详细介绍了他们的“think”工具,这基本上就是一个草稿本 (scratchpad):
通过“think”工具,我们让 Claude 能够在得出最终答案的过程中增加一个额外的思考步骤——并有其自己专属的空间……这在执行长链条的工具调用或与用户进行长时间多步骤对话时特别有帮助。
我真的很欣赏 Anthropic 发表的研究和其他文章,但我不太喜欢这个工具的名字。如果这个工具叫 scratchpad
(草稿本),你会立刻知道它的功能。它是一个让模型记笔记的地方,这些笔记不会干扰它的 Context,但又可以在之后参考。而“think”这个名字与“扩展思考”的概念冲突,并且不必要地将模型拟人化了……但这是题外话。
拥有一个记录笔记和进度的空间是有效的。Anthropic 展示了将“think”工具与特定领域的 Prompt (这在 Agent 中你本来就会做) 相结合,可以带来显著的收益,针对专门的 Agent,基准测试的提升高达 54%。
Anthropic 确定了三种 Context Offloading 模式有用的场景:
- 工具输出分析。当 Claude 需要在行动前仔细处理之前工具调用的输出,并可能需要回溯其方法时;
- 策略繁重的环境。当 Claude 需要遵循详细的指导方针并验证合规性时;以及
- 顺序决策。当每一步行动都建立在前一步的基础上,且错误代价高昂时(常见于多步骤领域)。
Context 管理通常是构建 Agent 最难的部分。正如 Karpathy 所说,对 LLM 进行编程,以“恰到好处地填充 Context 窗口”,巧妙地部署工具、信息,并定期维护 Context,这就是 Agent 设计师的工作。
上述所有策略的核心洞见在于,Context 不是免费的。Context 中的每个 Token 都会影响模型的行为,无论好坏。现代 LLM 巨大的 Context 窗口是一项强大的能力,但它不是你在信息管理上偷懒的借口。
当你构建下一个 Agent 或优化现有 Agent 时,问问自己:这个 Context 中的所有东西都物有所值吗?如果不是,你现在有六种方法来修复它。
2025-07-12 08:00:00
原文: https://www.dbreunig.com/2025/06/22/how-contexts-fail-and-how-to-fix-them.html
作者: D. B. Reunig
译者: Gemini 2.5 Pro
As frontier model context windows continue to grow1, with many supporting up to 1 million tokens, I see many excited discussions about how long context windows will unlock the agents of our dreams. After all, with a large enough window, you can simply throw everything into a prompt you might need – tools, documents, instructions, and more – and let the model take care of the rest.
随着前沿模型的 context 窗口不断扩大1,许多模型已支持高达 100 万 token,我看到很多激动人心的讨论,关于长 context 窗口将如何解锁我们梦想中的 agent。毕竟,只要窗口足够大,你就可以把可能需要的一切——工具、文档、指令等等——都扔进一个 prompt 里,然后让模型处理剩下的事情。
Long contexts kneecapped RAG enthusiasm (no need to find the best doc when you can fit it all in the prompt!), enabled MCP hype (connect to every tool and models can do any job!), and fueled enthusiasm for agents2.
长 context 的出现削弱了 RAG 的热度(既然能把所有文档都放进 prompt,何必费心去寻找最相关的那一篇呢!),助长了 MCP 的炒作(连接所有工具,模型就能做任何事!),也点燃了人们对 agent 的热情2。
But in reality, longer contexts do not generate better responses. Overloading your context can cause your agents and applications to fail in suprising ways. Contexts can become poisoned, distracting, confusing, or conflicting. This is especially problematic for agents, which rely on context to gather information, synthesize findings, and coordinate actions.
但实际上,更长的 context 并不会带来更好的响应。给 context 加载过多内容,可能会让你的 agent 和应用以意想不到的方式失败。Context 可能会被投毒、被干扰、被混淆或产生冲突。这对 agent 来说尤其成问题,因为 agent 正是依赖 context 来收集信息、综合发现和协调行动的。
Let’s run through the ways contexts can get out of hand, then review methods to mitigate or entirely avoid context fails.
我们来梳理一下 context 是如何失控的,然后探讨如何减轻或完全避免这些问题。
Context Poisoning is when a hallucination or other error makes it into the context, where it is repeatedly referenced.
Context Poisoning 是指幻觉或其他错误信息进入了 context,并被反复引用。
The Deep Mind team called out context poisoning in the Gemini 2.5 technical report, which we broke down last week. When playing Pokémon, the Gemini agent would occasionally hallucinate while playing, poisoning its context:
DeepMind 团队在 Gemini 2.5 技术报告中指出了 context 投毒的问题,我们上周也分析过这篇文章。在玩《精灵宝可梦》时,Gemini agent 偶尔会产生幻觉,从而毒化了它的 context:
An especially egregious form of this issue can take place with “context poisoning” – where many parts of the context (goals, summary) are “poisoned” with misinformation about the game state, which can often take a very long time to undo. As a result, the model can become fixated on achieving impossible or irrelevant goals.
这个问题有一种特别严重的形式,就是“context 投毒”——context 的许多部分(如目标、摘要)被关于游戏状态的错误信息“毒化”,而这往往需要很长时间才能纠正。结果,模型可能会固执地追求一些不可能或不相关的目标。
If the “goals” section of its context was poisoned, the agent would develop nonsensical strategies and repeat behaviors in pursuit of a goal that cannot be met.
如果 context 中的“目标”部分被投毒,agent 就会制定出荒谬的策略,并为了一个无法实现的目标而重复某些行为。
Context Distraction is when a context grows so long that the model over-focuses on the context, neglecting what it learned during training.
Context Distraction 是指 context 变得过长,导致模型过度关注 context 本身,而忽略了它在训练中学到的知识。
As context grows during an agentic workflow—as the model gathers more information and builds up history—this accumulated context can become distracting rather than helpful. The Pokémon-playing Gemini agent demonstrated this problem clearly:
在 agent 工作流中,随着模型收集更多信息、积累更多历史,context 会不断增长——这个累积的 context 可能会变得起干扰作用,而非有益。玩《精灵宝可梦》的 Gemini agent 清楚地展示了这个问题:
While Gemini 2.5 Pro supports 1M+ token context, making effective use of it for agents presents
a new research frontier. In this agentic setup, it was observed that as the context grew significantly beyond 100k tokens, the agent showed a tendency toward favoring repeating actions from its vast history rather than synthesizing novel plans. This phenomenon, albeit anecdotal, highlights an important distinction between long-context for retrieval and long-context for multi-step, generative reasoning.
虽然 Gemini 2.5 Pro 支持 1M+ token 的 context,但如何有效地将其用于 agent 仍是一个新的研究前沿。在这个 agent 设置中,我们观察到,当 context 显著超过 100k token 时,agent 表现出一种倾向,更喜欢重复其庞大历史记录中的动作,而不是综合信息制定新的计划。这一现象虽然只是个例,但它突显了用于信息检索的长 context 和用于多步生成式推理的长 context 之间的重要区别。
Instead of using its training to develop new strategies, the agent became fixated on repeating past actions from its extensive context history.
Agent 不再利用其训练成果来制定新策略,而是固执地重复其庞大 context 历史中的旧行为。
For smaller models, the distraction ceiling is much lower. A Databricks study found that model correctness began to fall around 32k for Llama 3.1 405b and earlier for smaller models.
对于更小的模型,这个干扰的上限要低得多。一项 Databricks 的研究发现,对于 Llama 3.1 405b,模型准确性在 32k token 左右开始下降,而对于更小的模型,这个临界点来得更早。
If models start to misbehave long before their context windows are filled, what’s the point of super large context windows? In a nutshell: summarization3 and fact retrieval. If you’re not doing either of those, be wary of your chosen model’s distraction ceiling.
如果模型在 context 窗口远未填满时就开始出问题,那么超大 context 窗口的意义何在?简而言之:用于摘要3和事实检索。如果你不是在做这两件事,就要小心你所选模型的干扰上限。
Context Confusion is when superfluous content in the context is used by the model to generate a low-quality response.
Context Confusion 是指 context 中无关紧要的内容被模型用来生成了低质量的响应。
For a minute there, it really seemed like everyone was going to ship an MCP. The dream of a powerful model, connected to all your services and stuff, doing all your mundane tasks felt within reach. Just throw all the tool descriptions into the prompt and hit go. Claude’s system prompt showed us the way, as it’s mostly tool definitions or instructions for using tools.
有一阵子,似乎每个人都准备推出一个 MCP。一个强大的模型,连接你所有的服务和东西,帮你处理所有杂事的梦想,感觉触手可及。只需把所有工具的描述都扔进 prompt,然后运行即可。Claude 的系统提示为我们指明了方向,因为它的内容大部分是工具定义或使用说明。
But even if consolidation and competition don’t slow MCPs, Context Confusion will. It turns out there can be such a thing as too many tools.
但即使整合与竞争没有减缓 MCP 的发展,Context Confusion 也会。事实证明,工具真的可能太多了。
The Berkeley Function-Calling Leaderboard is a tool-use benchmark that evaluates the ability of models to effectively use tools to respond to prompts. Now on its 3rd version, the leaderboard shows that every model performs worse when provided with more than one tool4. Further, the Berkeley team, “designed scenarios where none of the provided functions are relevant…we expect the model’s output to be no function call.” Yet, all models will occasionally call tools that aren’t relevant.
伯克利函数调用排行榜是一个评估模型使用工具响应 prompt 能力的基准测试。现在更新到第 3 版,排行榜显示,当提供不止一个工具时,每个模型的表现都会变差4。此外,伯克利团队“设计了一些场景,其中提供的所有函数都不相关……我们期望模型的输出是不进行任何函数调用。”然而,所有模型都偶尔会调用不相关的工具。
Browsing the function-calling leaderboard, you can see the problem get worse as the models get smaller:
浏览函数调用排行榜,你会发现随着模型变小,这个问题变得更加严重:
A striking example of context confusion can be seen in a recent paper which evaluated small model performance on the GeoEngine benchmark, a trial that features 46 different tools. When the team gave a quantized (compressed) Llama 3.1 8b a query with all 46 tools it failed, even though the context was well within the 16k context window. But when they only gave the model 19 tools, it succeeded.
一个关于 context 混淆的显著例子出现在最近的一篇论文中,该论文评估了小模型在 GeoEngine 基准测试上的表现,该测试包含多达 46 种不同的工具。当团队给一个量化(压缩)的 Llama 3.1 8b 模型一个包含全部 46 个工具的查询时,它失败了,尽管 context 远未达到 16k 的窗口上限。但当他们只给模型 19 个工具时,它却成功了。
The problem is: if you put something in the context the model has to pay attention to it. It may be irrelevant information or needless tool definitions, but the model will take it into account. Large models, especially reasoning models, are getting better at ignoring or discarding superfluous context, but we continually see worthless information trip up agents. Longer contexts let us stuff in more info, but this ability comes with downsides.
问题在于:如果你把某些东西放进 context,模型就必须关注它。这可能是无关紧要的信息或多余的工具定义,但模型会将其纳入考量。大型模型,特别是推理模型,在忽略或丢弃多余 context 方面正变得越来越好,但我们仍然不断看到无用的信息绊倒 agent。更长的 context 让我们能塞进更多信息,但这种能力也带来了负面影响。
Context Clash is when you accrue new information and tools in your context that conflicts with other information in the context.
Context Clash 是指你在 context 中累积的新信息和工具与 context 中已有的其他信息发生冲突。
This is a more problematic version of Context Confusion: the bad context here isn’t irrelevant, it directly conflicts with other information in the prompt.
这是 Context Confusion 的一个更麻烦的版本:这里的坏 context 不是无关紧要,而是与 prompt 中的其他信息直接冲突。
A Microsoft and Salesforce team documented this brilliantly in a recent paper. The team took prompts from multiple benchmarks and ‘sharded’ their information across multiple prompts. Think of it this way: sometimes, you might sit down and type paragraphs into ChatGPT or Claude before you hit enter, considering every necessary detail. Other times, you might start with a simple prompt, then add further details when the chatbot’s answer isn’t satisfactory. The Microsoft/Salesforce team modified benchmark prompts to look like these multistep exchanges:
一个微软和 Salesforce 的团队在最近的一篇论文中出色地记录了这一点。该团队从多个基准测试中提取 prompt,并将其信息“分片”到多个 prompt 中。可以这样理解:有时,你可能会在点击发送前,在 ChatGPT 或 Claude 中输入大段文字,考虑所有必要的细节。而其他时候,你可能从一个简单的 prompt 开始,然后在聊天机器人的回答不尽人意时再补充更多细节。这个微软/Salesforce 团队修改了基准 prompt,使其看起来像这些多步对话:
All the information from the prompt on the left side is contained within the several messages on the right side, which would be played out in multiple chat rounds.
左边 prompt 中的所有信息都包含在右边的几条消息中,这些消息会在多轮聊天中依次给出。
The sharded prompts yielded dramatically worse results, with an average drop of 39%. And the team tested a range of models – OpenAI’s vaunted o3’s score dropped from 98.1 to 64.1.
分片式 prompt 得到的结果要差得多,平均下降了 39%。该团队测试了一系列模型——OpenAI 备受赞誉的 o3 模型得分从 98.1 降至 64.1。
What’s going on? Why are models performing worse if information is gathered in stages rather than all at once?
发生了什么?为什么分阶段收集信息比一次性提供所有信息时,模型的表现更差?
The answer is Context Confusion: the assembled context, containing the entirety of the chat exchange, contains early attempts by the model to answer the challenge before it has all the information. These incorrect answers remain present in the context and influence the model when it generates its final answer. The team writes:
答案还是 Context Confusion:组装起来的 context 包含了整个聊天对话,其中也包括了模型在获得全部信息之前为解决问题所做的早期尝试。这些不正确的答案留在了 context 中,并在模型生成最终答案时对其产生影响。该团队写道:
We find that LLMs often make assumptions in early turns and prematurely attempt to generate final solutions, on which they overly rely. In simpler terms, we discover that when LLMs take a wrong turn in a conversation, they get lost and do not recover.
我们发现,LLM 常常在早期对话中做出假设,并过早地尝试生成最终解决方案,然后又过度依赖这些早期的方案。简单来说,我们发现当 LLM 在对话中走错一步时,它们就会迷失方向,无法恢复。
This does not bode well for agent builders. Agents assemble context from documents, tool calls, and from other models tasked with subproblems. All of this context, pulled from diverse sources, has the potential to disagree with itself. Further, when you connect to MCP tools you didn’t create there’s a greater chance their descriptions and instructions clash with the rest of your prompt.
这对 agent 的构建者来说不是个好兆头。Agent 从文档、工具调用以及负责子任务的其他模型中组装 context。所有这些来自不同来源的 context 都有可能自相矛盾。此外,当你连接到非自己创建的 MCP 工具时,它们的描述和指令与你 prompt 的其余部分发生冲突的可能性更大。
The arrival of million-token context windows felt transformative. The ability to throw everything an agent might need into the prompt inspired visions of superintelligent assistants that could access any document, connect to every tool, and maintain perfect memory.
百万级 token context 窗口的到来感觉像是一场变革。能够将 agent 可能需要的一切都扔进 prompt 的能力,激发了人们对超级智能助手的想象——它们可以访问任何文档,连接所有工具,并保持完美的记忆。
But as we’ve seen, bigger contexts create new failure modes. Context poisoning embeds errors that compound over time. Context distraction causes agents to lean heavily on their context and repeat past actions rather than push forward. Context confusion leads to irrelevant tool or document usage. Context clash creates internal contradictions that derail reasoning.
但正如我们所见,更大的 context 带来了新的失效模式。Context 投毒会植入错误,并随时间推移而放大。Context 干扰导致 agent 过度依赖其 context,重复过去的行为而不是向前推进。Context 混淆导致使用不相关的工具或文档。Context 冲突则产生内部矛盾,使推理偏离轨道。
These failures hit agents hardest because agents operate in exactly the scenarios where contexts balloon: gathering information from multiple sources, making sequential tool calls, engaging in multi-turn reasoning, and accumulating extensive histories.
这些失效对 agent 的打击最为严重,因为 agent 正是运行在那些 context 会急剧膨胀的场景中:从多个来源收集信息、进行连续的工具调用、参与多轮推理,以及积累大量的历史记录。
Fortunately, there are solutions! In an upcoming post we’ll cover techniques for mitigating or avoding these issues, from methods for dynamically loading tools to spinning up context quarantines.
幸运的是,我们有解决方案!在下一篇文章中,我们将介绍减轻或避免这些问题的技巧,从动态加载工具的方法到建立 context 隔离区等。
Read the follow up article, “How to Fix Your Context“
阅读后续文章:“如何修复你的 Context”
2025-07-11 08:00:10
原文: https://simonwillison.net/2025/Mar/11/using-llms-for-code/
作者: Simon Willison
译者: Gemini 2.5 Pro
Online discussions about using Large Language Models to help write code inevitably produce comments from developers who’s experiences have been disappointing. They often ask what they’re doing wrong—how come some people are reporting such great results when their own experiments have proved lacking?
网上关于使用大语言模型辅助编程的讨论,总会引来一些开发者,他们的经历令人失望。他们常常问自己到底做错了什么——为什么有些人说效果那么好,而他们自己的尝试却收效甚微?
Using LLMs to write code is difficult and unintuitive. It takes significant effort to figure out the sharp and soft edges of using them in this way, and there’s precious little guidance to help people figure out how best to apply them.
用 LLM 写代码是困难且反直觉的。你需要花很大力气才能摸清它的边界和能力范围,而且市面上几乎没有什么靠谱的指南能教你如何最好地运用它们。
If someone tells you that coding with LLMs is easy they are (probably unintentionally) misleading you. They may well have stumbled on to patterns that work, but those patterns do not come naturally to everyone.
如果有人告诉你用 LLM 编程很容易,那他很可能(也许是无意的)误导了你。他们或许是碰巧摸索出了一些行之有效的模式,但这些模式并非人人都能自然掌握。
I’ve been getting great results out of LLMs for code for over two years now. Here’s my attempt at transferring some of that experience and intution to you.
两年多来,我用 LLM 辅助编程一直效果很好。这篇文章就是我试图将这些经验和直觉分享给你的一次尝试。
Ignore the “AGI” hype—LLMs are still fancy autocomplete. All they do is predict a sequence of tokens—but it turns out writing code is mostly about stringing tokens together in the right order, so they can be extremely useful for this provided you point them in the right direction.
别管那些“AGI”的炒作——LLM 本质上还是个高级的自动补全工具。它们所做的,只是预测一连串的 token——但事实证明,写代码很大程度上就是把 token 以正确的顺序串联起来,所以只要你给它们指明了方向,它们就能变得极其有用。
If you assume that this technology will implement your project perfectly without you needing to exercise any of your own skill you’ll quickly be disappointed.
如果你指望这门技术能完美实现你的项目,而你完全不需要动用自己的任何技能,那你很快就会大失所望。
Instead, use them to augment your abilities. My current favorite mental model is to think of them as an over-confident pair programming assistant who’s lightning fast at looking things up, can churn out relevant examples at a moment’s notice and can execute on tedious tasks without complaint.
你应该把它用作增强自身能力的工具。我目前最喜欢的心智模型是,把它们看作一个过度自信的结对编程助手:它查资料快如闪电,能随时生成相关示例,还能毫无怨言地执行繁琐任务。
Over-confident is important. They’ll absolutely make mistakes—sometimes subtle, sometimes huge. These mistakes can be deeply inhuman—if a human collaborator hallucinated a non-existent library or method you would instantly lose trust in them.
过度自信这点很重要。它们绝对会犯错——有时是细微的,有时是巨大的。这些错误可能是非人般的——如果一个人类合作者“幻觉”出一个不存在的库或方法,你会立刻对他失去信任。
Don’t fall into the trap of anthropomorphizing LLMs and assuming that failures which would discredit a human should discredit the machine in the same way.
不要陷入拟人化 LLM 的陷阱,不要认为那些会让一个程序员信誉扫地的错误,同样也会让机器信誉扫地。
When working with LLMs you’ll often find things that they just cannot do. Make a note of these—they are useful lessons! They’re also valuable examples to stash away for the future—a sign of a strong new model is when it produces usable results for a task that previous models had been unable to handle.
和 LLM 一起工作时,你经常会发现它们就是做不到某些事。把这些记下来——它们是宝贵的教训!这些也是值得为未来收藏的例子——一个强大的新模型的标志,就是它能在一件旧模型搞不定的任务上,给出可用的结果。
A crucial characteristic of any model is its training cut-off date. This is the date at which the data they were trained on stopped being collected. For OpenAI’s models this is usually October 2023 or May 2024. Other providers may have more recent dates.
任何模型的一个关键特性是其训练数据截止日期。这是指它们所用训练数据的收集截止时间。对于 OpenAI 的模型,这个日期通常是 2023 年 10 月或 2024 年 5 月。其他提供商的日期可能更新一些。
This is extremely important for code, because it influences what libraries they will be familiar with. If the library you are using had a major breaking change since October 2023, some OpenAI models won’t know about it!
这对代码来说极其重要,因为它决定了模型熟悉哪些库。如果你正在使用的库在 2023 年 10 月之后有重大的破坏性更新,一些 OpenAI 模型对此将一无所知!
I gain enough value from LLMs that I now deliberately consider this when picking a library—I try to stick with libraries with good stability and that are popular enough that many examples of them will have made it into the training data. I like applying the principles of boring technology—innovate on your project’s unique selling points, stick with tried and tested solutions for everything else.
我从 LLM 中获益良多,以至于我现在选择库时会有意考虑这一点——我倾向于选择那些稳定性好、足够流行、有大量示例被纳入训练数据的库。我喜欢应用无聊技术的原则——在你项目的独特卖点上创新,其他所有事情都用经过验证的成熟方案。
LLMs can still help you work with libraries that exist outside their training data, but you need to put in more work—you’ll need to feed them recent examples of how those libraries should be used as part of your prompt.
LLM 仍然可以帮助你使用其训练数据之外的库,但你需要付出更多努力——你需要在你的 prompt 中提供这些库的最新用法示例。
This brings us to the most important thing to understand when working with LLMs:
这就引出了使用 LLM 时需要理解的最重要的一点:
Most of the craft of getting good results out of an LLM comes down to managing its context—the text that is part of your current conversation.
从 LLM 那里获得好结果的大部分技巧,都归结于管理它的上下文——也就是你当前对话中的文本。
This context isn’t just the prompt that you have fed it: successful LLM interactions usually take the form of conversations, and the context consists of every message from you and every reply from the LLM that exist in the current conversation thread.
这个上下文不仅仅是你输入的 prompt:成功的 LLM 交互通常以对话的形式进行,上下文包含了当前对话线程中你发出的每条消息以及 LLM 的每次回复。
When you start a new conversation you reset that context back to zero. This is important to know, as often the fix for a conversation that has stopped being useful is to wipe the slate clean and start again.
当你开始一个新的对话时,你就将上下文重置为零了。了解这一点很重要,因为当一段对话不再有用时,通常的解决方法就是清空重来。
Some LLM coding tools go beyond just the conversation. Claude Projects for example allow you to pre-populate the context with quite a large amount of text—including a recent ability to import code directly from a GitHub repository which I’m using a lot.
一些 LLM 编程工具的功能超出了对话本身。例如,Claude Projects 允许你用大量文本预先填充上下文——包括最近新增的直接从 GitHub 仓库导入代码的功能,我用得非常多。
Tools like Cursor and VS Code Copilot include context from your current editor session and file layout automatically, and you can sometimes use mechanisms like Cursor’s @commands to pull in additional files or documentation.
像 Cursor 和 VS Code Copilot 这样的工具会自动包含你当前编辑器会话和文件布局中的上下文,有时你还可以使用像 Cursor 的 @ 命令这样的机制来引入额外的文件或文档。
One of the reasons I mostly work directly with the ChatGPT and Claude web or app interfaces is that it makes it easier for me to understand exactly what is going into the context. LLM tools that obscure that context from me are less effective.
我主要直接使用 ChatGPT 和 Claude 的网页或应用界面的原因之一是,这样我能更容易地确切了解哪些内容进入了上下文。那些对我隐藏上下文的 LLM 工具,效果会更差。
You can use the fact that previous replies are also part of the context to your advantage. For complex coding tasks try getting the LLM to write a simpler version first, check that it works and then iterate on building to the more sophisticated implementation.
你可以利用之前的回复也是上下文一部分这个事实。对于复杂的编程任务,可以先让 LLM 写一个简单版本,检查它是否能用,然后再迭代构建更复杂的实现。
I often start a new chat by dumping in existing code to seed that context, then work with the LLM to modify it in some way.
我经常通过扔进现有代码来开启一个新的聊天,以此作为上下文的种子,然后与 LLM 一起对它进行修改。
One of my favorite code prompting techniques is to drop in several full examples relating to something I want to build, then prompt the LLM to use them as inspiration for a new project. I wrote about that in detail when I described my JavaScript OCR application that combines Tesseract.js and PDF.js—two libraries I had used in the past and for which I could provide working examples in the prompt.
我最喜欢的代码 prompt 技巧之一是,把我想要构建的东西相关的几个完整示例扔进去,然后让 LLM 以它们为灵感来创建一个新项目。我在描述我的 JavaScript OCR 应用时详细写过这一点,那个应用结合了 Tesseract.js 和 PDF.js——这两个库我过去都用过,并且可以在 prompt 中提供可行的示例。
Most of my projects start with some open questions: is the thing I’m trying to do possible? What are the potential ways I could implement it? Which of those options are the best?
我的大多数项目都是从一些开放性问题开始的:我想要做的事情可能实现吗?有哪些潜在的实现方式?这些选项中哪个是最好的?
I use LLMs as part of this initial research phase.
我会在这个初步研究阶段使用 LLM。
I’ll use prompts like “what are options for HTTP libraries in Rust? Include usage examples”—or “what are some useful drag-and-drop libraries in JavaScript? Build me an artifact demonstrating each one” (to Claude).
我会用类似这样的 prompt:“Rust 有哪些 HTTP 库选项?请包含使用示例”——或者(对 Claude 说)“JavaScript 有哪些好用的拖放库?为我构建一个 artifact 来演示每一个”。
The training cut-off is relevant here, since it means newer libraries won’t be suggested. Usually that’s OK—I don’t want the latest, I want the most stable and the one that has been around for long enough for the bugs to be ironed out.
训练数据截止日期在这里很重要,因为这意味着它不会推荐最新的库。通常这没关系——我不想要最新的,我想要最稳定的,以及那些已经存在了足够长时间、bug 都被修复得差不多的。
If I’m going to use something more recent I’ll do that research myself, outside of LLM world.
如果我要用一些比较新的东西,我会自己去研究,脱离 LLM 的世界。
The best way to start any project is with a prototype that proves that the key requirements of that project can be met. I often find that an LLM can get me to that working prototype within a few minutes of me sitting down with my laptop—or sometimes even while working on my phone.
启动任何项目的最佳方式都是先做一个原型,证明项目的关键需求可以被满足。我常常发现,在我坐到笔记本电脑前后几分钟内,LLM 就能帮我搞定一个可行的原型——有时甚至是在手机上工作时就能完成。
Once I’ve completed the initial research I change modes dramatically. For production code my LLM usage is much more authoritarian: I treat it like a digital intern, hired to type code for me based on my detailed instructions.
一旦我完成了初步研究,我就会彻底改变模式。对于生产代码,我对 LLM 的使用方式要专制得多:我把它当作一个数字实习生,雇来根据我的详细指令为我敲代码。
Here’s a recent example:
这里有一个最近的例子:
写一个使用 asyncio httpx 的 Python 函数,函数签名如下:
async def download_db(url, max_size_bytes=5 * 1025 * 1025): -> pathlib.Path
给定一个 URL,这个函数会将数据库下载到一个临时目录并返回其路径。但是,它在开始流式传输数据时会检查 content-length 头,如果超过限制就抛出错误。下载完成后,它会使用
sqlite3.connect(…)
然后运行PRAGMA quick_check
来确认 SQLite 数据有效——如果无效则抛出错误。最后,如果 content-length 头欺骗了我们——比如说它写的是 2MB 但我们下载了 3MB——我们一发现这个问题就立即抛出错误。
I could write this function myself, but it would take me the better part of fifteen minutes to look up all of the details and get the code working right. Claude knocked it out in 15 seconds.
我自己也能写这个函数,但要查阅所有细节并让代码正常工作,大概需要我十五分钟。而 Claude 只用了 15 秒就搞定了。
I find LLMs respond extremely well to function signatures like the one I use here. I get to act as the function designer, the LLM does the work of building the body to my specification.
我发现 LLM 对我使用的这种函数签名的响应非常好。我扮演函数设计师的角色,LLM 则负责根据我的规范构建函数体。
I’ll often follow-up with “Now write me the tests using pytest”. Again, I dictate my technology of choice—I want the LLM to save me the time of having to type out the code that’s sitting in my head already.
我经常会接着说“现在用 pytest 给我写测试”。同样,我指定我选择的技术——我希望 LLM 能帮我节省时间,不用去敲那些已经在我脑子里的代码。
If your reaction to this is “surely typing out the code is faster than typing out an English instruction of it”, all I can tell you is that it really isn’t for me any more. Code needs to be correct. English has enormous room for shortcuts, and vagaries, and typos, and saying things like “use that popular HTTP library” if you can’t remember the name off the top of your head.
如果你对此的反应是“打代码肯定比打一段英文指令要快”,我只能告诉你,对我来说真的不再是这样了。代码需要正确无误。而英语有巨大的空间来使用缩写、模糊表达、处理拼写错误,甚至在你一时想不起名字的时候可以说“用那个流行的 HTTP 库”。
The good coding LLMs are excellent at filling in the gaps. They’re also much less lazy than me—they’ll remember to catch likely exceptions, add accurate docstrings, and annotate code with the relevant types.
好的编程 LLM 非常擅长填补这些空白。它们也比我懒惰的程度要低得多——它们会记得捕获可能的异常,添加准确的文档字符串,并用相关的类型注解代码。
I wrote about this at length last week: the one thing you absolutely cannot outsource to the machine is testing that the code actually works.
我上周详细写过这个:有一件事你绝对不能外包给机器,那就是测试代码是否真的能用。
Your responsibility as a software developer is to deliver working systems. If you haven’t seen it run, it’s not a working system. You need to invest in strengthening those manual QA habits.
作为一名软件开发者,你的责任是交付可用的系统。如果你没亲眼见过它运行,那它就不是一个可用的系统。你需要投入精力来加强那些手动 QA 的习惯。
This may not be glamorous but it’s always been a critical part of shipping good code, with or without the involvement of LLMs.
这可能不那么光鲜,但它一直都是交付好代码的关键部分,无论有没有 LLM 的参与。
If I don’t like what an LLM has written, they’ll never complain at being told to refactor it! “Break that repetitive code out into a function”, “use string manipulation methods rather than a regular expression”, or even “write that better!”—the code an LLM produces first time is rarely the final implementation, but they can re-type it dozens of times for you without ever getting frustrated or bored.
如果我不喜欢 LLM 写的东西,它们永远不会抱怨被要求重构!“把那段重复的代码提取成一个函数”,“用字符串操作方法而不是正则表达式”,甚至“写得更好一点!”——LLM 第一次生成的代码很少是最终实现,但它们可以为你重写几十次,而从不感到沮丧或厌烦。
Occasionally I’ll get a great result from my first prompt—more frequently the more I practice—but I expect to need at least a few follow-ups.
偶尔我的第一个 prompt 就能得到很好的结果——随着我练习得越多,这种情况也越频繁——但我通常预期至少需要几次跟进。
I often wonder if this is one of the key tricks that people are missing—a bad initial result isn’t a failure, it’s a starting point for pushing the model in the direction of the thing you actually want.
我常常在想,这是否是人们错过的关键技巧之一——一个糟糕的初始结果不是失败,而是一个起点,用来推动模型朝你真正想要的方向前进。
An increasing number of LLM coding tools now have the ability to run that code for you. I’m slightly cautious about some of these since there’s a possibility of the wrong command causing real damage, so I tend to stick to the ones that run code in a safe sandbox. My favorites right now are:
越来越多的 LLM 编程工具现在都具备了为你运行代码的能力。我对其中一些持谨慎态度,因为错误的命令可能会造成真正的损害,所以我倾向于使用那些在安全沙箱中运行代码的工具。我目前最喜欢的是:
And if you’re willing to live a little more dangerously:
如果你愿意冒点险:
This run-the-code-in-a-loop pattern is so powerful that I chose my core LLM tools for coding based primarily on whether they can safely run and iterate on my code.
这种循环运行代码的模式如此强大,以至于我主要根据它们是否能安全地运行和迭代我的代码来选择我的核心 LLM 编程工具。
Andrej Karpathy coined the term vibe-coding just over a month ago, and it has stuck:
Andrej Karpathy 在一个多月前创造了 vibe-coding 这个词,并且它流传开来:
There’s a new kind of coding I call “vibe coding”, where you fully give in to the vibes, embrace exponentials, and forget that the code even exists. […] I ask for the dumbest things like “decrease the padding on the sidebar by half” because I’m too lazy to find it. I “Accept All” always, I don’t read the diffs anymore. When I get error messages I just copy paste them in with no comment, usually that fixes it.
有一种我称之为“vibe-coding”的新型编程方式,你完全凭感觉来,拥抱指数级增长,甚至忘记代码的存在。[…] 我会提一些最蠢的要求,比如“把侧边栏的内边距减少一半”,因为我懒得去找。我总是“全部接受”,不再阅读 diff。当我遇到错误信息时,我只是不加评论地复制粘贴进去,通常这样就能解决问题。
Andrej suggests this is “not too bad for throwaway weekend projects”. It’s also a fantastic way to explore the capabilities of these models—and really fun.
Andrej 建议这“对于一次性的周末项目来说还不错”。这也是探索这些模型能力的绝佳方式——而且真的很有趣。
The best way to learn LLMs is to play with them. Throwing absurd ideas at them and vibe-coding until they almost sort-of work is a genuinely useful way to accelerate the rate at which you build intuition for what works and what doesn’t.
学习 LLM 的最好方法就是和它们一起玩。向它们扔出荒谬的想法,然后进行 vibe-coding,直到它们差不多能用为止,这是一种真正有用的方式,可以加速你建立起对什么行得通、什么行不通的直觉。
I’ve been vibe-coding since before Andrej gave it a name! My simonw/tools GitHub repository has 77 HTML+JavaScript apps and 6 Python apps, and every single one of them was built by prompting LLMs. I have learned so much from building this collection, and I add to it at a rate of several new prototypes per week.
在 Andrej 给它命名之前,我就已经在 vibe-coding 了!我的 simonw/tools GitHub 仓库里有 77 个 HTML+JavaScript 应用和 6 个 Python 应用,每一个都是通过 prompt LLM 构建的。通过构建这个合集,我学到了太多东西,而且我每周都会增加几个新的原型。
You can try most of mine out directly on tools.simonwillison.net—a GitHub Pages published version of the repo. I wrote more detailed notes on some of these back in October in Everything I built with Claude Artifacts this week.
你可以在 tools.simonwillison.net 上直接尝试我的大部分工具——这是该仓库的 GitHub Pages 发布版本。我在去年十月的文章《我这周用 Claude Artifacts 构建的所有东西》中对其中一些做了更详细的记录。
If you want to see the transcript of the chat used for each one it’s almost always linked to in the commit history for that page—or visit the new colophon page for an index that includes all of those links.
如果你想看每个工具所用的聊天记录,几乎总能在那一页的 commit 历史中找到链接——或者访问新的版本说明页,那里有一个包含所有这些链接的索引。
While I was writing this article I had the idea for that tools.simonwillison.net/colophon page—I wanted something I could link to that showed the commit history of each of my tools in a more obvious way than GitHub.
在我写这篇文章的时候,我萌生了创建那个 tools.simonwillison.net/colophon 页面的想法——我想要一个可以链接到的页面,用比 GitHub 更直观的方式展示我每个工具的 commit 历史。
I decided to use that as an opportunity to demonstrate my AI-assisted coding process.
我决定以此为契机,展示我的 AI 辅助编程过程。
For this one I used Claude Code, because I wanted it to be able to run Python code directly against my existing tools repository on my laptop.
这次我用了 Claude Code,因为我希望它能直接在我笔记本电脑上对我现有的工具仓库运行 Python 代码。
Running the /cost
command at the end of my session showed me this:
在会话结束时运行 /cost
命令,我看到了这个:
> /cost
⎿ Total cost: $0.61
Total duration (API): 5m 31.2s
Total duration (wall): 17m 18.7s
The initial project took me just over 17 minutes from start to finish, and cost me 61 cents in API calls to Anthropic.
最初的项目从开始到完成花了我 17 分钟多一点,调用 Anthropic API 花了 61 美分。
I used the authoritarian process where I told the model exactly what I wanted to build. Here’s my sequence of prompts (full transcript here).
我用了专制的方式,明确告诉模型我想要构建什么。这是我的 prompt 序列(完整记录在这里)。
I started by asking for an initial script to gather the data needed for the new page:
我从请求一个初始脚本开始,用来收集新页面所需的数据:
Almost all of the HTML files in this directory were created using Claude prompts, and the details of those prompts are linked in the commit messages. Build a Python script that checks the commit history for each HTML file in turn and extracts any URLs from those commit messages into a list. It should then output a JSON file with this structure: {“pages”: {“name-of-file.html”: [“url”], {“name-of-file-2.html”: [“url1”, “url2”], …—as you can see, some files may have more than one URL in their commit history. The script should be called gather_links.py and it should save a JSON file called gathered_links.json
这个目录里几乎所有的 HTML 文件都是用 Claude 的 prompt 创建的,这些 prompt 的详情链接都在 commit message 里。写一个 Python 脚本,依次检查每个 HTML 文件的 commit 历史,并从这些 commit message 中提取出所有的 URL,放进一个列表里。然后,它应该输出一个这样结构的 JSON 文件:{“pages”: {“name-of-file.html”: [“url”], {“name-of-file-2.html”: [“url1”, “url2”], …——如你所见,有些文件在它们的 commit 历史中可能有多个 URL。这个脚本应该叫
gather_links.py
,并且应该把 JSON 文件保存为gathered_links.json
。
I really didn’t think very hard about this first prompt—it was more of a stream of consciousness that I typed into the bot as I thought about the initial problem.
我其实没怎么仔细思考这个第一个 prompt——它更像是我在思考初始问题时,随手敲给机器人的意识流。
I inspected the initial result and spotted some problems:
我检查了初始结果,发现了一些问题:
It looks like it just got the start of the URLs, it should be getting the whole URLs which might be to different websites—so just get anything that starts https:// and ends with whitespace or the end of the commit message
看起来它只获取了 URL 的开头部分,它应该获取完整的 URL,这些 URL 可能指向不同的网站——所以只要获取所有以 https:// 开头并以空白或 commit message 结尾的字符串就行。
Then I changed my mind—I wanted those full commit messages too:
然后我改主意了——我也想要完整的 commit message:
Update the script—I want to capture the full commit messages AND the URLs—the new format should be {“pages”: {“aria-live-regions.html”: {“commits”: [{“hash”: hash, “message”: message, “date”: iso formatted date], “urls”: [list of URLs like before]
更新脚本——我想要捕获完整的 commit message 和 URL——新格式应该是 {“pages”: {“aria-live-regions.html”: {“commits”: [{“hash”: 哈希值, “message”: 消息, “date”: iso 格式日期], “urls”: [和之前一样的 URL 列表]
Providing examples like this is a great shortcut to getting exactly what you want.
提供这样的例子是得到你想要的东西的一个绝佳捷径。
Note that at no point have I looked at the code it’s written in gather_links.py! This is pure vibe-coding: I’m looking at what it’s doing, but I’ve left the implementation details entirely up to the LLM.
注意,我自始至终都没有看过它在 gather_links.py 中写的代码!这是纯粹的 vibe-coding:我只看它在做什么,但把实现细节完全交给了 LLM。
The JSON looked good to me, so I said:
JSON 看起来不错,于是我说:
This is working great. Write me a new script called build_colophon.py which looks through that gathered JSON file and builds and saves an HTML page. The page should be mobile friendly and should list every page—with a link to that page—and for each one display the commit messages neatly (convert newlines to br and linkify URLs but no other formatting)—plus the commit message dates and links to the commits themselves which are in https://github.com/simonw/tools
这个工作得很好。给我写一个新脚本,叫
build_colophon.py
,它会遍历那个收集好的 JSON 文件,然后构建并保存一个 HTML 页面。这个页面应该是移动端友好的,并且应该列出每个页面——附上指向该页面的链接——对每一个页面,都要整洁地显示 commit message(将换行符转为 br,并将 URL 转为链接,不做其他格式化)——还要加上 commit message 的日期和指向 commit 本身的链接,它们在 https://github.com/simonw/tools。
Claude knows how GitHub URLs works, so telling it to link to the commits and providing the repo name was enough for it guess https://github.com/simonw/tools/commit/fd9daf885c924ba277806b3440457d52b0ad90a8
for those commit URLs.
Claude 知道 GitHub URL 的工作原理,所以告诉它链接到 commit 并提供仓库名称,就足以让它猜出那些 commit URL 的格式是 https://github.com/simonw/tools/commit/fd9daf885c924ba277806b3440457d52b0ad90a8
。
I tend to find Claude has good default taste when it comes to web page design—I said “the page should be mobile friendly” and left it at that.
我倾向于认为 Claude 在网页设计方面有不错的默认品味——我只说了“页面应该是移动端友好的”,然后就没再管了。
Claude churned away and built me a page that wasn’t right, so I said:
Claude 忙活了一阵,给我建了一个页面,但不对劲,于是我说:
it’s not working right. ocr.html had a bunch of commits but in colophon.html there is only one link and heading for the first commit and the rest are shown within that same block—there should be separate HTML chunks with links and formatted dates for each of the other commits. Also the neatly formatted date should include the HH:MM as well as the date
它工作得不对。ocr.html 有一堆 commit,但在 colophon.html 里,只有第一个 commit 有一个链接和标题,其余的都显示在同一个块里——其他的每个 commit 都应该有独立的 HTML 块,包含链接和格式化后的日期。另外,整洁格式化的日期也应该包括 HH:MM 以及年月日。
It fixed the bug all on its own, leaving just two changes I decided to make:
它自己修复了这个 bug,只剩下两个我决定要做的改动:
it’s almost perfect, but each page should have the commits displayed in the opposite order—oldest first
差不多完美了,但是每个页面的 commit 应该以相反的顺序显示——最旧的在前面。
And then:
然后:
One last change—the pages are currently listed alphabetically, lets instead list them with the most recently modified at the top
最后一个改动——页面目前是按字母顺序排列的,让我们改成按最近修改时间排序,最新的在最上面。
And that was the entire project! Here’s build_colophon.py, and the page it generated came out looking pretty good:
整个项目就这样完成了!这是 build_colophon.py,它生成的页面看起来还挺不错:
There was one remaining task: I needed to deploy the new colophon as part of my site, but I didn’t want to check that new colophon.html
page into the repository itself. I wanted a custom GitHub Pages build process.
还剩下一个任务:我需要把新的版本说明页部署到我的网站上,但我不想把那个新的 colophon.html
文件提交到仓库本身。我想要一个自定义的 GitHub Pages 构建流程。
I started a brand new Claude Code session (clearing the slate) to see if that was something Claude could figure out too:
我开启了一个全新的 Claude Code 会话(清空重来),看看 Claude 是否也能搞定这件事:
This repo is deployed using GitHub Pages. I want to modify the build slightly—before deploying to GitHub Pages it should run python gather_links.py which will generate a file called gathered_links.json—then it should run python build_colophon.py which will use that file to create colophon.html—I want both gathered_links.json and colophon.html to be published to GitHub pages but I do not want them checked into the repo itself
这个仓库是用 GitHub Pages 部署的。我想稍微修改一下构建流程——在部署到 GitHub Pages 之前,它应该运行
python gather_links.py
,这会生成一个叫gathered_links.json
的文件——然后它应该运行python build_colophon.py
,用那个文件来创建colophon.html
——我希望gathered_links.json
和colophon.html
都被发布到 GitHub Pages,但我不希望它们被提交到仓库本身。
Unlike the first time, this time I watched what it was doing very closely—I don’t know how to customize GitHub Pages builds in this way and I wanted to both learn how to do it and stay cautious that it might hallucinate details and fail at the task.
和第一次不同,这次我非常仔细地观察它在做什么——我不知道如何以这种方式自定义 GitHub Pages 的构建,我既想学习如何做,又想保持警惕,怕它会幻觉出细节导致任务失败。
It told me it wanted to create this file and asked for my permission:
它告诉我它想创建这个文件,并请求我的许可:
name: Deploy to GitHub Pages
on:
# Run on push to main branch
push:
branches: [ main ]
# Allow manual trigger
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # Need full history for git log in gather_links.py
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- name: Generate links and build colophon
run: |
python gather_links.py
python build_colophon.py
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Build with Jekyll
uses: actions/jekyll-build-pages@v1
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
I guess that’s right? I appreciated that comment about “Need full history for git log in gather_links.py”—that’s something I could easily have missed.
我猜这是对的?我很欣赏那句关于“gather_links.py 中的 git log 需要完整的历史记录”的注释——这是我很容易忽略的地方。
Then it said it wanted to add those files to .gitignore
—sounds like a good idea.
然后它说想把那些文件添加到 .gitignore
——听起来是个好主意。
It spat out this summary of its actions so far:
它输出了到目前为止的行动总结:
I’ve created a GitHub Actions workflow for GitHub Pages that:
- Creates a .github/workflows/pages.yml file that:
- Runs on pushes to main branch
- Checkouts the full git history (needed for gather_links.py)
- Sets up Python 3.12
- Runs both scripts in sequence to generate the files
- Builds and deploys to GitHub Pages
- Created .gitignore file to prevent the generated files from being committed
Now when you push to main, GitHub Actions will:
- Run the gather_links.py script to create gathered_links.json
- Run build_colophon.py to create colophon.html
- Deploy both files (and all other files) to GitHub Pages
I checked to see how much it had cost:
我检查了一下花了多少钱:
> /cost
⎿ Total cost: $0.1788
Total duration (API): 44.6s
Total duration (wall): 10m 18.6s
So 17 cents and 45 seconds using the Claude API. (I got distracted, hence the 10m of total time.) Here’s the full transcript.
所以用了 17 美分和 45 秒的 Claude API。(我分心了,所以总时长是 10 分钟。)这是完整的记录。
The code didn’t look like it would irreversibly break anything, so I pushed it to GitHub to see what would happen.
代码看起来不会造成不可逆的破坏,所以我把它推送到 GitHub,看看会发生什么。
… and it worked! My new colophon page was live.
……然后它成功了!我的新版本说明页上线了。
There’s a catch. I watched the GitHub Actions interface while it was running and something didn’t look right:
但有个问题。我看着 GitHub Actions 界面运行时,发现有些不对劲:
I was expecting that “Test” job, but why were there two separate deploys?
我预料到了那个“Test”任务,但为什么会有两个独立的部署?
I had a hunch that the previous, default Jekyll deploy was still running, while the new deploy ran at the same time—and it was pure luck of the timing that the new script finished later and over-wrote the result of the original.
我有一种预感,之前的默认 Jekyll 部署仍在运行,而新的部署同时进行——纯粹是运气好,新脚本完成得晚一些,覆盖了原始部署的结果。
It was time to ditch the LLMs and read some documentation!
是时候抛开 LLM,去读一些文档了!
I found this page on Using custom workflows with GitHub Pages but it didn’t tell me what I needed to know.
我找到了这篇关于在 GitHub Pages 中使用自定义工作流的页面,但它没有告诉我需要知道的信息。
On another hunch I checked the GitHub Pages settings interface for my repo and found this option:
凭着另一个直觉,我检查了我仓库的 GitHub Pages 设置界面,发现了这个选项:
My repo was set to “Deploy from a branch”, so I switched that over to “GitHub Actions”.
我的仓库被设置为“从分支部署”,所以我把它切换到了“GitHub Actions”。
I manually updated my README.md
to add a link to the new Colophon page in this commit, which triggered another build.
我手动更新了我的 README.md
,在这个 commit 中添加了指向新版本说明页的链接,这触发了另一次构建。
This time only two jobs ran, and the end result was the correctly deployed site:
这次只运行了两个任务,最终结果是正确部署的网站:
(I later spotted another bug—some of the links inadvertently included <br>
tags in their href=
, which I fixed with another 11 cent Claude Code session.)
(我后来又发现了一个 bug——有些链接的 href=
中无意中包含了 <br>
标签,我通过另一次花费 11 美分的 Claude Code 会话 修复了它。)
Update: I improved the colophon further by adding AI-generated descriptions of the tools.
更新:我通过添加 AI 生成的工具描述进一步改进了版本说明页。
I got lucky with this example because it helped illustrate my final point: expect to need to take over.
我这次的例子很幸运,因为它帮助阐明了我的最后一点:要预料到需要自己接管。
LLMs are no replacement for human intuition and experience. I’ve spent enough time with GitHub Actions that I know what kind of things to look for, and in this case it was faster for me to step in and finish the project rather than keep on trying to get there with prompts.
LLM 无法替代人类的直觉和经验。我在 GitHub Actions 上花了足够多的时间,知道要注意什么样的事情,在这种情况下,由我介入并完成项目比继续用 prompt 尝试要快得多。
My new colophon page took me just under half an hour from conception to finished, deployed feature.
我的新版本说明页从构思到完成部署,花了我不到半小时。
I’m certain it would have taken me significantly longer without LLM assistance—to the point that I probably wouldn’t have bothered to build it at all.
我敢肯定,如果没有 LLM 的帮助,我会花更长的时间——甚至可能根本就不会费心去构建它。
This is why I care so much about the productivity boost I get from LLMs so much: it’s not about getting work done faster, it’s about being able to ship projects that I wouldn’t have been able to justify spending time on at all.
这就是为什么我如此看重 LLM 带来的生产力提升:这不仅仅是为了更快地完成工作,更是为了能够交付那些我原本认为根本不值得花时间去做的项目。
I wrote about this in March 2023: AI-enhanced development makes me more ambitious with my projects. Two years later that effect shows no sign of wearing off.
我在 2023 年 3 月写过这个:AI 增强开发让我对我的项目更有野心。两年后,这种影响丝毫没有减弱的迹象。
It’s also a great way to accelerate learning new things—today that was how to customize my GitHub Pages builds using Actions, which is something I’ll certainly use again in the future.
这也是加速学习新事物的好方法——今天我学了如何使用 Actions 自定义我的 GitHub Pages 构建,这肯定是我将来会再次用到的东西。
The fact that LLMs let me execute my ideas faster means I can implement more of them, which means I can learn even more.
LLM 让我能更快地执行我的想法,这意味着我能实现更多的想法,也就意味着我能学到更多。
Could anyone else have done this project in the same way? Probably not! My prompting here leaned on 25+ years of professional coding experience, including my previous explorations of GitHub Actions, GitHub Pages, GitHub itself and the LLM tools I put into play.
其他人能以同样的方式完成这个项目吗?可能不行!我在这里的 prompt 依赖于我 25 年以上的专业编程经验,包括我之前对 GitHub Actions、GitHub Pages、GitHub 本身以及我所使用的 LLM 工具的探索。
I also knew that this was going to work. I’ve spent enough time working with these tools that I was confident that assembling a new HTML page with information pulled from my Git history was entirely within the capabilities of a good LLM.
我也知道这会成功。我和这些工具打交道的时间足够长,所以我确信,用从 Git 历史中提取的信息来组装一个新的 HTML 页面,完全在一个好的 LLM 的能力范围之内。
My prompts reflected that—there was nothing particularly novel here, so I dictated the design, tested the results as it was working and occasionally nudged it to fix a bug.
我的 prompt 也反映了这一点——这里并没有什么特别新颖的东西,所以我规定了设计,在它工作时测试结果,并偶尔推动它修复一个 bug。
If I was trying to build a Linux kernel driver—a field I know virtually nothing about—my process would be entirely different.
如果我试图构建一个 Linux 内核驱动程序——一个我几乎一无所知的领域——我的流程会完全不同。
If the idea of using LLMs to write code for you still feels deeply unappealing, there’s another use-case for them which you may find more compelling.
如果用 LLM 为你写代码这个想法对你来说仍然毫无吸引力,它们还有另一个你可能会觉得更有说服力的用例。
Good LLMs are great at answering questions about code.
好的 LLM 在回答关于代码的问题方面非常出色。
This is also very low stakes: the worst that can happen is they might get something wrong, which may take you a tiny bit longer to figure out. It’s still likely to save you time compared to digging through thousands of lines of code entirely by yourself.
这也是非常低风险的:最坏的情况就是它们可能会搞错一些东西,这可能会让你多花一点点时间去搞清楚。但与完全靠自己翻阅数千行代码相比,它仍然很可能为你节省时间。
The trick here is to dump the code into a long context model and start asking questions. My current favorite for this is the catchily titled gemini-2.0-pro-exp-02-05
, a preview of Google’s Gemini 2.0 Pro which is currently free to use via their API.
这里的诀窍是把代码扔进一个长上下文模型,然后开始提问。我目前最喜欢用的是名字很上口的 gemini-2.0-pro-exp-02-05
,这是 Google Gemini 2.0 Pro 的一个预览版,目前可以通过他们的 API 免费使用。
I used this trick just the other day. I was trying out a new-to-me tool called monolith, a CLI tool written in Rust which downloads a web page and all of its dependent assets (CSS, images etc) and bundles them together into a single archived file.
我前几天就用了这个技巧。我当时在试用一个对我来说很新的工具,叫做 monolith,这是一个用 Rust 写的 CLI 工具,它可以下载一个网页及其所有依赖资源(CSS、图片等),并将它们打包成一个单一的存档文件。
I was curious as to how it worked, so I cloned it into my temporary directory and ran these commands:
我很好奇它是如何工作的,所以我把它克隆到我的临时目录里,然后运行了这些命令:
cd /tmp
git clone https://github.com/Y2Z/monolith
cd monolith
files-to-prompt . -c | llm -m gemini-2.0-pro-exp-02-05 \
-s 'architectural overview as markdown'
I’m using my own files-to-prompt tool (built for me by Claude 3 Opus last year) here to gather the contents of all of the files in the repo into a single stream. Then I pipe that into my LLM tool and tell it (via the llm-gemini plugin) to prompt Gemini 2.0 Pro with a system prompt of “architectural overview as markdown”.
我在这里使用了我自己的 files-to-prompt 工具(是 Claude 3 Opus 去年为我构建的),用来将仓库中所有文件的内容收集到一个数据流中。然后我把它通过管道传给我自己的 LLM 工具,并告诉它(通过 llm-gemini 插件)用“以 markdown 格式提供架构概览”这个系统 prompt 来提示 Gemini 2.0 Pro。
This gave me back a detailed document describing how the tool works—which source files do what and, crucially, which Rust crates it was using. I learned that it used reqwest
, html5ever
, markup5ever_rcdom
and cssparser
and that it doesn’t evaluate JavaScript at all, an important limitation.
这给了我一份详细的文档,描述了这个工具的工作原理——哪些源文件做了什么,以及至关重要的是,它使用了哪些 Rust crates。我了解到它使用了 reqwest
、html5ever
、markup5ever_rcdom
和 cssparser
,而且它根本不执行 JavaScript,这是一个重要的限制。
I use this trick several times a week. It’s a great way to start diving into a new codebase—and often the alternative isn’t spending more time on this, it’s failing to satisfy my curiosity at all.
我每周都会用这个技巧好几次。这是开始深入研究一个新代码库的好方法——而且通常,如果不这样做,替代方案不是花更多时间,而是根本无法满足我的好奇心。
I included three more examples in this recent post.
我在最近的这篇文章中还包含了另外三个例子。
2025-07-11 08:00:09
原文: https://simonwillison.net/2025/Apr/19/claude-code-best-practices/
作者: Simon Willison
译者: Gemini 2.5 Pro
Claude Code: Best practices for agentic coding (via) Extensive new documentation from Anthropic on how to get the best results out of their Claude Code CLI coding agent tool, which includes this fascinating tip:
Claude Code:agentic 编程最佳实践 (来源) Anthropic 发布了一份详尽的新文档,介绍了如何从他们的 Claude Code CLI 编程 agent 工具中获得最佳效果。其中包含了一个很有意思的技巧:
We recommend using the word “think” to trigger extended thinking mode, which gives Claude additional computation time to evaluate alternatives more thoroughly. These specific phrases are mapped directly to increasing levels of thinking budget in the system: “think” < “think hard” < “think harder” < “ultrathink.” Each level allocates progressively more thinking budget for Claude to use.
我们建议使用“think”这个词来触发扩展思考模式,这能给 Claude 更多计算时间,从而更彻底地评估不同方案。这些特定短语直接对应系统中不断增加的思考预算等级:“think” < “think hard” < “think harder” < “ultrathink”。每个等级都会为 Claude 分配更多思考预算。
Apparently ultrathink is a magic word!
看来 ultrathink 是个魔法咒语!
I was curious if this was a feature of the Claude model itself or Claude Code in particular. Claude Code isn’t open source but you can view the obfuscated JavaScript for it, and make it a tiny bit less obfuscated by running it through Prettier. With Claude’s help I used this recipe:
我很好奇,这究竟是 Claude 模型本身的功能,还是 Claude Code 特有的功能。Claude Code 并不开源,但你可以查看它混淆过的 JavaScript 代码,然后用 Prettier 跑一遍,让代码的可读性高一点点。在 Claude 的帮助下,我用了下面这套方法:
mkdir -p /tmp/claude-code-examine
cd /tmp/claude-code-examine
npm init -y
npm install @anthropic-ai/claude-code
cd node_modules/@anthropic-ai/claude-code
npx prettier --write cli.js
Then used ripgrep to search for “ultrathink”:
然后用 ripgrep 搜索 “ultrathink”:
rg ultrathink -C 30
And found this chunk of code:
于是找到了下面这段代码:
let B = W.message.content.toLowerCase();
if (
B.includes("think harder") ||
B.includes("think intensely") ||
B.includes("think longer") ||
B.includes("think really hard") ||
B.includes("think super hard") ||
B.includes("think very hard") ||
B.includes("ultrathink")
)
return (
l1("tengu_thinking", { tokenCount: 31999, messageId: Z, provider: G }),
31999
);
if (
B.includes("think about it") ||
B.includes("think a lot") ||
B.includes("think deeply") ||
B.includes("think hard") ||
B.includes("think more") ||
B.includes("megathink")
)
return (
l1("tengu_thinking", { tokenCount: 1e4, messageId: Z, provider: G }), 1e4
);
if (B.includes("think"))
return (
l1("tengu_thinking", { tokenCount: 4000, messageId: Z, provider: G }),
4000
);
let B = W.message.content.toLowerCase();
if (
B.includes("think harder") ||
B.includes("think intensely") ||
B.includes("think longer") ||
B.includes("think really hard") ||
B.includes("think super hard") ||
B.includes("think very hard") ||
B.includes("ultrathink")
)
return (
l1("tengu_thinking", { tokenCount: 31999, messageId: Z, provider: G }),
31999
);
if (
B.includes("think about it") ||
B.includes("think a lot") ||
B.includes("think deeply") ||
B.includes("think hard") ||
B.includes("think more") ||
B.includes("megathink")
)
return (
l1("tengu_thinking", { tokenCount: 1e4, messageId: Z, provider: G }), 1e4
);
if (B.includes("think"))
return (
l1("tengu_thinking", { tokenCount: 4000, messageId: Z, provider: G }),
4000
);
So yeah, it looks like “ultrathink” is a Claude Code feature - presumably that 31999 is a number that affects the token thinking budget, especially since “megathink” maps to 1e4 tokens (10,000) and just plain “think” maps to 4,000.
所以,没错,看起来 “ultrathink” 是 Claude Code 的一个特性——那个 31999 很可能是一个影响 token thinking budget 的数字,尤其是 “megathink” 对应 1e4 (10,000) tokens,而普通的 “think” 只对应 4,000。