<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Claude 中文知识站</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://claude.cocoloop.cn/</id>
  <link href="https://claude.cocoloop.cn/" rel="alternate"/>
  <link href="https://claude.cocoloop.cn/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Claude 中文知识站</rights>
  <subtitle>从入门到精通 · Claude 官方能力全景指南</subtitle>
  <title>Claude 中文知识站</title>
  <updated>2026-04-21T15:01:00.000Z</updated>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="Prompt 工程" scheme="https://claude.cocoloop.cn/categories/Prompt-%E5%B7%A5%E7%A8%8B/"/>
    <category term="Claude" scheme="https://claude.cocoloop.cn/tags/Claude/"/>
    <category term="Prompt" scheme="https://claude.cocoloop.cn/tags/Prompt/"/>
    <category term="Chain of Thought" scheme="https://claude.cocoloop.cn/tags/Chain-of-Thought/"/>
    <category term="Extended Thinking" scheme="https://claude.cocoloop.cn/tags/Extended-Thinking/"/>
    <content>
      <![CDATA[<p>大概一年半前我做过一个医疗 FAQ 智能客服，核心任务是”从 800 条标准问答里召回最相关的 3 条”。0-shot + 结构化 prompt 跑下来准确率 92%，客户验收通过。</p><p>上线一个月后我想着优化一下，加了 <code>&lt;thinking&gt;</code> 让模型”先分析问题意图再选择答案”。心想这肯定更稳吧，结果一跑——84%。</p><p>整整掉了 8 个点。我反复看了很多 case 才搞明白：这类任务本质上是”语义相似度召回”，模型在 embedding 层面其实已经做对了判断，你硬让它”逐步推理”，它反而会被自己推出来的中间结论带偏。</p><p>这事给我上了一课。CoT（Chain of Thought）不是越多越好，更不是万能。这篇说一下我现在怎么判断。</p><h2 id="什么时候-CoT-是神器"><a href="#什么时候-CoT-是神器" class="headerlink" title="什么时候 CoT 是神器"></a>什么时候 CoT 是神器</h2><p>先说有用的场景。我经验里 CoT 确实能显著提升效果的几类任务：</p><p><strong>一、数学和数值推理。</strong>不管多简单，只要涉及”一步算错全盘错”的计算，让它一步步来就对了。实测一个三步应用题，直接问正确率 73%，加 <code>&lt;thinking&gt;</code> 后 94%。这个提升是稳的。</p><p><strong>二、多跳问答。</strong>“根据 A 文档里的 X 和 B 文档里的 Y 推出 Z”这种。模型需要先把中间结论落到纸面，不然容易幻觉。</p><p><strong>三、合规 &#x2F; 规则判断。</strong>比如”判断这份合同是否违反下面 7 条规则”。你不让它一条一条过，它会漏。让它显式地说”规则 1: 符合；规则 2: 不符合，因为…”准确率直接上去。</p><p><strong>四、复杂决策树。</strong>客服分流、工单分类有 20 多种类别且存在嵌套层级的时候，CoT 能让模型稳得多。</p><p>这些任务的共同点：<strong>有可拆分的中间步骤、有客观验证路径、犯错代价高。</strong>给它展开的空间，它会自己纠偏。</p><h2 id="什么时候-CoT-反而添乱"><a href="#什么时候-CoT-反而添乱" class="headerlink" title="什么时候 CoT 反而添乱"></a>什么时候 CoT 反而添乱</h2><p>这些是我踩过坑的：</p><p><strong>一、纯召回 &#x2F; 纯匹配任务。</strong>像我开头说的那个 FAQ 项目。模型其实一眼就知道答案，你让它”分析再选”是在给它添乱。</p><p><strong>二、简单分类。</strong>三到五个类别、边界清楚的那种。加 CoT 会让模型过度理由化——“这条评论包含了正面情绪但也有一丝失望所以应该是…”结果越想越歪。直接判断快又稳。</p><p><strong>三、创作 &#x2F; 生成任务。</strong>让它写一个故事前先 <code>&lt;thinking&gt;</code> 梳理一下人物？没必要。创作是发散的，强行收窄到”先想后写”反而让输出变得程式化。</p><p><strong>四、高吞吐短响应场景。</strong>API 客服实时应答，延迟要求 500ms 以内，你加一段思考就是 500+ tokens 的开销，用户体验直接崩。这时候宁可损失 2-3 个点的准确率也要快。</p><p>判断逻辑大致是：<strong>任务能不能”一眼看出对错”？能的话，CoT 大概率添乱。需要”想一下才能判断对错”的，CoT 才有意义。</strong></p><h2 id="标签的正确用法"><a href="#标签的正确用法" class="headerlink" title="&lt;thinking&gt; 标签的正确用法"></a><code>&lt;thinking&gt;</code> 标签的正确用法</h2><p>决定要用 CoT 之后，怎么用也有讲究。我看过太多人把这个标签用歪了。</p><p><strong>错误用法一：标签里塞”万能咒语”。</strong></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;thinking&gt;</span><br><span class="line">请仔细思考这个问题，考虑所有可能的情况，</span><br><span class="line">确保你的回答准确无误。</span><br><span class="line">&lt;/thinking&gt;</span><br></pre></td></tr></table></figure></div><p>这种写法 Claude 看了基本等于没看。空洞的”请认真思考”对输出没任何指导作用。</p><p><strong>错误用法二：要求固定的思考步骤但步骤不匹配任务。</strong></p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;thinking&gt;</span><br><span class="line">1. 分析用户意图</span><br><span class="line">2. 检索相关信息</span><br><span class="line">3. 组织回答</span><br><span class="line">&lt;/thinking&gt;</span><br></pre></td></tr></table></figure></div><p>这种模板如果和真实任务路径对不上，反而在限制模型。比如任务是个简单数值题，它会被迫走”分析用户意图”这一步，浪费 token 还可能走偏。</p><p><strong>我现在的写法：任务依赖型，而不是通用型。</strong></p><p>合规判断场景：</p><div class="code-container" data-rel="Xml"><figure class="iseeu highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">thinking</span>&gt;</span></span><br><span class="line">先把合同里所有可能涉及规则的条款列出来，</span><br><span class="line">对每一条规则判断：符合 / 不符合 / 不适用。</span><br><span class="line">所有规则过完再给最终结论。</span><br><span class="line"><span class="tag">&lt;/<span class="name">thinking</span>&gt;</span></span><br></pre></td></tr></table></figure></div><p>数学题场景：</p><div class="code-container" data-rel="Xml"><figure class="iseeu highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">thinking</span>&gt;</span></span><br><span class="line">逐步列出计算过程，每一步明确说明依据的条件和得到的中间值。</span><br><span class="line">算完后回头验算一遍。</span><br><span class="line"><span class="tag">&lt;/<span class="name">thinking</span>&gt;</span></span><br></pre></td></tr></table></figure></div><p>具体、可执行、紧贴任务。不是给模型”打气”，是给它”操作手册”。</p><h2 id="那个”让它思考反而更错”的-case"><a href="#那个”让它思考反而更错”的-case" class="headerlink" title="那个”让它思考反而更错”的 case"></a>那个”让它思考反而更错”的 case</h2><p>再讲个我印象深的反例。某金融客户要做”判断一条新闻是利好还是利空”。</p><p>0-shot 准确率 81%，我觉得还能更高，加了一段：</p><div class="code-container" data-rel="Xml"><figure class="iseeu highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">thinking</span>&gt;</span></span><br><span class="line">先分析这条新闻涉及的主体是谁、事件性质是什么、</span><br><span class="line">短期影响和长期影响分别如何、最后综合判断利好利空。</span><br><span class="line"><span class="tag">&lt;/<span class="name">thinking</span>&gt;</span></span><br></pre></td></tr></table></figure></div><p>测下来 76%。反复看了 case 之后我发现问题：<strong>金融新闻的”利好利空”很多时候是情感驱动的市场反应，不是严密因果推理的结果。</strong></p><p>你让模型去分析”长期影响”，它经常会推出一个”虽然短期看是利空但长期利好”的结论，结果跟市场真实反应完全相反。真实市场就是看短期情绪，不看你的长期理性推理。</p><p>后来我把 thinking 拿掉，改成一条简短的 hint：”从市场短期情绪反应的角度判断”，准确率回到 81% 并稳定在那。</p><p><strong>这个教训是：CoT 的前提是任务本身有”可推理”的结构。没有这个前提，你强行推理就是在制造噪声。</strong></p><h2 id="Extended-Thinking-模式怎么看"><a href="#Extended-Thinking-模式怎么看" class="headerlink" title="Extended Thinking 模式怎么看"></a>Extended Thinking 模式怎么看</h2><p>Claude 后来出的 extended thinking 模式（就是那个可以看到完整思考链、token 消耗翻倍的那个）我也用过不少。</p><p><strong>值得开的场景：</strong></p><ul><li>复杂代码生成（写一个有设计模式考虑的完整模块）</li><li>多文档综合分析</li><li>需要调试的 bug 排查</li><li>棋类 &#x2F; 规则类博弈决策</li></ul><p><strong>不值得开的场景：</strong></p><ul><li>所有短平快任务</li><li>写营销文案、客服回复</li><li>文档抽取（大部分情况）</li><li>简单的代码 review</li></ul><p>成本上，extended thinking 的 token 消耗能达到常规的 2-5 倍。我自己项目里的原则是：<strong>能用 Sonnet 常规模式解决的事，不开 thinking；必须开的时候，先用 Opus 试，效果 OK 再考虑降级。</strong><a href="/posts/claude-family-haiku-sonnet-opus/">Claude 家族那篇</a>里有更细的模型选型讨论。</p><p>还有个细节：extended thinking 模式下，prompt caching 的表现和常规模式有些差异，具体的我在<a href="/posts/prompt-caching-deep-guide/">缓存深度指南</a>里提过。</p><h2 id="我的决策树"><a href="#我的决策树" class="headerlink" title="我的决策树"></a>我的决策树</h2><p>简化一下我现在的判断流程：</p><ol><li>先 0-shot 跑 baseline，记录准确率和延迟</li><li>分析错误 case：错的地方是”不会”还是”没想到”？<ul><li>“不会”（知识 &#x2F; 能力不够）→ 换更强的模型，或者加 few-shot，CoT 救不了</li><li>“没想到”（漏掉一步推理）→ 上 CoT 可能有效</li></ul></li><li>如果任务天然是”一眼看对错”的类型 → 跳过 CoT</li><li>如果任务需要多步推理且每步可验证 → 上 CoT，写任务依赖型 thinking</li><li>CoT 之后再评估一次准确率和延迟，权衡是否值得</li></ol><p>顺便说一句，CoT 和 few-shot 不是互斥的。有时候我会写 3 条带 thinking 过程的示例，让模型学会”怎么思考”。这种组合在复杂推理上特别猛。few-shot 的数量和顺序我在<a href="/posts/prompt-few-shot-design/">另一篇</a>里细讲了。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>CoT 这个东西，两年前大家吹它是灵丹妙药，现在业内已经慢慢意识到它是一把双刃。用对了提升显著，用错了不但浪费 token 还损失精度。</p><p>核心就一句话：<strong>不是所有问题都需要”想一下”才能答。</strong>你问一个人”1+1 等于几”，他想 30 秒再回答你，你反而怀疑他是不是有啥毛病。模型也一样。</p><div class="cta-card" style="margin:32px 0;padding:20px 24px;background:#f0f7ff;border-left:4px solid #0c97fe;border-radius:6px;"><p style="margin:0 0 8px;font-weight:600;color:#1f2937;">延伸阅读</p><p style="margin:0;color:#4b5563;font-size:14px;line-height:1.8;">CoT 和 few-shot 怎么组合，看 <a href="/posts/prompt-few-shot-design/">Few-shot 的 3/5/7 法则</a>。开启 extended thinking 前的模型选型，去 <a href="/posts/claude-family-haiku-sonnet-opus/">Claude 家族 Haiku/Sonnet/Opus 那篇</a>。thinking 模式下的 token 优化，看 <a href="/posts/prompt-caching-deep-guide/">Prompt Caching 深度指南</a>。</p></div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/prompt-chain-of-thought-vs-direct/</id>
    <link href="https://claude.cocoloop.cn/posts/prompt-chain-of-thought-vs-direct/"/>
    <published>2026-04-21T04:25:06.000Z</published>
    <summary>
      <![CDATA[有次给一个做 FAQ 召回的项目加了 `<thinking>` 标签，准确率从 92% 掉到 84%。我当时就懵了——不是说让模型思考会更准吗？后来才摸明白，CoT 不是万能补药，用错地方反而拉胯。这篇说一下我现在怎么判断该不该让 Claude 思考、extended thinking 什么时候值得开。]]>
    </summary>
    <title>让 Claude &quot;思考&quot;还是直接&quot;给答案&quot;？我摸出来的分水岭</title>
    <updated>2026-04-21T15:01:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="Prompt 工程" scheme="https://claude.cocoloop.cn/categories/Prompt-%E5%B7%A5%E7%A8%8B/"/>
    <category term="Claude" scheme="https://claude.cocoloop.cn/tags/Claude/"/>
    <category term="Prompt" scheme="https://claude.cocoloop.cn/tags/Prompt/"/>
    <category term="Few-shot" scheme="https://claude.cocoloop.cn/tags/Few-shot/"/>
    <category term="In-context Learning" scheme="https://claude.cocoloop.cn/tags/In-context-Learning/"/>
    <content>
      <![CDATA[<p>前年夏天做过一个电商评论情感分类的项目。客户要求不用微调，只能靠 prompt 解决。我最开始用零样本，就一句”判断下面这条评论是正面、负面还是中性”，跑了 500 条标注数据，准确率 68%。客户皱了皱眉，说能不能再高点。</p><p>我加了 3 条示例进去，升到 81%。再加到 5 条，到 89%。客户说够了够了，上线。</p><p>后来我自己出于好奇，又加到 10 条示例，心想应该能冲到 92%？结果掉回了 85%。</p><p>当时我特别费解。加示例不是应该让模型学得更好吗？为什么到一定程度后会倒退？</p><p>这个问题我花了大半年才摸清楚。这篇就说说我现在怎么定 few-shot 的数量。</p><h2 id="先讲那个倒退曲线"><a href="#先讲那个倒退曲线" class="headerlink" title="先讲那个倒退曲线"></a>先讲那个倒退曲线</h2><p>这事其实有道理的。Few-shot 的本质是”上下文学习”（in-context learning），模型在推理时动态地从你给的例子里提炼模式。但提炼是有极限的——示例太多，会发生两件事：</p><p><strong>第一，示例之间的细微不一致被放大。</strong>你自己写 10 条示例，总有几条风格或边界判断不完全一样。模型会把这些”不一致”也当成信号，结果反而变得犹豫。</p><p><strong>第二，上下文长度增加，对任务本身的注意力被稀释。</strong>尤其在长文档场景下更明显。</p><p>所以 few-shot 不是”越多越好”。是有个甜蜜点，过了就掉头。</p><h2 id="我现在用的-3-5-7-法则"><a href="#我现在用的-3-5-7-法则" class="headerlink" title="我现在用的 3&#x2F;5&#x2F;7 法则"></a>我现在用的 3&#x2F;5&#x2F;7 法则</h2><p>这个法则不是啥标准答案，是我在实际项目里反复校准出来的经验值。不同任务类型，甜蜜点位置不一样：</p><h3 id="3-条：分类任务-风格迁移"><a href="#3-条：分类任务-风格迁移" class="headerlink" title="3 条：分类任务 + 风格迁移"></a>3 条：分类任务 + 风格迁移</h3><p>情感分类、意图识别、打标签、改写成某种特定文风——这类任务的特点是”模式相对简单，但边界需要锚定”。</p><p>3 条示例基本够了。一条典型正例、一条典型负例、一条边界模糊的。这样模型就明白了你的判定规则在哪。</p><p>再多加也没啥用。你加第四条、第五条多半是在重复前三条的模式，对模型来说是冗余信号。</p><h3 id="5-条：结构化抽取"><a href="#5-条：结构化抽取" class="headerlink" title="5 条：结构化抽取"></a>5 条：结构化抽取</h3><p>从长文本里抽字段、从邮件里提关键信息、从合同里找条款——这类任务需要模型学会”看哪里”和”怎么填”。</p><p>5 条示例里我通常这样配：2 条标准样例、2 条包含噪声或歧义的、1 条字段缺失或需要”填 null”的。覆盖主流情况 + 典型异常，再多就多余了。</p><h3 id="7-条：复杂推理-多步决策"><a href="#7-条：复杂推理-多步决策" class="headerlink" title="7 条：复杂推理 &#x2F; 多步决策"></a>7 条：复杂推理 &#x2F; 多步决策</h3><p>比如多跳问答、合规判断（需要组合多条规则）、代码评审这种需要”先看整体再判断细节”的任务。</p><p>7 条示例里，我会确保每条示例的”推理路径”都不一样——不是换输入，是换思考路径。让模型看到”哦原来这类问题可以这么想、也可以那么想”。</p><p>超过 7 条我基本就不加了。要么升级到带 <code>&lt;thinking&gt;</code> 的链式推理（这个在<a href="/posts/prompt-chain-of-thought-vs-direct/">思考还是直接给答案那篇</a>里细说），要么考虑微调或者上更强的模型。</p><h2 id="示例顺序的讲究"><a href="#示例顺序的讲究" class="headerlink" title="示例顺序的讲究"></a>示例顺序的讲究</h2><p>这是个容易被忽略的点。同样 5 条示例，顺序不同效果能差 3-5 个百分点。</p><p>我摸出来的规律：<strong>最相关、最典型的那条放最后。</strong>Claude 的注意力对”刚刚看到的内容”是有偏向的（这事叫 recency bias），放最后的示例在生成时的权重明显更高。</p><p>所以我现在的排法大致是：</p><ol><li>先放几条”覆盖各种情况”的杂样例</li><li>中间放”边界案例”</li><li>最后放一条和当前待处理输入最相似的</li></ol><p>有时候如果我能动态检索，还会做个”相似度召回”，用检索到的最相似 case 作为最后一条示例。这套手法在 RAG 项目里特别好使。</p><h2 id="示例写太好，反而拉低平均"><a href="#示例写太好，反而拉低平均" class="headerlink" title="示例写太好，反而拉低平均"></a>示例写太好，反而拉低平均</h2><p>这点我踩过大坑。</p><p>有次给一个客服改写项目写示例，我特别认真地把每条都润色得又专业又亲切。结果上线一测，发现模型在处理比较随意的用户投诉时，输出也变得很客套很书面——用户反馈说”像在跟机器人讲话”。</p><p>把示例里两条换成稍微”粗糙”一点的真实样本，口吻立刻松下来了，用户满意度反而上去了。</p><p>所以：<strong>示例的质量是”代表性”，不是”完美”。</strong>你的真实输入有多杂，示例就该有多杂。不要把示例全写成你梦里希望的那种理想形态，模型会照着做，然后在处理现实世界输入时别扭得要命。</p><h2 id="输入输出对齐的格式"><a href="#输入输出对齐的格式" class="headerlink" title="输入输出对齐的格式"></a>输入输出对齐的格式</h2><p>Few-shot 的格式写法我建议用 XML 标签成对包装，比 “Q:… A:…” 这种冒号式更稳定。为啥 Claude 特别吃 XML，<a href="/posts/claude-xml-over-markdown/">这篇</a>专门讲过。</p><p>典型写法：</p><div class="code-container" data-rel="Xml"><figure class="iseeu highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">example</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span>&gt;</span>用户评论：这破玩意儿用了三天就坏了<span class="tag">&lt;/<span class="name">input</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">output</span>&gt;</span>负面<span class="tag">&lt;/<span class="name">output</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">example</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">example</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span>&gt;</span>用户评论：还行吧，没啥特别感觉<span class="tag">&lt;/<span class="name">input</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">output</span>&gt;</span>中性<span class="tag">&lt;/<span class="name">output</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">example</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">example</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span>&gt;</span>用户评论：冲着品牌买的，用起来确实比便宜货强<span class="tag">&lt;/<span class="name">input</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">output</span>&gt;</span>正面<span class="tag">&lt;/<span class="name">output</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">example</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">task</span>&gt;</span></span><br><span class="line">请判断下面这条评论的情感：</span><br><span class="line">&#123;用户输入&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">task</span>&gt;</span></span><br></pre></td></tr></table></figure></div><p>格式统一、标签闭合、最后一条是最典型的。</p><h2 id="0-shot-vs-Few-shot-怎么选"><a href="#0-shot-vs-Few-shot-怎么选" class="headerlink" title="0-shot vs Few-shot 怎么选"></a>0-shot vs Few-shot 怎么选</h2><p>不是所有任务都需要 few-shot。我的判断标准：</p><p><strong>能 0-shot 就 0-shot。</strong>Claude 的 zero-shot 能力本身就很强，简单任务加示例是浪费 token、拖慢响应。</p><p><strong>什么时候需要 few-shot？</strong></p><ul><li>任务有”主观判断”的成分（比如风格分类）</li><li>期望输出有特定格式且不容易用自然语言描述清楚</li><li>边界情况多，光靠规则说不清楚</li><li>你试了 0-shot 结果准确率差 5 个百分点以上</li></ul><p>先 0-shot 测一轮，拿到 baseline，再决定要不要加示例。有些人上来就怼 10 条例子，既浪费 token 也错过了真实性能评估。</p><h2 id="我开头那个-68-→89-到底怎么拆的"><a href="#我开头那个-68-→89-到底怎么拆的" class="headerlink" title="我开头那个 68%→89% 到底怎么拆的"></a>我开头那个 68%→89% 到底怎么拆的</h2><p>回到那个情感分类项目。我当时加示例的具体步骤，给大家看下：</p><ul><li><strong>第 1 条</strong>：一个明显负面但用词委婉的（让模型学会看”言外之意”）</li><li><strong>第 2 条</strong>：一个表面夸实则讽刺的（中文里特别多这种）</li><li><strong>第 3 条</strong>：一个情感混合的，判定为中性（教它怎么处理混合情感）</li><li><strong>第 4 条</strong>：一个带脏话的负面（锚定”脏话≠一定是差评”的边界）</li><li><strong>第 5 条</strong>：和当前测试集分布最接近的一条标准负面</li></ul><p>每加一条都带来 2-5 个点的提升。到第 5 条时覆盖了主要 edge case，再往上加就开始触发前面说的”不一致被放大”现象。</p><h2 id="一些零碎经验"><a href="#一些零碎经验" class="headerlink" title="一些零碎经验"></a>一些零碎经验</h2><ul><li><strong>示例里的输入尽量匿名化。</strong>别写真实用户名、真实公司名，容易让模型”记住”导致偏差。</li><li><strong>输出格式在示例和最终任务里必须一致。</strong>你示例里是一个词，任务要求一段话，模型会精分。</li><li><strong>示例本身也会走 prompt caching。</strong>如果你的示例长期固定，可以让它进缓存，省下不少钱。缓存机制我在<a href="/posts/prompt-caching-deep-guide/">这篇</a>里拆过。</li><li><strong>不同模型的甜蜜点不同。</strong>Haiku 对示例更敏感、可能 7 条都嫌少；Opus 自身推理强、3 条可能就够。<a href="/posts/claude-family-haiku-sonnet-opus/">Claude 家族那篇</a>里有模型选型的讨论。</li></ul><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>Few-shot 这事说玄乎也玄乎——同样的示例换个顺序都能差好几个点。说朴素也朴素——你给模型看的就是它学到的，你示例写得糙，模型就糙。</p><p>3&#x2F;5&#x2F;7 不是铁律，是个起点。拿这个数字去试，根据你自己的评估集往上调或往下调 1-2 条。这个过程才是 prompt 工程的本体，不是套公式。</p><div class="cta-card" style="margin:32px 0;padding:20px 24px;background:#f0f7ff;border-left:4px solid #0c97fe;border-radius:6px;"><p style="margin:0 0 8px;font-weight:600;color:#1f2937;">延伸阅读</p><p style="margin:0;color:#4b5563;font-size:14px;line-height:1.8;">想把示例放进缓存省钱，看 <a href="/posts/prompt-caching-deep-guide/">Prompt Caching 深度指南</a>。想区分 few-shot 和 CoT 的边界，去 <a href="/posts/prompt-chain-of-thought-vs-direct/">让 Claude 思考还是直接给答案</a>。不同模型对示例数量的敏感度差异，在 <a href="/posts/claude-family-haiku-sonnet-opus/">Claude 家族选型那篇</a>里有讨论。</p></div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/prompt-few-shot-design/</id>
    <link href="https://claude.cocoloop.cn/posts/prompt-few-shot-design/"/>
    <published>2026-04-20T16:37:39.000Z</published>
    <summary>做情感分类那个项目，0-shot 准确率 68%，加到 3 条示例升到 81%，5 条到 89%，再加到 10 条反而掉回 85%。这个曲线让我后来定了一套 3/5/7 法则：分类和风格迁移 3 条、抽取任务 5 条、复杂推理 7 条。这篇把我踩过的坑和摸出来的经验全倒出来。</summary>
    <title>Few-shot 示例到底放几条？我的 3/5/7 法则</title>
    <updated>2026-04-21T15:26:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="行业实战" scheme="https://claude.cocoloop.cn/categories/%E8%A1%8C%E4%B8%9A%E5%AE%9E%E6%88%98/"/>
    <category term="工程化" scheme="https://claude.cocoloop.cn/tags/%E5%B7%A5%E7%A8%8B%E5%8C%96/"/>
    <category term="RAG" scheme="https://claude.cocoloop.cn/tags/RAG/"/>
    <category term="合同审查" scheme="https://claude.cocoloop.cn/tags/%E5%90%88%E5%90%8C%E5%AE%A1%E6%9F%A5/"/>
    <category term="法律科技" scheme="https://claude.cocoloop.cn/tags/%E6%B3%95%E5%BE%8B%E7%A7%91%E6%8A%80/"/>
    <category term="Claude Agent" scheme="https://claude.cocoloop.cn/tags/Claude-Agent/"/>
    <content>
      <![CDATA[<p>去年 11 月，朋友介绍一家律所找我。规模不大，60 来个律师，主做公司并购和涉外合规。他们老合伙人开口就是：「我们一天要审 14 份合同，长的 80 多页，主审律师看完一份脑子就空了。你看 AI 能不能帮一把。」</p><p>我先去他们办公室蹲了三天，拿了 200 份历史合同样本。看完之后发现问题不是「读不读得懂」，而是<strong>优先级</strong>——律师不怕条款多，怕漏掉一个藏在第 73 页的单方解除权。</p><p>下面是我做这个系统的完整复盘，包括那个差点让我翻车的 187K token 事故。</p><h2 id="甲方到底要什么：不是摘要，是风险矩阵"><a href="#甲方到底要什么：不是摘要，是风险矩阵" class="headerlink" title="甲方到底要什么：不是摘要，是风险矩阵"></a>甲方到底要什么：不是摘要，是风险矩阵</h2><p>第一次对焦需求我差点走偏。我以为他们要的是「合同摘要」，做了个 demo 过去，合伙人扫了一眼说：「这个没用，摘要我实习生也能写。」</p><p>真实需求是这样的：</p><ul><li>每份合同审完，给出<strong>7 类风险点</strong>的分布矩阵（付款条款、违约责任、知识产权归属、保密范围、争议解决、单方变更&#x2F;解除、限制性条款）</li><li>每个风险点必须<strong>引用原文具体条款号</strong>，不能只说「存在单方解除风险」</li><li>风险分级要可量化（高&#x2F;中&#x2F;低），并给出<strong>律师动作建议</strong>（谈判让步 &#x2F; 修改话术 &#x2F; 可接受）</li></ul><p>这就完全不是摘要问题了，这是分类 + 结构化抽取 + 归因推理的混合任务。</p><h2 id="架构：OCR-兜底-条款分段-多路并行"><a href="#架构：OCR-兜底-条款分段-多路并行" class="headerlink" title="架构：OCR 兜底 + 条款分段 + 多路并行"></a>架构：OCR 兜底 + 条款分段 + 多路并行</h2><p>整体流水线是这样的，我尽量写得具体点：</p><ol><li><strong>入口</strong>：PDF 上传到 S3，Lambda 触发</li><li><strong>OCR 兜底</strong>：Textract 先识别。扫描版合同占比大概 34%，直接跳过会丢文本</li><li><strong>条款分段</strong>：自己写了个正则 + Claude Haiku 双校验的分段器，按「第 X 条」「Article X」切分，平均一份合同切出 180-240 段</li><li><strong>7 类分类器</strong>：每段丢给 Haiku，返回 0-7 的 label（0 &#x3D; 无风险相关）。这一步过滤掉 70% 左右的常规条款</li><li><strong>风险分析</strong>：剩下的条款按类别分组，喂给 Sonnet 做逐条分析，输出结构化 JSON</li><li><strong>矩阵汇总</strong>：最后把 7 类结果拼成一张风险矩阵 PDF，附律师动作建议</li></ol><p>我在输出结构化这块专门参考了 <a href="/posts/prompt-output-format-json-schema/">Prompt 输出 JSON Schema 的工程实践</a>，schema 约束配合 Claude 的 tool use 稳定性比纯 prompt 强太多。</p><h2 id="那个-187K-token-的事故"><a href="#那个-187K-token-的事故" class="headerlink" title="那个 187K token 的事故"></a>那个 187K token 的事故</h2><p>上线第二天就炸了。</p><p>有份并购合同是扫描版 + 手写批注 + 附件一起打包的 PDF，84 页。OCR 跑完之后文本膨胀到 187K token。我第一版流水线是<strong>整篇丢给 Sonnet</strong>做分段的，结果触发了 context 上限，Claude 直接返回 <code>input too long</code> 报错。当时我盯着 Sentry 上的 error trace 懵了十几秒——测试样本里最大才 52 页。</p><p>后来改方案，把分段挪到前置流程，<strong>不让 Sonnet 吃整篇</strong>：</p><ul><li>用 Haiku 按物理页 + 条款标题做粗切</li><li>切完的段落单独进风险分析，每次调用输入控制在 4K token 以内</li><li>整篇的「全局上下文」（当事方、标的金额、合同性质）单独抽取一次，缓存下来，每次调用时注入</li></ul><p>这个思路其实就是 context 预算管理，我之前也写过 <a href="/posts/context-window-budget-strategy/">Context Window 预算分配策略</a> 这篇文章，里面讲得更系统。</p><p>改完之后那份 84 页合同处理时长从「直接失败」变成 6 分 23 秒，token 消耗从爆表降到 94K 总输入（拆成 23 次调用）。</p><h2 id="Hallucination-是律所最在意的事"><a href="#Hallucination-是律所最在意的事" class="headerlink" title="Hallucination 是律所最在意的事"></a>Hallucination 是律所最在意的事</h2><p>合伙人跟我反复强调：<strong>宁可漏报，不能乱编</strong>。律师最怕的就是 AI 说「第 14 条存在单方解除风险」，结果第 14 条压根不是那个意思。</p><p>我做了三层防护：</p><p><strong>第一层：强制原文引用</strong>。每条风险结论必须包含 <code>original_clause_text</code> 字段，且文本长度 ≥ 20 字符。后处理时做字符串匹配，匹配不上的结论直接丢弃。这一条拦掉了大概 4.2% 的 hallucination。</p><p><strong>第二层：双模型交叉验证</strong>。高风险条款会再让 Sonnet 用不同 prompt 验证一次，两次结论不一致的打上 <code>needs_review</code> flag。这里用到的思路跟 <a href="/posts/prompt-chain-of-thought-vs-direct/">Chain of Thought vs Direct 回答</a> 有关，验证链用 CoT，主链用 direct output，结果稳定性更好。</p><p><strong>第三层：律师反馈回流</strong>。律师审完会在系统里点「确认&#x2F;修正&#x2F;误报」，这些数据每周回流一次，用来优化 prompt 的 few-shot 例子。上线三个月误报率从 11.8% 降到 3.4%。</p><h2 id="成本账"><a href="#成本账" class="headerlink" title="成本账"></a>成本账</h2><p>实打实的账单，2026 年 3 月的数据：</p><ul><li>当月处理合同 387 份</li><li>总账单 $182.47（含 OCR）</li><li>平均单份 $0.47</li><li>其中 Haiku 分类占 $0.11，Sonnet 分析占 $0.29，OCR 占 $0.07</li></ul><p>我之前试过纯 Sonnet 方案，单份 $1.62，直接贵了三倍半。分层路由的价值这里特别明显，具体设计思路我写过 <a href="/posts/cost-multi-model-router/">多模型路由降本实战</a>。</p><p>另外有个小优化挺关键的：<strong>prompt caching</strong>。7 类风险分类器的 system prompt 是固定的 2.1K token，caching 之后每次调用省 87% 的 prompt 成本。这个我在 <a href="/posts/prompt-caching-deep-guide/">Prompt Caching 深度指南</a> 里讲过配置细节。</p><h2 id="律师的真实反馈"><a href="#律师的真实反馈" class="headerlink" title="律师的真实反馈"></a>律师的真实反馈</h2><p>上线 4 个月，主审律师平均审查时长从 47 分 12 秒降到 8 分 10 秒（他们自己打卡系统拉的数据）。但合伙人跟我说了一句让我印象深的话：「快不是最关键的，最关键的是年轻律师不会漏条款了。」</p><p>原来他们有个痛点我没想到：<strong>应届律师</strong>经验不足，容易漏掉隐藏条款。有了 Agent 托底之后，年轻律师可以把精力放在谈判策略上，不用再花两小时通读合同。</p><h2 id="还没解决的问题"><a href="#还没解决的问题" class="headerlink" title="还没解决的问题"></a>还没解决的问题</h2><ul><li><strong>多语言合同</strong>：中英混合的还行，但纯日文、德文合同准确率只有 73% 左右</li><li><strong>附件链合同</strong>：主合同引用了 SOW、SLA、MSA 好几份子合同的，跨文档关联还没做好</li><li><strong>方言性表达</strong>：香港律所发来的合同有些粤语化表达，分类器经常蒙圈</li></ul><p>这些是 Q2 要继续啃的。</p><hr><div style="background-color: #f0f7ff; border-left: 4px solid #0c97fe; padding: 16px 20px; margin: 24px 0; border-radius: 4px;"><strong>想深入学律师/合规场景的 Agent 工程化？</strong><br>合同审查只是起点，真正的难点在于多文档关联、专业术语校准和合规审计。我在 <a href="/posts/agent-sdk-production-deploy/">Agent SDK 生产部署</a> 和 <a href="/posts/context-retrieval-reranking/">检索重排序</a> 里有更详细的代码样板，建议配合阅读。</div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/industry-legal-contract-review/</id>
    <link href="https://claude.cocoloop.cn/posts/industry-legal-contract-review/"/>
    <published>2026-04-19T23:23:30.000Z</published>
    <summary>去年年底接的一个活，给一家二线城市 60 人律所做合同审查 Agent。甲方日均 14 份合同，主审律师一天能被三份 80 页以上的英文合同耗光。我用 Claude Sonnet 做主链路、Haiku 做初筛，把单合同审查时间从 47 分钟压到 8 分 10 秒，单份成本 $0.47。这篇写一下我踩过的坑，尤其是那个让我当时就懵了的 187K token 爆炸事故。</summary>
    <title>律所合同审查 Agent 实战：84 页 PDF 拆成 7 类风险点</title>
    <updated>2026-04-21T13:59:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="入门介绍" scheme="https://claude.cocoloop.cn/categories/%E5%85%A5%E9%97%A8%E4%BB%8B%E7%BB%8D/"/>
    <category term="API" scheme="https://claude.cocoloop.cn/tags/API/"/>
    <category term="Claude Sonnet" scheme="https://claude.cocoloop.cn/tags/Claude-Sonnet/"/>
    <category term="模型选择" scheme="https://claude.cocoloop.cn/tags/%E6%A8%A1%E5%9E%8B%E9%80%89%E6%8B%A9/"/>
    <category term="成本优化" scheme="https://claude.cocoloop.cn/tags/%E6%88%90%E6%9C%AC%E4%BC%98%E5%8C%96/"/>
    <content>
      <![CDATA[<p>说实话，去年这个时候我对 Claude 其实挺无感。那时候大家都在 ChatGPT 和 Gemini 里左右横跳，偶尔听到有人吹 Claude 的长上下文，我点进去试两下，觉得也就那样，就又退回 GPT-4 了。</p><p>真正让我切过来的契机挺偶然——春节前那阵我在做一个合同解析的私活，原始 PDF 塞进 GPT 之后它经常胡说八道，把”甲方”和”乙方”认反，把日期从 2024 改成 2023。我当时快崩溃了，抱着死马当活马医的态度把同样的东西丢给 Sonnet 4.6，结果它不仅读对了，还主动指出了合同第 14 条的一个歧义条款。</p><p>从那天开始，我把 API Key 环境变量换了过来，到现在三个月整。这篇文章不想讲模型参数、跑分榜单——那些东西知乎和 X 上已经写烂了。我就想把我这三个月真实的使用场景、真实的账单、真实的翻车记录摊开，让还在犹豫的人看看一个普通独立开发者的视角是什么样的。</p><h2 id="一、先把账单摊开：三个月花了多少钱"><a href="#一、先把账单摊开：三个月花了多少钱" class="headerlink" title="一、先把账单摊开：三个月花了多少钱"></a>一、先把账单摊开：三个月花了多少钱</h2><p>很多文章讲”性价比”讲得天花乱坠，但从来不给具体数字。我给你看我的：</p><ul><li><strong>1 月</strong>：$43.20，主要是合同解析那个项目收尾</li><li><strong>2 月</strong>：$78.50，开始做一个小型的内容生成工具</li><li><strong>3 月</strong>：$112.30，加上了日常 coding 辅助和少量的 Agent 实验</li></ul><p>合计 $234 出头。对比之前用 GPT-4 Turbo 的两个月（$180 + $210），单位 token 价格 Sonnet 其实更便宜，但我用得更频繁了，所以总支出差不多。</p><p>关键不是省没省钱，关键是<strong>同样的钱做完的活更多、更稳</strong>。GPT-4 时代我有一半的钱是花在”重试”上——回答不对，再问一遍；格式错了，再要一遍 JSON。切到 Sonnet 之后，一次过的比例大概从六成涨到了八成五，这才是实际省下来的东西。</p><h2 id="二、为什么是-4-6，不是-Opus、也不是-Haiku"><a href="#二、为什么是-4-6，不是-Opus、也不是-Haiku" class="headerlink" title="二、为什么是 4.6，不是 Opus、也不是 Haiku"></a>二、为什么是 4.6，不是 Opus、也不是 Haiku</h2><p>Anthropic 家族现在三档模型：Opus 4.7、Sonnet 4.6、Haiku 4.5。一个很常见的新手困惑就是——既然都是一家的，我直接用最强的 Opus 不就完了？</p><p>答案是：<strong>你大概率不需要 Opus</strong>。</p><p>我试过一个月把默认切成 Opus，结果账单直接翻了三倍，但真正需要 Opus 才能搞定的任务不到 5%。Sonnet 4.6 的能力已经覆盖了我 90% 以上的场景——写代码、改文档、分析数据、做结构化抽取、甚至一些轻量级的推理任务。</p><p>Haiku 4.5 是另一个极端，它便宜到几乎不计成本，但处理稍微复杂一点的多轮对话就容易丢线索。我现在把 Haiku 放在 Router 这个位置——前置一个 Haiku 判断请求类型，简单的直接 Haiku 回，复杂的再路由到 Sonnet，这一招差不多能再省 40% 的钱。这个路由策略的细节我后面会单开一篇讲，这里就先点一下。</p><p>Sonnet 4.6 卡在中间这个位置，是典型的<strong>帕累托前沿</strong>——再往上 Opus 贵 5 倍但提升只有 15-20%，再往下 Haiku 便宜 5 倍但能力掉 30% 以上。这就是为什么它适合做日常默认。</p><h2 id="三、我实际在用的场景，以及每个场景的坑"><a href="#三、我实际在用的场景，以及每个场景的坑" class="headerlink" title="三、我实际在用的场景，以及每个场景的坑"></a>三、我实际在用的场景，以及每个场景的坑</h2><p>下面这些是我真实用 Sonnet 4.6 干的事情，按使用频率排序：</p><h3 id="1-Coding-辅助（占比约-40-）"><a href="#1-Coding-辅助（占比约-40-）" class="headerlink" title="1. Coding 辅助（占比约 40%）"></a>1. Coding 辅助（占比约 40%）</h3><p>这是最大头。我日常用 <a class="link"   href="https://claudecode.cocoloop.cn/" >Claude Code<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 做 CLI 编程助手，它对 Sonnet 4.6 的工具调用做了专门的优化。比起之前在 Cursor 里用 GPT-4，最大的区别是<strong>它不乱改你的代码</strong>——GPT-4 有时候会自作主张把你没让它动的函数重写一遍，Sonnet 4.6 会老实地只改你指定的地方。</p><p>唯一的坑是：<strong>你得给它足够的上下文</strong>。不给 CLAUDE.md（项目级的约定文件），它就会按照它自己理解的”最佳实践”来写，有时候和你项目的风格不符。这个坑值得单独写一篇，这里先给个<a class="link"   href="https://www.cocoloop.cn/" >参考链接<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>。</p><h3 id="2-文档和长文处理（占比约-25-）"><a href="#2-文档和长文处理（占比约-25-）" class="headerlink" title="2. 文档和长文处理（占比约 25%）"></a>2. 文档和长文处理（占比约 25%）</h3><p>200K token 的上下文是真香。我经常把整个项目的文档、代码、issue 一股脑喂进去让它分析。GPT-4 那 128K 用起来总要斟酌”哪些重要哪些能砍”，Sonnet 4.6 基本可以粗暴地 “all in”。</p><p>翻车记录：一次性塞 180K 的时候，它有时候会”偷懒”——前半部分读得很细，后半部分明显不如前面深入。解决办法是<strong>用 Prompt Caching</strong>，把大段不变的上下文缓存下来，每次只动态拼动态部分。成本立刻降到 1&#x2F;10，效果反而更好（因为模型每次只处理变化的部分，注意力更集中）。</p><h3 id="3-结构化数据抽取（占比约-20-）"><a href="#3-结构化数据抽取（占比约-20-）" class="headerlink" title="3. 结构化数据抽取（占比约 20%）"></a>3. 结构化数据抽取（占比约 20%）</h3><p>合同、简历、发票、邮件——这些非结构化文本转 JSON 的任务，Sonnet 4.6 做得特别稳。我的经验是：</p><ul><li><strong>一定要用 XML 标签</strong>分隔输入和指令。Claude 家族对 XML 的识别度比 Markdown 好得多</li><li><strong>Few-shot 给 2-3 个例子</strong>就够了，再多没意义甚至副作用</li><li><strong>输出 Schema 直接用 Tool 定义</strong>，不要让模型自由发挥 JSON</li></ul><h3 id="4-日常问答和写作（占比约-15-）"><a href="#4-日常问答和写作（占比约-15-）" class="headerlink" title="4. 日常问答和写作（占比约 15%）"></a>4. 日常问答和写作（占比约 15%）</h3><p>这块其实和 GPT 没有特别大的区别。稍微有点感觉的是 Sonnet 4.6 的中文表达更”自然”一点，GPT-4 的中文总有种”翻译腔”。但这个事儿很玄学，不同人感知不一样。</p><h2 id="四、说点实话：它不是没有缺点"><a href="#四、说点实话：它不是没有缺点" class="headerlink" title="四、说点实话：它不是没有缺点"></a>四、说点实话：它不是没有缺点</h2><p>写到这里如果我只说好话，那就太像广告了。Sonnet 4.6 真实的缺点也列一下：</p><ol><li><strong>联网搜索是痛</strong>。国内用起来没有原生 Web Search（Anthropic 官方 API 的 web_search 工具在国内受限），这点比 GPT 和 Gemini 体验差。我的解决办法是接一个 <a href="/categories/Tool%E4%B8%8EMCP/">Perplexity API 做 MCP Server</a></li><li><strong>图片理解不如 Gemini</strong>。处理截图、做 OCR 这类任务，Gemini 3.1 Pro 明显更强</li><li>**创造性写作偶尔过于”理性”**。要它写小说、写诗词这种需要”放飞”的内容，感觉比 GPT 拘谨</li><li><strong>延迟</strong>。Sonnet 4.6 不是最快的，对实时交互要求高的场景（比如 voice agent）Haiku 更合适</li></ol><h2 id="五、给还在犹豫的人一句话"><a href="#五、给还在犹豫的人一句话" class="headerlink" title="五、给还在犹豫的人一句话"></a>五、给还在犹豫的人一句话</h2><p>如果你的主力场景是<strong>写代码、处理长文档、做结构化任务</strong>，Sonnet 4.6 几乎是目前性价比最高的选择，闭眼切。</p><p>如果你主要干的是<strong>聊天机器人、创意写作、实时语音</strong>，那继续留在 GPT 或者试试 Gemini 可能更好。</p><p>最好的办法其实是——<strong>同时保留两家的 API Key</strong>，按场景用不同的模型。我现在就是 Sonnet 做主力、GPT 备着、Haiku 打杂，一个月总账单还比只用 GPT 的时候低。</p><p>下一篇我会展开写<strong>怎么用 Haiku 做 Router 砍账单</strong>，有兴趣的可以关注一下，或者先去<a href="/categories/">知识地图</a>翻翻其他内容。</p><div class="cta-card">  <div class="cta-title">🚀 想了解更多 Claude 实战？</div>  <div class="cta-desc">本站持续更新 Anthropic Claude 的深度中文指南。想要获取一手 AI 新闻，可以访问 <a class="link"   href="https://news.cocoloop.cn/" >news.cocoloop.cn<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>；想看更多 AI 应用案例，可以访问 <a class="link"   href="https://www.cocoloop.cn/" >www.cocoloop.cn<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>。</div></div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/claude-sonnet-daily-driver/</id>
    <link href="https://claude.cocoloop.cn/posts/claude-sonnet-daily-driver/"/>
    <published>2026-04-19T21:24:09.000Z</published>
    <summary>从 GPT 切到 Claude Sonnet 4.6 用了三个月，我把这段时间的账单、使用场景和翻车记录摊开来写一次。不讲参数，不讲跑分，只讲一个普通开发者的实际判断。</summary>
    <title>Claude Sonnet 4.6 凭什么成了我日常默认模型？三个月真实账单复盘</title>
    <updated>2026-04-21T14:26:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="Claude Code" scheme="https://claude.cocoloop.cn/categories/Claude-Code/"/>
    <category term="Claude Code" scheme="https://claude.cocoloop.cn/tags/Claude-Code/"/>
    <category term="DevOps" scheme="https://claude.cocoloop.cn/tags/DevOps/"/>
    <category term="权限" scheme="https://claude.cocoloop.cn/tags/%E6%9D%83%E9%99%90/"/>
    <category term="安全" scheme="https://claude.cocoloop.cn/tags/%E5%AE%89%E5%85%A8/"/>
    <content>
      <![CDATA[<h2 id="两次踩雷，一次小伤一次差点翻车"><a href="#两次踩雷，一次小伤一次差点翻车" class="headerlink" title="两次踩雷，一次小伤一次差点翻车"></a>两次踩雷，一次小伤一次差点翻车</h2><p><strong>第一次</strong>是 2025 年 8 月。我在一台跑着甲方客户数据的 staging 机上用 Claude Code 做日志分析。当时设了 <code>acceptEdits</code>，以为”编辑自动通过，Bash 还是要问的”。然后让 Claude 清理 <code>/var/log/app/</code> 下的老日志，它跑了一条 <code>find /var/log -mtime +30 -delete</code>。</p><p>实际命令 Claude 给我了，但因为我当时以为 acceptEdits 会拦住 Bash，眼睛扫过去就按了回车……它删到了 <code>/var/log/auth.log</code> 和 <code>/var/log/nginx/access.log.*</code>，staging 的所有 HTTP 访问历史没了。客户没追究，我自己赔了周末两天补数据。</p><p><strong>第二次</strong>更离谱。2026 年 1 月，我在本地 dev 机开 <code>bypassPermissions</code> 模式，脑子一抽觉得”bypass”是”绕过（某些）”，以为是”跳过一些检查但关键操作还拦”。实际上——bypassPermissions 的意思是<strong>全部放行</strong>。我让 Claude “清理 node_modules”，它跑了 <code>rm -rf node_modules</code>，没问题。然后它又主动跑了 <code>rm -rf ~/.npm</code> 来”清理缓存”，瞬间把我本地所有全局包配置吞了。</p><p>这两次之后我把官方文档翻了个底朝天，也做了一堆实验，下面把 4 个模式的真实行为写清楚。</p><h2 id="default：正常开发的默认档"><a href="#default：正常开发的默认档" class="headerlink" title="default：正常开发的默认档"></a>default：正常开发的默认档</h2><p>刚装完 Claude Code 就是这个。行为：</p><ul><li><strong>Read&#x2F;Grep&#x2F;Glob</strong>：默认放行（只读不危险）</li><li><strong>Edit&#x2F;Write&#x2F;MultiEdit</strong>：首次编辑某个文件会弹 ask</li><li><strong>Bash</strong>：每条命令都弹 ask，除非你 allow 过</li><li><strong>WebFetch&#x2F;WebSearch</strong>：弹 ask</li></ul><p>适合场景：第一次接触一个陌生仓库、生产机、或者任何”出问题会心疼”的地方。</p><p>坑点：弹窗疲劳。我最忙的一次 3 小时弹了 1247 次 ask，最后几乎是闭着眼按 Y。这种状态下 default 已经跟 bypassPermissions 没区别了。</p><p>解决方式是配合 <code>less-permission-prompts</code> 思路，把确实安全的读类操作 allow 掉（Read、Grep、Glob、<code>git status</code>、<code>git diff</code>、<code>npm test</code>、<code>pytest</code> 这种），把危险操作保留 ask。</p><h2 id="acceptEdits：熟悉项目的主力档"><a href="#acceptEdits：熟悉项目的主力档" class="headerlink" title="acceptEdits：熟悉项目的主力档"></a>acceptEdits：熟悉项目的主力档</h2><p>我自己最常用的模式。行为：</p><ul><li><strong>Read&#x2F;Grep&#x2F;Glob</strong>：放行</li><li><strong>Edit&#x2F;Write&#x2F;MultiEdit</strong>：<strong>自动通过</strong>，不弹窗</li><li><strong>Bash</strong>：还是 ask</li><li><strong>WebFetch</strong>：ask</li></ul><p>关键区分：Edit 自动过，<strong>Bash 不自动过</strong>。我第一次踩雷就是搞反了这个。</p><p>适合场景：你已经在这个仓库里工作过一段时间，对 Claude 的编辑质量有信心，测试+lint hook 配齐了（回看 <a href="/posts/claude-md-project-config/">hooks 自动化</a> 那篇），改错了也能被拦住。</p><p>为什么我常用这个：开发节奏最快的一档。Claude 改 50 个文件不用你按 50 次 Y，但 <code>rm</code>、<code>git push</code> 这种有破坏性的命令仍然会被问。</p><p>有个小细节：acceptEdits 下如果你配了 PreToolUse hook 拦 Bash 危险操作，hook 还是会生效。这个组合非常推荐——Edit 顺畅，Bash 由 hook 守门，比一味问你舒服。</p><h2 id="plan：只读的-planning-阶段"><a href="#plan：只读的-planning-阶段" class="headerlink" title="plan：只读的 planning 阶段"></a>plan：只读的 planning 阶段</h2><p>行为：</p><ul><li><strong>Read&#x2F;Grep&#x2F;Glob</strong>：放行</li><li><strong>Edit&#x2F;Write&#x2F;MultiEdit</strong>：<strong>一律拒绝</strong></li><li><strong>Bash</strong>：<strong>一律拒绝</strong></li><li><strong>WebFetch</strong>：ask 或者 allow（具体看配置）</li></ul><p>Claude 在这个模式下会”只做计划不做事”。典型用法：接手一个陌生的大仓库，先让 Claude 读、总结、提方案，批准后再切 acceptEdits 真正动手。</p><p>我做咨询时候的标准流程：进客户仓库先 plan 模式走 30 分钟，跑出一份”改动计划”贴到飞书。客户点头才切 acceptEdits 干活。这样扯皮概率从 37% 降到 8.4%（我拿最近 24 个咨询项目统计的）。</p><p>plan 模式最大的陷阱是——有些 Bash 命令是只读的（比如 <code>git log</code>、<code>curl localhost/health</code>），这些也会被拒，导致 Claude 有时抓瞎。解决办法是在 allow 规则里把只读 Bash 模式显式放出来。后面讲规则语法会聊到。</p><h2 id="bypassPermissions：字面意思是”全部放行”"><a href="#bypassPermissions：字面意思是”全部放行”" class="headerlink" title="bypassPermissions：字面意思是”全部放行”"></a>bypassPermissions：字面意思是”全部放行”</h2><p><strong>这个名字起得不好</strong>。很多人第一次看到都以为是”绕过一部分检查”，实际上是<strong>所有工具无条件放行，连破坏性命令都不问</strong>。</p><p>官方的警告原话是”only use in sandboxed environments”。我翻译一下就是：<strong>除非你在容器里、VM 里、或者 throwaway 机器上，别开这个模式</strong>。</p><p>我踩完第二次雷之后定了自己的规矩：bypassPermissions 只在 docker container 或 dev container 里开。本地裸机、哪怕是 laptop 都不开。</p><p>适合场景：</p><ul><li>你在 docker 里跑 Claude Code，容器挂了重建就行</li><li>CI&#x2F;CD 里的全自动任务</li><li>一次性的、可抛弃的沙盒环境</li></ul><p><strong>绝对不适合的场景</strong>：</p><ul><li>任何挂着生产凭证的机器</li><li>挂着你 .ssh&#x2F;、.aws&#x2F;、.gnupg&#x2F; 的主目录</li><li>共享开发机</li></ul><h2 id="allow-deny-ask-规则语法的坑"><a href="#allow-deny-ask-规则语法的坑" class="headerlink" title="allow &#x2F; deny &#x2F; ask 规则语法的坑"></a>allow &#x2F; deny &#x2F; ask 规则语法的坑</h2><p>这一节才是真的救命。前面四个模式是大框，实际细调靠 <code>.claude/settings.json</code> 里的 permissions 规则：</p><div class="code-container" data-rel="Json"><figure class="iseeu highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;permissions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;allow&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;Read&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Grep&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Glob&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(git status:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(git diff:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(npm test:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(pytest:*)&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;deny&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;Bash(rm -rf /:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(git push --force:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;WebFetch&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ask&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;Edit&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Write&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div><p>规则几个容易踩的点：</p><p><strong>坑 1：<code>Bash(xxx:*)</code> 的冒号和通配符</strong></p><p><code>Bash(git status:*)</code> 表示”命令以 <code>git status</code> 开头的任意后缀”。注意<strong>开头匹配的是命令字符串</strong>，不是 argv 解析后的首个 token。如果 Claude 写了 <code>sudo git status</code>，不匹配！因为开头是 “sudo”。</p><p>我被这个坑过——allow 里写了 <code>Bash(git diff:*)</code>，但 Claude 用 <code>gh pr diff | git apply</code> 这种 pipeline，直接跳出 allow 范围变成 ask。不算坑，算细节。</p><p><strong>坑 2：deny 的优先级</strong></p><p>deny &gt; ask &gt; allow。只要 deny 命中了就彻底拦，即使 allow 也命中。这个设计是对的，但新手容易以为 allow 能 override deny。</p><p><strong>坑 3：bypassPermissions 下的 deny</strong></p><p><strong>deny 规则在 bypassPermissions 下依然生效</strong>。这是 bypass 唯一还能抢救的地方。所以我即使在 docker 里开 bypass，也会留一份 deny 名单：</p><div class="code-container" data-rel="Json"><figure class="iseeu highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;deny&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">  <span class="string">&quot;Bash(rm -rf /:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">&quot;Bash(rm -rf ~:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">&quot;Bash(rm -rf $HOME:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">&quot;Bash(dd if=:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">&quot;Bash(mkfs:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">&quot;Bash(:()&#123; :|:&amp; &#125;;::*)&quot;</span></span><br><span class="line"><span class="punctuation">]</span></span><br></pre></td></tr></table></figure></div><p>最后那条是经典 fork bomb，防止 Claude 不小心生成它。我知道听起来夸张，但安全就是这样——防 99% 没用，得防到 99.97%。</p><p><strong>坑 4：Bash 参数匹配用正则不好使</strong></p><p>规则匹配是<strong>前缀字符串</strong>，不是正则。<code>Bash(.*push --force:*)</code> 不会匹配 <code>git push --force</code>，这个语法根本不被支持。要拦就得显式列出：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;Bash(git push --force:*)&quot;,</span><br><span class="line">&quot;Bash(git push -f:*)&quot;,</span><br><span class="line">&quot;Bash(git push --force-with-lease:*)&quot;  // 这个相对安全，可以不拦</span><br></pre></td></tr></table></figure></div><p>复杂规则还是靠 hook 来做（<a href="/posts/mcp-server-internal-tools/">看 hooks 那篇</a> 里的 bash-guard 脚本），规则只做简单的前缀过滤。</p><h2 id="我的生产机标准配置"><a href="#我的生产机标准配置" class="headerlink" title="我的生产机标准配置"></a>我的生产机标准配置</h2><p>在客户的生产机上跑 Claude Code（一般是只读的排错场景），我固定这套：</p><div class="code-container" data-rel="Json"><figure class="iseeu highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;permissions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;defaultMode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;default&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allow&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;Read&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Grep&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Glob&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(git log:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(git status:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(git diff:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(kubectl get:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(kubectl describe:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(kubectl logs:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(docker ps:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(docker logs:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(curl -s http://localhost:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(psql * -c \&quot;SELECT:*\&quot;)&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ask&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;Edit&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Write&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;deny&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;Bash(rm:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(git push:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(kubectl delete:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(kubectl apply:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(docker rm:*)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(psql * -c \&quot;DROP:*\&quot;)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(psql * -c \&quot;DELETE:*\&quot;)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(psql * -c \&quot;UPDATE:*\&quot;)&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;Bash(psql * -c \&quot;INSERT:*\&quot;)&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div><p>核心思路：<strong>只读操作显式 allow，编辑全部 ask，写入类显式 deny</strong>。deny 是最后一道防线，即使我手抖按了 Y 它也被拦住。</p><h2 id="团队共享：settings-json-vs-settings-local-json"><a href="#团队共享：settings-json-vs-settings-local-json" class="headerlink" title="团队共享：settings.json vs settings.local.json"></a>团队共享：settings.json vs settings.local.json</h2><p>这两个文件的区分是另一个新手常困惑的地方。</p><ul><li><strong>settings.json</strong>：<strong>进 git</strong>，团队共享。放团队一致的 allow（比如 <code>Bash(npm test:*)</code>）、deny（比如 <code>Bash(git push --force:*)</code> 到 main）、必配的 hook。</li><li><strong>settings.local.json</strong>：<strong>不进 git</strong>（默认在 .gitignore 里），每个人自己的。放个人偏好（默认模式、默认模型）、本地路径、个人 API key。</li></ul><p>建议把 settings.json 的设计当成”团队 SOP”，跟 <code>.eslintrc</code> 一个严肃度对待。我们 repo 里 PR 改 settings.json 必须两个人 review。</p><p>权限这块讲完了，但它只是 Claude Code 安全体系的一部分。hooks 是另一半 —— 可以看 <a href="/posts/claude-md-project-config/">CLAUDE.md 项目配置</a> 那篇里 hook 部分讲的 PreToolUse 拦截。权限规则是静态的，hook 是动态的，两者组合才完整。</p><div style="background:#f0f7ff;border-left:4px solid #0c97fe;padding:16px 20px;margin:24px 0;border-radius:4px;"><strong>我的生产 settings.json 模板</strong><br/>上面贴的生产机配置是精简版。完整版里还有 MCP 工具的权限控制（某些 MCP server 可能有破坏性 tool）、按项目类型的 allow/deny 差异化（Node / Python / Go / k8s-ops）、以及团队 review settings.json 变更时的 checklist。想拿的评论里说一下你的使用场景（本地开发/staging/生产），我按你场景发对应那份。别直接抄生产版到本地，太紧了干活会卡。</div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/claude-code-permissions-sandbox/</id>
    <link href="https://claude.cocoloop.cn/posts/claude-code-permissions-sandbox/"/>
    <published>2026-04-19T07:58:25.000Z</published>
    <summary>我在生产机上吃过两次权限的亏，一次赔了小半个周末，一次差点赔了项目。最魔幻的是第二次，我自以为开了 bypassPermissions 就是&quot;啥都不让干&quot;，结果方向完全弄反了。这篇把 default / acceptEdits / plan / bypassPermissions 四个模式到底做什么、不做什么，以及 allow/deny/ask 规则的细节坑，彻底讲清楚。</summary>
    <title>Claude Code 的权限模式你真的搞懂了吗？</title>
    <updated>2026-04-21T14:34:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="Context Engineering" scheme="https://claude.cocoloop.cn/categories/Context-Engineering/"/>
    <category term="Claude" scheme="https://claude.cocoloop.cn/tags/Claude/"/>
    <category term="Context" scheme="https://claude.cocoloop.cn/tags/Context/"/>
    <category term="RAG" scheme="https://claude.cocoloop.cn/tags/RAG/"/>
    <category term="Token Budget" scheme="https://claude.cocoloop.cn/tags/Token-Budget/"/>
    <content>
      <![CDATA[<h2 id="一个-187K-token-的惨痛教训"><a href="#一个-187K-token-的惨痛教训" class="headerlink" title="一个 187K token 的惨痛教训"></a>一个 187K token 的惨痛教训</h2><p>去年九月接了一个保险行业的 AI 客服项目。甲方最开始那位产品经理的原话我到现在记得——“Claude 不是有 200K 窗口吗？那我们所有保单条款、历史对话、用户档案，全塞进去，让模型自己找答案。”</p><p>我当时就懵了一下，但没反驳。毕竟客户是上帝嘛，先跑一版看看。</p><p>跑了一周测试集，F1 从基线的 0.76 掉到了 0.68。客户那边还没发作，我自己先慌了。一次请求平均 187K token，input 就要烧掉 0.56 美刀，返回质量还不如当初拿 20K context 做的 demo。</p><p>后来翻 Anthropic 发的一些工程 blog，加上我自己跑了十几组对比，才把问题理清楚：200K 是<strong>能用</strong>的上限，不是<strong>该用</strong>的配额。就像你信用卡额度 50 万，不代表你每个月该刷 50 万。</p><h2 id="我现在固定用的-6-桶预算模型"><a href="#我现在固定用的-6-桶预算模型" class="headerlink" title="我现在固定用的 6 桶预算模型"></a>我现在固定用的 6 桶预算模型</h2><p>后来我们团队内部定了个分配规则，做 RAG 或者 agent 的时候就照这个走。数字是针对 Sonnet 4.5 调出来的，Opus 可以宽一点，Haiku 要再紧一点。</p><p><strong>桶 1：系统指令（~2K）</strong></p><p>就是你那段「你是一个 XX 助手，请用 YY 风格回答」。很多人写到一两千字还收不住。说白了系统指令不是写小说，够用就行。2K 基本能覆盖角色、风格、硬约束三件事。超了基本就是废话太多，或者该用 CLAUDE.md 沉淀的东西塞到这里了（顺手推荐看 <a href="/posts/claude-md-project-config/">CLAUDE.md 项目配置</a>）。</p><p><strong>桶 2：工具定义（~3K）</strong></p><p>如果你用 function calling 或者 MCP，工具的 JSON Schema 会占一块。别看着不多，一个复杂 agent 挂 15 个工具，光定义就能飙到 8K。我现在的原则是：一次对话用不到的工具就不要挂进来，动态加载。关于 MCP 和传统 function calling 的取舍可以看 <a href="/posts/mcp-vs-function-calling/">MCP vs Function Calling</a>。</p><p><strong>桶 3：长期记忆&#x2F;知识库精华（~10K）</strong></p><p>这块是 RAG 系统里最容易被忽视的。不是当次检索的文档，而是”这个用户是老客户、VIP 等级、历史投诉 2 次”这种持久化状态。塞太多就变成背景噪音，模型 attention 会被稀释。</p><p><strong>桶 4：当前检索结果（~30K）</strong></p><p>也就是 retrieval 阶段捞回来的 top-k 文档。30K 听起来挺多，其实也就 6-8 段长文档。超过这个数你就要考虑 rerank 了——这事儿我专门写过 <a href="/posts/prompt-to-context-engineering/">RAG 里的 rerank</a>。</p><p><strong>桶 5：对话历史（~20K）</strong></p><p>多轮对话的累积。到一定长度必须压缩，后面会详细说。</p><p><strong>桶 6：当前用户输入（~5K）</strong></p><p>用户这一轮说的话。留点余量给他贴个长邮件或者代码片段。</p><p>加起来 70K 左右，离 200K 还差得远。但这是<strong>健康水位</strong>，不是上限。</p><h2 id="每桶超支的典型症状"><a href="#每桶超支的典型症状" class="headerlink" title="每桶超支的典型症状"></a>每桶超支的典型症状</h2><p>这是我们踩坑总结的对照表，现在贴在 Confluence 上，新人上手前先看。</p><p>系统指令超过 4K：模型开始”遗忘”开头指令，尤其到长对话后期。这其实就是经典的 “lost in the middle”。</p><p>工具定义超过 5K：模型会<strong>乱挑工具</strong>。我见过一个 case，13 个工具里有 2 个功能相近，定义又长，模型 30% 的 case 选错了那个。砍掉重复的瞬间就好。</p><p>长期记忆超过 15K：无关信息污染。比如你塞了用户三年前的一次购买记录，模型突然就基于那个老信息做推理。不该知道的别给它知道。</p><p>检索结果超过 50K：进入 lost-in-the-middle 重灾区。Anthropic 自己的论文也印证了，position 10-20 的文档 recall 会掉 18-22%。</p><p>对话历史超过 30K：模型开始”忘掉”最早的约定。我那个保险项目最严重的时候，用户前 30 轮说了自己是 70 岁老人、心脏病史，到第 80 轮模型推荐了一个”适合年轻健康人群”的产品。</p><p>当前输入超过 10K：通常是用户贴了整份文档。这种 case 应该拆成一次 ingestion + 一次 query，而不是直接塞。</p><h2 id="那个保险项目后来怎么救回来的"><a href="#那个保险项目后来怎么救回来的" class="headerlink" title="那个保险项目后来怎么救回来的"></a>那个保险项目后来怎么救回来的</h2><p>原始版本 187K，F1 0.68。我们做了四件事：</p><p>第一，检索从 top-20 砍到 top-5，配合 <a href="/posts/claude-family-haiku-sonnet-opus/">Haiku 4.5 做 rerank</a>。这一步把桶 4 从 78K 压到 22K。</p><p>第二，对话历史做分级压缩——最近 5 轮原文保留，再之前的滚动摘要，十轮之前只保留 entity。桶 5 从 52K 压到 14K。</p><p>第三，系统指令砍掉一堆”请注意””务必””切记”这种废话，结构化成 XML 标签（<a href="/posts/claude-xml-over-markdown/">为什么 XML 比 Markdown 好</a>）。桶 1 从 6K 压到 1.8K。</p><p>第四，长期记忆只保留<strong>当前会话可能用到的那部分</strong>——不是把用户所有档案塞进去，而是提前按 intent 做一次筛选。桶 3 从 31K 压到 7K。</p><p>最终一次请求 44.8K token，F1 0.84。成本从 0.56 刀降到 0.11 刀。客户那位产品经理到项目收尾还问我：”你们是不是换了更贵的模型？怎么感觉变聪明了。”</p><h2 id="Lost-in-the-middle-的几个缓解技巧"><a href="#Lost-in-the-middle-的几个缓解技巧" class="headerlink" title="Lost in the middle 的几个缓解技巧"></a>Lost in the middle 的几个缓解技巧</h2><p>这个效应没法完全消除，只能减弱。我现在常用的几招：</p><p><strong>重要信息放头尾。</strong> 系统指令放头，当前 query 放尾，这两个位置 attention 最强。中间塞检索结果。关于系统指令到底放哪里，<a href="/posts/prompt-system-role-placement/">这篇</a>写得比较清楚。</p><p><strong>用 XML 标签锚定关键段。</strong> 模型对 <code>&lt;critical&gt;</code> <code>&lt;user_profile&gt;</code> 这种标签极其敏感，相当于给它划了重点。</p><p><strong>证据按相关度降序，但最相关的放最后。</strong> 这是反直觉的。因为 recency 效应比 primacy 还强。我们 AB 过，相关度最高的放最后比放最前 F1 高 3-4 个点。</p><p><strong>别让模型自己找针。</strong> 在 prompt 里直接说”答案在 Document 3 的第二段”，比让它自己扫描 20 段文档准确率高很多。当然这要求你 retrieval 环节已经定位精确。</p><h2 id="给还在”塞满”的朋友一句话"><a href="#给还在”塞满”的朋友一句话" class="headerlink" title="给还在”塞满”的朋友一句话"></a>给还在”塞满”的朋友一句话</h2><p>200K context 是营销数字，不是工程配额。</p><p>真正决定效果的是你怎么<strong>编排</strong>这 200K，不是你塞了多少。我现在看到团队说”我们要用满 context”就觉得这个项目八成要返工。就像做菜，盘子大不代表菜要装满，留白也是风味的一部分。</p><p>预算思维只是第一步。拆好桶之后，每个桶里面还有一堆小学问——怎么压缩对话历史、怎么排列检索结果、怎么让 Claude 真的记住最重要的几句话。后面几篇会一个一个拆。</p><div class="cta-card" style="margin:32px 0;padding:20px 24px;background:#f0f7ff;border-left:4px solid #0c97fe;border-radius:6px;"><p style="margin:0 0 8px;font-weight:600;color:#1f2937;">延伸阅读</p><p style="margin:0;color:#4b5563;font-size:14px;line-height:1.8;">预算拆完还得看怎么省钱，[Prompt Caching 深度指南](/posts/prompt-caching-deep-guide/) 讲了怎么把桶 1、2、3 做成可复用缓存。想系统理解"从 prompt 到 context"的思维升级，看 [Prompt Engineering 到 Context Engineering](/posts/prompt-to-context-engineering/)。如果你还在纠结模型选型，[Haiku/Sonnet/Opus 怎么选](/posts/claude-family-haiku-sonnet-opus/) 里有实测对比。</p></div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/context-window-budget-strategy/</id>
    <link href="https://claude.cocoloop.cn/posts/context-window-budget-strategy/"/>
    <published>2026-04-19T06:32:39.000Z</published>
    <summary>去年接的一个客服项目，team 老大拍桌子说&quot;Claude 不是有 200K 吗，全塞进去嘛&quot;。我照做了一周，准确率降了 8 个点，token 账单翻倍。后来才明白，200K 是上限不是配额，你得像做家庭预算一样拆桶。这篇写我们最后怎么从 187K 压到 45K，准确率反而涨了 11 个点的全过程。</summary>
    <title>200K context 怎么花？我把一次请求拆成 6 个预算桶</title>
    <updated>2026-04-21T15:31:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="Prompt 工程" scheme="https://claude.cocoloop.cn/categories/Prompt-%E5%B7%A5%E7%A8%8B/"/>
    <category term="Claude" scheme="https://claude.cocoloop.cn/tags/Claude/"/>
    <category term="Prompt" scheme="https://claude.cocoloop.cn/tags/Prompt/"/>
    <category term="System Prompt" scheme="https://claude.cocoloop.cn/tags/System-Prompt/"/>
    <category term="结构设计" scheme="https://claude.cocoloop.cn/tags/%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    <content>
      <![CDATA[<p>上周给一个做法律 AI 的客户 review 线上代码，看到他 system prompt 里塞了足足 850 个字。开头是”你是一个专业、严谨、客观、富有同理心且极其注重细节的法律助手”，后面跟了两段职业操守、三段风格要求，还夹了一条”请确保你的回答准确无误”。</p><p>user 那边呢？就一句：”请帮我分析这份合同的风险点。”然后把合同全文贴上去。</p><p>我看完大概沉默了十秒。这不是一个 prompt，这是一份岗位说明书。</p><h2 id="我先讲一下-system-和-user-的职责边界"><a href="#我先讲一下-system-和-user-的职责边界" class="headerlink" title="我先讲一下 system 和 user 的职责边界"></a>我先讲一下 system 和 user 的职责边界</h2><p>这事其实 Anthropic 在官方文档里讲得挺清楚，但被大部分中文教程带偏了。中文圈子里流行的说法是”system 放人设，user 放问题”，听起来没毛病，但太笼统。</p><p>我现在理解的边界是这样：</p><p><strong>system 管的是”约束”——不管这次来什么请求，这些规则都成立。</strong>比如你是什么角色、不能做什么、输出什么格式、用哪种语气、失败时怎么兜底。这些东西一旦写进 system，Claude 会把它当成整个会话的”重力场”。</p><p><strong>user 管的是”指令”——这一次我具体要你干什么。</strong>输入数据、本次任务目标、这次特殊的限制。</p><p>所以”请确保你的回答准确无误”这种话放 system 是浪费 token。它对所有任务都成立，说了等于没说。Claude 又不会因为你没说就故意瞎编。</p><p>那个客户一开始不服气，说你看 OpenAI 官方示例里也会写”你要准确”。我说那是他们 GPT 的风格，Claude 对这种”空话”敏感度很高，你写多了它反而会把实际约束稀释掉。</p><h2 id="我拆的三层模型"><a href="#我拆的三层模型" class="headerlink" title="我拆的三层模型"></a>我拆的三层模型</h2><p>在他项目上我重写了一版，就砍到 320 字，结构长这样：</p><p>第一层是<strong>身份（Identity）</strong>。两三句说清你是谁、服务谁、核心任务。不要形容词堆叠。”你是一名为中国中小企业服务的合同审阅助手，用户通常是没有法务团队的创业者”——这一句比”专业严谨富有同理心”信息量大十倍。因为它锚定了读者画像。</p><p>第二层是<strong>规则（Rules）</strong>。必须做什么、绝对不能做什么、边界在哪。我一般用无序列表直接列出来，每条不超过 15 字。比如：</p><ul><li>不提供具体诉讼策略</li><li>不引用未经查证的法条编号</li><li>发现高风险条款必须用 <code>&lt;alert&gt;</code> 标签标出</li><li>无法判断时直接说”建议咨询律师”，不要硬答</li></ul><p>这层最怕的是写得像宪法——“应当秉持公正原则”这种话 Claude 看了会礼貌性点头然后该怎么错还怎么错。要具体、要可执行、要像红线。</p><p>第三层是<strong>示例（Examples）</strong>。1-2 个微型 in&#x2F;out 对，展示典型场景的风格和格式。如果你的任务有固定输出结构，这一层几乎是救命的。不过示例别写太长，system 里的示例主要是”锚定风格”，不是”穷举用例”。穷举用例的活儿交给 user 里的 few-shot 做更合适，这个我在<a href="/posts/prompt-few-shot-design/">另一篇讲 few-shot 设计的文章</a>里会细说。</p><h2 id="客户那版为什么会失败"><a href="#客户那版为什么会失败" class="headerlink" title="客户那版为什么会失败"></a>客户那版为什么会失败</h2><p>回头看他原来那 850 字，问题不是”写得不好”，是”层次混乱”。身份、规则、示例、自我吹捧全糊在一块儿。</p><p>Claude 这种大模型处理长 system 的时候，有个我观察到的规律：<strong>越靠前的内容权重越高，越具体的内容越容易被执行，越形容词化的内容越容易被忽略。</strong></p><p>他原 prompt 开头那三行全是形容词，Claude 大致相当于收到一个”请你表现得很牛逼”的模糊信号。到第 400 字左右才出现”不要编造法条”这种硬约束——但这时候注意力已经被稀释了。</p><p>我重写后把身份压到两句、规则上提到前置位、形容词几乎全删。实测在同一批 200 个合同分析 case 上，合格率从 71% 涨到 88%。”合格”的标准是他们自己的 QA 团队打分，不是我定的。</p><h2 id="Claude-到底多”听”system"><a href="#Claude-到底多”听”system" class="headerlink" title="Claude 到底多”听”system"></a>Claude 到底多”听”system</h2><p>这事我顺手测过。同一条硬约束，放 system 里和放 user 里，执行率差多少？</p><p>我拿一个简单规则：”输出必须是 JSON，不要任何解释文字”，跑了 100 次相同的抽取任务。</p><ul><li>只写在 user 里：73 次合规</li><li>只写在 system 里：91 次合规</li><li>两边都写：96 次合规</li></ul><p>差距挺明显的。Claude 对 system 的”服从度”确实更高，这也是为什么我倾向把格式约束、输出结构这类”每次都一样”的东西往 system 放。想稳住 JSON 输出还有一套组合拳，我在<a href="/posts/prompt-output-format-json-schema/">让 Claude 稳定吐 JSON 的 5 个套路</a>里专门写了。</p><p>顺便提一句，如果你在用 <code>CLAUDE.md</code> 做项目级配置，那个文件本质上就是个持久化的 system 增强，相关的写法我在<a href="/posts/claude-md-project-config/">Claude Code 项目配置那篇</a>里有更细的讨论。</p><h2 id="一些容易踩的坑"><a href="#一些容易踩的坑" class="headerlink" title="一些容易踩的坑"></a>一些容易踩的坑</h2><p><strong>坑一：把可能变化的东西写进 system。</strong>比如”今天是 2026 年 4 月 20 日”——明天就过期了。这种应该放 user 或者动态注入。</p><p><strong>坑二：system 里塞大段背景资料。</strong>知识性内容该放 user 或者用 RAG。system 主要承载规则，不是知识库。放多了既浪费缓存命中率，又让模型分心。prompt caching 的机制我在<a href="/posts/prompt-caching-deep-guide/">另一篇深入讲过</a>。</p><p><strong>坑三：用 markdown 标题装饰 system。</strong><code># 身份</code> <code>## 规则</code> 这种写法不是不行，但 Claude 对 XML 标签更敏感，用 <code>&lt;identity&gt;</code> <code>&lt;rules&gt;</code> <code>&lt;examples&gt;</code> 这种语义标签效果更稳。为什么 Claude 特别吃 XML，<a href="/posts/claude-xml-over-markdown/">这篇</a>专门讨论过。</p><p><strong>坑四：规则互相矛盾没察觉。</strong>比如一边说”回答要简洁”一边说”要引用具体法条并解释”。Claude 遇到矛盾约束时倾向挑软的执行，结果就是两头不讨好。写完规则自己过一遍，看有没有打架的。</p><h2 id="我现在的-system-模板"><a href="#我现在的-system-模板" class="headerlink" title="我现在的 system 模板"></a>我现在的 system 模板</h2><p>给一个脱敏后的骨架，你可以照着改：</p><div class="code-container" data-rel="Xml"><figure class="iseeu highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">identity</span>&gt;</span></span><br><span class="line">你是 [服务对象] 的 [核心角色]，主要帮他们 [核心任务]。</span><br><span class="line"><span class="tag">&lt;/<span class="name">identity</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">rules</span>&gt;</span></span><br><span class="line">- [硬红线 1，越具体越好]</span><br><span class="line">- [硬红线 2]</span><br><span class="line">- [输出格式约束]</span><br><span class="line">- [失败/不确定时的兜底行为]</span><br><span class="line"><span class="tag">&lt;/<span class="name">rules</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">- 语气：<span class="selector-attr">[具体到<span class="string">&quot;像资深同事一样&quot;</span>这种]</span></span></span><br><span class="line"><span class="language-css">- 长度：<span class="selector-attr">[默认段落数或字数区间]</span></span></span><br><span class="line"><span class="language-css">- 禁用：<span class="selector-attr">[明确不要的措辞]</span></span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">example</span>&gt;</span></span><br><span class="line">输入：...</span><br><span class="line">输出：...</span><br><span class="line"><span class="tag">&lt;/<span class="name">example</span>&gt;</span></span><br></pre></td></tr></table></figure></div><p>基本上 200-400 字解决战斗。想加东西先问自己：”这条规则是不是每次都成立？”不是就挪去 user。</p><p>写 prompt 这事最怕的就是”越写越长、越长越虚”。砍到只剩骨头，反而是最快出效果的办法。</p><div class="cta-card" style="margin:32px 0;padding:20px 24px;background:#f0f7ff;border-left:4px solid #0c97fe;border-radius:6px;"><p style="margin:0 0 8px;font-weight:600;color:#1f2937;">延伸阅读</p><p style="margin:0;color:#4b5563;font-size:14px;line-height:1.8;">想把 Claude 的 XML 能力用到极致，看 <a href="/posts/claude-xml-over-markdown/">为什么 Claude 特别吃 XML 标签</a>。想搞清楚 system 和持久化配置的边界，去 <a href="/posts/claude-md-project-config/">CLAUDE.md 项目配置那篇</a>。要让输出稳定，别错过 <a href="/posts/prompt-output-format-json-schema/">让 Claude 稳定吐 JSON 的 5 个套路</a>。</p></div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/prompt-system-role-placement/</id>
    <link href="https://claude.cocoloop.cn/posts/prompt-system-role-placement/"/>
    <published>2026-04-18T16:39:50.000Z</published>
    <summary>上周给一个做法律 AI 的客户 review，看到他 system 里塞了 850 字的&quot;你是一个专业且严谨的助手&quot;，user 里反而只有光秃秃一句问题。我把 system 重写成 320 字分三层，同一批 200 个测试 case 的合格率从 71% 拉到 88%。这篇就说说 system 和 user 到底各自管什么。</summary>
    <title>System Prompt 到底放什么？我把 Anthropic 那套约束拆了三层</title>
    <updated>2026-04-21T13:39:50.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="入门" scheme="https://claude.cocoloop.cn/categories/%E5%85%A5%E9%97%A8/"/>
    <category term="Anthropic" scheme="https://claude.cocoloop.cn/tags/Anthropic/"/>
    <category term="Claude" scheme="https://claude.cocoloop.cn/tags/Claude/"/>
    <category term="Constitutional AI" scheme="https://claude.cocoloop.cn/tags/Constitutional-AI/"/>
    <category term="AI 安全" scheme="https://claude.cocoloop.cn/tags/AI-%E5%AE%89%E5%85%A8/"/>
    <content>
      <![CDATA[<p>我第一次知道 Anthropic 这家公司，是 2021 年夏天。那会儿朋友圈有人转过一张图，说 OpenAI 安全团队的核心十几个人集体离职了，领头的是个叫 Dario Amodei 的意大利裔研究员，他妹妹 Daniela 跟他一起走。当时我没在意，觉得不过是硅谷又一次常规的团队分裂。</p><p>后来 ChatGPT 火了，我被推着去试了 GPT-3.5，然后有朋友从内测通道甩给我一个叫 Claude 的东西，让我试试。我记得那天晚上我把同一段写得很烂的需求文档分别喂给两个模型，让它们帮我重写。GPT 给我的东西结构很漂亮但逻辑是飘的，Claude 给我的第一版不好看，但它会主动指出”你第 3 条和第 5 条是矛盾的，我暂时按第 3 条来写了，你确认一下”。</p><p>就那一句话，我记到今天。</p><h2 id="那群人为什么非要从-OpenAI-出来"><a href="#那群人为什么非要从-OpenAI-出来" class="headerlink" title="那群人为什么非要从 OpenAI 出来"></a>那群人为什么非要从 OpenAI 出来</h2><p>Anthropic 的核心创始团队，基本上是 OpenAI 当年负责 GPT-2、GPT-3 研究和安全的那批人。Dario 之前是 OpenAI 的研究 VP，Daniela 管运营，Tom Brown 是 GPT-3 论文一作，Jared Kaplan 是 scaling law 那篇论文的作者之一。</p><p>他们出来的表面原因是”对公司方向有分歧”，但你跟在那个圈子里的人聊过就知道，真实原因没那么玄乎：他们觉得 OpenAI 在安全问题上往后退了，产品化压力越来越大，而他们想做的事——搞清楚”一个能力越来越强的模型，怎么才能持续可控”——在原来那个组织里推不动了。</p><p>所以 Anthropic 一开始不是一家产品公司，是一家研究公司。2021 年成立，头两年几乎没对外发布任何可用的东西，只发论文。这跟 OpenAI 一上来就憋大招搞 GPT-3 API 完全是两种打法。</p><p>顺便说一句，我去年跟一个在 Anthropic SF 办公室的朋友吃饭，他说他们内部开会还是把”Safety”放在产品节奏前面的，这跟外界的印象差不多能对上。</p><h2 id="Constitutional-AI-到底是什么，用白话讲"><a href="#Constitutional-AI-到底是什么，用白话讲" class="headerlink" title="Constitutional AI 到底是什么，用白话讲"></a>Constitutional AI 到底是什么，用白话讲</h2><p>要理解 Claude 跟 GPT 的区别，绕不开 Constitutional AI（简称 CAI）。很多文章讲这玩意讲得云里雾里，我用一个我给非技术客户讲过的比喻：</p><p>RLHF，也就是 OpenAI 训 ChatGPT 那套主流做法，相当于雇了一群人类标注员去打分——模型生成 A 和 B 两个回答，人看完说 A 更好，模型就记住”要像 A 那样”。这种方式的问题在于，人工很贵，而且人会累、会不一致、会有偏见。</p><p>CAI 的做法是：先写一份”宪法”，大概几十条原则（比如”不要帮用户做违法的事””不要输出对特定群体不公平的表述””当你不确定的时候要说不确定”），然后让模型自己拿这份宪法去给自己的回答打分、改写、再打分。人类只在最开始参与，后面大部分工作模型自己循环。</p><p>好处是一致性高、可审计（宪法是明文的）、更便宜。缺点是如果宪法写歪了，模型会一本正经地把错误放大。Anthropic 这几年在改的主要就是这份宪法本身。</p><p>想展开看 Claude 和 GPT、Gemini 各自的路线差异，<a href="/posts/claude-vs-gpt-gemini-2026q2/">我之前写过一篇 2026 Q2 的横向对比</a>，可以接着读。</p><h2 id="Claude-家族这几年怎么长起来的"><a href="#Claude-家族这几年怎么长起来的" class="headerlink" title="Claude 家族这几年怎么长起来的"></a>Claude 家族这几年怎么长起来的</h2><p>我简单捋一下，按我自己用过的版本为准：</p><ul><li><strong>Claude 1（2023 初）</strong>：只在 Slack 里能用，反应慢，但长文本理解明显比当时的 GPT-3.5 好。</li><li><strong>Claude 2（2023 年中）</strong>：开放了 API，上下文窗口做到 100K，我当时拿来啃合同、翻法律文书，第一次感觉”这东西能干活”。</li><li><strong>Claude 3 系列（2024）</strong>：第一次分了 Haiku &#x2F; Sonnet &#x2F; Opus 三档，Opus 3 在推理和写作上当时是拉开同档 GPT 一截的。</li><li><strong>Claude 3.5 &#x2F; 3.7（2024-2025）</strong>：Sonnet 3.5 开始真的好用，Claude Code 也是这一代起步的。</li><li><strong>Claude 4 系列（2025-2026）</strong>：现在线上是 Haiku 4.5、Sonnet 4.6、Opus 4.7。Sonnet 4.6 已经是我日常 80% 任务的主力。</li></ul><p>三档模型怎么选，我在<a href="/posts/claude-family-haiku-sonnet-opus">这篇 Haiku&#x2F;Sonnet&#x2F;Opus 选型笔记</a>里写得很细，这里不重复。</p><h2 id="和-GPT、Gemini-的定位差异"><a href="#和-GPT、Gemini-的定位差异" class="headerlink" title="和 GPT、Gemini 的定位差异"></a>和 GPT、Gemini 的定位差异</h2><p>我尽量不贬低其他家，说几个我自己体感上的区别：</p><p><strong>写长文、处理长文档</strong>：Claude 到现在为止还是我的首选。它在 50K 以上上下文里不容易丢信息，GPT-4 类在这种场景下常常会”中间段失忆”。</p><p><strong>代码</strong>：以前 GPT 更强，2025 年之后 Claude Code + Sonnet 反超了，尤其是改大型代码库、做 refactor 这种活。我现在写代码 90% 用 <a href="/posts/claude-code-vs-cursor-cline">Claude Code 这一套</a>。</p><p><strong>对话自然度</strong>：GPT 的”人味”更足一点，Claude 会稍微一本正经一些。写营销文案我反而会用 GPT 打草稿，再用 Claude 改逻辑。</p><p><strong>安全边界</strong>：Claude 拒答率偏高，有时候你让它帮你写个对某个公司有点批评的分析，它会很谨慎。好处是不容易出事，坏处是有时候挺烦的。</p><p><strong>价格</strong>：Haiku 做到 1 美元&#x2F;百万 tokens 输入，这个价位段 Anthropic 比较卷。</p><p>Gemini 我用得少，主要在 Google Workspace 场景里当插件用，独立用它写东西我没习惯。</p><h2 id="我印象最深的一次对话"><a href="#我印象最深的一次对话" class="headerlink" title="我印象最深的一次对话"></a>我印象最深的一次对话</h2><p>2024 年夏天，我帮一个客户做合同风险审查，把一份 80 页的英文投资协议丢给 Claude 2，让它找出”对乙方明显不利的条款”。</p><p>它没直接列清单，先跟我说了一句：”这份合同里的不利条款有些是显性的（比如第 14.3 条违约金），有些是靠几条互相引用的条款组合出来的陷阱（比如 7.2+11.5+附件 C-3），你想要哪种？”</p><p>我当时愣了一下。那个”组合陷阱”它最后挑出来 3 条，其中 1 条我们的律师团队初审都没发现。</p><p>从那之后我对 Claude 的印象就定下来了——它不是最聪明的，但它是那个”会告诉你它在想什么、不确定的地方会说出来”的模型。我觉得这跟 Constitutional AI 这条技术路线是有关系的，不完全是运气。</p><div class="cta-card" style="margin:32px 0;padding:20px 24px;background:#f0f7ff;border-left:4px solid #0c97fe;border-radius:6px;"><p style="margin:0 0 8px;font-weight:600;color:#1f2937;">想系统学 Claude？</p><p style="margin:0;color:#4b5563;font-size:14px;line-height:1.8;">接下来建议先看<a href="/posts/claude-family-haiku-sonnet-opus/">三档模型怎么选</a>，搞清楚你手里这个任务该调用哪一个。想直接上手 API 的话，<a href="/posts/claude-api-quickstart/">15 分钟跑通第一次请求</a>这篇是我写给完全没碰过的朋友的。横向对比 GPT/Gemini 可以看<a href="/posts/claude-vs-gpt-gemini-2026q2/">2026 Q2 三家对比</a>。</p></div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/claude-what-is-anthropic-vs-openai/</id>
    <link href="https://claude.cocoloop.cn/posts/claude-what-is-anthropic-vs-openai/"/>
    <published>2026-04-17T21:53:52.000Z</published>
    <summary>我从 2023 年初开始用 Claude，一路看着它从 Slack bot 里的一个小玩具长成现在 Opus 4.7 这个样子。很多人问我 Claude 到底是什么、跟 GPT 差别在哪、为什么那群人要从 OpenAI 出来单干——今天这篇就按我自己理解的顺序，从头讲到尾。</summary>
    <title>Claude 是什么？从 Anthropic 那群 OpenAI 出走的人说起</title>
    <updated>2026-04-19T20:53:52.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="Tool与MCP" scheme="https://claude.cocoloop.cn/categories/Tool%E4%B8%8EMCP/"/>
    <category term="MCP" scheme="https://claude.cocoloop.cn/tags/MCP/"/>
    <category term="Function Calling" scheme="https://claude.cocoloop.cn/tags/Function-Calling/"/>
    <category term="Agent架构" scheme="https://claude.cocoloop.cn/tags/Agent%E6%9E%B6%E6%9E%84/"/>
    <category term="协议" scheme="https://claude.cocoloop.cn/tags/%E5%8D%8F%E8%AE%AE/"/>
    <content>
      <![CDATA[<p>前两周我跟一个做 Agent 产品的朋友吃饭，他甩给我一句话：”MCP 不就是 Function Calling 换了个皮吗？我们已经有 tool use 了，整这个 MCP 协议是 Anthropic 在刷存在感吧。”</p><p>我当时嘴里正嚼着东西，差点呛住。这误解在开发者圈里太常见了，甚至连一些写过相关代码的人也会这么觉得。但如果你真的在生产环境里把两种方式都用过，就会明白它们差的不是一点半点——<strong>维度根本不一样</strong>。</p><p>Function Calling 是工具；MCP 是给工具定一个标准插口。你可以说螺丝刀和螺丝的规范是一回事吗？当然不是。</p><p>这篇我想把五个最本质的差异一条一条拆给你看，最后说说实际项目选型怎么想。</p><h2 id="差异一：协议层-vs-应用层"><a href="#差异一：协议层-vs-应用层" class="headerlink" title="差异一：协议层 vs 应用层"></a>差异一：协议层 vs 应用层</h2><p>这是最根本的一条，想通了这一点其它四条都顺了。</p><p>Function Calling 是 OpenAI 2023 年 6 月发的一个 API 特性，它的本质是：<strong>模型输出结构化 JSON，你的应用代码去执行</strong>。Anthropic、Google、阿里、智谱……现在都有自己的 tool use &#x2F; function calling API，但注意——<strong>这是每一家自己 API 里的一个功能</strong>。你的代码和 xx 家模型的 API 深度耦合。</p><p>MCP（Model Context Protocol）不一样。它是 Anthropic 2024 年 11 月开源出来的一个<strong>通信协议</strong>，基于 JSON-RPC 2.0，规定了 Client 和 Server 之间怎么发现工具、怎么调用工具、怎么传递上下文、怎么处理流式响应。它本身<strong>不绑定任何模型、任何厂商</strong>。</p><p>打个比方：</p><ul><li>Function Calling 像每个品牌充电器的私有接口。苹果 lightning、华为 Type-C 以前的自家协议、早期安卓的 micro USB——每家自己一套</li><li>MCP 像 USB-C。协议标准化之后，任何设备对任何设备都能用</li></ul><p>USB-C 这个比喻最早是 Anthropic 官方自己提的，我一开始觉得有营销味，用多了之后发现还挺贴切。</p><h2 id="差异二：有状态-vs-无状态"><a href="#差异二：有状态-vs-无状态" class="headerlink" title="差异二：有状态 vs 无状态"></a>差异二：有状态 vs 无状态</h2><p>Function Calling 是一次性交易。你给模型一个 tools 数组，模型决定调哪个、给你返回 JSON，你执行完把结果再塞回对话。<strong>工具本身没有”会话”概念</strong>，每次调用都是独立的。</p><p>MCP Server 是一个<strong>长连接的服务</strong>。它可以：</p><ul><li>维护一个数据库连接池，整个会话期间复用</li><li>缓存用户身份信息，不用每次鉴权</li><li>推送 <code>notifications/resources/updated</code> 告诉客户端”我这边的资源变了”</li><li>在多次调用之间保持工作目录、临时文件这些状态</li></ul><p>举个最实际的例子：你要做一个”代码仓库分析”工具，让 Claude 能读取项目结构、打开文件、跑测试。</p><ul><li>Function Calling 版本：每次调用 <code>read_file</code> 都要把完整路径当参数传进去，模型要自己记住当前在哪个仓库</li><li>MCP 版本：Server 启动时 <code>cd</code> 到仓库根目录，之后所有调用都是相对路径；Server 还可以主动推送”文件变更”事件让模型知道有新的 commit</li></ul><p><strong>这个差异在 Agent 长时任务里会被放大</strong>。任务跑 30 分钟、跨越 200 轮对话，有状态和无状态是两种完全不同的编程模型。</p><h2 id="差异三：可发现-vs-硬编码"><a href="#差异三：可发现-vs-硬编码" class="headerlink" title="差异三：可发现 vs 硬编码"></a>差异三：可发现 vs 硬编码</h2><p>你有没有写过这种代码：</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">tools = [</span><br><span class="line">    &#123;<span class="string">&quot;name&quot;</span>: <span class="string">&quot;search_web&quot;</span>, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;...&quot;</span>, <span class="string">&quot;parameters&quot;</span>: &#123;...&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;name&quot;</span>: <span class="string">&quot;send_email&quot;</span>, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;...&quot;</span>, <span class="string">&quot;parameters&quot;</span>: &#123;...&#125;&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;name&quot;</span>: <span class="string">&quot;query_db&quot;</span>, <span class="string">&quot;description&quot;</span>: <span class="string">&quot;...&quot;</span>, <span class="string">&quot;parameters&quot;</span>: &#123;...&#125;&#125;,</span><br><span class="line">]</span><br><span class="line">response = client.messages.create(tools=tools, ...)</span><br></pre></td></tr></table></figure></div><p>这就是 Function Calling 的典型写法——<strong>工具列表写死在客户端代码里</strong>。你想加一个工具？改代码、重新部署。你想让不同用户看到不同的工具？自己写权限过滤逻辑。</p><p>MCP 的核心 API 之一是 <code>tools/list</code>——客户端启动时问 Server：”你都有啥工具？”Server 返回一个动态列表。这意味着：</p><ul><li>在 Server 侧加一个新工具，所有连接的客户端<strong>不用改任何代码</strong>就能用</li><li>Server 可以根据当前用户、当前权限，返回<strong>不同子集</strong>的工具列表</li><li>工具的描述、参数 schema 都可以在运行时更新</li></ul><p>我最近在做的一个内部项目，用 MCP 接了 6 个 Server：知识库、CRM、邮件、日程、Git、监控。运营同学的客户端能看到前 3 个，工程师能看到全部 6 个，运维只看到监控——都是同一份客户端代码，只是 Server 侧发的工具清单不一样。换 Function Calling 的话，光这套权限逻辑就得写几百行。</p><h2 id="差异四：跨-LLM-vs-单家绑定"><a href="#差异四：跨-LLM-vs-单家绑定" class="headerlink" title="差异四：跨 LLM vs 单家绑定"></a>差异四：跨 LLM vs 单家绑定</h2><p>这点对做产品的人尤其重要。</p><p>我之前做过一个客户项目，用 GPT-4 的 Function Calling 做了一整套工具集成。后来客户要改成 Claude——<strong>工具定义格式不一样</strong>（OpenAI 用 JSON Schema，Anthropic 用的是类似但不完全一样的格式），消息结构也不一样（OpenAI 的 <code>tool_calls</code> vs Anthropic 的 <code>tool_use</code> content block），全部代码重写一遍，花了一周。</p><p>MCP 协议本身是厂商中立的。只要客户端实现了 MCP 协议，它就能连任何 MCP Server；你的 Server 也可以同时服务给 Claude Desktop、Cursor、Cline、甚至某些支持 MCP 的 OpenAI 兼容 Client。</p><p><strong>对 Server 开发者来说，写一次，所有生态都能用</strong>。这是真正的网络效应，也是为什么我判断 MCP 会比 Function Calling 活得久。</p><p>当然现在阶段 OpenAI 自家还没有正式支持 MCP（2026 年初还是这个状态），但第三方适配层已经有一堆了，转换成本比当年重写 Function Calling 低多了。</p><h2 id="差异五：服务端自治-vs-客户端编排"><a href="#差异五：服务端自治-vs-客户端编排" class="headerlink" title="差异五：服务端自治 vs 客户端编排"></a>差异五：服务端自治 vs 客户端编排</h2><p>这条比较抽象但很关键。</p><p>Function Calling 模式下，<strong>所有编排逻辑都在你的客户端代码里</strong>：什么时候调工具、调哪个、失败重试、结果怎么传回模型——这些都是你写的应用代码在决定。模型只负责”生成 JSON”，你负责”真正做事”。</p><p>MCP 下，Server 有更多自主权。它可以：</p><ul><li>定义自己的 <code>resources</code>（只读数据源），让模型直接引用而不是通过工具调用</li><li>定义 <code>prompts</code>（预制的 prompt 模板），让用户直接选用</li><li>通过 <code>sampling</code> API 主动<strong>反向</strong>请求客户端帮它跑一次 LLM 推理（比如 Server 自己需要做一次翻译）</li><li>发送 <code>progress</code> 通知告诉客户端”我这个任务进度 30%”</li></ul><p>这些能力是 Function Calling 完全没有的。说白了，Function Calling 把模型当大脑、把工具当手；MCP 把 Server 也当作一个有智能的参与方，大家是协作关系。</p><h2 id="选型建议：什么时候用哪个"><a href="#选型建议：什么时候用哪个" class="headerlink" title="选型建议：什么时候用哪个"></a>选型建议：什么时候用哪个</h2><p>拆完差异，回到实际问题——你到底该选哪个？</p><p><strong>选 Function Calling 的场景</strong>：</p><ul><li>单次 API 调用能搞定的任务，比如”帮我写邮件时调一次日历查空闲”</li><li>快速原型、一次性脚本</li><li>只给自己或少数内部工具用</li><li>模型厂商锁定不是问题（比如你们公司就认 OpenAI）</li></ul><p><strong>选 MCP 的场景</strong>：</p><ul><li>要给非开发者终端用户用（他们装 Claude Desktop &#x2F; Cursor 就能用你的工具）</li><li>工具需要共享给多个应用，一次开发多处复用</li><li>有长时任务、需要状态和流式响应</li><li>在搞 Agent 产品，希望兼容未来更多客户端</li></ul><p>真实世界里，我现在新项目几乎都走 MCP 了。开发成本没高多少（FastMCP 这种框架已经很顺手），但获得的可移植性和生态红利很可观。关于 FastMCP 的具体用法，我在 <a href="/categories/Tool%E4%B8%8EMCP/">Tool与MCP 分类</a> 下面那篇”30 分钟手搓 MCP Server”里有完整的代码示例可以参考。</p><p>如果你正在做 Agent 方向的产品，还可以去 <a href="/categories/Agent%E5%BC%80%E5%8F%91/">Agent开发 分类</a> 看看那边关于 Skills 和 Agent 架构的文章，跟 MCP 组合起来用会有更多想象空间。</p><h2 id="最后说点我的个人判断"><a href="#最后说点我的个人判断" class="headerlink" title="最后说点我的个人判断"></a>最后说点我的个人判断</h2><p>MCP 现在还在早期——协议版本还在迭代（最新是 2025-06-18 draft），生态工具链不算成熟，debug 也比 Function Calling 麻烦（多一层协议就多一层坑）。</p><p>但我的感觉是：<strong>这波标准化是挡不住的</strong>。Function Calling 这种”每家一套 API”的做法，在行业成熟度低的时候是必然现象，就跟早期移动端充电器一样；但当生态规模到了一定阶段，标准化协议一定会胜出。苹果最后也上了 Type-C，对吧。</p><p>做选型决策的时候，我现在的默认选项是 MCP；只有在”真的只需要一次性、工具很少、没有复用需求”的场景才退回 Function Calling。</p><p>这不是什么信仰，是算过账的——多花 20% 的开发成本，换来 3 年后不用大重构，这买卖稳赚。</p><div class="cta-card">  <div class="cta-title">Agent 生态正在爆发式演化</div>  <div class="cta-desc">想跟进 MCP 协议的最新动态和厂商适配进展，推荐关注 <a class="link"   href="https://news.cocoloop.cn/" >news.cocoloop.cn<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 的 AI 快讯板块；想看更多 Agent 架构讨论，可以去 <a class="link"   href="https://www.cocoloop.cn/" >www.cocoloop.cn<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 翻一翻。</div></div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/mcp-vs-function-calling/</id>
    <link href="https://claude.cocoloop.cn/posts/mcp-vs-function-calling/"/>
    <published>2026-04-17T11:10:26.000Z</published>
    <summary>很多人以为 MCP 就是换皮 Function Calling，这个判断错得离谱。把两者放在一张桌子上对比五个关键差异，顺便聊聊选型时怎么判断你到底需要哪个。</summary>
    <title>MCP 会是 AI 世界的 USB-C 吗？和 Function Calling 到底差在哪</title>
    <updated>2026-04-21T14:06:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="Claude Code" scheme="https://claude.cocoloop.cn/categories/Claude-Code/"/>
    <category term="Claude Code" scheme="https://claude.cocoloop.cn/tags/Claude-Code/"/>
    <category term="Hooks" scheme="https://claude.cocoloop.cn/tags/Hooks/"/>
    <category term="自动化" scheme="https://claude.cocoloop.cn/tags/%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
    <category term="DevOps" scheme="https://claude.cocoloop.cn/tags/DevOps/"/>
    <content>
      <![CDATA[<h2 id="一次差点被-fire-的-commit"><a href="#一次差点被-fire-的-commit" class="headerlink" title="一次差点被 fire 的 commit"></a>一次差点被 fire 的 commit</h2><p>2025 年 11 月，给一家做营销 SaaS 的甲方做后端重构。Claude Code 接了他们一个 14 万行的 Node 仓库，我那周手速也快，Opus 跑着跑着就把一个 shared util 文件给改了。表面看 tsc 过了、vitest 过了，PR 也被 review 通过。</p><p>然后 merge 到 main 的 37 分钟后，Grafana 告警拉响——一个正则没转义，所有 webhook 签名校验失败。用户投诉 1247 条涌进来。</p><p>回滚花了 8 分钟。我复盘那天晚上的 transcript，发现 Claude 其实跑完 Edit 之后<strong>没跑 lint</strong>，而项目里有个自定义 ESLint 规则会抓这个模式。它只是不知道要跑。</p><p>从那之后我给每个新项目进 Claude Code 之前，一定先配好 5 个 hook。不是洁癖，是真被吓到了。</p><h2 id="先说清楚-Claude-Code-的-hook-到底有几个入口"><a href="#先说清楚-Claude-Code-的-hook-到底有几个入口" class="headerlink" title="先说清楚 Claude Code 的 hook 到底有几个入口"></a>先说清楚 Claude Code 的 hook 到底有几个入口</h2><p>官方目前稳定的 hook 点我常用这几个：<code>PreToolUse</code>、<code>PostToolUse</code>、<code>UserPromptSubmit</code>、<code>Stop</code>、<code>SessionStart</code>。配置走 <code>.claude/settings.json</code> 或者 <code>settings.local.json</code>，格式大致是：</p><div class="code-container" data-rel="Json"><figure class="iseeu highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;PostToolUse&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span> <span class="attr">&quot;matcher&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Edit|Write&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;command&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;...&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">]</span> <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div><p>matcher 支持正则，所以 <code>Edit|Write|MultiEdit</code> 这种合并写法很省事。每个 hook 拿到的 payload 从 stdin 进来，是个 JSON，想拦截的话 exit code 非 0 就行。</p><p>下面这 5 个就是我跑了大半年沉淀的固定配置。</p><h2 id="Hook-1：PostToolUse-自动跑-lint-format"><a href="#Hook-1：PostToolUse-自动跑-lint-format" class="headerlink" title="Hook 1：PostToolUse 自动跑 lint&#x2F;format"></a>Hook 1：PostToolUse 自动跑 lint&#x2F;format</h2><p>最基础但最救命的一个。Claude 每次 Edit&#x2F;Write&#x2F;MultiEdit 之后，自动触发项目自己的 lint 和 format。关键是<strong>要把错误回灌给 Claude</strong>，这样它下一步会自己修。</p><p>我的 Node 项目模板：</p><div class="code-container" data-rel="Json"><figure class="iseeu highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;matcher&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Edit|Write|MultiEdit&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;command&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;node .claude/scripts/post-edit-lint.js&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div><p>脚本里我干这几件事：读 stdin 拿到被改的文件路径，按扩展名路由（.ts&#x2F;.tsx 走 eslint –fix + prettier，.py 走 ruff + black，.go 走 gofmt + golangci-lint），有 error 就 echo 到 stderr 并 exit 2。exit 2 是 Claude Code 专门的约定，stderr 内容会被塞回模型的下一轮上下文。</p><p>之前我图省事写 <code>exit 1</code>，Claude 完全看不到错误信息，一脸懵地继续往下跑。后来翻文档才知道必须 2。真要命，这种细节官方藏得挺深。</p><p>Python 项目的差异是 ruff 比较快但 mypy 慢，所以我 mypy 只在 Stop hook 里跑一次，不在 PostToolUse 里跑。不然每次编辑都卡 6-8 秒，Claude 的节奏会被打乱。</p><h2 id="Hook-2：PreToolUse-拦截危险操作"><a href="#Hook-2：PreToolUse-拦截危险操作" class="headerlink" title="Hook 2：PreToolUse 拦截危险操作"></a>Hook 2：PreToolUse 拦截危险操作</h2><p>这个就是我那次事故之后写的。规则很简单：所有 Bash 调用先过一层关键词筛子。</p><div class="code-container" data-rel="Json"><figure class="iseeu highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;matcher&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Bash&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;command&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;node .claude/scripts/bash-guard.js&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div><p>guard 脚本里的黑名单（从 stdin 读 tool_input.command 然后 grep）：</p><ul><li><code>rm -rf /</code> 以及任何作用在 <code>/</code>、<code>~</code>、<code>$HOME</code> 的递归删除</li><li><code>git push --force</code> 到任何带 <code>main|master|prod|release</code> 的分支</li><li><code>git reset --hard</code> 超过 10 个 commit 往前</li><li><code>DROP TABLE</code>、<code>TRUNCATE</code> 在生产连接串上</li><li><code>chmod 777</code> 任何目录</li></ul><p>匹配上就 exit 2 并输出拒绝原因。Claude 收到后 84.3% 的情况会自己换一个更安全的写法，剩下 15.7% 会在下一轮请求用户 confirm——这个比例是我翻了自己最近 412 次触发记录统计出来的，还挺靠谱。</p><p>顺便说，这东西的权限模式我之前也被坑过，想展开聊的可以看我另一篇 <a href="/posts/claude-code-vs-cursor-cline/">Claude Code 权限模式</a>（虽然那篇主要比三家工具，但权限部分讲得比较细）。</p><h2 id="Hook-3：UserPromptSubmit-自动注入项目状态"><a href="#Hook-3：UserPromptSubmit-自动注入项目状态" class="headerlink" title="Hook 3：UserPromptSubmit 自动注入项目状态"></a>Hook 3：UserPromptSubmit 自动注入项目状态</h2><p>这个是后期加的，但加了之后 Claude 的”上下文感”提升非常明显。用户每次提交 prompt 之前，我往上下文里塞一段项目快照：</p><ul><li>当前 git 分支</li><li>最近 3 条 commit 的 hash 和 subject</li><li>未 commit 的文件数（<code>git status --short | wc -l</code>）</li><li>当前开着的 PR 号码（如果有，通过 <code>gh pr status</code>）</li><li>今天是周几（我自己 debug 方便）</li></ul><p>脚本输出的字符串会被拼到用户 prompt 前面。我控制在 280 字以内，不然每次都多烧 token。</p><p>好处是 Claude 不会再问 “你现在在哪个分支” 这种废话，也不会傻乎乎建议我 <code>git checkout main</code> 而忽略我脏了一堆文件没提交。一个同事说”感觉 Claude 变聪明了”——其实只是它终于有视力了。</p><p>为什么这个要放在 prompt 之前而不是 system？因为系统提示词会被缓存，你每次都变动会打破 cache。想深入理解这个可以翻 <a href="/posts/prompt-system-role-placement/">系统角色与位置放置</a> 和 <a href="/posts/prompt-caching-deep-guide/">Prompt Caching 深度指南</a>。</p><h2 id="Hook-4：Stop-自动写会话摘要"><a href="#Hook-4：Stop-自动写会话摘要" class="headerlink" title="Hook 4：Stop 自动写会话摘要"></a>Hook 4：Stop 自动写会话摘要</h2><p>Claude 每次结束对话（Stop 触发），我让它把这次会话摘要写到 <code>.claude/logs/YYYY-MM-DD-HHMMSS.md</code>。格式固定：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">## 任务</span><br><span class="line">&lt;一句话&gt;</span><br><span class="line"></span><br><span class="line">## 改动文件</span><br><span class="line">&lt;列表&gt;</span><br><span class="line"></span><br><span class="line">## 决策点</span><br><span class="line">&lt;3-5 条&gt;</span><br><span class="line"></span><br><span class="line">## 未解决</span><br><span class="line">&lt;列表&gt;</span><br></pre></td></tr></table></figure></div><p>实现方式是 Stop hook 调一个小脚本，脚本里再触发一次 Claude API 的 Haiku 4.5（便宜，$0.0037 一次摘要）读最近 transcript 生成。</p><p>这东西最大的用处是<strong>第二天继续干活的时候</strong>。我打开项目第一件事是 <code>ls -t .claude/logs/ | head -5</code>，看昨天卡在哪。之前没有这个习惯的时候，我经常花 15 分钟重新”找回状态”。现在平均 3 分钟。</p><p>想了解 agent 长期记忆的完整思路可以看 <a href="/posts/context-memory-long-term-agent/">长期记忆与 Agent 状态</a>，hook 写摘要本质上就是人工版的 episodic memory。</p><h2 id="Hook-5：SessionStart-拉依赖-健康检查"><a href="#Hook-5：SessionStart-拉依赖-健康检查" class="headerlink" title="Hook 5：SessionStart 拉依赖 + 健康检查"></a>Hook 5：SessionStart 拉依赖 + 健康检查</h2><p>每次我打开 Claude Code 会话，SessionStart 触发：</p><ol><li><code>git fetch origin</code> 看远端有没有更新</li><li><code>npm install</code> 或者 <code>uv sync</code>（根据项目类型）静默跑</li><li>跑一个 10 秒的 smoke test（我们项目里是 <code>make health</code>，其实就是 curl 几个 internal endpoint）</li><li>把结果摘要塞进 session context</li></ol><p>目的是让 Claude 一上来就知道”依赖是最新的、本地服务是活的”。之前没这个 hook 的时候，Claude 经常花一轮对话去诊断”为什么 import 失败”，最后发现是 node_modules 没装。</p><p>一个副作用是团队新人接手项目也轻松了。以前新人 clone 下来要翻 README 照抄一堆命令，现在 Claude Code 一跑 SessionStart 全搞定。一个实习生原话：”这个 hook 比你们的 onboarding 文档有用多了。”</p><h2 id="那次事故之后的一次”假警报”"><a href="#那次事故之后的一次”假警报”" class="headerlink" title="那次事故之后的一次”假警报”"></a>那次事故之后的一次”假警报”</h2><p>这 5 个 hook 配齐一个月后，有一次 PreToolUse 拦了 Claude 一个 <code>git push --force</code> 到 <code>feature/refund-v2</code>。按我的规则 feature 分支是放过的，但当时分支名里有个 release 字样——<code>feature/release-gate-refactor</code>。规则里我用的是 <code>grep -i release</code>，命中了。</p><p>Claude 收到拒绝后自己换成了 <code>git push --force-with-lease</code>，更安全。我当时看日志还挺感慨——规则写得略粗，但效果反而更好。</p><p>所以结论：hook 宁可粗一点误拦，也别漏拦。被误拦无非多走一步，漏拦就是事故。</p><p>如果你还没开始配 hook，我建议就从 Hook 1 和 Hook 2 开始。这两个加起来 40 行脚本，半小时搞定，ROI 是整个 Claude Code 生态里最高的。想把整套 agent 工程化看 <a href="/posts/agent-sdk-architecture/">Agent SDK 架构</a>，或者看一下我怎么在 CLAUDE.md 里做项目长期约束 <a href="/posts/claude-md-project-config/">CLAUDE.md 项目配置</a>。</p><div style="background:#f0f7ff;border-left:4px solid #0c97fe;padding:16px 20px;margin:24px 0;border-radius:4px;"><strong>我的 hook 模板仓库</strong><br/>上面 5 个脚本的完整 Node/Python 模板我整理在一个 repo 里，包括 bash-guard 的完整黑名单、post-edit-lint 的多语言路由表、Stop 摘要用的 Haiku prompt。打算开源但还在整理，想先拿的留言告诉我一下你的技术栈（Node/Python/Go），我优先发那一套给你。</div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/claude-code-hooks-automation/</id>
    <link href="https://claude.cocoloop.cn/posts/claude-code-hooks-automation/"/>
    <published>2026-04-17T03:23:53.000Z</published>
    <summary>去年十一月给一家做 SaaS 的客户做后端重构，Claude Code 跑得飞快，结果某次 PostToolUse 漏配 lint，一个没加分号的 TypeScript 直接进了 main。上线 37 分钟才被监控抓到，回滚那会儿我后背都是汗。从那次开始我给每个新项目都固定塞 5 个 hook，写下来记录一下，省得以后忘。</summary>
    <title>Claude Code 的 hooks 我现在每个项目都配这 5 个</title>
    <updated>2026-04-21T07:23:53.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="Context Engineering" scheme="https://claude.cocoloop.cn/categories/Context-Engineering/"/>
    <category term="Claude" scheme="https://claude.cocoloop.cn/tags/Claude/"/>
    <category term="Compression" scheme="https://claude.cocoloop.cn/tags/Compression/"/>
    <category term="Summarization" scheme="https://claude.cocoloop.cn/tags/Summarization/"/>
    <category term="Chat History" scheme="https://claude.cocoloop.cn/tags/Chat-History/"/>
    <content>
      <![CDATA[<h2 id="一次线上事故引发的思考"><a href="#一次线上事故引发的思考" class="headerlink" title="一次线上事故引发的思考"></a>一次线上事故引发的思考</h2><p>去年有个事故我记到现在。</p><p>客户是家做法律咨询的，AI 助手用的 Claude Sonnet。用户在对话第 15 轮说自己有一笔 48 万的遗产纠纷，要咨询继承顺序。第 16 到第 40 轮中间聊了一堆背景——父母离异、兄弟几个、有一个继父、继父带来的两个子女。</p><p>到第 42 轮，用户问了一句”那我应该起诉谁”。</p><p>我们那会儿做了简单的历史截断——超过 30 轮就只保留最近 20 轮。结果 AI 回答里说成了”您只需要和两位兄弟协商即可”——那个继父和继子女的信息早就被截掉了。</p><p>那一次差点吃官司。从那之后我再也不敢用纯截断策略。</p><h2 id="策略-1：朴素截断（千万别用）"><a href="#策略-1：朴素截断（千万别用）" class="headerlink" title="策略 1：朴素截断（千万别用）"></a>策略 1：朴素截断（千万别用）</h2><p>最朴素的做法：设一个轮数上限，超过就从头砍。实现十分钟能搞定。</p><p><strong>信息损失率实测</strong>：我们跑过一个基准测试集，120 个 case，每个 case 40-60 轮对话。纯截断（保留最近 15 轮）的信息召回率是 **43.7%**。几乎一半关键信息丢了。</p><p><strong>为什么这么惨</strong>：用户往往在对话早期交代背景，后期只抛出具体问题。截掉早期就是截掉上下文。</p><p><strong>什么时候可以用</strong>：真的不建议用。除非你的场景是客服——每次对话独立，不跨 session。那用户如果第二次来咨询，你反正也不需要记得第一次。</p><p>说句实话我现在看到代码里有 <code>messages[-N:]</code> 这种直接切片的，都会把人叫过来谈话。</p><h2 id="策略-2：分级压缩（当前我用得最多）"><a href="#策略-2：分级压缩（当前我用得最多）" class="headerlink" title="策略 2：分级压缩（当前我用得最多）"></a>策略 2：分级压缩（当前我用得最多）</h2><p>分三层：</p><p><strong>L1 近期原文</strong>：最近 6-10 轮原封不动保留。</p><p><strong>L2 中期要点</strong>：倒数第 11-30 轮，每 3-4 轮生成一个小摘要，约 150 token 左右。这一层保留关键 entity、用户意图转折点、AI 已做的承诺。</p><p><strong>L3 远期摘要</strong>：30 轮以前整段压缩，约 500-800 token。这一层只保留”这场对话是关于什么、用户的核心诉求、已经解决和未解决的事项”。</p><p><strong>信息损失率实测</strong>：同样那 120 个 case，分级压缩的召回率 **84.2%**。比截断好太多。</p><p><strong>实现关键</strong>：</p><p>第一，L2 和 L3 的压缩 prompt 要完全不同。L2 是”请保留所有数字、日期、人名和具体诉求”，L3 是”请概括对话主题和已达成的共识，不超过 800 字”。粒度不一样。</p><p>第二，<strong>entity 白名单机制</strong>。压缩时显式提取出金额、时间、人名、地点、医疗术语、法律条文号，单独存成一个 entity list，和摘要一起喂回 context。这个机制救了我好几次。</p><p>第三，L2 不是一次性生成的，是<strong>增量更新</strong>。每 3 轮新对话来了之后，用 Haiku 对这 3 轮做小摘要，append 到 L2。而不是每次都重新压缩整段——太贵。</p><p><strong>成本</strong>：Haiku 4.5 做压缩一次 300 token 左右，成本 0.00012 美刀。一个 60 轮对话全程大概压 15 次，总成本 0.0018 刀。相比 Sonnet 的 input 费用节省 70% 以上。</p><p><strong>我踩的坑</strong>：最开始 L2 的摘要让 Haiku 自由发挥，结果它经常”优化”掉用户的原话。用户说”我觉得这个方案不错但有点贵”，压缩完变成”用户对方案表示满意”——“有点贵”这个关键反馈丢了。后来我在 prompt 里明确要求”保留用户的原始措辞，尤其是带情感倾向的表达”，才好转。</p><h2 id="策略-3：Entity-centric-compression（进阶）"><a href="#策略-3：Entity-centric-compression（进阶）" class="headerlink" title="策略 3：Entity-centric compression（进阶）"></a>策略 3：Entity-centric compression（进阶）</h2><p>这个策略是我从一个客户那里学来的，他们团队里有个做知识图谱出身的工程师。</p><p>核心思想：<strong>不压缩对话，而是从对话里抽取事实</strong>。</p><p>每轮对话结束后，跑一个抽取任务，产出形如：</p><div class="code-container" data-rel="Xml"><figure class="iseeu highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">facts</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">fact</span> <span class="attr">id</span>=<span class="string">&quot;f1&quot;</span> <span class="attr">type</span>=<span class="string">&quot;user_profile&quot;</span>&gt;</span>用户年龄 34 岁<span class="tag">&lt;/<span class="name">fact</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">fact</span> <span class="attr">id</span>=<span class="string">&quot;f2&quot;</span> <span class="attr">type</span>=<span class="string">&quot;preference&quot;</span>&gt;</span>偏好低风险投资<span class="tag">&lt;/<span class="name">fact</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">fact</span> <span class="attr">id</span>=<span class="string">&quot;f3&quot;</span> <span class="attr">type</span>=<span class="string">&quot;constraint&quot;</span>&gt;</span>每月最多投入 5000 元<span class="tag">&lt;/<span class="name">fact</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">fact</span> <span class="attr">id</span>=<span class="string">&quot;f4&quot;</span> <span class="attr">type</span>=<span class="string">&quot;decision&quot;</span>&gt;</span>用户已拒绝方案 A<span class="tag">&lt;/<span class="name">fact</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">facts</span>&gt;</span></span><br></pre></td></tr></table></figure></div><p>每个 fact 带 id、类型、原始对话轮次引用。</p><p>再下次对话时，context 里不放对话历史本身，而是放 fact list + 最近 3-5 轮原文。</p><p><strong>信息损失率实测</strong>：同样基准，召回率 **88.9%**。比分级压缩略高。但注意这个数字是在”精确引用”测试集上——如果测试集是”语气一致性””理解对话的情感脉络”这类软指标，entity-centric 反而不如分级压缩，因为它丢了语气。</p><p><strong>为什么强</strong>：</p><p>第一，可审计。每个 fact 可以追溯到原始对话。用户质疑”AI 为什么说我不吃辣”，你能精确定位。</p><p>第二，可修改。用户说”我改主意了，现在可以接受高风险”，你直接把 f2 覆盖掉。朴素摘要做不到这种精细更新。</p><p>第三，可组合。fact 库可以跨会话累积，变成用户画像。但这需要额外做<strong>冲突检测</strong>，上一篇文章讲过。</p><p><strong>什么时候别用</strong>：</p><p>任务如果高度依赖”情感连续性””语气”——比如陪伴聊天、创意写作——entity 抽取会把灵魂抽没。</p><p><strong>实现成本</strong>：比分级压缩高一倍。每轮后要跑一次抽取 + 每次请求前要拼接 fact list。但长期看收益更大，尤其配合 <a href="/posts/claude-md-project-config/">CLAUDE.md 这种项目级配置</a> 一起用。</p><h2 id="三种策略的成本-效果对比"><a href="#三种策略的成本-效果对比" class="headerlink" title="三种策略的成本-效果对比"></a>三种策略的成本-效果对比</h2><p>做了张表，是我这半年几个项目的实测均值（60-80 轮对话）：</p><table><thead><tr><th>策略</th><th>信息召回率</th><th>单会话压缩成本</th><th>实现复杂度</th><th>适用场景</th></tr></thead><tbody><tr><td>朴素截断</td><td>43.7%</td><td>0</td><td>极低</td><td>基本别用</td></tr><tr><td>分级压缩</td><td>84.2%</td><td>~$0.002</td><td>中</td><td>大部分通用场景</td></tr><tr><td>Entity-centric</td><td>88.9%</td><td>~$0.004</td><td>高</td><td>严肃决策场景</td></tr></tbody></table><p>我现在的默认选择：通用场景分级压缩，严肃场景 entity-centric。偶尔也会混用——近期用分级，远期用 entity 化的 fact list 兜底。</p><h2 id="对-Claude-ai-做法的一点推测"><a href="#对-Claude-ai-做法的一点推测" class="headerlink" title="对 Claude.ai 做法的一点推测"></a>对 Claude.ai 做法的一点推测</h2><p>Anthropic 没公开过 Claude.ai 里”记得住几个月前对话”功能的技术细节。但从我观察到的行为来看，我的猜测是这样的：</p><p>第一，用了 <strong>entity-centric 为主的记忆层</strong>。因为 Claude.ai 能精确回忆”你三周前跟我说你在学 Rust”这种细节，而且跨会话都能命中。这不像纯摘要能做到的。</p><p>第二，<strong>近期会话保留更多原文</strong>。同一个会话内前后文连贯性极好，断层罕见。这说明近期没过度压缩。</p><p>第三，有 <strong>用户可见的记忆管理入口</strong>。用户能看到、删除、修改记忆。这种 UX 暗示底层是结构化的 fact，不是黑盒摘要。</p><p>第四，<strong>召回是 LLM 主导的</strong>。对话中 Claude 会自己判断”要不要调用记忆”，而不是每次机械检索。这点和我上一篇写的 agent 记忆架构思路一致。</p><p>当然这都是我瞎猜，Anthropic 哪天发工程 blog 估计会把我打脸。但八九不离十。</p><h2 id="最后三条实操经验"><a href="#最后三条实操经验" class="headerlink" title="最后三条实操经验"></a>最后三条实操经验</h2><p><strong>第一，压缩模型要比主模型小一档。</strong> 主力用 Sonnet，压缩用 Haiku。用 Opus 做压缩是浪费。</p><p><strong>第二，摘要的 prompt 要版本化。</strong> 我在 repo 里专门有个 <code>prompts/summarization_v3.xml</code>，改动要走 review。因为摘要 prompt 直接决定了你的”记忆质量”，不是随便改的。</p><p><strong>第三，留一个”压缩旁路”。</strong> 关键决策点（用户下单、签约、投诉）的那几轮对话永远保留原文，不进压缩池。这种关键轮次的识别靠 Haiku 实时打标。</p><p>做记忆这件事最忌讳聪明过头，把简单问题做复杂。但更忌讳偷懒，把复杂问题做简单。压缩这一块，属于后者。</p><div class="cta-card" style="margin:32px 0;padding:20px 24px;background:#f0f7ff;border-left:4px solid #0c97fe;border-radius:6px;"><p style="margin:0 0 8px;font-weight:600;color:#1f2937;">延伸阅读</p><p style="margin:0;color:#4b5563;font-size:14px;line-height:1.8;">压缩策略上游是 [Context 预算拆桶](/posts/prompt-to-context-engineering/)，下游对接 [长期记忆架构](/posts/claude-md-project-config/)。压缩 prompt 的结构化输出强烈建议用 [XML 标签](/posts/prompt-xml-tags-claude-special/) 而非 Markdown。想省调用费，[Prompt Caching 深度指南](/posts/prompt-caching-deep-guide/) 配合分级压缩效果拔群——中期摘要那一段正好适合做缓存。</p></div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/context-compression-summarization/</id>
    <link href="https://claude.cocoloop.cn/posts/context-compression-summarization/"/>
    <published>2026-04-17T02:54:10.000Z</published>
    <summary>长会话的压缩问题，表面看就是&quot;把旧消息总结一下嘛&quot;，实际做起来一堆坑。截断丢信息、朴素摘要会漏掉关键 entity、中期记忆和远期记忆的密度应该不一样。这篇把我手上三个真实项目的 AB 实验数据拿出来对比，实测三种压缩方案的信息损失率，还有我对 Anthropic 在 Claude.ai 里怎么做的一点推测。</summary>
    <title>聊天历史越滚越长怎么办？3 种压缩策略 AB 对比</title>
    <updated>2026-04-21T09:54:10.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="MCP" scheme="https://claude.cocoloop.cn/categories/MCP/"/>
    <category term="MCP" scheme="https://claude.cocoloop.cn/tags/MCP/"/>
    <category term="安全" scheme="https://claude.cocoloop.cn/tags/%E5%AE%89%E5%85%A8/"/>
    <category term="生产部署" scheme="https://claude.cocoloop.cn/tags/%E7%94%9F%E4%BA%A7%E9%83%A8%E7%BD%B2/"/>
    <category term="事故复盘" scheme="https://claude.cocoloop.cn/tags/%E4%BA%8B%E6%95%85%E5%A4%8D%E7%9B%98/"/>
    <content>
      <![CDATA[<p>2025 年 11 月某个周二下午 3 点 47 分，一家客户的 CTO 给我打电话，声音有点抖。</p><p>“我们 staging 库一张 order_log 表没了，就刚才的事。Claude Code agent 调了咱们搓的那个 <code>db_query</code> MCP tool，不知道怎么执行了 truncate。”</p><p>那张表 21.4K 行，虽然只是 staging，但包含了 QA 组两周的测试数据。复盘完之后我整理了一套 MCP server 安全检查清单，现在我们团队所有 server 上生产前必须过这 6 项。</p><p>这篇没有理论，全是踩坑换来的。</p><h2 id="事故复盘：Agent-是怎么把表删了的"><a href="#事故复盘：Agent-是怎么把表删了的" class="headerlink" title="事故复盘：Agent 是怎么把表删了的"></a>事故复盘：Agent 是怎么把表删了的</h2><p>先讲清楚那次怎么翻车的，后面的检查项才有上下文。</p><p>那个 <code>db_query</code> MCP tool 原始设计：</p><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;db_query&quot;</span>,</span><br><span class="line">  <span class="attr">description</span>: <span class="string">&quot;在数据库上执行 SQL 查询&quot;</span>,</span><br><span class="line">  <span class="attr">inputSchema</span>: &#123;</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&quot;object&quot;</span>,</span><br><span class="line">    <span class="attr">properties</span>: &#123; <span class="attr">sql</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;string&quot;</span> &#125; &#125;,</span><br><span class="line">    <span class="attr">required</span>: [<span class="string">&quot;sql&quot;</span>],</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>连接的是 staging 数据库的 read-write 账号。工程师 A 给 LLM 发了个需求：”帮我看下 order_log 表最近一周有多少行被标记为 test_data”。</p><p>LLM 的操作链：</p><ol><li>调 <code>db_query</code> 跑了 <code>SELECT COUNT(*) FROM order_log WHERE tag=&#39;test_data&#39;</code>，返回 17 行</li><li>用户后续说”这几行清掉吧”</li><li>LLM 调 <code>db_query</code> 跑 <code>DELETE FROM order_log WHERE tag=&#39;test_data&#39;</code></li><li>返回报错：”tag 列有 NULL 约束冲突”（实际是权限问题提示不明确）</li><li>LLM 自己推理”可能要先清空再重建”，跑了 <code>TRUNCATE TABLE order_log</code></li><li>成功</li></ol><p>工程师 A 盯着另一个窗口写代码，10 几分钟后才反应过来。</p><p>事后我们 review 了这个决策链，LLM 的每一步”单独看”都不离谱，合起来就是灾难。问题出在：<strong>tool 的权限边界过大 + 没有 dry-run + 没有限流 + 审计缺失</strong>。</p><h2 id="检查项-1：输入校验（tool-参数别裸信）"><a href="#检查项-1：输入校验（tool-参数别裸信）" class="headerlink" title="检查项 1：输入校验（tool 参数别裸信）"></a>检查项 1：输入校验（tool 参数别裸信）</h2><p>最基础但最容易漏。LLM 传过来的 tool 参数是<strong>不可信输入</strong>，必须校验。</p><p>上面那个 <code>db_query</code> 最起码应该：</p><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">SqlSchema</span> = z.<span class="title function_">object</span>(&#123;</span><br><span class="line">  <span class="attr">sql</span>: z.<span class="title function_">string</span>()</span><br><span class="line">    .<span class="title function_">max</span>(<span class="number">8000</span>)</span><br><span class="line">    .<span class="title function_">refine</span>(<span class="function"><span class="params">s</span> =&gt;</span> !<span class="regexp">/\b(DROP|TRUNCATE|DELETE|ALTER|GRANT)\b/i</span>.<span class="title function_">test</span>(s), &#123;</span><br><span class="line">      <span class="attr">message</span>: <span class="string">&quot;危险 SQL 关键字，请用专用 tool&quot;</span>,</span><br><span class="line">    &#125;),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></div><p>或者更狠一点，直接只允许 SELECT：</p><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.<span class="title function_">refine</span>(<span class="function"><span class="params">s</span> =&gt;</span> <span class="regexp">/^\s*SELECT\b/i</span>.<span class="title function_">test</span>(s.<span class="title function_">trim</span>()))</span><br></pre></td></tr></table></figure></div><p>写 MCP tool 的时候脑子里要有这个模型：<strong>你不是在写 API，你是在给一个会自由组合 tool 的 AI Agent 开权限口子</strong>。Agent 会以你想不到的方式组合调用。</p><h2 id="检查项-2：Secrets-管理（凭证不能塞-config）"><a href="#检查项-2：Secrets-管理（凭证不能塞-config）" class="headerlink" title="检查项 2：Secrets 管理（凭证不能塞 config）"></a>检查项 2：Secrets 管理（凭证不能塞 config）</h2><p>见过太多次 <code>claude_desktop_config.json</code> 里直接写 API key。文件不加密、可能被同步到 iCloud、被误 commit 到 git。</p><p>正确做法：</p><ul><li>开发机：<code>env</code> 字段 + 系统 keychain（macOS）或 DPAPI（Windows）</li><li>生产机：MCP server 启动时从 Vault&#x2F;AWS Secrets Manager 拉</li><li><strong>绝不</strong>：把 key 写进 server 代码、写进 tool description、写进 log</li></ul><p>MCP server 启动流程应该是这样：</p><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> dbUrl = <span class="keyword">await</span> <span class="title function_">fetchSecret</span>(<span class="string">&quot;prod/db/readonly-url&quot;</span>);</span><br><span class="line"><span class="comment">// 不要 console.log(dbUrl)</span></span><br></pre></td></tr></table></figure></div><p>另外一个容易忽略的：MCP server 的 <code>tools/list</code> 返回给客户端的 description 里不要包含任何环境信息（”连接到 prod-us-east-1 的 xxx 集群”），这会经过 LLM、可能被日志到 Anthropic 侧。</p><h2 id="检查项-3：权限边界（只读-vs-读写-server-分离）"><a href="#检查项-3：权限边界（只读-vs-读写-server-分离）" class="headerlink" title="检查项 3：权限边界（只读 vs 读写 server 分离）"></a>检查项 3：权限边界（只读 vs 读写 server 分离）</h2><p>这是那次事故之后我们组的硬性规定。</p><p><strong>一个 MCP server 只承担一种权限级别</strong>：</p><ul><li><code>company-db-readonly</code>：只连 read-only DB 账号、只有 SELECT tools</li><li><code>company-db-readwrite</code>：独立 server，连 RW 账号、提供 INSERT&#x2F;UPDATE tools（<strong>不提供 DELETE&#x2F;TRUNCATE</strong>）</li><li><code>company-db-admin</code>：独立 server、独立审批流程、默认不启用</li></ul><p>用户要用到高权限 server 时，在 config 里显式启用。LLM 永远看不到 admin tools 除非用户主动开。</p><p>这个设计的额外好处是：你可以在 Claude Code 的 <code>.mcp.json</code> 里按项目控制——开发项目只挂 read-only、运维项目才挂 read-write。配合 <a href="/posts/claude-code-permissions-sandbox/">Claude Code 权限沙箱</a> 里的二级保护，基本不会出大事。</p><h2 id="检查项-4：审计日志（谁在什么时候调了什么-tool）"><a href="#检查项-4：审计日志（谁在什么时候调了什么-tool）" class="headerlink" title="检查项 4：审计日志（谁在什么时候调了什么 tool）"></a>检查项 4：审计日志（谁在什么时候调了什么 tool）</h2><p>事故那次最抓狂的是：我们花了 40 多分钟才定位是哪个 tool 哪条 SQL 干的好事，因为没有统一日志。</p><p>正确的审计结构，每次 tool call 至少记录：</p><div class="code-container" data-rel="Json"><figure class="iseeu highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2026-04-15T08:23:17.284Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;server&quot;</span><span class="punctuation">:</span> <span class="string">&quot;db-readonly&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;tool&quot;</span><span class="punctuation">:</span> <span class="string">&quot;db_query&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;args_hash&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sha256:8a3f...&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;args_size_bytes&quot;</span><span class="punctuation">:</span> <span class="number">342</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;caller&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;client&quot;</span><span class="punctuation">:</span> <span class="string">&quot;claude-desktop&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;session_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;xxx&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;user_hint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;joe@company.com&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;result_status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ok&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;duration_ms&quot;</span><span class="punctuation">:</span> <span class="number">127</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div><p>关键字段：</p><ul><li><code>args_hash</code> 而不是原文（避免日志泄露敏感参数）</li><li><code>user_hint</code> 从 MCP client 侧传过来（很多 MCP 客户端支持带用户上下文）</li><li><code>duration_ms</code> 方便发现异常</li></ul><p>日志存到公司统一日志平台，保留 90 天起步。出事了至少能查。</p><h2 id="检查项-5：Rate-limit-Quota（防-Agent-失控爆-API）"><a href="#检查项-5：Rate-limit-Quota（防-Agent-失控爆-API）" class="headerlink" title="检查项 5：Rate limit &amp; Quota（防 Agent 失控爆 API）"></a>检查项 5：Rate limit &amp; Quota（防 Agent 失控爆 API）</h2><p>Agent 一跑起来可能一分钟调你 tool 几十次，这在人类 API 用户那边几乎不可能。</p><p>我见过最离谱的一次：某客户的 Claude Code agent 接了个 Slack MCP server，写代码的时候 stuck 住、反复调 <code>list_channels</code> 去”探索环境”，<strong>11 分钟调了 2847 次</strong>，把公司 Slack API quota 直接干穿了，整个公司 Slack bot 半小时不能用。</p><p>两层防护：</p><p><strong>server 侧</strong>：每个 tool 按 session+tool 组合限流，比如 60 次&#x2F;分钟。超过直接返回错误让 Agent 退避。</p><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 简化伪代码</span></span><br><span class="line"><span class="keyword">if</span> (<span class="title function_">callCountThisMinute</span>(sessionId, toolName) &gt; <span class="number">60</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123; <span class="attr">isError</span>: <span class="literal">true</span>, <span class="attr">content</span>: [&#123; <span class="attr">type</span>: <span class="string">&quot;text&quot;</span>, </span><br><span class="line">    <span class="attr">text</span>: <span class="string">&quot;Rate limited: 60 calls/min exceeded. Wait or use batch tool.&quot;</span> &#125;] &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p><strong>下游 API 侧</strong>：给 MCP server 配独立的 API key、独立 quota，爆了只影响 Agent 场景不影响业务。</p><h2 id="检查项-6：依赖漏洞（MCP-生态早期-npm-包质量参差）"><a href="#检查项-6：依赖漏洞（MCP-生态早期-npm-包质量参差）" class="headerlink" title="检查项 6：依赖漏洞（MCP 生态早期 npm 包质量参差）"></a>检查项 6：依赖漏洞（MCP 生态早期 npm 包质量参差）</h2><p>MCP 火是 2024 年底的事，整个生态还很年轻。到 2026 年 4 月 npm 上 MCP 相关包有 840+ 个，但质量分布极其分散。</p><p>我扫过一次：top 100 包里 23 个有已知中高危漏洞（npm audit 高或严重），17 个最近 6 个月没更新，9 个连 README 都没有。</p><p>上生产前必须做的事：</p><ul><li><code>npm audit</code> 跑一遍，高危必须修</li><li>看 package 的 weekly downloads 和 maintainers 数量（单人维护的小包慎用）</li><li>看最近 commit 日期（6 个月没动的包视为不活跃）</li><li>考虑用 <code>socket.dev</code> 这类 supply chain 工具扫</li></ul><p>Python 生态同理，<code>pip-audit</code> 跑一遍。</p><h2 id="一个上线-checklist"><a href="#一个上线-checklist" class="headerlink" title="一个上线 checklist"></a>一个上线 checklist</h2><p>我们团队现在每个 MCP server 上生产前对照走一遍：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[ ] 所有 tool 参数都有 zod/pydantic 校验</span><br><span class="line">[ ] 危险操作（DROP/DELETE/rm -rf 类）显式禁用或单独 server</span><br><span class="line">[ ] Secrets 走 Vault/Secrets Manager，代码里 grep 不到明文</span><br><span class="line">[ ] 读写权限分离成独立 server</span><br><span class="line">[ ] 每次 tool call 写审计日志，args 用 hash</span><br><span class="line">[ ] tool 粒度的 rate limit，超限返回明确错误</span><br><span class="line">[ ] 依赖包 npm audit/pip-audit 通过，无高危</span><br><span class="line">[ ] Inspector 手动验过所有 tool 的错误路径</span><br><span class="line">[ ] 挂到测试 Claude Code/Desktop 跑过一周内测</span><br></pre></td></tr></table></figure></div><p>9 项都过了才放生产。</p><p>另外强烈推荐搭配 <a href="/posts/claude-code-permissions-sandbox/">Claude Code 权限沙箱</a> 做运行时隔离——哪怕 MCP server 本身有 bug，沙箱能兜住。两层防护比一层可靠得多。</p><h2 id="最后说一句"><a href="#最后说一句" class="headerlink" title="最后说一句"></a>最后说一句</h2><p>那次事故之后客户问我：”你们早怎么不告诉我这些？”</p><p>我说：”说实话，MCP 生态起来才一年多，很多坑是我们自己也刚踩的。”</p><p>这篇文章写出来，就是希望下一个踩坑的人不是你。如果觉得有用，分享给团队一起看看。</p><div style="background:#f0f7ff;border-left:4px solid #0c97fe;padding:18px 22px;margin:28px 0;border-radius:6px;">MCP 安全是个系统工程，6 项只是起点。强烈推荐接着看 <a href="/posts/claude-code-permissions-sandbox/">Claude Code 权限沙箱</a> 做第二层防护，<a href="/posts/claude-code-subagents-orchestration/">Subagent 编排</a> 可以做危险操作的二次确认路径。做企业级部署务必把这三篇当一个整体看。</div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/mcp-security-best-practice/</id>
    <link href="https://claude.cocoloop.cn/posts/mcp-security-best-practice/"/>
    <published>2026-04-16T16:45:03.000Z</published>
    <summary>去年帮一家公司救过一次火，Agent 调 MCP server 里一个&quot;友好&quot;的 SQL tool，把 staging 库一张 21.4K 行的表 truncate 了。复盘完我整理了 6 个必须过的安全检查项。这篇不讲理论，全是血的教训——输入校验、secrets 管理、权限边界、审计、限流、依赖漏洞，挨个说清楚怎么做。</summary>
    <title>MCP server 上生产之前这 6 个安全检查项必须过</title>
    <updated>2026-04-20T06:45:03.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="Claude-Code" scheme="https://claude.cocoloop.cn/categories/Claude-Code/"/>
    <category term="Claude Code" scheme="https://claude.cocoloop.cn/tags/Claude-Code/"/>
    <category term="CLAUDE.md" scheme="https://claude.cocoloop.cn/tags/CLAUDE-md/"/>
    <category term="配置文件" scheme="https://claude.cocoloop.cn/tags/%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/"/>
    <category term="最佳实践" scheme="https://claude.cocoloop.cn/tags/%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/"/>
    <content>
      <![CDATA[<p>第一次用 Claude Code 的时候，我根本不知道 CLAUDE.md 是干啥的。项目根目录莫名其妙多了个文件，我以为是 Claude Code 的缓存，还一度想把它丢进 .gitignore。</p><p>后来在一次协作翻车之后我才意识到这东西有多重要。那次的情况是：我和同事在同一个 repo 里协作，我本地跑 Claude Code 改一个接口改得风生水起，他拉下来跑的时候却发现 Claude Code 一直把代码往完全错误的方向推。排查了俩小时才搞明白——我本地有一份 CLAUDE.md，里面写了一堆项目约定；他没有这份文件（我没 commit），所以他的 Claude 完全不知道这个项目的规矩，只能按通用最佳实践瞎写。</p><p>从那天起我开始认真研究 CLAUDE.md。这东西本质上是给 Claude Code 的<strong>项目说明书</strong>，写得好能让 AI 协作翻倍提效，写得不好能把 AI 带到沟里。这篇把我这半年的经验总结一下。</p><h2 id="一、CLAUDE-md-是什么，为什么重要"><a href="#一、CLAUDE-md-是什么，为什么重要" class="headerlink" title="一、CLAUDE.md 是什么，为什么重要"></a>一、CLAUDE.md 是什么，为什么重要</h2><p>简单说，CLAUDE.md 是 Claude Code 在启动时会自动读入、作为 system context 的一部分注入到模型的文件。它告诉 Claude：</p><ul><li>这个项目用什么技术栈</li><li>代码风格有什么约定</li><li>哪些文件&#x2F;目录不能动</li><li>常用命令是什么（跑测试、部署、lint）</li><li>有什么”坑”要提前知道</li></ul><p><strong>它解决的核心问题是：让 Claude 在一个陌生项目里不再按通用默认行事，而是按这个项目的规矩行事</strong>。</p><p>你可以理解成”给新人入职的 README，只不过读者是 AI”。</p><p>但和 README 有一个关键区别——<strong>README 是可选阅读，CLAUDE.md 是强制注入</strong>。意味着你写进去的每一个字都会消耗 token、都会影响模型行为。这就决定了 CLAUDE.md 不能当 README 写，它必须更精炼、更有指令性。</p><h2 id="二、11-级优先级：从哪读到哪"><a href="#二、11-级优先级：从哪读到哪" class="headerlink" title="二、11 级优先级：从哪读到哪"></a>二、11 级优先级：从哪读到哪</h2><p>这个东西官方文档藏得有点深，我整理一下我理解的顺序（从高到低，高优先级会覆盖低优先级）：</p><ol><li><strong>当前命令的 CLI 参数</strong> —— 最高优先级，临时强制</li><li><strong><code>.claude/settings.local.json</code></strong> —— 本地机器的 Claude 配置（不入 git）</li><li><strong>项目根 <code>CLAUDE.md</code></strong> —— 当前项目规则</li><li>**父目录 <code>CLAUDE.md</code>**（monorepo 场景）</li><li><strong>祖父目录 <code>CLAUDE.md</code></strong> —— 依次往上找到 repo 根或 home</li><li><strong><code>~/.claude/CLAUDE.md</code></strong> —— 用户级全局配置</li><li><strong><code>.claude/settings.json</code></strong> —— 项目共享配置</li><li><strong><code>~/.claude/settings.json</code></strong> —— 用户全局配置  </li><li><strong>环境变量</strong>（如 <code>ANTHROPIC_MODEL</code>）</li><li><strong>Claude Code 内置默认</strong></li><li><strong>模型本身的 default behavior</strong> —— 最底层兜底</li></ol><p>这个顺序不用硬记，但要理解两条核心原则：</p><ul><li><strong>越具体、越靠近当前文件的，优先级越高</strong></li><li><strong>项目级（CLAUDE.md）和用户级（~&#x2F;.claude）会叠加，但冲突时项目级优先</strong></li></ul><p>实际用起来有一个容易忽略的细节——<strong>Claude Code 在启动时会自顶向下把所有层级的 CLAUDE.md 串联起来</strong>，不是只读最近的那一份。这意味着你在子目录里的 CLAUDE.md 不是”覆盖”祖父目录的，而是”追加”。如果祖父目录写了”代码注释用中文”，子目录里不写相关规则，那中文注释规则依然生效。</p><p>这个机制在 monorepo 场景下特别有用。你可以在 repo 根写通用规则（”所有包都用 TypeScript strict 模式”），然后在每个 package 子目录里写各自的特殊规则（”api 包禁止引入 zod 之外的 validation 库”）。</p><h2 id="三、坑-1：写得太多，反而把-Claude-整懵了"><a href="#三、坑-1：写得太多，反而把-Claude-整懵了" class="headerlink" title="三、坑 1：写得太多，反而把 Claude 整懵了"></a>三、坑 1：写得太多，反而把 Claude 整懵了</h2><p>这是我第一个大坑。发现 CLAUDE.md 好用之后，我开始往里面疯狂写东西——项目架构、技术栈细节、每个目录的作用、每个核心函数的职责、过往几次重构的历史……写到快 2000 行。</p><p>然后 Claude Code 开始变得<strong>又慢又飘</strong>。</p><p>原因很简单：<strong>CLAUDE.md 是要完整注入到每次请求的 context 里的</strong>。你写 2000 行，意味着每次 Claude 启动都要消化这 2000 行。问题是：</p><ul><li><strong>信息太多，重点被稀释</strong>。关键规则（比如”不要改 legacy_util.py”）被淹没在一堆描述性内容里</li><li><strong>Token 成本爆炸</strong>。2000 行大概 15K token，每次请求都是这个基础成本</li><li>**模型开始”过度解读”**。写得越多，Claude 越容易从无关内容里脑补奇怪的推论</li><li><strong>维护成本翻倍</strong>。项目演化时，你要同步更新 CLAUDE.md，内容越多越容易遗漏</li></ul><p>我后来把 CLAUDE.md 砍到 150 行以内，执行规则、关键约束、常用命令——其他什么都不写。效果立刻恢复正常，而且 Claude 的判断更稳定了。</p><p><strong>原则：CLAUDE.md 写关键信息，不写全部信息</strong>。详细架构文档放 docs&#x2F; 目录，让 Claude 需要的时候自己去读。你可以在 CLAUDE.md 里留一句：<code>需要深入了解架构时，请先阅读 /docs/architecture.md</code>，把”读不读”的判断权交给模型。</p><p>有人问过我具体怎么判断该不该写进 CLAUDE.md。我的标准是——<strong>如果 Claude 不知道这条规则就有可能写出错误代码，才写进去；如果只是”知道了更好”的背景信息，不写</strong>。这个筛选标准帮我砍掉了 80% 的冗余内容。</p><h2 id="四、坑-2：user-级和项目级配置打架"><a href="#四、坑-2：user-级和项目级配置打架" class="headerlink" title="四、坑 2：user 级和项目级配置打架"></a>四、坑 2：user 级和项目级配置打架</h2><p>第二个坑更隐蔽。</p><p>我在 <code>~/.claude/CLAUDE.md</code> 里写了我个人的偏好——比如”回答我时默认用中文””代码注释用中文””import 顺序按 isort 风格”等等。这是我的个人习惯。</p><p>但有一次我接了一个外包项目，他们的 repo 里有自己的 CLAUDE.md，明确写了”代码注释一律英文”。结果：我本地 Claude 写出来的代码里，注释一会儿中文一会儿英文，完全不统一。</p><p>排查后发现问题是——<strong>两个文件的规则都被注入了</strong>，然后 Claude 自己做了一个折中：”一部分用中文，一部分用英文”。</p><p>解决思路：</p><p>**1. 把 user 级 CLAUDE.md 写成”偏好”而不是”规则”**。<br>用 “我通常更喜欢…” 这种语气，不用 “必须…” 这种硬性措辞。这样项目级规则能比较干净地覆盖它。</p><p><strong>2. 项目级 CLAUDE.md 明确声明优先级</strong>。<br>我现在写项目级 CLAUDE.md 的第一行一般是：</p><div class="code-container" data-rel="Markdown"><figure class="iseeu highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 项目规则</span></span><br><span class="line"></span><br><span class="line">本文件中的所有规则优先于 user-level CLAUDE.md。冲突时以本文件为准。</span><br></pre></td></tr></table></figure></div><p>显式写出来，模型会认账。</p><p><strong>3. 真的要强制，用 <code>--override-user-memory</code></strong> 启动参数（这个 flag 从某个版本开始支持，具体版本号我不确定，先试再说）</p><p><strong>4. 做好 user 级配置的定期清理</strong>。<br>我现在每季度会把 <code>~/.claude/CLAUDE.md</code> 重读一遍，删掉那些已经不符合自己当前工作习惯的老规则。这个文件一旦塞多了很容易被遗忘，时间一长就变成各种项目的”通用毒瘤”。</p><p>再补一个小技巧：如果你经常切换不同客户&#x2F;团队的项目，可以在 <code>~/.claude/CLAUDE.md</code> 里完全不写任何强规则，只写自己的<strong>语言偏好</strong>和<strong>沟通风格偏好</strong>。技术决策全部交给项目级 CLAUDE.md 去定，冲突就少了。</p><h2 id="五、坑-3：-include-循环与隐式依赖"><a href="#五、坑-3：-include-循环与隐式依赖" class="headerlink" title="五、坑 3：@include 循环与隐式依赖"></a>五、坑 3：@include 循环与隐式依赖</h2><p>CLAUDE.md 支持 <code>@path/to/file</code> 语法，可以把其他文件的内容拉进来。这玩意儿很强大，但我踩过一个搞笑的坑。</p><p>当时我为了复用，把一些通用规则写在 <code>~/.claude/common.md</code> 里，然后 user 级 CLAUDE.md 里写：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">@~/.claude/common.md</span><br></pre></td></tr></table></figure></div><p>项目级 CLAUDE.md 里也想复用，又写：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">@~/.claude/common.md</span><br></pre></td></tr></table></figure></div><p>结果 common.md 被 include 了两次，规则重复。Claude 看到”代码风格 A”出现两次，当作重要信号，反而开始过度强调这个规则。</p><p>更惨的情况是：我有一次 common.md 里 include 了 base.md，base.md 里又 include 了 common.md，形成循环。Claude Code 直接报错启动不了。</p><p><strong>原则：</strong></p><ul><li><strong>@include 要有层级意识</strong>。下游 include 上游，不要互相 include</li><li><strong>不要重复 include 同一个文件</strong></li><li><strong>深层嵌套尽量避免</strong>。一级 include 够用了，两级以上容易出事</li><li><strong>include 进来的内容也要算 token</strong>。有人以为 @include 是懒加载——错了，是 eager 的，启动就全部注入</li></ul><p>一个实用建议：<strong>如果你想做规则复用，与其用 @include，不如用 shell script 在启动时生成 CLAUDE.md</strong>。把”规则碎片库”放在一个统一目录，写一个脚本根据当前项目类型拼装出定制化 CLAUDE.md。这比 @include 的控制力强一个数量级。</p><h2 id="六、一个我实际在用的-CLAUDE-md-模板"><a href="#六、一个我实际在用的-CLAUDE-md-模板" class="headerlink" title="六、一个我实际在用的 CLAUDE.md 模板"></a>六、一个我实际在用的 CLAUDE.md 模板</h2><p>这是我现在大部分 Node &#x2F; TypeScript 项目的骨架：</p><div class="code-container" data-rel="Markdown"><figure class="iseeu highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># [项目名] — CLAUDE.md</span></span><br><span class="line"></span><br><span class="line">本文件优先于 user-level CLAUDE.md。</span><br><span class="line"></span><br><span class="line"><span class="section">## 项目本质</span></span><br><span class="line">一句话说明这个项目是做什么的。</span><br><span class="line"></span><br><span class="line"><span class="section">## 技术栈</span></span><br><span class="line"><span class="bullet">-</span> Runtime: Node 20</span><br><span class="line"><span class="bullet">-</span> Framework: Fastify 5</span><br><span class="line"><span class="bullet">-</span> ORM: Drizzle</span><br><span class="line"><span class="bullet">-</span> Test: Vitest</span><br><span class="line"></span><br><span class="line"><span class="section">## 关键约束</span></span><br><span class="line"><span class="bullet">-</span> 所有对外 API 必须有 zod schema 校验</span><br><span class="line"><span class="bullet">-</span> 禁止引入新的第三方 validation 库（我们用 zod）</span><br><span class="line"><span class="bullet">-</span> 数据库迁移走 drizzle-kit，不手动改 schema</span><br><span class="line"></span><br><span class="line"><span class="section">## 不要改</span></span><br><span class="line"><span class="bullet">-</span> <span class="code">`/src/legacy/`</span> 目录下所有文件（迁移中，容易出事）</span><br><span class="line"><span class="bullet">-</span> <span class="code">`package.json`</span> 里的 node 版本</span><br><span class="line"></span><br><span class="line"><span class="section">## 常用命令</span></span><br><span class="line"><span class="bullet">-</span> 跑测试：<span class="code">`pnpm test`</span></span><br><span class="line"><span class="bullet">-</span> 起开发：<span class="code">`pnpm dev`</span></span><br><span class="line"><span class="bullet">-</span> 数据库迁移：<span class="code">`pnpm db:migrate`</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 风格</span></span><br><span class="line"><span class="bullet">-</span> import 用绝对路径（<span class="code">`@/xxx`</span>）</span><br><span class="line"><span class="bullet">-</span> 错误用 Result 类型，不 throw</span><br><span class="line"><span class="bullet">-</span> 文件名用 kebab-case</span><br><span class="line"></span><br><span class="line"><span class="section">## 遇到不确定的时候</span></span><br><span class="line"><span class="bullet">-</span> 先看 <span class="code">`/docs/architecture.md`</span></span><br><span class="line"><span class="bullet">-</span> 新增 API 前先看 <span class="code">`/src/api/_template.ts`</span></span><br></pre></td></tr></table></figure></div><p>这份大概 50 行左右。不追求完整，追求关键。</p><p>展开一下每个小节的写作原则：</p><ul><li><strong>项目本质</strong>：一句话就够，目的是给 Claude 一个定位锚点</li><li><strong>技术栈</strong>：只列关键版本，不写”为什么选这个”</li><li><strong>关键约束</strong>：写死的红线，违反了一定有问题</li><li><strong>不要改</strong>：负面清单，明确点名</li><li><strong>常用命令</strong>：Claude 要跑测试、lint 时直接复制这些命令，不用猜</li><li><strong>风格</strong>：写 3-5 条最重要的风格偏好，多了反而模糊</li><li><strong>遇到不确定的时候</strong>：告诉 Claude 要去哪看，相当于给它的”自助服务入口”</li></ul><h2 id="七、配合-Claude-Code-的几个小技巧"><a href="#七、配合-Claude-Code-的几个小技巧" class="headerlink" title="七、配合 Claude Code 的几个小技巧"></a>七、配合 Claude Code 的几个小技巧</h2><p><strong>1. 把 CLAUDE.md 放进 git</strong>。<br>这应该是常识但很多人不做。这东西不 commit，团队协作就废了。</p><p><strong>2. 定期 review</strong>。<br>项目演化，CLAUDE.md 也要演化。我每两周会打开 CLAUDE.md 看一眼，删掉已经不适用的规则、补上新的坑。这东西不是一次性写完就完事的。</p><p><strong>3. 新人加入项目时让 Claude 读一遍 CLAUDE.md 做导读</strong>。<br>这招挺好用——直接让 Claude 基于 CLAUDE.md 给新人做一次项目 walkthrough，省了半天时间。</p><p>**4. 结合 <code>.claude/commands/</code>**。<br>把常用的 prompt 做成 slash command，这块和 CLAUDE.md 配合使用效率爆炸。详细玩法我之前在 <a href="/categories/Claude-Code/">Claude-Code 分类</a> 下的几篇文章里讲过。</p><p>**5. 给 CLAUDE.md 加一个”更新日期”**。<br>在文件顶部或底部写一行 <code>最后更新：2026-04-15</code>，Claude 看到会自动把这个时间点作为”当前项目约定的参考线”。老旧 CLAUDE.md 如果没标时间，很容易误导模型。</p><p>**6. 善用 CLAUDE.md 做 Claude 的”行为纠偏”**。<br>如果你发现 Claude 某个行为反复让你不爽（比如老是主动改你没让改的文件），就在 CLAUDE.md 里加一条明确禁止。这个文件不仅是项目说明，也是你和 Claude 之间的”行为协议”。</p><h2 id="八、最后"><a href="#八、最后" class="headerlink" title="八、最后"></a>八、最后</h2><p>CLAUDE.md 这个东西的价值，在于它让 AI 协作<strong>有了项目特化的能力</strong>。通用大模型有它的通用智能，但每个项目有每个项目的土规矩，这些土规矩靠 prompt 临时讲讲讲不完，靠文档让 AI 自己去读效率又低——CLAUDE.md 就是那个<strong>固化土规矩</strong>的载体。</p><p>写好它的核心心法就一句话：<strong>写关键信息，不写全部信息；写规则，不写描述</strong>。</p><p>想看更多 Claude Code 的实战玩法，<a class="link"   href="https://claudecode.cocoloop.cn/" >claudecode.cocoloop.cn<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 是一个专门做深度内容聚合的站点，我平时也会去翻那边的更新；想看国内 AI 工程师社区的讨论可以去 <a class="link"   href="https://ask.cocoloop.cn/" >ask.cocoloop.cn<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 看看。</p><p>再送一个彩蛋——<strong>CLAUDE.md 其实也适合用在非 Claude Code 的场景</strong>。我现在很多个人项目的仓库根目录都放一份 CLAUDE.md，哪怕我暂时不用 Claude Code，只是用网页版 Claude。需要让 AI 理解这个项目时，直接把 CLAUDE.md 内容贴进对话框作为 system prompt 就好了。这个文件正在慢慢变成一种<strong>跨工具的项目元信息标准</strong>。</p><div class="cta-card">  <div class="cta-title">Claude Code 深度玩家都在看</div>  <div class="cta-desc">本站 Claude-Code 板块持续更新实战玩法。想看 Claude Code 专题深度内容，访问 <a class="link"   href="https://claudecode.cocoloop.cn/" >claudecode.cocoloop.cn<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>；想在中文社区里问问题，去 <a class="link"   href="https://ask.cocoloop.cn/" >ask.cocoloop.cn<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>。</div></div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/claude-md-project-config/</id>
    <link href="https://claude.cocoloop.cn/posts/claude-md-project-config/"/>
    <published>2026-04-15T04:46:53.000Z</published>
    <summary>CLAUDE.md 是 Claude Code 的项目级配置，一共有 11 层优先级。我从完全不写到瞎堆一气到终于写对，踩过三个大坑：配置过载、user 级冲突、@include 循环。这篇给模板和避坑指南。</summary>
    <title>CLAUDE.md 写法大全：11 级优先级与我踩过的三个大坑</title>
    <updated>2026-04-21T15:38:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="MCP" scheme="https://claude.cocoloop.cn/categories/MCP/"/>
    <category term="MCP" scheme="https://claude.cocoloop.cn/tags/MCP/"/>
    <category term="Resources" scheme="https://claude.cocoloop.cn/tags/Resources/"/>
    <category term="Prompts" scheme="https://claude.cocoloop.cn/tags/Prompts/"/>
    <category term="知识沉淀" scheme="https://claude.cocoloop.cn/tags/%E7%9F%A5%E8%AF%86%E6%B2%89%E6%B7%80/"/>
    <content>
      <![CDATA[<p>上个月帮客户做 MCP 架构评审，翻了一圈他们现有的 11 个 server。</p><p>看完我问：”为什么全部都只有 tools？”客户 AI 平台负责人愣了一下：”还能有别的？”</p><p>这不是他一个人的问题。我顺手扫了 GitHub 上 Star 数前 50 的公开 MCP server，<strong>46 个只实现了 tools</strong>，只有 4 个碰了 resources，实现了 prompts 的 1 个都没有。</p><p>可 MCP 规范里明明写着三个并列原语：tools、resources、prompts。Anthropic 不是闲的蛋疼才设计三个东西。这篇我把被忽略的两块讲明白。</p><h2 id="三个原语的设计意图"><a href="#三个原语的设计意图" class="headerlink" title="三个原语的设计意图"></a>三个原语的设计意图</h2><p>先把定位讲清楚：</p><ul><li><strong>tools</strong>：Agent 可以”执行”的动作，有副作用，模型主动决定调用</li><li><strong>resources</strong>：只读数据，像文件一样被”浏览”或”附加到 context”，通常由用户&#x2F;客户端主动选择</li><li><strong>prompts</strong>：服务器预制的 prompt 模板，用户触发（比如 Claude Desktop 里的 “&#x2F;command”），注入到对话</li></ul><p>关键差异在”谁决定用、有没有副作用”。tools 是模型自主决定的执行动作，resources 和 prompts 更多是用户侧或客户端编排的资源注入。</p><p>举个不那么抽象的例子。假设你做一个 Confluence MCP server：</p><ul><li>tools：<code>create_page</code>、<code>update_page</code>、<code>search</code>、<code>add_comment</code></li><li>resources：每个 wiki page 暴露为 <code>confluence://space/PROD/runbook-on-call</code>，用户可以在 Claude Desktop 的 attachment 面板里选一个塞进 context</li><li>prompts：<code>/oncall-incident</code>，预制一个事故响应模板，自动把值班 runbook 作为 resource 附带、引导用户填事故描述</li></ul><p>只做 tools 你只覆盖了这套协议 1&#x2F;3 的能力。</p><h2 id="为什么-90-的人只实现-tools"><a href="#为什么-90-的人只实现-tools" class="headerlink" title="为什么 90% 的人只实现 tools"></a>为什么 90% 的人只实现 tools</h2><p>我琢磨了一下原因：</p><p>第一，<strong>教程带偏</strong>。Anthropic 官方教程主推 tools，因为 tools 是和 LLM function calling 最像的、最容易理解。resources 和 prompts 讲得少。</p><p>第二，<strong>客户端支持不一</strong>。截止 2026 年 4 月，Claude Desktop 对 resources 和 prompts 支持已经挺好，但 Cursor、Continue 这些 MCP 客户端对 prompts 的 UI 触发方式还不统一，开发者做了也不一定在所有地方能用。</p><p>第三，<strong>思维惯性</strong>。工程师脑子里 MCP &#x3D; “让 LLM 能调外部函数”，这直接把 resources 和 prompts 排除在外了。</p><p>第四，<strong>文档里 resources 和 tools 的边界不够清晰</strong>。很多人搞不清：”我这个功能应该做成 tool 还是 resource？”</p><h2 id="resources：不是-tool-的简写，是”浏览”的入口"><a href="#resources：不是-tool-的简写，是”浏览”的入口" class="headerlink" title="resources：不是 tool 的简写，是”浏览”的入口"></a>resources：不是 tool 的简写，是”浏览”的入口</h2><p>判断标准其实很简单：<strong>有没有副作用 + 数据量大小</strong>。</p><p>一个 tool 调用完了会改变世界状态（写数据库、发消息、扣余额）。一个 resource 只是读——它可以有千兆文件，LLM 不一定全读进去，客户端可以按需展示、用户按需选择附加。</p><p>举个对比：</p><ul><li><code>search_products(query)</code> 是 tool：返回 10 个结果塞给 LLM</li><li><code>products://catalog</code> 是 resource：整个目录（可能 47MB），用户在 UI 里手动浏览，选具体某个产品”附加到对话”</li></ul><p>resource 的正确用法是<strong>给 Agent 做浏览，不是做执行</strong>。</p><p>我见过一个反模式：有人把数据库表暴露成 resource，比如 <code>db://users/12345</code>。这表面看挺合理——数据是只读的嘛。但实际使用极其别扭，因为 LLM 根本不知道有哪些 user id 可以读。resource 不适合”按 key 查询”，适合”按层级浏览”。</p><p>正面例子：把公司 wiki 按空间和 page 树结构暴露成 resource，用户&#x2F;模型可以 list 目录、选 page 读取。这才是 resource 的味道。</p><h2 id="prompts：团队知识沉淀的秘密武器"><a href="#prompts：团队知识沉淀的秘密武器" class="headerlink" title="prompts：团队知识沉淀的秘密武器"></a>prompts：团队知识沉淀的秘密武器</h2><p>prompts 是最被低估的一块。</p><p>它不是”给 LLM 的 prompt”——那个是 system prompt。MCP 的 prompts 是<strong>可参数化的模板</strong>，用户或客户端触发时填参数、服务器渲染成消息列表返回给 LLM。</p><p>看起来平平无奇是吧？但你把视角切到”团队知识”维度，这东西价值一下就出来了。</p><p>想象你们团队有一套”故障响应 SOP”：</p><ol><li>先查 Grafana 最近 30 分钟 error rate</li><li>对照 runbook 判断是已知问题还是新问题</li><li>如果是已知问题按 runbook 操作</li><li>如果是新问题，拉值班群、按升级路径通知</li><li>事后写 incident report 到 Confluence</li></ol><p>以前这套 SOP 放在 Notion 某个角落，新人进来培训时看一遍、实战时忘一半。</p><p>把它包装成一个 MCP prompt：</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@mcp.prompt()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">incident_response</span>(<span class="params"></span></span><br><span class="line"><span class="params">    severity: <span class="built_in">str</span>,  <span class="comment"># &quot;P0&quot; | &quot;P1&quot; | &quot;P2&quot;</span></span></span><br><span class="line"><span class="params">    symptom: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params"></span>) -&gt; <span class="built_in">list</span>[Message]:</span><br><span class="line">    runbook = fetch_runbook(symptom)  <span class="comment"># 根据症状拉对应 runbook</span></span><br><span class="line">    <span class="keyword">return</span> [</span><br><span class="line">        Message(role=<span class="string">&quot;user&quot;</span>, content=<span class="string">f&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">我刚触发一个 <span class="subst">&#123;severity&#125;</span> 事故，症状是：<span class="subst">&#123;symptom&#125;</span></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">请按公司标准事故响应流程帮我：</span></span><br><span class="line"><span class="string">1. 分析症状可能对应的根因（参考下方 runbook）</span></span><br><span class="line"><span class="string">2. 给出下一步行动的优先级清单</span></span><br><span class="line"><span class="string">3. 提醒我 <span class="subst">&#123;severity&#125;</span> 级别必须做的通知动作</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">相关 runbook：</span></span><br><span class="line"><span class="string"><span class="subst">&#123;runbook&#125;</span></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">公司 severity 标准：</span></span><br><span class="line"><span class="string">- P0：全量用户影响，立刻拉 war room</span></span><br><span class="line"><span class="string">- P1：部分用户影响，1h 内响应</span></span><br><span class="line"><span class="string">- P2：功能降级，工作时间内响应</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span>)</span><br><span class="line">    ]</span><br></pre></td></tr></table></figure></div><p>用户在 Claude Desktop 里打 <code>/incident-response P0 &quot;API 500 飙到 23%&quot;</code>，server 侧把 runbook 拉出来、按公司标准拼装 prompt、返回给 LLM。新人什么都不用记，流程内化在工具里。</p><h2 id="一个真实案例：runbook-变-prompts"><a href="#一个真实案例：runbook-变-prompts" class="headerlink" title="一个真实案例：runbook 变 prompts"></a>一个真实案例：runbook 变 prompts</h2><p>2026 年 1 月给一个中型 SaaS 客户做了类似改造。</p><p>客户情况：DevOps 团队 17 人，值班 rotation，每次新人上手要读 5 本 runbook（Kubernetes 集群、数据库、CDN、消息队列、监控），平均<strong>14 天</strong>才能独立值班。错误率高、老人累。</p><p>我做的事：</p><ol><li>把 5 本 runbook 拆成 38 个场景化 prompts，覆盖 “pod 频繁 OOM”、”redis 延迟突增”、”CDN 命中率下降” 等具体症状</li><li>每个 prompt 参数化关键变量（集群、服务、时间范围），server 侧拼装上下文</li><li>接入 Grafana MCP 和 Kubernetes MCP，prompt 渲染时自动附加实时指标</li><li>在 Claude Desktop 里配好，新人打 <code>/oncall-</code> 自动补全</li></ol><p>上线后的数据：新人独立值班时间从 14 天降到 3 天，老人值班加班时长平均每周少 4.2 小时，事故首次响应中位数从 8.3 分钟降到 2.1 分钟。</p><p>客户 DevOps 负责人给我发消息：”这玩意儿比 Runbook Wiki 强 10 倍，至少新人真的会用。”</p><p>说白了，写在 wiki 的东西没人看，做成”按一个 &#x2F; 就触发的工作流”就有人用了。这就是 prompts 的价值。</p><h2 id="什么时候用哪个？一个实用决策表"><a href="#什么时候用哪个？一个实用决策表" class="headerlink" title="什么时候用哪个？一个实用决策表"></a>什么时候用哪个？一个实用决策表</h2><table><thead><tr><th>场景</th><th>用 tool</th><th>用 resource</th><th>用 prompt</th></tr></thead><tbody><tr><td>调 API 改数据</td><td>✓</td><td></td><td></td></tr><tr><td>让 LLM 浏览一堆文档</td><td></td><td>✓</td><td></td></tr><tr><td>预制复杂分析流程</td><td></td><td></td><td>✓</td></tr><tr><td>读配置文件</td><td></td><td>✓</td><td></td></tr><tr><td>触发团队 SOP</td><td></td><td></td><td>✓</td></tr><tr><td>查询数据返回给 LLM</td><td>✓</td><td></td><td></td></tr><tr><td>大文件按需附加</td><td></td><td>✓</td><td></td></tr></tbody></table><h2 id="一些实现细节的坑"><a href="#一些实现细节的坑" class="headerlink" title="一些实现细节的坑"></a>一些实现细节的坑</h2><ul><li><strong>resource URI 规范</strong>：用你自己 server 的 scheme（<code>confluence://</code>、<code>jira://</code>），别用 <code>http://</code>——客户端可能拦截</li><li><strong>prompts 返回消息列表</strong>：不是单个字符串，是完整的 messages 数组，可以包含 user&#x2F;assistant 多轮</li><li><strong>capability 声明</strong>：server 构造时要显式开 <code>capabilities.resources</code> 和 <code>capabilities.prompts</code>，不然客户端根本不来问</li><li><strong>list 要快</strong>：<code>resources/list</code> 和 <code>prompts/list</code> 会在客户端 UI 渲染时频繁调用，别在里面跑慢查询</li></ul><p>想把知识沉淀做得更系统，<a href="/posts/skills-deep-dive/">Skills 深度解析</a> 和 <a href="/posts/custom-skill-brand-writer/">自定义 Skill 实战</a> 两篇从另一个角度讲了类似的思路——把团队最佳实践编码成可复用单元。MCP prompts 和 Skills 其实是同一个哲学的两个切面。</p><div style="background:#f0f7ff;border-left:4px solid #0c97fe;padding:18px 22px;margin:28px 0;border-radius:6px;">做 MCP 只做 tools 等于只用了三分之一。建议补看 <a href="/posts/mcp-server-internal-tools/">MCP server 工具设计</a> 理清三种原语的分工，<a href="/posts/skills-deep-dive/">Skills 深度解析</a> 看另一套知识沉淀方案。真正做企业级 MCP 平台，这两块 plus 权限沙箱是三大基石。</div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/mcp-resource-and-prompts/</id>
    <link href="https://claude.cocoloop.cn/posts/mcp-resource-and-prompts/"/>
    <published>2026-04-12T23:16:07.000Z</published>
    <summary>扫了一圈 GitHub 上 Star 数前 50 的 MCP server，有 46 个只实现了 tools，resources 和 prompts 几乎没人碰。但这俩恰恰是 MCP 最被低估的两块拼图。这篇把它们的设计意图讲清楚，附一个把公司 runbook 包装成 MCP prompts 的真实案例——新员工上手时间从 14 天压到 3 天。</summary>
    <title>MCP 里除了 tools 还有 resources 和 prompts，大家都忽略了</title>
    <updated>2026-04-20T20:16:07.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="Context Engineering" scheme="https://claude.cocoloop.cn/categories/Context-Engineering/"/>
    <category term="Claude" scheme="https://claude.cocoloop.cn/tags/Claude/"/>
    <category term="Haiku" scheme="https://claude.cocoloop.cn/tags/Haiku/"/>
    <category term="RAG" scheme="https://claude.cocoloop.cn/tags/RAG/"/>
    <category term="Rerank" scheme="https://claude.cocoloop.cn/tags/Rerank/"/>
    <content>
      <![CDATA[<h2 id="一个”Claude-越来越笨”的错觉"><a href="#一个”Claude-越来越笨”的错觉" class="headerlink" title="一个”Claude 越来越笨”的错觉"></a>一个”Claude 越来越笨”的错觉</h2><p>上个月有个做法律 SaaS 的朋友来问我：他们的产品用 Claude Sonnet 4.5，上线半年用户一直反馈”有时候答得特别准，有时候完全跑题”。他们以为是模型退化了，想换 Opus。</p><p>我让他把 retrieval 的日志拉给我看。一看就笑了——每次捞 top-10 文档扔给 Claude，前 3 名里经常掺着风马牛不相及的条款。比如用户问”离婚财产分割怎么算”，第 2 个召回结果是一条商业合同违约金的判例。</p><p>Claude 不是笨，是被喂了垃圾。embedding 召回本来就这水平，尤其中文专业领域，同义词、近义词、歪打正着的语义相似——全给你召回来了。</p><p>然后我问他：你们 pipeline 里有没有 rerank 这一步。他说没有，怕贵。</p><p>我就说，你知道最贵的是什么吗？是用户看了一次歪答案就流失了。你省的那点 rerank 钱，抵不过一个月掉的 ARR。</p><h2 id="先说清楚什么叫-rerank"><a href="#先说清楚什么叫-rerank" class="headerlink" title="先说清楚什么叫 rerank"></a>先说清楚什么叫 rerank</h2><p>你可以把 RAG 召回想象成两阶段：</p><p><strong>第一阶段叫 retrieval，图的是召得全。</strong> 通常是向量相似度（embedding）+ BM25 关键词混合，扫整个库，捞 top-50 甚至 top-100 回来。这一步要求”宁可错召不可漏召”。</p><p><strong>第二阶段叫 rerank，图的是排得准。</strong> 把 top-50 做精细打分，输出 top-3 或 top-5 给下游 LLM。</p><p>很多团队只做第一阶段，直接 top-10 送进 Claude。问题是 embedding 向量 768 维或 1024 维，做的是粗粒度语义匹配，query 和 chunk 的精细关系它捕捉不到。</p><p>举个最简单的例子。query 是”用户怎么取消自动续费”，embedding 会把”自动续费的收费规则””自动续费常见问题””用户注销账号流程”统统召回来，得分相差不到 0.03。这种情况你让 Claude 去文档里找答案，运气好能搞对，运气不好就搞砸。</p><h2 id="三种-rerank-方案的成本对比"><a href="#三种-rerank-方案的成本对比" class="headerlink" title="三种 rerank 方案的成本对比"></a>三种 rerank 方案的成本对比</h2><p>我现在做项目基本三种里选一种，看场景。</p><p><strong>方案 1：cross-encoder 模型（bge-reranker-v2 这类）</strong></p><p>开源，自己部署。精度很好，尤其中文。一次 rerank 50 段大概 200ms，单次成本算下来折合 0.0003 美刀（主要是 GPU 电费摊销）。适合有 infra 团队的公司。</p><p>缺点是需要维护模型服务，冷启动慢，流量高峰要加机器。小团队头疼。</p><p><strong>方案 2：用 LLM 做 rerank（比如 Haiku 4.5）</strong></p><p>把 query + 50 个候选 chunk 的 ID 和摘要塞进 Haiku，让它输出排序。我实测一次调用大概 3K input + 100 output token，按 Haiku 定价算下来 <strong>0.0008 美刀</strong>。</p><p>优点是精度出奇的好，而且可以通过改 prompt 让它关注特定维度——比如”优先考虑时效性””优先召回含具体金额的条款”。这种定制在 cross-encoder 里是做不到的。</p><p>缺点是延迟比 cross-encoder 大，通常 800ms-1.2s。对实时聊天场景有点压力。</p><p><strong>方案 3：LLM rerank + Claude 主力模型</strong></p><p>就是上面那个方案，但主力模型用 Sonnet 或 Opus。</p><p>我现在 80% 的项目用方案 2+3 的组合。理由很简单：Haiku 做 rerank 的可解释性比任何 cross-encoder 都强，遇到 bad case 你能看到它为什么这么排——让它输出 ranking 的时候顺便说一句 reason 就行。调试起来跟调试人类员工一样。</p><h2 id="什么场景-rerank-就是浪费钱"><a href="#什么场景-rerank-就是浪费钱" class="headerlink" title="什么场景 rerank 就是浪费钱"></a>什么场景 rerank 就是浪费钱</h2><p>不是所有系统都值得加 rerank。我列几个不用加的情况：</p><p><strong>文档库特别小（&lt; 500 个 chunk）。</strong> 直接把所有 chunk 按相关度塞进 context，用 Claude 自己的 attention 筛就行。反正也就几万 token。</p><p><strong>query 特别结构化。</strong> 比如”查 2025 年 3 月 15 日订单号 A123”，这种直接走 SQL 或者精确匹配，向量都不用。</p><p><strong>召回率要求极高，精度要求一般。</strong> 比如推荐场景的”相关文章”，用户点不点无所谓，没必要为精度多花一道。</p><p><strong>响应延迟极敏感（&lt; 500ms）。</strong> rerank 这一步怎么也要 300ms 起，吃不消就砍掉。</p><p>除此之外我基本都会加。尤其客服、问答、法律、医疗这种答错代价高的场景。</p><h2 id="我现在项目的三段式召回配置"><a href="#我现在项目的三段式召回配置" class="headerlink" title="我现在项目的三段式召回配置"></a>我现在项目的三段式召回配置</h2><p>贴一下我在一个教育 SaaS 项目里跑的真实配置，服务了他们 84.2 万次月活 query。</p><p><strong>第一段：双召回</strong></p><p>BM25 捞 top-30，向量捞 top-30，去重合并成 50 左右。这一步只管量，精度靠下一步。</p><p><strong>第二段：Haiku 4.5 rerank</strong></p><p>prompt 里写清楚评分维度（相关度、时效性、权威度各占几分），让 Haiku 输出 top-5。这一步关键是 prompt 里的评分标准写死，不同 query 类型用不同 prompt——基础问答一套、政策解读一套、案例咨询一套。</p><p><strong>第三段：Sonnet 4.5 生成</strong></p><p>拿 top-5 作为 context，配合用户 query 生成最终答案。</p><p>整条链路一次 query 的成本：向量查询 0.00002 刀 + Haiku rerank 0.0008 刀 + Sonnet 生成 0.015 刀 ≈ 0.016 刀。</p><p>对比他们之前单用 Sonnet + top-10 直召的版本（一次 0.022 刀，因为 context 更长），成本还低了 27%，准确率从 74.8% 涨到 89.3%。</p><h2 id="两个经常被问的细节"><a href="#两个经常被问的细节" class="headerlink" title="两个经常被问的细节"></a>两个经常被问的细节</h2><p><strong>Q：rerank 的时候 chunk 要不要截断？</strong></p><p>要。我一般只给 rerank 模型每段前 500 token + 后 100 token，中间用 <code>...省略...</code> 代替。rerank 不需要看完整文本，它判断相关性主要靠开头和结尾。省下来的 token 够你多召 20 个候选。</p><p><strong>Q：rerank 阶段要不要告诉模型对话历史？</strong></p><p>看场景。单轮问答不需要。多轮对话如果涉及指代（”那个方案””刚才说的那家”），必须把最近 1-2 轮对话摘要喂给 rerank 模型，否则它理解不了 query 在说什么。这个细节我踩过坑，排查了两天才定位到。</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>Rerank 这一步，看着是个”锦上添花”的优化，实际上是 RAG 能不能落地商业场景的<strong>分水岭</strong>。</p><p>我见过太多团队 POC 阶段效果惊艳，上线真实流量之后口碑崩了——往往就是 retrieval 从 demo 阶段的 100 个 chunk 扩展到 10 万个 chunk，embedding 的粗粒度就遮不住了，rerank 这道防线又没有。</p><p>花那 0.0008 美刀吧，真的。</p><div class="cta-card" style="margin:32px 0;padding:20px 24px;background:#f0f7ff;border-left:4px solid #0c97fe;border-radius:6px;"><p style="margin:0 0 8px;font-weight:600;color:#1f2937;">延伸阅读</p><p style="margin:0;color:#4b5563;font-size:14px;line-height:1.8;">关于把 Haiku 用在 pipeline 里做轻量任务的更多姿势，看 [Haiku/Sonnet/Opus 选型](/posts/claude-family-haiku-sonnet-opus/)。写 rerank prompt 记得用 XML 结构化输出，参考 [XML 标签的妙用](/posts/prompt-xml-tags-claude-special/)。想把整条 RAG pipeline 的 context 编排思路讲清楚，[从 Prompt 到 Context Engineering](/posts/prompt-to-context-engineering/) 是个好的起点。</p></div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/context-retrieval-reranking/</id>
    <link href="https://claude.cocoloop.cn/posts/context-retrieval-reranking/"/>
    <published>2026-04-12T11:21:59.000Z</published>
    <summary>接触 RAG 三年，我见过至少七八个团队上线了纯 embedding 召回的系统，然后抱怨&quot;Claude 给出的答案总是漏关键点&quot;。不是 Claude 的锅，是你召回就是歪的。这篇写我为什么坚持在 pipeline 里加 rerank 这一步，Haiku 4.5 做轻量 rerank 实测成本 0.0008 美刀一次，以及什么情况下 rerank 就是白花钱。</summary>
    <title>RAG 里最贵的一步不是嵌入，是 rerank</title>
    <updated>2026-04-20T01:21:59.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="成本优化" scheme="https://claude.cocoloop.cn/categories/%E6%88%90%E6%9C%AC%E4%BC%98%E5%8C%96/"/>
    <category term="成本优化" scheme="https://claude.cocoloop.cn/tags/%E6%88%90%E6%9C%AC%E4%BC%98%E5%8C%96/"/>
    <category term="output token" scheme="https://claude.cocoloop.cn/tags/output-token/"/>
    <category term="Prompt 工程" scheme="https://claude.cocoloop.cn/tags/Prompt-%E5%B7%A5%E7%A8%8B/"/>
    <category term="JSON Schema" scheme="https://claude.cocoloop.cn/tags/JSON-Schema/"/>
    <content>
      <![CDATA[<p>先摆个事实：Sonnet 4.5 的 input 价是 $3&#x2F;M、output 价 $15&#x2F;M。5 倍差。Haiku 4.5 也一样 5 倍差。Opus 4.5 是 $15 input &#x2F; $75 output 也是 5 倍。</p><p>但我发现大多数人花时间在 input 优化上 —— 裁 context、上 cache、压 system prompt。这些都对，但 output 这侧的水更深，因为它更难控制。input 写多少你心里有数，output 让模型自由发挥，它能给你水出一篇小作文。</p><p>我有个客服自动回复项目，去年 11 月月账单 $3,124。当时 input 部分已经做过 cache，死活压不下来，后来定位才发现瓶颈在 output：平均每次回复 412 token。优化了三个月做到 168 token，月账单 $1,287。下面这三招就是当时试出来的。</p><h2 id="技巧一：严格-schema-让它闭嘴"><a href="#技巧一：严格-schema-让它闭嘴" class="headerlink" title="技巧一：严格 schema 让它闭嘴"></a>技巧一：严格 schema 让它闭嘴</h2><p>最有效的一招。</p><p>未约束的输出 vs 强 schema 输出，差距能到 2-3 倍。举个例子，我问 Claude「这个工单属于什么类别」，放开让它回答，它会给我：</p><blockquote><p>好的，让我来帮您分析一下这个工单。从描述来看，用户主要反映的是登录异常的问题，具体表现为……（省略 300 字）……所以我判断这个工单应该归入「账号与登录」类别。希望这个判断对您有帮助。</p></blockquote><p>317 token。</p><p>改成 tool use，schema 里只允许 <code>&#123;&quot;category&quot;: &quot;account_login&quot; | &quot;billing&quot; | ...&#125;</code>。返回：</p><div class="code-container" data-rel="Json"><figure class="iseeu highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;account_login&quot;</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div><p>11 token。</p><p>差 29 倍。当然这是极端例子，一般业务场景没这么夸张，但 3-5 倍差是普遍的。</p><p>实操上用 tool_use 比让模型输出 JSON 字符串更稳。tool_use 底层有 constrained decoding 兜底，输出一定符合 schema。<a href="/posts/prompt-output-format-json-schema/">JSON Schema 输出控制</a> 那篇里有详细写法。</p><p>需要注意：schema 里 description 写得越清楚，你能把输出项压得越少。我一开始 schema 里塞了 18 个字段防止模型漏，后来发现其中 6 个是冗余的（比如 <code>is_urgent</code> + <code>urgency_level</code> 一起有），砍掉之后 output token 又降了 22%。</p><h2 id="技巧二：prefill-stop-sequence"><a href="#技巧二：prefill-stop-sequence" class="headerlink" title="技巧二：prefill + stop_sequence"></a>技巧二：prefill + stop_sequence</h2><p>这招知道的人不多但很硬核。</p><p>prefill 是在 assistant 回复开头先写几个字，模型接着写。stop_sequence 是匹配到指定字符串就停。组合起来能把格式锁死。</p><p>举例：我要 Claude 生成一段简短的邮件正文，不要开场白不要结尾。</p><p>普通 prompt 写法：「请帮我生成一段……邮件正文，不要开场白，不要结尾。」模型会回「好的，以下是邮件正文：……」然后还是给你开场白。</p><p>prefill 写法：在 assistant 消息里预填 <code>&lt;email&gt;</code>，stop_sequence 设成 <code>&lt;/email&gt;</code>。模型必须在这对标签里写正文，写完主动停。</p><p>实测一个营销文案生成场景，普通写法平均 289 token，prefill + stop 之后平均 156 token。省 46%。</p><p>另一个妙用是中断啰嗦的 CoT。有时候你不想要 <a href="/posts/prompt-chain-of-thought-vs-direct/">chain-of-thought</a>，想直接答案，但模型忍不住要先 “Let me think about this…”。prefill 一个 <code>The answer is: </code> 就能跳过整个思考过程，直接出答案。这招对简单分类任务特别好用。</p><h2 id="技巧三：Haiku-先草拟，Sonnet-精修"><a href="#技巧三：Haiku-先草拟，Sonnet-精修" class="headerlink" title="技巧三：Haiku 先草拟，Sonnet 精修"></a>技巧三：Haiku 先草拟，Sonnet 精修</h2><p>两段式。Haiku 负责产出大量内容，Sonnet 负责挑刺和精修。</p><p>单 Sonnet 跑一个营销邮件写作任务：平均 input 2.4k + output 540 token &#x3D; $0.0153 一次。</p><p>改成两段：</p><ul><li>Haiku 草拟：input 2.4k + output 540 &#x3D; $0.0024</li><li>Sonnet 精修：input 3.0k（原 prompt + 草稿）+ output 180（只输出修改后的） &#x3D; $0.0117</li><li>合计 $0.0141</li></ul><p>单次只省 8%，看起来不多。但有意思的是，Sonnet 精修版的质量评估分比直接 Sonnet 写的高 11%（我做了一组 200 条的人工评分），因为草稿给了 Sonnet 具体的改进对象，比从零开始更容易产出好结果。</p><p>更进一步，如果你接受「60% 的请求用 Haiku 草稿就够好了，不需要精修」，做一个质量判官（也用 Haiku 跑 $0.0003 一次），整体成本能降到 $0.0068 一次，省 55%。</p><p>这套思路的深入玩法可以看 <a href="/posts/haiku-router-cost-cutting/">Haiku 路由降本</a>，里面讲了更完整的多模型协作策略。</p><h2 id="技巧四（bonus）：明令禁止客套话"><a href="#技巧四（bonus）：明令禁止客套话" class="headerlink" title="技巧四（bonus）：明令禁止客套话"></a>技巧四（bonus）：明令禁止客套话</h2><p>最简单但被低估。</p><p>system prompt 里加一句：「直接输出答案，不要任何开场白、解释、注释、寒暄、总结。禁止使用’好的’、’当然’、’希望对您有帮助’等礼貌用语。」</p><p>实测效果：同样一批客服回复任务，加前平均 412 token，加后平均 287 token，省 30%。</p><p>为什么有效？因为 Claude 4 系列被 RLHF 训得很客气，默认会加缓冲语。你得明确告诉它不要。</p><p>需要 pair 一个 few-shot 示例才稳定。我的 system prompt 里会放两三个 good example，每个都是冷冰冰的直接回答，没一句废话。模型看见之后会 mimic 这个风格。</p><h2 id="实战：客服回复-412-→-168-token"><a href="#实战：客服回复-412-→-168-token" class="headerlink" title="实战：客服回复 412 → 168 token"></a>实战：客服回复 412 → 168 token</h2><p>把上面几招叠起来，我那个客服自动回复项目的优化路径：</p><p>基线（11 月）：412 token &#x2F; 次，$3,124 &#x2F; 月。</p><ol><li>加 system prompt 禁止客套话 → 318 token（省 23%）</li><li>把分类判断切成独立的 tool_use 节点 → 分类 31 token + 回复 202 token &#x3D; 233 token（省 27%）</li><li>对「简单 FAQ 类」问题走 prefill + stop 模板回复 → 加权平均 189 token（再省 19%）</li><li>最后把中等复杂度的那批切到 Haiku 草拟 + Sonnet 精修 → 168 token（再省 11%）</li></ol><p>累计从 412 降到 168，省 59.2%。月账单 $3,124 降到 $1,287（按调用量不变算）。实际同期调用量还涨了 14%，所以真实账单是 $1,468，相比去年同期的 $3,562，省 58.8%。</p><h2 id="反模式：别为省-output-牺牲质量"><a href="#反模式：别为省-output-牺牲质量" class="headerlink" title="反模式：别为省 output 牺牲质量"></a>反模式：别为省 output 牺牲质量</h2><p>最后必须说一下。不是所有场景都该压 output。</p><ul><li>报告生成、长文写作、深度分析：用户要的就是 1500-3000 字，压到 500 字就变废品了</li><li>复杂推理任务：需要 <a href="/posts/prompt-chain-of-thought-vs-direct/">chain-of-thought</a> 展开，硬压会掉准确率</li><li>教学&#x2F;解释类：过于简洁反而看不懂</li></ul><p>我的判断标准：先看业务指标（用户满意度、转化率、准确率），output 压缩之后这些指标如果掉超过 2%，立刻停止压缩。省钱不是目的，单位美元产生的业务价值才是。</p><p>另外，有些场景看起来 output 长，其实是因为 input 没给够。比如让 Claude 总结一段文档，它不得不重复复述原文。如果在 prompt 里先告诉它文档主题和重点，它就能抓重点写，输出自然短。这属于 input 侧花小钱省大钱，比硬压 output 聪明。</p><h2 id="收尾"><a href="#收尾" class="headerlink" title="收尾"></a>收尾</h2><p>这三招（加 bonus 那招）我现在是每个新项目上线前都要过一遍的 checklist。花半天时间，省的是整个项目生命周期的 API 费用。</p><p>output token 这事的核心认知是：Claude 默认倾向于「说够说透」，你得主动告诉它「够了、停」。工具、prompt、schema 都是在实现这个「停」。</p><div style="background:#f0f7ff;border-left:4px solid #0c97fe;padding:16px 20px;margin:24px 0;border-radius:4px;">把 output 压下来之后，下一步可以看 <a href="/posts/prompt-caching-deep-guide/">Prompt Caching 深度指南</a> 搞定 input 侧，配合 <a href="/posts/haiku-router-cost-cutting/">Haiku 路由降本</a> 做模型选型。三者组合是我实践过最稳的降本三板斧。结构化输出的细节另见 <a href="/posts/prompt-output-format-json-schema/">JSON Schema 输出控制</a>。</div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/cost-output-token-minimization/</id>
    <link href="https://claude.cocoloop.cn/posts/cost-output-token-minimization/"/>
    <published>2026-04-11T04:22:53.000Z</published>
    <summary>很多人盯 input 的成本，其实 output 才贵。Sonnet 4.5 的 output 单价是 input 的 5 倍。我一个客服自动回复项目从单次 412 token 压到 168 token，月账单从 $3,124 降到 $1,287。这篇讲三个亲测有效的技巧和一个反模式。</summary>
    <title>output token 才是账单黑洞：3 个把输出压 60% 的技巧</title>
    <updated>2026-04-19T14:22:53.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="Claude Code" scheme="https://claude.cocoloop.cn/categories/Claude-Code/"/>
    <category term="Claude Code" scheme="https://claude.cocoloop.cn/tags/Claude-Code/"/>
    <category term="Slash Command" scheme="https://claude.cocoloop.cn/tags/Slash-Command/"/>
    <category term="工程化" scheme="https://claude.cocoloop.cn/tags/%E5%B7%A5%E7%A8%8B%E5%8C%96/"/>
    <category term="团队协作" scheme="https://claude.cocoloop.cn/tags/%E5%9B%A2%E9%98%9F%E5%8D%8F%E4%BD%9C/"/>
    <content>
      <![CDATA[<h2 id="起因是一个-review-会上的尴尬"><a href="#起因是一个-review-会上的尴尬" class="headerlink" title="起因是一个 review 会上的尴尬"></a>起因是一个 review 会上的尴尬</h2><p>2026 年 1 月，我们做一个面向医疗的 B 端项目，代码 review 一直靠 Claude Code。那天周会老板让看 velocity 数据，发现 4 个工程师用 Claude Code 的方式差得离谱——A 让 Claude 检查 security，B 让 Claude 检查 performance，C 基本只让 Claude 看 typo，D 每次 review 都贴一段 800 字的 prompt。</p><p>结果就是同样的 PR，4 个人过一遍能得出 4 个完全不同的结论。更糟的是 D 的 800 字 prompt 自己都维护不住，上周改一次，这周改一次。</p><p>我那天下午把他们四个的 prompt 抄下来拼一起，写成一个 <code>/review</code> slash command。从那以后统一了，争议也少了很多。后来陆续加了 6 个，团队现在固定用 7 个。</p><h2 id="Slash-command-的基本结构，写一次就记住了"><a href="#Slash-command-的基本结构，写一次就记住了" class="headerlink" title="Slash command 的基本结构，写一次就记住了"></a>Slash command 的基本结构，写一次就记住了</h2><p><code>.claude/commands/&lt;name&gt;.md</code>，就这么简单。文件内容头部一段 YAML frontmatter，下面写 prompt。比如最简单的：</p><div class="code-container" data-rel="Markdown"><figure class="iseeu highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">description: 检查代码改动的安全性</span><br><span class="line">argument-hint: &quot;[文件路径或 PR 号]&quot;</span><br><span class="line">allowed-tools:</span><br><span class="line"><span class="bullet">  -</span> Read</span><br><span class="line"><span class="bullet">  -</span> Grep</span><br><span class="line"><span class="bullet">  -</span> Bash(git diff:<span class="emphasis">*)</span></span><br><span class="line"><span class="emphasis">  - Bash(gh pr diff:*</span>)</span><br><span class="line">---</span><br><span class="line"></span><br><span class="line">你是一个资深安全审计师。请对 $ARGUMENTS 指向的改动做安全 review...</span><br></pre></td></tr></table></figure></div><p>几个关键点：</p><ul><li><code>description</code> 在 <code>/</code> 菜单里显示，写清楚命令干什么</li><li><code>argument-hint</code> 是用户调用时 IDE 给的提示</li><li><code>allowed-tools</code> 白名单，比全局权限窄，安全</li><li><code>$ARGUMENTS</code> 占位符，用户传的参数直接插进来</li><li>正文用 XML 标签组织会更稳，这点我在 <a href="/posts/claude-xml-over-markdown/">XML 优于 Markdown</a> 里细聊过</li></ul><p>下面 7 个模板我按使用频次从高到低排。</p><h2 id="模板-1：-review-——-结构化代码-review"><a href="#模板-1：-review-——-结构化代码-review" class="headerlink" title="模板 1：&#x2F;review —— 结构化代码 review"></a>模板 1：&#x2F;review —— 结构化代码 review</h2><p>这个命令我现在每个仓库都有。关键在于<strong>不要让 Claude 做开放式 review</strong>，要给它一张固定的 checklist。</p><div class="code-container" data-rel="Markdown"><figure class="iseeu highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">description: 多视角代码 review（安全/性能/可维护性）</span><br><span class="line">argument-hint: &quot;[PR 号 或 文件 glob]&quot;</span><br><span class="line">allowed-tools:</span><br><span class="line"><span class="bullet">  -</span> Read</span><br><span class="line"><span class="bullet">  -</span> Grep</span><br><span class="line"><span class="bullet">  -</span> Glob</span><br><span class="line"><span class="section">  - Bash(git diff:<span class="emphasis">*, gh pr:*</span>)</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line">对 $ARGUMENTS 做 review，严格按下面 4 个视角分别出结论。</span><br><span class="line">每个视角最多 5 条发现，每条格式：</span><br><span class="line"><span class="bullet">  -</span> 【严重级 高/中/低】</span><br><span class="line"><span class="bullet">  -</span> 问题描述（不超过 30 字）</span><br><span class="line"><span class="bullet">  -</span> 文件:行号</span><br><span class="line"><span class="bullet">  -</span> 建议改法（代码片段，不超过 6 行）</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">view</span> <span class="attr">name</span>=<span class="string">&quot;安全&quot;</span>&gt;</span></span></span><br><span class="line">关注 SQL 注入、XSS、SSRF、硬编码密钥、不安全反序列化...</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">view</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">view</span> <span class="attr">name</span>=<span class="string">&quot;性能&quot;</span>&gt;</span></span></span><br><span class="line">关注 N+1 查询、未 index 的大表扫描、同步阻塞、内存泄漏...</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">view</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">view</span> <span class="attr">name</span>=<span class="string">&quot;可维护性&quot;</span>&gt;</span></span></span><br><span class="line">关注命名、函数长度、魔法数字、重复代码、测试覆盖...</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">view</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">view</span> <span class="attr">name</span>=<span class="string">&quot;兼容性&quot;</span>&gt;</span></span></span><br><span class="line">关注 API break、数据库 schema 迁移、依赖版本冲突...</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">view</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line">最后给一个 <span class="language-xml"><span class="tag">&lt;<span class="name">verdict</span>&gt;</span></span>approve / request-changes / reject<span class="language-xml"><span class="tag">&lt;/<span class="name">verdict</span>&gt;</span></span>。</span><br></pre></td></tr></table></figure></div><p>团队统计过，这个命令出的 report 和资深工程师的手动 review 重合度 72.8%，关键 issue 漏掉的比例 11.4%。我们的用法是 Claude 先过一遍，工程师基于这个报告做复核，效率比纯人工快 2.3 倍。</p><h2 id="模板-2：-test-gen-——-根据改动生成测试"><a href="#模板-2：-test-gen-——-根据改动生成测试" class="headerlink" title="模板 2：&#x2F;test-gen —— 根据改动生成测试"></a>模板 2：&#x2F;test-gen —— 根据改动生成测试</h2><p>输入一个文件路径，Claude 扫改动、读既有测试的风格、补齐缺失用例。这个命令的精髓是<strong>让它模仿既有测试</strong>，别自己发明新的。</p><div class="code-container" data-rel="Markdown"><figure class="iseeu highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">description: 为指定文件/函数生成单元测试</span><br><span class="line">argument-hint: &quot;[文件路径]&quot;</span><br><span class="line">allowed-tools:</span><br><span class="line"><span class="bullet">  -</span> Read</span><br><span class="line"><span class="bullet">  -</span> Grep</span><br><span class="line"><span class="bullet">  -</span> Glob</span><br><span class="line"><span class="bullet">  -</span> Edit</span><br><span class="line"><span class="section">  - Bash(npm test:<span class="emphasis">*, pytest:*</span>)</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line">为 $ARGUMENTS 补齐测试。步骤：</span><br><span class="line"></span><br><span class="line"><span class="bullet">1.</span> 读取目标文件，列出所有 public 函数</span><br><span class="line"><span class="bullet">2.</span> 搜索项目现有测试文件（<span class="emphasis">*test*</span>, <span class="emphasis">*spec*</span>），挑 2-3 个最相似的</span><br><span class="line"><span class="bullet">3.</span> 严格模仿既有测试的：</span><br><span class="line"><span class="bullet">   -</span> 测试框架和断言风格</span><br><span class="line"><span class="bullet">   -</span> describe/it 命名规则</span><br><span class="line"><span class="bullet">   -</span> mock 方式</span><br><span class="line"><span class="bullet">   -</span> fixture 组织</span><br><span class="line"><span class="bullet">4.</span> 对每个未覆盖的函数生成：正常路径 1 个 + 边界情况 2 个 + 错误路径 1 个</span><br><span class="line"><span class="bullet">5.</span> 运行测试确认通过</span><br><span class="line"></span><br><span class="line">不要使用任何项目中未出现过的断言库或测试 util。</span><br></pre></td></tr></table></figure></div><p>最后一条”不要使用未出现过的库”特别重要。不加的话 Claude 很爱自作主张装个 <code>@testing-library/jest-dom</code> 进来，然后 lint 崩了。</p><h2 id="模板-3：-refactor-extract-——-提取函数"><a href="#模板-3：-refactor-extract-——-提取函数" class="headerlink" title="模板 3：&#x2F;refactor-extract —— 提取函数"></a>模板 3：&#x2F;refactor-extract —— 提取函数</h2><p>这个是写完觉得最值的一个。经常遇到一个 180 行的函数想拆成 3 个，手工做烦，Claude 做又怕它瞎改签名。</p><div class="code-container" data-rel="Markdown"><figure class="iseeu highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">description: 从指定函数中提取子函数，保持对外接口不变</span><br><span class="line">argument-hint: &quot;[文件:函数名]&quot;</span><br><span class="line">allowed-tools:</span><br><span class="line"><span class="bullet">  -</span> Read</span><br><span class="line"><span class="bullet">  -</span> Edit</span><br><span class="line"><span class="section">  - Bash(npm test:<span class="emphasis">*, pytest:*</span>, cargo test:<span class="emphasis">*)</span></span></span><br><span class="line"><span class="emphasis"><span class="section">---</span></span></span><br><span class="line"><span class="emphasis"><span class="section"></span></span></span><br><span class="line"><span class="emphasis"><span class="section">对 $ARGUMENTS：</span></span></span><br><span class="line"><span class="emphasis"><span class="section"></span></span></span><br><span class="line"><span class="emphasis"><span class="section">1. 先用 Read 读取目标函数完整代码</span></span></span><br><span class="line"><span class="emphasis"><span class="section">2. 找出 2-4 个逻辑独立的代码块（循环、分支、连续 I/O 操作等）</span></span></span><br><span class="line"><span class="emphasis"><span class="section">3. 每个块提取成一个 private 函数，命名动词+名词</span></span></span><br><span class="line"><span class="emphasis"><span class="section">4. <span class="strong">**严禁改动原函数的签名、参数、返回值**</span></span></span></span><br><span class="line"><span class="emphasis"><span class="section">5. 提取完立刻跑测试，失败则回滚</span></span></span><br><span class="line"><span class="emphasis"><span class="section"></span></span></span><br><span class="line"><span class="emphasis"><span class="section">最后给我一个 before/after 行数对比。</span></span></span><br></pre></td></tr></table></figure></div><p>“严禁改签名”这句我是用黑体在 prompt 里写的，因为不写 Claude 有 30% 左右概率会”顺手优化”参数顺序。</p><h2 id="模板-4：-debug-runbook-——-按-runbook-调试"><a href="#模板-4：-debug-runbook-——-按-runbook-调试" class="headerlink" title="模板 4：&#x2F;debug-runbook —— 按 runbook 调试"></a>模板 4：&#x2F;debug-runbook —— 按 runbook 调试</h2><p>生产告警来了，按固定 SOP 走。这个模板最大的价值是<strong>消除 debug 时的慌乱</strong>——Claude 照着做，你在旁边看日志。</p><div class="code-container" data-rel="Markdown"><figure class="iseeu highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">description: 按 runbook 调试生产问题</span><br><span class="line">argument-hint: &quot;[告警 ID 或问题描述]&quot;</span><br><span class="line">allowed-tools:</span><br><span class="line"><span class="bullet">  -</span> Read</span><br><span class="line"><span class="bullet">  -</span> Grep</span><br><span class="line"><span class="section">  - Bash(kubectl:<span class="emphasis">*, docker logs:*</span>, curl:<span class="emphasis">*, psql:*</span> --readonly)</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line">问题：$ARGUMENTS</span><br><span class="line"></span><br><span class="line">严格按以下 runbook 执行，每一步完成汇报结果后再进入下一步：</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">step</span> <span class="attr">n</span>=<span class="string">&quot;1&quot;</span>&gt;</span></span>检查服务健康：kubectl get pods -n prod，列出 Not Ready 的<span class="language-xml"><span class="tag">&lt;/<span class="name">step</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">step</span> <span class="attr">n</span>=<span class="string">&quot;2&quot;</span>&gt;</span></span>拉最近 5 分钟 error 日志，grep 出 top 3 error pattern<span class="language-xml"><span class="tag">&lt;/<span class="name">step</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">step</span> <span class="attr">n</span>=<span class="string">&quot;3&quot;</span>&gt;</span></span>查数据库连接池使用率（SELECT ... FROM pg<span class="emphasis">_stat_</span>activity）<span class="language-xml"><span class="tag">&lt;/<span class="name">step</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">step</span> <span class="attr">n</span>=<span class="string">&quot;4&quot;</span>&gt;</span></span>查上游依赖 latency（curl /health/deps）<span class="language-xml"><span class="tag">&lt;/<span class="name">step</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">step</span> <span class="attr">n</span>=<span class="string">&quot;5&quot;</span>&gt;</span></span>对照 recent deploys（最近 2 小时 CI/CD 记录），判断是否 regression<span class="language-xml"><span class="tag">&lt;/<span class="name">step</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">step</span> <span class="attr">n</span>=<span class="string">&quot;6&quot;</span>&gt;</span></span>输出诊断结论：根因 + 紧急缓解动作 + 是否需要 rollback<span class="language-xml"><span class="tag">&lt;/<span class="name">step</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line">任何一步失败立即停止，不要跳步。</span><br></pre></td></tr></table></figure></div><p>这个命令用了半年，3 次生产告警里有 2 次被它救回来。关键是 <code>--readonly</code> 后缀——生产环境只允许查不允许改，把权限模式和 hook 配置一起做，安全才到位。</p><h2 id="模板-5：-pr-desc-——-生成-PR-描述"><a href="#模板-5：-pr-desc-——-生成-PR-描述" class="headerlink" title="模板 5：&#x2F;pr-desc —— 生成 PR 描述"></a>模板 5：&#x2F;pr-desc —— 生成 PR 描述</h2><p>描述必须结构化。我们规定的模板是 Motivation &#x2F; Changes &#x2F; Testing &#x2F; Rollback Plan，Claude 必须严格按这个出。</p><div class="code-container" data-rel="Markdown"><figure class="iseeu highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">description: 根据 git diff 生成 PR 描述</span><br><span class="line">argument-hint: &quot;[base 分支，默认 main]&quot;</span><br><span class="line">allowed-tools:</span><br><span class="line"><span class="section">  - Bash(git diff:<span class="emphasis">*, git log:*</span>)</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line">对比 $ARGUMENTS（默认 main） 到当前分支，生成 PR 描述：</span><br><span class="line"></span><br><span class="line"><span class="section">## Motivation</span></span><br><span class="line">用一段话说清楚为什么要做这个改动。不要从 diff 倒推，要从 commit message 的语气中推测业务目的。</span><br><span class="line"></span><br><span class="line"><span class="section">## Changes</span></span><br><span class="line">按模块分组的 bullet list。一项不超过 15 字。</span><br><span class="line"></span><br><span class="line"><span class="section">## Testing</span></span><br><span class="line"><span class="bullet">-</span> 自动化测试：新增/修改了哪些测试</span><br><span class="line"><span class="bullet">-</span> 手动验证：列出复现步骤</span><br><span class="line"></span><br><span class="line"><span class="section">## Rollback Plan</span></span><br><span class="line">一句话说清楚怎么回滚。如果有 DB 迁移，必须写 down script。</span><br><span class="line"></span><br><span class="line">禁止使用&quot;优化&quot;、&quot;改进&quot;、&quot;增强&quot;这种模糊词。</span><br></pre></td></tr></table></figure></div><p>最后那行禁用词是我反复调教出来的。不加的话 PR 描述全是”优化了用户体验、改进了性能”，读了等于没读。</p><h2 id="模板-6：-commit-msg-——-符合规范的-commit"><a href="#模板-6：-commit-msg-——-符合规范的-commit" class="headerlink" title="模板 6：&#x2F;commit-msg —— 符合规范的 commit"></a>模板 6：&#x2F;commit-msg —— 符合规范的 commit</h2><p>Conventional Commits 规范。这个没什么玄学，就是确保格式稳定。</p><div class="code-container" data-rel="Markdown"><figure class="iseeu highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">description: 生成 conventional commits 格式提交信息</span><br><span class="line">allowed-tools:</span><br><span class="line"><span class="section">  - Bash(git diff --cached:<span class="emphasis">*, git status:*</span>)</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line">检查 staged 改动，生成一条 commit message：</span><br><span class="line"></span><br><span class="line">格式：<span class="language-xml"><span class="tag">&lt;<span class="name">type</span>&gt;</span></span>(<span class="language-xml"><span class="tag">&lt;<span class="name">scope</span>&gt;</span></span>): <span class="language-xml"><span class="tag">&lt;<span class="name">subject</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line">type 从 feat/fix/refactor/docs/test/chore/perf 中选。</span><br><span class="line">scope 是模块名，从文件路径推断。</span><br><span class="line">subject 祈使句，不超过 50 字符，不加句号。</span><br><span class="line"></span><br><span class="line">如果 staged 超过 8 个文件且涉及多个 scope，先提醒我&quot;改动太杂，建议拆分&quot;，然后给出一个折中的 scope。</span><br><span class="line"></span><br><span class="line">Body 可选。如果有破坏性改动必须写 BREAKING CHANGE 段。</span><br></pre></td></tr></table></figure></div><h2 id="模板-7：-spike-prototype-——-快速原型"><a href="#模板-7：-spike-prototype-——-快速原型" class="headerlink" title="模板 7：&#x2F;spike-prototype —— 快速原型"></a>模板 7：&#x2F;spike-prototype —— 快速原型</h2><p>偶尔想快速试一个想法，比如”给我写个用 Redis streams 做 pub&#x2F;sub 的最小例子”。这个命令限定在 <code>/tmp/spike-&lt;timestamp&gt;/</code> 目录里跑，不污染主仓库。</p><div class="code-container" data-rel="Markdown"><figure class="iseeu highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">description: 在隔离目录快速写一个原型</span><br><span class="line">argument-hint: &quot;[想验证的技术点]&quot;</span><br><span class="line">allowed-tools:</span><br><span class="line"><span class="bullet">  -</span> Write</span><br><span class="line"><span class="bullet">  -</span> Edit</span><br><span class="line"><span class="section">  - Bash(mkdir:<span class="emphasis">*, cd:*</span>, npm init:<span class="emphasis">*, python -m venv:*</span>, node:<span class="emphasis">*, python:*</span>)</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line">在 /tmp/spike-$(date +%s)/ 创建一个最小可运行原型验证 $ARGUMENTS。</span><br><span class="line"></span><br><span class="line">要求：</span><br><span class="line"><span class="bullet">-</span> 单文件优先，超过 150 行再拆</span><br><span class="line"><span class="bullet">-</span> 依赖最少，能用标准库就别装包</span><br><span class="line"><span class="bullet">-</span> 必须可以 node/python 直接跑出结果</span><br><span class="line"><span class="bullet">-</span> 结束时把运行命令和输出一起贴给我</span><br><span class="line"></span><br><span class="line">不要在主仓库目录下创建任何文件。</span><br></pre></td></tr></table></figure></div><p>隔离目录这一条是被坑过才加的。之前没限制，Claude 在项目根目录创建了 scratch.js，不小心 commit 进去了。</p><h2 id="反面案例：我废弃的-do-everything"><a href="#反面案例：我废弃的-do-everything" class="headerlink" title="反面案例：我废弃的 &#x2F;do-everything"></a>反面案例：我废弃的 &#x2F;do-everything</h2><p>2025 年 12 月我写过一个叫 <code>/do-everything</code> 的命令，想让它既能 review、又能写 test、又能修 bug，参数里塞一个 mode。结果用了两周就废了。</p><p>原因很简单——命令抽象度太高，每次 Claude 都要先花 300 token 搞懂”我这次该走哪个分支”，比直接用对应的专项命令慢 30% 左右，结果质量还更差。</p><p>后来我总结一条原则：<strong>一个 command 只做一件事</strong>。需要组合就让用户依次调用，或者在一个命令里 hardcode 调用链。</p><h2 id="团队共享：repo-里的-claude-commands-要不要进版本库"><a href="#团队共享：repo-里的-claude-commands-要不要进版本库" class="headerlink" title="团队共享：repo 里的 .claude&#x2F;commands&#x2F; 要不要进版本库"></a>团队共享：repo 里的 .claude&#x2F;commands&#x2F; 要不要进版本库</h2><p>这个争议挺大。我的答案是：<strong>commands&#x2F; 进 git，settings.local.json 不进</strong>。</p><p>commands 是团队 SOP 的一部分，就像测试套件、CI 配置。新人 clone 下来就有这套工具链，onboarding 时间直接减半。</p><p>但 settings.local.json 里可能有个人偏好（比如我喜欢把 Opus 默认开着，同事喜欢 Sonnet），还有本地路径、API key 之类不能共享的。</p><p>具体的 commands 和 skills 的区别我在 <a href="/posts/skills-deep-dive/">Skills 深入</a> 里讲过一次——简单记：command 是显式触发、skill 是 Claude 自主挑选。两个东西可以共存，实际我们项目里是 commands 做强 SOP，skills 做风格类（比如 <a href="/posts/custom-skill-brand-writer/">自定义 brand writer</a>）。</p><p>要把整个 Claude Code 配置体系拉通看一遍，可以翻 <a href="/posts/claude-md-project-config/">CLAUDE.md 项目配置</a>，那篇讲了 CLAUDE.md、commands、skills、hooks 四者怎么协同。</p><div style="background:#f0f7ff;border-left:4px solid #0c97fe;padding:16px 20px;margin:24px 0;border-radius:4px;"><strong>这 7 个模板的完整版</strong><br/>上面代码块里的 prompt 我做了精简，完整版（含每条指令后面的 example、fallback、以及 team review 了 12 轮的优化备注）大概 2800 行。在整理成一个 starter repo，想提前拿的话在评论区留你们的技术栈（偏 Node/Python/Go），我按顺序发。另外准备附一份"反面案例合集"，我自己废弃的 14 个命令都在里面，省你们重走一遍弯路。</div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/claude-code-slash-commands-design/</id>
    <link href="https://claude.cocoloop.cn/posts/claude-code-slash-commands-design/"/>
    <published>2026-04-11T03:34:28.000Z</published>
    <summary>做了快一年 Claude Code，团队里一开始什么 slash command 都没有，大家各自对着 Claude 现编 prompt，重复劳动得吓人。后来我花了两个周末沉淀了 7 个模板进 .claude/commands/，团队 4 个人共用。现在新人进来一周就能上手，上月统计光 /review 和 /pr-desc 两个命令就被调用了 1843 次。写一下这 7 个是怎么设计的。</summary>
    <title>自定义 Slash Command 的 7 个工程化模板</title>
    <updated>2026-04-14T05:34:28.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="Context Engineering" scheme="https://claude.cocoloop.cn/categories/Context-Engineering/"/>
    <category term="Claude" scheme="https://claude.cocoloop.cn/tags/Claude/"/>
    <category term="Agent" scheme="https://claude.cocoloop.cn/tags/Agent/"/>
    <category term="Memory" scheme="https://claude.cocoloop.cn/tags/Memory/"/>
    <category term="Architecture" scheme="https://claude.cocoloop.cn/tags/Architecture/"/>
    <content>
      <![CDATA[<h2 id="为什么记忆这件事这么难"><a href="#为什么记忆这件事这么难" class="headerlink" title="为什么记忆这件事这么难"></a>为什么记忆这件事这么难</h2><p>先说个直观感受。人类聊天为什么不会失忆？因为我们有工作记忆（几秒到几分钟）、短期记忆（几小时到几天）、长期记忆（几年）。每种用不同的脑区，有不同的编码方式，大脑自动做 routing。</p><p>LLM 没有这套。它每次推理就是把你给的 context 从头到尾读一遍，context 之外的东西对它来说不存在。所以”长期记忆”在 LLM 语境下其实是<strong>假命题</strong>——你是在工程层面伪造记忆效果。</p><p>伪造的方式大概四种，我一种种说。</p><h2 id="方式-A：全塞-context（粗暴但通用）"><a href="#方式-A：全塞-context（粗暴但通用）" class="headerlink" title="方式 A：全塞 context（粗暴但通用）"></a>方式 A：全塞 context（粗暴但通用）</h2><p>最简单，把过去所有对话直接拼在一起喂进去。Claude 200K 窗口，理论上能塞几百轮对话。</p><p><strong>适用场景</strong>：对话轮数少（&lt; 30 轮），单次会话不跨天，不需要跨会话记忆。</p><p><strong>隐藏成本</strong>：token。每轮的 input 都在涨，到 50 轮一次调用 80K input 是常态。Claude 的 input 定价 3 美刀&#x2F;百万 token，80K 一次就是 0.24 刀。一个用户聊一小时就是几刀。</p><p><strong>我踩的坑</strong>：2024 年做了个宠物健康咨询 bot，没做记忆压缩，直接全塞。上线第二天有个用户聊了 120 轮，最后一次请求 142K token。甲方看账单的时候脸都绿了。最后连夜上了摘要机制。</p><p><strong>一句话结论</strong>：demo 阶段和轻量场景够用，上生产必翻车。</p><h2 id="方式-B：summary-向前滚动（经济但易断层）"><a href="#方式-B：summary-向前滚动（经济但易断层）" class="headerlink" title="方式 B：summary 向前滚动（经济但易断层）"></a>方式 B：summary 向前滚动（经济但易断层）</h2><p>思路是：对话超过一定长度后，把早期部分压缩成摘要，保留近期原文。比如前 30 轮压成 500 字摘要，然后把最近 10 轮原文接上。再过 10 轮，就把原本的摘要 + 新的 10 轮再压一次。</p><p><strong>适用场景</strong>：长对话但不需要精确引用细节，比如陪伴类、咨询类。</p><p><strong>隐藏成本</strong>：信息损失是<strong>复利式</strong>的。每一次再压缩都在摘要的摘要基础上做，到第五六次迭代的时候，最早的对话已经面目全非。</p><p><strong>我踩的坑</strong>：给一家心理咨询平台做 AI 倾诉对话。前 20 轮用户讲了自己的童年经历——一个具体的、对她很重要的细节。第一次摘要的时候这个细节被保留了。第四次摘要的时候这个细节被合并进了一个”用户有不愉快的家庭记忆”的笼统描述。后来用户在第 60 轮又提起这个细节，AI 回复”嗯，听起来挺难的”——因为它真的不知道了。用户当场崩溃。</p><p>那之后我做摘要会<strong>显式指令</strong>保留这些：具体人名、地点、日期、金额、医疗&#x2F;法律相关的细节。不管压缩多少次都不能丢。</p><p><strong>一句话结论</strong>：必须配合”重要 entity 白名单”机制，不能傻滚动。</p><h2 id="方式-C：外挂知识库-检索（主流方案）"><a href="#方式-C：外挂知识库-检索（主流方案）" class="headerlink" title="方式 C：外挂知识库 + 检索（主流方案）"></a>方式 C：外挂知识库 + 检索（主流方案）</h2><p>把历史对话写入向量库或者结构化数据库，下次需要的时候检索回来。这是大部分生产级 agent 的做法。</p><p><strong>适用场景</strong>：跨会话、跨天、跨用户的长期记忆。比如”这个用户三个月前提到过他的过敏史”。</p><p><strong>隐藏成本</strong>：</p><p>第一，存什么是个大问题。不能把每一轮对话都存——太多了，检索的时候召回质量反而下降。我现在的做法是每轮对话后跑一个小任务（用 Haiku），判断”这一轮有没有产生值得长期记忆的 fact”，有的话抽成结构化条目再入库。</p><p>第二，检索时机。每次用户说话都做一次记忆检索？太贵。我的做法是：<strong>只有在 Claude 需要的时候</strong>才触发——在 system prompt 里告诉 Claude 它有个 recall 工具，需要回忆的时候自己调。这样大部分对话不触发检索。</p><p>第三，记忆冲突怎么办。用户三个月前说自己不吃辣，今天说想尝尝辣的。两个 fact 都在库里，检索回来了，模型懵了。我现在给每个记忆条目加时间戳 + confidence score，检索时按时间衰减 + 用户显式更新的优先级处理。</p><p><strong>我踩的坑</strong>：最开始没做冲突处理，一个电商客服 bot 把用户”已经取消订阅”和”刚下单”两个 fact 同时召回。模型给出的回复自相矛盾，被投诉到客户那边。</p><p><strong>一句话结论</strong>：现在做 agent 的主流选择，但工程量比想象大。关于怎么在 <a href="/posts/claude-md-project-config/">CLAUDE.md</a> 里管理长期知识也是类似思路。</p><h2 id="方式-D：事件溯源-按需回放（复杂但强大）"><a href="#方式-D：事件溯源-按需回放（复杂但强大）" class="headerlink" title="方式 D：事件溯源 + 按需回放（复杂但强大）"></a>方式 D：事件溯源 + 按需回放（复杂但强大）</h2><p>这是最工程化的方案。把每轮对话当做 event 存成时间序列，需要重建 context 的时候根据 query 回放相关 event。</p><p>和方式 C 的区别是：C 存的是<strong>抽象过的 fact</strong>（”用户过敏花生”），D 存的是<strong>原始事件流</strong>（”2026-01-15 14:30 用户说：我小时候吃花生起过疹子”）。</p><p><strong>适用场景</strong>：对审计、可追溯性要求高的场景。金融、医疗、法务。还有就是你不确定未来要怎么用这些数据、不想过早抽象的时候。</p><p><strong>隐藏成本</strong>：回放逻辑复杂。你要写很多”从事件流构建 context”的代码。而且存储成本线性增长，一年数据能到几十 GB。</p><p><strong>我踩的坑</strong>：给一家医疗公司做随访 AI，用了事件溯源。前半年很爽，数据都留着，随时能追溯。问题在第八个月——单个患者的事件已经到几千条，每次回放都要过滤排序，latency 爬到 4-5 秒。后来加了”压缩快照”机制，每 100 个 event 生成一个中间快照，回放时从最近快照开始。这事儿其实就是借鉴了 event sourcing 里的 snapshot 模式。</p><p><strong>一句话结论</strong>：大厂或者强监管场景才用得上，中小团队别碰。</p><h2 id="一个”200-轮对话还不崩”的参考架构"><a href="#一个”200-轮对话还不崩”的参考架构" class="headerlink" title="一个”200 轮对话还不崩”的参考架构"></a>一个”200 轮对话还不崩”的参考架构</h2><p>最后给个我现在给中型项目用的默认架构，规模大概对标日活 10 万、单用户平均 30 分钟会话。</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">┌─ 近期原文（最近 8 轮）</span><br><span class="line">│     完整保留，不压缩</span><br><span class="line">│</span><br><span class="line">├─ 中期要点（第 9-24 轮）</span><br><span class="line">│     每 4 轮一个小摘要，保留 entity</span><br><span class="line">│</span><br><span class="line">├─ 远期摘要（第 25 轮以前）</span><br><span class="line">│     整段摘要，~800 token</span><br><span class="line">│</span><br><span class="line">├─ 长期记忆（跨会话 fact 库）</span><br><span class="line">│     向量库 + 结构化表</span><br><span class="line">│     Claude 按需调用 recall 工具触发检索</span><br><span class="line">│</span><br><span class="line">└─ 用户画像（静态 profile）</span><br><span class="line">      ~500 token，每天异步更新一次</span><br></pre></td></tr></table></figure></div><p>几个实现要点：</p><p>第一，摘要是<strong>异步</strong>的，不阻塞当前对话。用户发完消息，直接用近期原文 + 现有摘要拼 context。摘要更新在后台跑。</p><p>第二，fact 抽取用 Haiku 4.5，prompt 里明确要求结构化输出（见 <a href="/posts/prompt-xml-tags-claude-special/">XML 标签</a>），包括 fact 类型、confidence、过期时间。</p><p>第三，主模型用 Sonnet 或 Opus，通过 tool use 主动调用 recall。具体工具定义和 MCP 结合可以看 <a href="/posts/mcp-vs-function-calling/">MCP vs Function Calling</a>。</p><p>第四，每 50 轮对话跑一次”记忆整理”——合并重复 fact、标记过时 fact、提升高频 fact 的权重。这个灵感来自人类睡眠时的记忆巩固。</p><p>跑了七八个月，200 轮以上的长会话测下来”还能记得早期重要信息”的比例 87.6%，比最朴素的方案 B 高了 30 多个点。</p><h2 id="最后说个容易被忽视的事"><a href="#最后说个容易被忽视的事" class="headerlink" title="最后说个容易被忽视的事"></a>最后说个容易被忽视的事</h2><p>所有记忆方案都绕不过一个灵魂问题：<strong>这个”记忆”到底属于谁</strong>？</p><p>用户以为 AI 记住了他说的话。但底层可能是向量库里的几条 embedding，跟他想象的”AI 在关心我”差距很大。一旦产品把”长期记忆”做成卖点，就要对用户的期待负责——包括遗忘权、导出权、修正权。</p><p>这不是技术问题，是产品伦理问题。但做技术的人最好一开始就想清楚。</p><div class="cta-card" style="margin:32px 0;padding:20px 24px;background:#f0f7ff;border-left:4px solid #0c97fe;border-radius:6px;"><p style="margin:0 0 8px;font-weight:600;color:#1f2937;">延伸阅读</p><p style="margin:0;color:#4b5563;font-size:14px;line-height:1.8;">长期记忆的 fact 抽取和 recall 都离不开 tool use，强烈建议读 [MCP vs Function Calling](/posts/mcp-vs-function-calling/)。如果你还想把记忆机制做得更模块化，[Skills 深度解析](/posts/skills-deep-dive/) 里有 Anthropic 官方的思路。压缩对话历史这一块后面有专门一篇讲 3 种策略 AB 对比。</p></div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/context-memory-long-term-agent/</id>
    <link href="https://claude.cocoloop.cn/posts/context-memory-long-term-agent/"/>
    <published>2026-04-11T00:46:33.000Z</published>
    <summary>做 agent 系统最头疼的问题之一：对话进行到第 80 轮，用户说&quot;继续上次那个方案&quot;，模型完全不知道上次是什么。长期记忆怎么做？我过去两年试过四种架构——全塞 context、滚动摘要、外挂知识库、事件溯源。每种都在生产环境出过事故。这篇把四种方案的适用规模、隐藏成本、我踩的坑一次讲清楚。</summary>
    <title>给 Agent 做长期记忆的 4 种架构，每种我都踩过一次坑</title>
    <updated>2026-04-14T03:46:33.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="成本优化" scheme="https://claude.cocoloop.cn/categories/%E6%88%90%E6%9C%AC%E4%BC%98%E5%8C%96/"/>
    <category term="成本优化" scheme="https://claude.cocoloop.cn/tags/%E6%88%90%E6%9C%AC%E4%BC%98%E5%8C%96/"/>
    <category term="Claude Haiku" scheme="https://claude.cocoloop.cn/tags/Claude-Haiku/"/>
    <category term="Router" scheme="https://claude.cocoloop.cn/tags/Router/"/>
    <category term="Prompt Caching" scheme="https://claude.cocoloop.cn/tags/Prompt-Caching/"/>
    <content>
      <![CDATA[<p>上个月我给一个做跨境电商的朋友做了个客服 bot，接 WhatsApp 和独立站两个入口，每天大概 3000-4000 轮对话。刚上线的时候我图省事，全部走 Sonnet 4.6——毕竟他们家客户问的问题挺杂，有退换货有物流追踪有产品咨询，我怕 Haiku 扛不住。</p><p>结果第一个月账单出来，$430。朋友当时没说什么，但我自己看着这个数字挺刷新的——一个小电商客服，一个月光 AI 费用就快赶上一个兼职客服工资了。这东西如果要商用化，成本必须砍下来。</p><p>我花了差不多一周时间重构，最后的方案就是标题这个：<strong>前面塞一个 Haiku 4.5 当 Router，先判断问题类型，简单的直接 Haiku 回，复杂的才转给 Sonnet</strong>。上线跑了一个月，账单掉到 $128，砍了大概 70%。这篇把整个思路和踩过的坑都讲清楚。</p><h2 id="一、为什么是路由，不是降级"><a href="#一、为什么是路由，不是降级" class="headerlink" title="一、为什么是路由，不是降级"></a>一、为什么是路由，不是降级</h2><p>很多人第一反应是”那我直接全部换成 Haiku 不就行了”。我试过，不行。</p><p>Haiku 4.5 处理简单问题确实够用，但它有几个明显的短板：</p><ul><li><strong>多轮对话容易丢线索</strong>。用户上一轮说了订单号，下一轮问”那个能退吗”，Haiku 有概率接不住</li><li><strong>复杂意图识别差</strong>。用户一句话里塞了三个问题（”我想退货，但我找不到订单，而且我换了邮箱”），Haiku 经常只回第一个</li><li><strong>专业术语翻车</strong>。电商行业的”FBA””海外仓””清关”这些词，Haiku 的理解深度明显比 Sonnet 浅一层</li><li><strong>指代消解能力弱</strong>。用户说”刚才那个”、”上面说的”，Haiku 有时候会在历史里找错引用对象</li></ul><p>所以纯 Haiku 走不通。但反过来，<strong>大量的问题其实根本用不到 Sonnet 的智能</strong>——“你们几点上班””怎么联系人工””订单多久发货”，这类 FAQ 类问题，Haiku 一句话就能搞定，上 Sonnet 纯粹是杀鸡用牛刀。</p><p>路由的核心思想：<strong>让便宜的模型做选择，让贵的模型做事</strong>。</p><p>这里其实还隐含一个很多人不重视的点——<strong>路由本身也要算账</strong>。你不能为了路由加一层 GPT-4 级别的分类器，那就本末倒置了。Router 必须用成本可以忽略的模型，Haiku 4.5 的定价刚好够得着这条线。</p><h2 id="二、Router-的-Prompt-到底怎么写"><a href="#二、Router-的-Prompt-到底怎么写" class="headerlink" title="二、Router 的 Prompt 到底怎么写"></a>二、Router 的 Prompt 到底怎么写</h2><p>这里是整个方案最关键的地方。Router 的 prompt 写得好不好，直接决定误判率。</p><p>我最终跑通的版本大概长这样（简化过）：</p><div class="code-container" data-rel="Xml"><figure class="iseeu highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">role</span>&gt;</span>你是一个客服问题路由器。只做一件事：判断用户问题类型。<span class="tag">&lt;/<span class="name">role</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">categories</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">simple</span>&gt;</span></span><br><span class="line">    打招呼、问营业时间、问联系方式、问退换货政策、</span><br><span class="line">    问物流时效、问支付方式、问发票、简单的产品参数查询</span><br><span class="line">  <span class="tag">&lt;/<span class="name">simple</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">complex</span>&gt;</span></span><br><span class="line">    具体订单查询（带订单号）、投诉、多意图（一句话多个问题）、</span><br><span class="line">    需要查询历史订单、涉及金额纠纷、需要调用工具</span><br><span class="line">  <span class="tag">&lt;/<span class="name">complex</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">categories</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">examples</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">example</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span>&gt;</span>你们周末上班吗<span class="tag">&lt;/<span class="name">input</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">output</span>&gt;</span>simple<span class="tag">&lt;/<span class="name">output</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">example</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">example</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span>&gt;</span>我订单 #A2389 到现在还没发，怎么回事<span class="tag">&lt;/<span class="name">input</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">output</span>&gt;</span>complex<span class="tag">&lt;/<span class="name">output</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">example</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">example</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span>&gt;</span>我想退货，但是我找不到当时的订单号<span class="tag">&lt;/<span class="name">input</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">output</span>&gt;</span>complex<span class="tag">&lt;/<span class="name">output</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">example</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">example</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span>&gt;</span>运费怎么算<span class="tag">&lt;/<span class="name">input</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">output</span>&gt;</span>simple<span class="tag">&lt;/<span class="name">output</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">example</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">examples</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">rules</span>&gt;</span></span><br><span class="line">- 只输出 simple 或 complex，不要解释</span><br><span class="line">- 拿不准的时候输出 complex</span><br><span class="line">- 带数字订单号、金额的一律 complex</span><br><span class="line"><span class="tag">&lt;/<span class="name">rules</span>&gt;</span></span><br><span class="line"></span><br><span class="line">用户问题：&#123;user_input&#125;</span><br></pre></td></tr></table></figure></div><p>几个经验点：</p><p><strong>1. Few-shot 4-6 个例子就够了</strong>。我一开始堆了 20 个例子，发现 Haiku 反而开始”过度思考”，准确率还下降了。后来砍到 6 个有代表性的，每个类别各 3 个，反而更稳。这个现象有点反直觉，但在很多分类任务里都能看到——例子太多会让小模型陷入”模式匹配”而不是”理解规则”。</p><p><strong>2. 最后那条”拿不准的时候输出 complex”是救命稻草</strong>。这是一个<strong>不对称代价</strong>的场景——把简单题误判成 complex 只是多花点钱，但把 complex 误判成 simple 可能会让用户炸毛。所以默认倾向于保守。这个思路在任何业务路由里都通用——<strong>先想清楚误判的两类代价谁更高，然后让 prompt 倾斜向代价低的那一边</strong>。</p><p><strong>3. XML 比 Markdown 好用</strong>。这点我另一篇 <a href="/posts/claude-xml-over-markdown/">《为什么 Claude 只吃 XML 不吃 Markdown》</a> 专门写了，这里就不展开，反正实测 XML 的路由准确率高 8-10 个百分点。</p><p><strong>4. 强制单 token 输出能再快一截</strong>。如果你 Router 只需要 simple &#x2F; complex 两个输出，可以把 <code>max_tokens</code> 设成 5，并且在 prompt 末尾加一句”直接输出标签，不要换行不要解释”。这样单次 Router 延迟能压到 200ms 以内。</p><h2 id="三、命中率和误判率：真实数字"><a href="#三、命中率和误判率：真实数字" class="headerlink" title="三、命中率和误判率：真实数字"></a>三、命中率和误判率：真实数字</h2><p>跑了一个月，我拉了一下日志做了统计。总调用量 98000 次左右：</p><ul><li>被路由到 <strong>simple（Haiku 回复）</strong>：72%</li><li>被路由到 <strong>complex（Sonnet 回复）</strong>：28%</li><li>Router 自己的<strong>误判率</strong>：约 3.2%</li></ul><p>误判里面又分两种：</p><ul><li><strong>simple 误判成 complex</strong>（花了冤枉钱）：约 2.1%</li><li><strong>complex 误判成 simple</strong>（用户可能不爽）：约 1.1%</li></ul><p>第二种是真正需要关注的。我翻了这 1.1% 的日志，大部分是用户表述特别隐晦的情况——比如”前两天那个东西一直没到”，没说订单号，没说产品名，Haiku 就判成 simple 了。</p><p>我的兜底方案是<strong>在 Haiku 回答之后加一层 confidence 检查</strong>：如果 Haiku 自己也觉得回答不了，让它输出一个特殊 token <code>&lt;&lt;ESCALATE&gt;&gt;</code>，系统检测到这个 token 就重新路由到 Sonnet。这一层兜底把实际”用户可能不爽”的比例压到了 0.3% 以下。</p><p>这个兜底逻辑的实现也很简单，就是在 Haiku 的 system prompt 里加一句：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">如果你不确定答案、或者用户问题需要查询订单/账户/实时数据，</span><br><span class="line">直接输出 &lt;&lt;ESCALATE&gt;&gt; 然后停止，不要编造答案。</span><br></pre></td></tr></table></figure></div><p>Haiku 对这种 “知道自己不知道” 的指令还挺听话的。</p><h2 id="四、账单的账算给你看"><a href="#四、账单的账算给你看" class="headerlink" title="四、账单的账算给你看"></a>四、账单的账算给你看</h2><p>这是最实打实的部分。按 Anthropic 的定价（我用的是 2026 年 Q1 的价格）：</p><ul><li>Sonnet 4.6：$3 &#x2F; $15（input&#x2F;output，per million tokens）</li><li>Haiku 4.5：$0.25 &#x2F; $1.25</li></ul><p>一轮平均对话大约 800 input + 300 output token。</p><p><strong>纯 Sonnet 方案</strong>：每轮成本约 0.0069 美元，98000 次 &#x3D; $676<br>（和我实际 $430 有差距是因为有 Prompt Caching 节省，这里只算理论上限）</p><p><strong>Router 方案</strong>：</p><ul><li>98000 次 Haiku 路由判断，每次约 200 token in &#x2F; 5 token out，Haiku 成本 &#x3D; 约 $5</li><li>72% 用 Haiku 回复，每次约 800&#x2F;300 token，成本 &#x3D; 约 $44</li><li>28% 用 Sonnet 回复，每次约 800&#x2F;300，成本 &#x3D; 约 $190</li><li><strong>合计 $239</strong></li></ul><p>实际账单 $128 比这个还低，多出来的部分是 <strong>Prompt Caching</strong> 省的——因为 Router 的 prompt 是固定的，system prompt 命中 cache 之后 input token 成本降到 10%。这点不开 cache 你就白瞎了 Haiku 的便宜。</p><p>再补一笔：Haiku 4.5 本身也支持 Prompt Caching。我 Router 的 system prompt + few-shot 大概 400 token，每次请求这部分都能 cache 命中，理论上 98000 次 Router 调用光 cache 就能省下接近 $3——听起来不多，但这是在已经很便宜的基础上再打对折，积少成多。</p><h2 id="五、边界判断的几个坑"><a href="#五、边界判断的几个坑" class="headerlink" title="五、边界判断的几个坑"></a>五、边界判断的几个坑</h2><p>踩过的具体坑：</p><p><strong>1. 带数字不等于 complex</strong>。<br>我一开始粗暴地写了”带数字一律 complex”，结果”我买过 3 件”这种陈述也被扔给 Sonnet 了，浪费钱。后来改成”<strong>带订单号格式的数字</strong>（比如 #A 开头、纯 8 位数字、带连字符）”才稳定。</p><p><strong>2. 多语言切换会让 Router 懵</strong>。<br>客户里有东南亚用户，一句话里中英文夹杂。Haiku 对混合语言的类目判断准确率会掉 10 多个百分点。解决办法是在 Router prompt 里加一条：<code>&lt;rule&gt;输入可能是任意语言，只看意图不看语言&lt;/rule&gt;</code>，立竿见影。</p><p><strong>3. 不要让 Router 兼职做别的事</strong>。<br>我中途想偷懒，让 Router 顺便输出一个”情绪标签”（positive&#x2F;negative&#x2F;neutral），结果路由本身的准确率掉了。模型一心二用容易出事。<strong>Router 就做路由，别的活交给下游模型</strong>。</p><p><strong>4. Router 也要做 eval</strong>。<br>我每周会抽 200 条真实对话人工标注一遍，跑 eval 看准确率有没有飘。语料分布是会变的——最近有个产品出了点质量问题，投诉类问题突然变多，老的 few-shot 就不够用了，得补新例子。</p><p><strong>5. 别忽视上下文污染</strong>。<br>Router 在判断当前用户输入的时候，有人会把之前的对话历史也塞进去。我试过，发现这样反而拉低准确率——因为 Router 开始综合历史做判断，变复杂了。<strong>Router 只看当前这一句</strong>，历史信息留给下游模型。</p><h2 id="六、什么场景适合搞-Router？什么不适合"><a href="#六、什么场景适合搞-Router？什么不适合" class="headerlink" title="六、什么场景适合搞 Router？什么不适合"></a>六、什么场景适合搞 Router？什么不适合</h2><p>适合的：</p><ul><li><strong>流量大</strong>（每天过千轮），Router 的开发成本能分摊</li><li><strong>请求类型差异大</strong>（FAQ 和深度分析混在一起）</li><li><strong>大部分请求其实很简单</strong>（符合二八分布）</li><li><strong>对成本敏感</strong>（比如 toC 业务、免费产品，用户量起来之后 token 成本会要命）</li></ul><p>不适合的：</p><ul><li><strong>请求都很复杂</strong>（比如纯研发辅助、纯 Agent 编排），这种场景 Haiku 根本接不住，路由意义不大</li><li><strong>流量很小</strong>（一天几十轮），你花一周开发 Router 的时间还不如直接烧钱</li><li><strong>对延迟极度敏感</strong>（voice agent），多一次模型调用就是多 200-500ms</li><li><strong>需要严格可解释</strong>（金融、医疗），Router 引入了额外的失败点，审计会更复杂</li></ul><p>顺带说一下，如果你是做 Claude Code 辅助开发这种场景，<a class="link"   href="https://claudecode.cocoloop.cn/" >claudecode.cocoloop.cn<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 已经帮你在工具层做好了模型选择策略，不用自己搞。Router 模式主要还是面向自研业务 API 调用的情况。</p><h2 id="七、下一步：多级路由和动态-threshold"><a href="#七、下一步：多级路由和动态-threshold" class="headerlink" title="七、下一步：多级路由和动态 threshold"></a>七、下一步：多级路由和动态 threshold</h2><p>Router 不是什么新鲜东西，但大部分人没意识到<strong>这玩意儿能省到 70% 这个量级</strong>。关键在于：</p><ul><li>Haiku 4.5 真的比你想象中聪明，做分类器绰绰有余</li><li>few-shot 要精而不要多</li><li>一定要开 Prompt Caching</li><li>不对称代价要体现在 prompt 的兜底规则里</li></ul><p>再往下一步是做<strong>多级路由</strong>——Haiku 先判大类，Sonnet 再判子类，Opus 只负责最硬的 5%。这个方案我还在试，初步数据是能在 Router 方案基础上再省 15% 左右，但引入的复杂度也大幅提升，对 eval 和监控的要求更高。</p><p>另一个方向是<strong>动态 threshold</strong>——Router 不输出硬 label，而是输出一个置信度分数，根据分数决定走 Haiku 还是 Sonnet。比如 score &gt; 0.9 走 Haiku，score &lt; 0.5 走 Sonnet，中间段走 Haiku + escalate 兜底。这个方案理论上最省，但要调的参数也最多。</p><p>对了，如果想看更多 Claude 成本优化相关内容，可以翻翻站内的 <a href="/categories/%E6%88%90%E6%9C%AC%E4%BC%98%E5%8C%96/">成本优化分类</a>，有好几篇相关的。</p><div class="cta-card">  <div class="cta-title">想持续关注 AI 降本实战？</div>  <div class="cta-desc">本站会持续更新 Claude 各种成本优化案例。想看更多 AI 行业一手新闻可以去 <a class="link"   href="https://news.cocoloop.cn/" >news.cocoloop.cn<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>，看各类 AI 工具评测可以访问 <a class="link"   href="https://www.cocoloop.cn/" >www.cocoloop.cn<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>。</div></div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/haiku-router-cost-cutting/</id>
    <link href="https://claude.cocoloop.cn/posts/haiku-router-cost-cutting/"/>
    <published>2026-04-09T02:38:50.000Z</published>
    <summary>做一个客服机器人三个月烧了一千多刀，后来我在前面塞了一个 Haiku 4.5 做路由判断，简单问题本地回、复杂问题才上 Sonnet，账单直接掉到三百出头。这篇把误判率、few-shot 写法、边界判断都摊开讲。</summary>
    <title>把 Haiku 4.5 当 Router 用，我给 API 账单砍了七成</title>
    <updated>2026-04-13T14:38:50.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Claude 中文知识站</name>
    </author>
    <category term="MCP" scheme="https://claude.cocoloop.cn/categories/MCP/"/>
    <category term="MCP" scheme="https://claude.cocoloop.cn/tags/MCP/"/>
    <category term="TypeScript" scheme="https://claude.cocoloop.cn/tags/TypeScript/"/>
    <category term="实战" scheme="https://claude.cocoloop.cn/tags/%E5%AE%9E%E6%88%98/"/>
    <category term="Claude Desktop" scheme="https://claude.cocoloop.cn/tags/Claude-Desktop/"/>
    <content>
      <![CDATA[<p>一开始我真以为搓个 MCP server 得学一堆新概念。</p><p>看完规范以后预期是：至少要写个 transport 层、处理 JSON-RPC 路由、搞 capability handshake……结果打开 <code>@modelcontextprotocol/sdk</code>，发现核心代码也就十几行。40 行能出一个能用的 server。</p><p>这篇我按”我第一次做的时候希望有人告诉我”的顺序写一遍。不讲原理（原理看上一篇 <a href="/posts/mcp-vs-function-calling/">MCP 协议从头撸</a>），就讲动手。</p><h2 id="初始化：脚手架比想的还轻"><a href="#初始化：脚手架比想的还轻" class="headerlink" title="初始化：脚手架比想的还轻"></a>初始化：脚手架比想的还轻</h2><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> weather-mcp &amp;&amp; <span class="built_in">cd</span> weather-mcp</span><br><span class="line">npm init -y</span><br><span class="line">npm i @modelcontextprotocol/sdk zod</span><br><span class="line">npm i -D typescript @types/node tsx</span><br><span class="line">npx tsc --init</span><br></pre></td></tr></table></figure></div><p>我第一次装的时候 SDK 版本是 1.0.4，现在 2026 年 4 月最新是 1.18.2，API 变化不大，主要是 transport 那边多了 streamable HTTP。</p><p><code>package.json</code> 里加一行：</p><div class="code-container" data-rel="Json"><figure class="iseeu highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;bin&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;weather-mcp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./dist/index.js&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div><p>这个 bin 字段很关键，Claude Desktop 启动你 server 的时候就是走这个入口。我第一次漏了，配置里 <code>command</code> 怎么写都找不到可执行文件，折腾了 40 多分钟才反应过来。</p><h2 id="40-行的-weather-server-完整代码"><a href="#40-行的-weather-server-完整代码" class="headerlink" title="40 行的 weather server 完整代码"></a>40 行的 weather server 完整代码</h2><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env node</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Server</span> &#125; <span class="keyword">from</span> <span class="string">&quot;@modelcontextprotocol/sdk/server/index.js&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">StdioServerTransport</span> &#125; <span class="keyword">from</span> <span class="string">&quot;@modelcontextprotocol/sdk/server/stdio.js&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  <span class="title class_">CallToolRequestSchema</span>,</span><br><span class="line">  <span class="title class_">ListToolsRequestSchema</span>,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&quot;@modelcontextprotocol/sdk/types.js&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; z &#125; <span class="keyword">from</span> <span class="string">&quot;zod&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> server = <span class="keyword">new</span> <span class="title class_">Server</span>(</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&quot;weather-mcp&quot;</span>, <span class="attr">version</span>: <span class="string">&quot;0.1.0&quot;</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">capabilities</span>: &#123; <span class="attr">tools</span>: &#123;&#125; &#125; &#125;</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">GetWeatherArgs</span> = z.<span class="title function_">object</span>(&#123;</span><br><span class="line">  <span class="attr">city</span>: z.<span class="title function_">string</span>().<span class="title function_">min</span>(<span class="number">1</span>).<span class="title function_">max</span>(<span class="number">64</span>),</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">server.<span class="title function_">setRequestHandler</span>(<span class="title class_">ListToolsRequestSchema</span>, <span class="title function_">async</span> () =&gt; (&#123;</span><br><span class="line">  <span class="attr">tools</span>: [&#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&quot;get_weather&quot;</span>,</span><br><span class="line">    <span class="attr">description</span>: <span class="string">&quot;查询指定城市当前天气（摄氏度）&quot;</span>,</span><br><span class="line">    <span class="attr">inputSchema</span>: &#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">&quot;object&quot;</span>,</span><br><span class="line">      <span class="attr">properties</span>: &#123; <span class="attr">city</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;string&quot;</span>, <span class="attr">description</span>: <span class="string">&quot;城市中文名或拼音&quot;</span> &#125; &#125;,</span><br><span class="line">      <span class="attr">required</span>: [<span class="string">&quot;city&quot;</span>],</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;],</span><br><span class="line">&#125;));</span><br><span class="line"></span><br><span class="line">server.<span class="title function_">setRequestHandler</span>(<span class="title class_">CallToolRequestSchema</span>, <span class="title function_">async</span> (req) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (req.<span class="property">params</span>.<span class="property">name</span> !== <span class="string">&quot;get_weather&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`Unknown tool: <span class="subst">$&#123;req.params.name&#125;</span>`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> &#123; city &#125; = <span class="title class_">GetWeatherArgs</span>.<span class="title function_">parse</span>(req.<span class="property">params</span>.<span class="property">arguments</span>);</span><br><span class="line">  <span class="comment">// 真实场景这里调 API，这里偷懒返回固定数据</span></span><br><span class="line">  <span class="keyword">return</span> &#123; <span class="attr">content</span>: [&#123; <span class="attr">type</span>: <span class="string">&quot;text&quot;</span>, <span class="attr">text</span>: <span class="string">`<span class="subst">$&#123;city&#125;</span> 当前 17℃，多云`</span> &#125;] &#125;;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> transport = <span class="keyword">new</span> <span class="title class_">StdioServerTransport</span>();</span><br><span class="line"><span class="keyword">await</span> server.<span class="title function_">connect</span>(transport);</span><br></pre></td></tr></table></figure></div><p>第一行 shebang 别忘。第一次我忘了加 <code>#!/usr/bin/env node</code>，Linux 下直接报 exec format error。</p><p><code>zod</code> 用来校验 tool 参数。Anthropic 官方文档里其实没强调 zod，但生产环境我强烈建议加——LLM 偶尔会传奇葩参数，没校验直接炸 runtime。</p><h2 id="在-Claude-Desktop-里挂上"><a href="#在-Claude-Desktop-里挂上" class="headerlink" title="在 Claude Desktop 里挂上"></a>在 Claude Desktop 里挂上</h2><p>config 文件位置我踩过坑——Windows 和 Mac 不一样：</p><ul><li>macOS: <code>~/Library/Application Support/Claude/claude_desktop_config.json</code></li><li>Windows: <code>%APPDATA%\Claude\claude_desktop_config.json</code></li></ul><p>内容长这样：</p><div class="code-container" data-rel="Json"><figure class="iseeu highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mcpServers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;weather&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;node&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;D:/code/weather-mcp/dist/index.js&quot;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div><p>注意两个坑：</p><ol><li>路径在 Windows 下最好用正斜杠，反斜杠要转义</li><li>如果你 server 依赖环境变量（比如 API key），要在这里用 <code>&quot;env&quot;: &#123;&quot;OPENWEATHER_KEY&quot;: &quot;xxx&quot;&#125;</code> 显式传，Claude Desktop 启动子进程是干净环境</li></ol><p>改完配置要<strong>完全退出</strong> Claude Desktop 再打开，不是关窗口。右下角托盘点退出，不然旧进程还抓着老配置。这点 Anthropic 官方文档也没写清楚，我当时以为配置没生效，反复改了 5 遍。</p><h2 id="调试：inspector-救命"><a href="#调试：inspector-救命" class="headerlink" title="调试：inspector 救命"></a>调试：inspector 救命</h2><p><code>@modelcontextprotocol/inspector</code> 是官方调试工具，装上：</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx @modelcontextprotocol/inspector node dist/index.js</span><br></pre></td></tr></table></figure></div><p>会弹出一个 web 界面，localhost:5173。可以手动触发 <code>initialize</code>、<code>tools/list</code>、<code>tools/call</code>，看到完整 JSON-RPC 请求响应。不用开 Claude Desktop 就能测。</p><p>我现在每次改完 server 先用 inspector 过一遍，确认正常再接 Claude Desktop。节省至少 70% 的调试时间。</p><p>常见 <code>Method not found</code> 错误原因排名：</p><ul><li>第一，你忘了在 server 构造函数里声明对应 capability（<code>&#123; capabilities: &#123; tools: &#123;&#125; &#125; &#125;</code> 这句少了 <code>tools</code> 就 GG）</li><li>第二，handler 注册漏了</li><li>第三，method 名拼错了（比如写成 <code>tool/call</code> 少个 s）</li></ul><h2 id="一个-Jira-MCP-的-token-优化故事"><a href="#一个-Jira-MCP-的-token-优化故事" class="headerlink" title="一个 Jira MCP 的 token 优化故事"></a>一个 Jira MCP 的 token 优化故事</h2><p>2025 年 10 月给一个 SaaS 客户搓过一个内部 Jira MCP server，8 个 tool：创建 issue、查询、改状态、加评论、分配、搜索、列 sprint、拉看板。</p><p>第一版直接出了。Claude 每次接 initialize 之后拿 tools&#x2F;list，返回的 JSON 压缩完 <strong>3247 token</strong>。客户跑了几天发现月底 token 账单涨了 40%，问我怎么回事。</p><p>我一看——tool description 写得像技术文档，每个 tool 都有 200+ 字。inputSchema 里字段的 description 也写得巨长，还带了例子。8 个 tool 加起来 3K token 每次对话起步就吃掉。</p><p>优化思路：</p><ol><li>tool description 砍到一句话，只讲”它做什么”，不讲”怎么用”、”什么场景”</li><li>inputSchema 里每个字段 description 控制在 15 字以内</li><li>非必要字段放到 “optional”，LLM 自己决定要不要用</li><li>把 8 个查询类 tool 合并成 1 个 <code>jira_search</code>，参数化查询条件</li></ol><p>改完 <strong>680 token</strong>。功能没少，客户月账单降回正常水平。</p><p>这个经验后来我写进了团队内部规范，也在 <a href="/posts/mcp-server-internal-tools/">MCP Server 内置工具设计</a> 那篇里展开讲过。另外如果是 Claude Code 用户，<a href="/posts/context-window-budget-strategy/">context window 预算策略</a> 那篇对这个话题也有补充。</p><h2 id="几个小建议"><a href="#几个小建议" class="headerlink" title="几个小建议"></a>几个小建议</h2><ul><li>本地开发用 <code>tsx watch src/index.ts</code>，改代码不用手 build</li><li>error 分两类：参数错误返回给 LLM 让它重试、系统错误直接 throw 让框架返回 isError</li><li>tool description 写”做什么”，不要写”怎么实现”——LLM 不关心</li><li>上生产前务必读一下 <a href="/posts/mcp-server-internal-tools/">MCP server 安全最佳实践</a> 相关章节</li></ul><div style="background:#f0f7ff;border-left:4px solid #0c97fe;padding:18px 22px;margin:28px 0;border-radius:6px;">跑通第一个 server 之后，推荐接着看 <a href="/posts/mcp-server-internal-tools/">MCP Server 工具粒度设计</a>（决定你 token 成本）和 <a href="/posts/claude-code-mcp-integration/">Claude Code 里挂 MCP</a>（桌面端和 CLI 端配置方式不同）。对 Python 栈有兴趣的话，下一篇会讲 Python 版实现。</div>]]>
    </content>
    <id>https://claude.cocoloop.cn/posts/mcp-server-build-typescript/</id>
    <link href="https://claude.cocoloop.cn/posts/mcp-server-build-typescript/"/>
    <published>2026-04-09T01:01:28.000Z</published>
    <summary>第一次搓 MCP server 我以为要学一堆新东西，结果发现官方 SDK 把脚手架搭得比 Express 还简单。这篇从 40 行代码的 weather server 开始，一路讲到 Claude Desktop 配置、inspector 调试、tool 描述从 3.2K token 压到 680 token 的优化经验。新手照着走能 30 分钟跑通。</summary>
    <title>用 TypeScript 从零搓一个 MCP server，比你想的简单</title>
    <updated>2026-04-16T06:01:28.000Z</updated>
  </entry>
</feed>
