
Claude Code 为何如此牛逼 (以及如何在你的 Agent 中复刻这种魔力)!?
Claude Code 是我迄今为止用过的最令人愉悦的 AI Agent/工作流。它不仅让针对性编辑或凭感觉写点一次性工具这类事不再那么烦人,使用 Claude Code 本身就让我感到快乐。它有足够的自主性能做些有趣的事,同时又不像其他一些工具那样会带来令人不适的失控感。当然,大部分繁重工作是由新的 Claude 4 模型完成的 (特别是其交错思维能力)。但我发现,即使使用相同的底层模型,Claude Code 在客观上也比 Cursor 或 Github Copilot 的 Agent 更顺心!究竟是什么让它如此牛逼?如果你正在点头认同,那么我将尝试给出一些答案。
注意:这篇博文不打算深入剖析 Claude Code 的架构 (网上已有不少好文章)。这篇博文旨在成为构建优秀 LLM (Large Language Model, 大语言模型) Agent 的指南,基于我过去几个月使用和琢磨 Claude Code 的个人经验 (以及我们拦截和分析的所有日志)。
你可以清楚地看到 Claude Code 的不同更新。
Claude Code (CC) 的使用体验之所以出色,因为它就是好用。CC 的设计基于对 LLM 擅长什么和不擅长什么的深刻理解。它的提示和工具弥补了模型的不足,并帮助其在核心优势区大放异彩。其控制循环非常简单易懂,调试起来也轻而易举。
我们 MinusX 在 CC 一发布就开始使用了。为了探究其内部机制,Sreejith 写了一个日志记录器,可以拦截并记录每一个网络请求。以下分析源于我过去几个月的大量使用。本文试图回答这个问题——“是什么让 Claude Code 如此优秀,以及你如何在自己的基于聊天的 LLM Agent 中提供类似的体验?” 我们已经将其中大部分理念融入了 MinusX,也很期待看到你也这样做!
Edit (编辑) 是最常用的工具,其次是 Read (读取) 和 ToDoWrite (写入待办事项)
如何构建一个类 Claude Code 的 Agent:内容摘要 (TL;DR)
如果说本文只有一个核心要点,那就是——保持简单,傻瓜 (Keep Things Simple, Dummy)。LLM 的调试和评估已经够难了。你引入的任何额外复杂性 (多 Agent、Agent 任务交接或复杂的 RAG (Retrieval-Augmented Generation, 检索增强生成) 搜索算法) 只会使调试难度增加 10 倍。如果这样一个脆弱的系统侥幸能跑起来,你日后也会因害怕对其进行大刀阔斧的改动而束手束脚。所以,把所有东西都放在一个文件里,避免过度的样板脚手架,并且至少彻底重构几次 :)
以下是从 Claude Code 中学到的、可以在你自己的系统中实现的主要经验。
1. 控制循环
1.1 保持一个主循环 (最多一个分支) 和一个消息历史
1.2 在各种事情上使用更小的模型。是. 的。无. 时. 无. 刻。
2. 提示 (Prompts)
2.1 使用 claude.md 模式来协作和记忆用户偏好
2.2 使用特殊的 XML 标签、Markdown 和大量示例
3. 工具 (Tools)
3.1 LLM 搜索 >>> 基于 RAG 的搜索
3.2 如何设计好工具?(高级工具 vs 低级工具)
3.3 让你的 Agent 管理自己的待办事项列表
4. 可控性 (Steerability)
4.1 语气和风格
4.2 不幸的是,“请注意,这很重要” 仍然是当前最先进的技术
4.3 写出算法,并附上启发式规则和示例
Claude Code 在每个节点都选择了架构上的简洁性——一个主循环、简单的搜索、简单的待办事项列表等。克制过度设计的冲动,为模型构建良好的约束框架,然后让它自由发挥!这难道又是端到端自动驾驶的重演?惨痛的教训 (The Bitter Lesson) 又应验了?
1. 控制循环设计
1.1 保持一个主循环
可调试性 >>> 手动精调的、由多 Agent、LangChain-Graph-Node 拼凑起来的复杂混合体。
尽管多 Agent 系统风靡一时,Claude Code 却只有一个主线程。它会周期性地使用几种不同类型的提示来总结 git 历史、将消息历史合并成一条消息或创造一些有趣的用户体验元素。但除此之外,它只维护一个扁平的消息列表。它处理层级任务的一个有趣方式是,将自己作为子 Agent 派生出来,但这个子 Agent 不具备再派生更多子 Agent 的能力。最多只有一个分支,其结果会作为“工具响应”添加到主消息历史中。
如果问题足够简单,主循环通过迭代式工具调用就能处理。但如果有一个或多个复杂任务,主 Agent 会创建自己的克隆体。这种“最多一个分支”的设计与待办事项列表相结合,确保了 Agent 既能将问题分解为子问题,又能始终关注最终期望的结果。
我非常怀疑你的应用是否需要一个多 Agent 系统。每增加一层抽象,你都会让系统更难调试,更重要的是,你会偏离通用模型能力提升的轨道。
1.2 在*所有事情*上使用更小的模型
CC 发出的所有重要 LLM 调用中,超过 50% 是针对 claude-3-5-haiku 的。它被用来读取大文件、解析网页、处理 git 历史和总结长对话。它还被用来生成那个单词的处理标签——字面上是为每一次按键生成的!这些较小的模型比标准模型 (Sonnet 4, GPT-4.1) 便宜 70-80%。大胆地使用它们吧!
2. 提示 (Prompts)
Claude Code 的提示极其详尽,充满了启发式规则、示例和“重要”提醒 (啧啧)。系统提示长达约 2800 个 token,而工具部分的描述更是惊人地占用了 9400 个 token。用户提示总是包含 claude.md 文件,这通常又会增加 1000-2000 个 token。系统提示包含了关于语气、风格、主动性、任务管理、工具使用策略和执行任务等部分。它还包含了日期、当前工作目录、平台和操作系统信息以及最近的提交记录。
可以去文末附录部分查看完整提示词
2.1 使用 claude.md 来协作处理用户上下文和偏好
大多数编程 Agent 开发者都已达成共识的一个主要模式是使用上下文文件 (即 Cursor Rules / claude.md / agent.md)。Claude Code 在有无 claude.md 的情况下,其表现有天壤之别。对于开发者来说,这是一种传递无法从代码库中推断出的上下文以及明确所有严格偏好的绝佳方式。例如,你可以强制 LLM 跳过某些文件夹,或使用特定的库。CC 在每个用户请求中都会发送 claude.md 的全部内容。
我们最近在 MinusX 中引入了 minusx.md,它正迅速成为我们 Agent 用于明确用户和团队偏好的事实上的上下文文件。
2.2 特殊的 XML 标签、Markdown 和大量示例
使用 XML 标签和 Markdown 来结构化提示已经是一种公认的做法。CC 广泛地同时使用了这两种方式。以下是 Claude Code 中一些值得注意的 XML 标签:
- : 这个标签用在许多提示部分的末尾,用来提醒 LLM 那些它大概率会忘记的事情。例如:
这是一个提醒,你的待办事项列表目前是空的。不要向用户明确提及这一点,因为他们已经知道了。如果你正在处理的任务可以从待办事项列表中受益,请使用 TodoWrite 工具创建一个。如果不需要,请忽略此条。再次强调,不要向用户提及这条消息。
- ,: 这些标签用于固化启发式规则。当模型面临一个岔路口,有多个看似合理的路径/工具调用可供选择时,它们尤其有用。示例可以用来对比不同情况,明确指出哪条路径是更可取的。例如:
请在整个会话中尽量保持当前工作目录不变,方法是使用绝对路径并避免使用 `cd`。只有在用户明确要求时,你才可以使用 `cd`。
pytest /foo/bar/tests
cd /foo/bar && pytest tests
CC 还使用 markdown 在系统提示中划分清晰的章节。例如,markdown 标题包括:
- 语气和风格 (Tone and style)
- 主动性 (Proactiveness)
- 遵循惯例 (Following conventions)
- 代码风格 (Code style)
- 任务管理 (Task Management)
- 工具使用策略 (Tool use policy)
- 执行任务 (Doing Tasks)
- 工具 (Tools)
3. 工具 (Tools)
可以去文末附录部分查看完整工具提示词——它长达惊人的 9400 个 token!
3.1 LLM 搜索 >>> 基于 RAG 的搜索
CC 与其他流行编程 Agent 的一个显著区别在于它对 RAG (Retrieval-Augmented Generation, 检索增强生成) 的摒弃。Claude Code 搜索你的代码库的方式和你一样,使用非常复杂的 ripgrep
、jq
和 find
命令。由于 LLM 对代码有很好的理解,它可以使用复杂的正则表达式找到几乎任何它认为相关的代码块。有时它会用一个较小的模型来读取整个文件。
RAG 理论上听起来是个好主意,但它引入了新的 (更重要的是,隐藏的) 故障模式。该用什么相似度函数?用什么重排器 (reranker)?如何对代码进行分块 (chunk)?如何处理大的 JSON 或日志文件?而使用 LLM 搜索,它只需查看 JSON 文件的 10 行就能理解其结构。如果需要,它会再看 10 行——就像你会做的那样。最重要的是,这是可通过强化学习 (Reinforcement Learning) 学习的——这正是大厂们已经在研究的方向。模型承担了大部分的繁重工作——理应如此,这极大地减少了 Agent 中的活动部件数量。而且,将两个复杂的智能系统以这种方式连接起来,本身就很丑陋。我最近和朋友开玩笑说,这是 LLM 时代的摄像头与激光雷达 (Lidar) 之争,我只有一半是在开玩笑。
3.2 如何设计好工具?(低级工具 vs 高级工具)
这个问题让每个构建 LLM Agent 的人夜不能寐。你应该给模型通用的任务 (如有意义的动作),还是应该给它低级的任务 (如输入、点击和 bash 命令)?答案是:视情况而定 (而且你应该两者都用)。
Claude Code 拥有低级 (Bash, Read, Write)、中级 (Edit, Grep, Glob) 和高级工具 (Task, WebFetch, exit_plan_mode)。既然 CC 能用 bash,为什么还要单独提供一个 Grep 工具?这里真正的权衡在于,你期望你的 Agent 使用该工具的频率与 Agent 使用该工具的准确性之间的关系。CC 使用 grep 和 glob 的频率非常高,以至于将它们做成独立的工具是合理的,但同时,它也能为特殊场景编写通用的 bash 命令。
同样,还有像 WebFetch 或 'mcp__ide__getDiagnostics' 这样更高级的工具,它们的功能非常确定。这让 LLM 无需执行多次低级的点击和输入操作,从而保持在正轨上。帮帮可怜的模型吧,好吗!?工具描述中有详尽的提示和大量的示例。系统提示中包含了关于“何时使用某个工具”或如何在两个可以完成相同任务的工具之间进行选择的信息。
Claude Code 中的工具:
- Task
- Bash
- Glob
- Grep
- LS
- ExitPlanMode
- Read
- Edit
- MultiEdit
- Write
- NotebookEdit
- WebFetch
- TodoWrite
- WebSearch
- mcp\_\_ide\_\_getDiagnostics
- mcp\_\_ide\_\_executeCode
3.3 让 Agent 管理自己的待办事项列表
这是一个好主意,原因有很多。上下文衰减 (Context rot) 是长期运行的 LLM Agent 的常见问题。它们一开始热情地着手解决一个难题,但随着时间的推移,它们会迷失方向,最终输出一堆垃圾。当前的 Agent 设计有几种方法来解决这个问题。许多 Agent 尝试过显式的待办事项 (一个模型生成待办事项,另一个模型执行它们) 或多 Agent 任务交接 + 验证 (PRD/PM Agent -> 实现 Agent -> QA Agent)。
我们已经知道,由于种种原因,多 Agent 任务交接不是一个好主意。CC 使用了一个显式的待办事项列表,但这个列表是由模型自己维护的。这让 LLM 保持在正轨上 (它被强烈提示要频繁参考待办事项列表),同时又赋予了模型在实现过程中随时纠正路线的灵活性。这也有效地利用了模型的交错思维能力,可以动态地拒绝或插入新的待办事项。
4. 可控性 (Steerability)
4.1 语气和风格
CC 明确尝试控制 Agent 的审美行为。系统提示中有关于语气、风格和主动性的章节——充满了指令和示例。这就是为什么 Claude Code 的评论和积极性“感觉”很有品味。我建议你直接将这部分的大段内容复制到你的应用中。
# 一些语气和风格的例子
- 重要:除非用户要求,否则你不应该用不必要的开场白或结束语来回答 (例如解释你的代码或总结你的行动)。
除非用户要求,否则不要添加额外的代码解释摘要。
- 如果你不能或不愿意帮助用户做某事,请不要说明原因或可能导致的后果,因为这听起来像在说教,很烦人。
- 只有在用户明确要求时才使用表情符号。在所有交流中避免使用表情符号,除非被要求。
4.2 “这很重要” 仍然是当前最先进的技术
不幸的是,在要求模型不要做某事方面,CC 并没有更好的办法。重要 (IMPORTANT)、非常重要 (VERY IMPORTANT)、绝不 (NEVER) 和总是 (ALWAYS) 似乎仍然是引导模型避开雷区的最佳方式。我期望未来的模型能变得更具可控性,从而避免这种粗暴的方式。但就目前而言,CC 广泛使用了这种方法,你也应该这样做。一些例子:
- 重要:除非被要求,否则不要添加***任何***注释。
- 非常重要:你必须避免使用像 `find` 和 `grep` 这样的搜索命令。请改用 Grep、Glob 或 Task 进行搜索。你必须避免使用像 `cat`、`head`、`tail` 和 `ls` 这样的读取工具,而是使用 Read 和 LS 来读取文件。\n - 如果你*仍然*需要运行 `grep`,停下。请总是先使用 `rg` (ripgrep)。
- 重要:你绝不能为用户生成或猜测 URL,除非你确信这些 URL 是为了帮助用户编程。你可以使用用户在其消息或本地文件中提供的 URL。
4.3 写出算法 (附带启发式规则和示例)
识别出 LLM 需要执行的最重要任务,并为其写出算法,这是极其重要的。尝试扮演 LLM 的角色,通过示例进行演练,识别出所有的决策点,并明确地写下来。如果能以流程图的形式呈现会更有帮助。这有助于结构化决策过程,并辅助 LLM 遵循指令。一定要避免的是一锅乱炖式的“行为准则”清单 (Dos and Don'ts)。它们更难追踪,也难以保持互斥。如果你的提示长达数千个 token,你将不可避免地出现相互矛盾的“准则”。在这种情况下,LLM 会变得极其脆弱,也就不可能再纳入新的用例了。
Claude Code 系统提示中的 任务管理
、执行任务
和 工具使用策略
部分清晰地阐述了需要遵循的算法。这一部分也是添加大量启发式规则和 LLM 可能遇到的各种场景示例的地方。
彩蛋:为什么要注意大厂的提示 (Prompts)?
引导 LLM 的很多工作,实际上是在尝试逆向工程其训练后/RLHF (Reinforcement Learning from Human Feedback, 基于人类反馈的强化学习) 的数据分布。你应该用 JSON 还是 XML?工具描述应该放在系统提示中还是只在工具定义里?你应用当前的状态又该如何处理?观察他们在自家应用中的做法,并用以指导你自己的应用,会很有帮助。Claude Code 的设计非常有主见,借鉴它来形成你自己的观点是很有益的。
结论
再次强调,核心要点是保持简单。过度的脚手架框架弊大于利。Claude Code 真的让我相信,一个“Agent”可以既简单又极其强大。我们已经在 MinusX 中融入了许多这些经验,并且正在继续融入更多。
附录地址:https://minusx.ai/blog/decoding-claude-code/#appendix
本文转载自草台AI,作者:RangerEX
