<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>DoneHub</title>
  
  
  <link href="https://donehub.github.io/atom.xml" rel="self"/>
  
  <link href="https://donehub.github.io/"/>
  <updated>2026-07-12T16:30:06.144Z</updated>
  <id>https://donehub.github.io/</id>
  
  <author>
    <name>Zou Rongsheng</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>GPT-5.6 来了</title>
    <link href="https://donehub.github.io/2026/07/12/chatgpt-5.6-introduction/"/>
    <id>https://donehub.github.io/2026/07/12/chatgpt-5.6-introduction/</id>
    <published>2026-07-11T16:00:00.000Z</published>
    <updated>2026-07-12T16:30:06.144Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>2026 年 7 月 10 日，OpenAI 发布了 GPT-5.6。</p><p>这次发布的信息量很大，但如果只抓一个核心变化，那就是：<strong>大模型正在从聊天机器人变成能干活的智能体</strong>。过去你问它问题，它给你答案；现在你给它目标，它能自己规划步骤、调用工具、协调多个 Agent 并行工作，最终把任务完成。</p><p>方向对了还得看数字。GPT-5.6 在效率上也交出了非常亮眼的成绩单：以更少的 Token、更低的成本，在编程、知识型工作、网络安全和科学领域均取得了行业前沿水平。用官方的话说：<strong>树立了智能与效率的新标杆</strong>。</p><p>这篇文章基于 OpenAI 官方发布文档，从核心定位、多智能体架构、编程能力、设计能力、安全机制和开发者影响几个维度展开分析。</p><h2 id="一、核心定位：从对话到执行"><a href="#一、核心定位：从对话到执行" class="headerlink" title="一、核心定位：从对话到执行"></a>一、核心定位：从对话到执行</h2><h3 id="1-1-一个关键转变"><a href="#1-1-一个关键转变" class="headerlink" title="1.1 一个关键转变"></a>1.1 一个关键转变</h3><p>过去几年，大模型的发展路径很清晰：GPT-3 解决语言生成问题，GPT-4 解决复杂推理问题，GPT-5.6 开始解决<strong>任务执行问题</strong>。</p><div class="table-container"><table><thead><tr><th>阶段</th><th>代表模型</th><th>核心能力</th><th>关注点</th></tr></thead><tbody><tr><td><strong>语言生成</strong></td><td>GPT-3</td><td>给定上下文，预测最合理的后续内容</td><td>文本质量</td></tr><tr><td><strong>复杂推理</strong></td><td>GPT-4</td><td>在复杂信息中建立逻辑关系，找到可靠答案</td><td>推理准确性</td></tr><tr><td><strong>任务执行</strong></td><td>GPT-5.6</td><td>理解目标，在真实环境中持续采取行动，完成目标</td><td>任务完成率</td></tr></tbody></table></div><p>这三种能力不是替代关系，而是叠加关系。GPT-5.6 依然具备强大的语言生成和推理能力，但它更进一步——正如官方文档所说，它能够理解目标、规划步骤、调用工具并完成复杂任务。</p><h3 id="1-2-这意味着什么"><a href="#1-2-这意味着什么" class="headerlink" title="1.2 这意味着什么"></a>1.2 这意味着什么</h3><p>用一个具体例子来理解。假设你在线上遇到一个接口变慢的问题：</p><p><strong>传统模型</strong>会给你一套排查建议：查慢 SQL、检查 Redis 命中率、分析 JVM GC、看链路追踪……建议完全正确，但<strong>给建议不等于解决问题</strong>——你还是得自己去连监控、查日志、改代码、验证效果。</p><p><strong>GPT-5.6 代表的方向</strong>是：模型自己去连监控系统、查日志、分析 SQL、定位问题、修改代码、跑测试、验证效果。用官方的描述，它能够编写并运行轻量级程序，在工作开展过程中协调工具、处理中间结果、监控进度并自主选择下一步操作。当然，这是一个理想化的场景，实际效果取决于工具集成的完整度和权限配置。</p><p>用一个类比来说：传统模型像一个<strong>百科全书式的顾问</strong>——你问什么它都知道，但不动手；GPT-5.6 更像一个<strong>能干的同事</strong>——你给它一个任务，它会自己想办法完成。</p><h3 id="1-3-数据佐证"><a href="#1-3-数据佐证" class="headerlink" title="1.3 数据佐证"></a>1.3 数据佐证</h3><p>这不是概念层面的畅想，具体数据已经在多个 Benchmark 上得到了验证。</p><p><strong>Agents’ Last Exam</strong>——覆盖 55 个专业领域的长周期 Agent 工作流评估。GPT-5.6 Sol 在该评测中<strong>领先 Claude Fable 5（自适应推理）13.1 分</strong>，即便在中等推理强度下，也以约 <strong>1/4 的成本</strong>领先 Fable 5 达 11.4 分。</p><p><strong>OSWorld 2.0</strong>——衡量模型在真实计算机环境中的操作能力。Sol 得分 <strong>62.6%</strong>，超越 Claude Opus 4.8，同时<strong>输出 Token 用量减少 85%</strong>。</p><p><strong>BrowseComp</strong>——智能体网页浏览评估。Sol 得分 <strong>90.4%</strong>，ultra 模式下达到 <strong>92.2%</strong>。</p><p>这些数据的共同指向是：GPT-5.6 不只是更聪明了，而是<strong>能独立完成更长周期、更复杂的任务了</strong>。</p><p>需要说明的是，GPT-5.6 本身并不等同于 Agent。更准确地说，它提供了更强的 Agent 核心模型能力，而一个真正可靠的智能体系统仍然需要开发者构建工具层、状态管理层和执行框架——也就是 Agent = LLM + Tools + Memory + Runtime + Verification。GPT-5.6 解决的是其中 LLM 这一环的能力跃升，其余部分仍然是工程问题。</p><h2 id="二、多智能体：从单兵到协同"><a href="#二、多智能体：从单兵到协同" class="headerlink" title="二、多智能体：从单兵到协同"></a>二、多智能体：从单兵到协同</h2><h3 id="2-1-ultra-模式"><a href="#2-1-ultra-模式" class="headerlink" title="2.1 ultra 模式"></a>2.1 ultra 模式</h3><p>GPT-5.6 最值得关注的新能力之一是 <strong>ultra 模式下的多智能体并行</strong>。官方文档的描述是：</p><p>ultra 是我们最高性能的设置，能够跨多个并行工作流协调多个智能体，更快地完成复杂任务。</p><p>ultra 模式默认<strong>并行 4 个智能体</strong>，在 BrowseComp 和 SEC-Bench Pro 的测试中还展示了 <strong>16 个智能体</strong>的配置。</p><p>官方给出的数据结论是：增加并行智能体会将得分-延迟前沿向左上方推移——<strong>在更短时间内取得更优的结果</strong>。</p><h3 id="2-2-为什么这很重要"><a href="#2-2-为什么这很重要" class="headerlink" title="2.2 为什么这很重要"></a>2.2 为什么这很重要</h3><p>用一个类比来理解：过去的模型像一个超级聪明的实习生——能力很强，但一次只能干一件事，你得等它做完 A 才能让它做 B。多智能体模式更像是一个小型团队——一个项目经理负责协调，几个执行者分头干活，最后汇总结果。</p><p>对于复杂任务（比如分析最近一个月销售下降原因并生成优化方案），多个 Agent 可以分头查销售数据、查用户画像、分析市场趋势、查库存情况——然后汇总分析，生成报告。<strong>任务完成时间大幅缩短</strong>。</p><p>不过需要澄清一点：多智能体的核心价值不在于简单堆叠 Agent 数量——4 个 Agent 重复犯同样的错误并不会比 1 个 Agent 更好。真正的价值在于<strong>任务分解和角色分工</strong>：不同 Agent 负责不同子任务（比如 Research Agent 负责数据收集、Coding Agent 负责实现、Testing Agent 负责验证），配合共享记忆和验证机制，才能发挥协同效应。ultra 模式的意义也应该从这个角度理解。</p><h3 id="2-3-API-层面的支持"><a href="#2-3-API-层面的支持" class="headerlink" title="2.3 API 层面的支持"></a>2.3 API 层面的支持</h3><p>开发者可以通过 Responses API 的 <strong>multi-agent 功能</strong>（测试阶段）实现类似 ultra 的体验：在单个请求中并行运行多个子智能体，并整合它们的工作成果。</p><h2 id="三、模型体系：从单一到分层"><a href="#三、模型体系：从单一到分层" class="headerlink" title="三、模型体系：从单一到分层"></a>三、模型体系：从单一到分层</h2><h3 id="3-1-三个层级，按需调度"><a href="#3-1-三个层级，按需调度" class="headerlink" title="3.1 三个层级，按需调度"></a>3.1 三个层级，按需调度</h3><p>GPT-5.6 提供了三个模型层级：</p><div class="table-container"><table><thead><tr><th>模型</th><th>定位</th><th>输入价格（每百万 Token）</th><th>输出价格（每百万 Token）</th><th>一句话说明</th></tr></thead><tbody><tr><td><strong>Sol</strong></td><td>旗舰</td><td>$5</td><td>$30</td><td>最强能力，复杂任务首选</td></tr><tr><td><strong>Terra</strong></td><td>中端</td><td>$2.50</td><td>$15</td><td>性能≈GPT-5.5，成本更低</td></tr><tr><td><strong>Luna</strong></td><td>轻量</td><td>$1</td><td>$6</td><td>速度最快，性价比最高</td></tr></tbody></table></div><p>值得注意的是命名策略：<strong>数字标注代系（5.6），Sol/Terra/Luna 是持久的能力层级</strong>，可以按各自节奏独立演进（据官方文档描述）。从产品设计来看，OpenAI 正尝试建立一套长期的能力分层体系——如果这套策略持续下去，未来的 GPT-5.7 大概率仍然是 Sol/Terra/Luna 三档。</p><h3 id="3-2-效率才是关键"><a href="#3-2-效率才是关键" class="headerlink" title="3.2 效率才是关键"></a>3.2 效率才是关键</h3><p>不同任务对模型能力的要求差异巨大。所有任务都用最强模型，成本直接爆炸；都用轻量模型，复杂任务又搞不定。GPT-5.6 的效率数据很有说服力：</p><ul><li><strong>Luna</strong> 表现超越 Claude Fable 5，而成本仅为后者的约 <strong>1/16</strong>（据官方文档）</li><li><strong>Terra</strong> 以更低的成本超越了 GPT-5.5 的表现</li><li>在编程场景中，Sol 相比 Fable 5：<strong>输出 Token 减少一半以上，耗时缩短一半以上，成本降低约三分之一</strong></li><li>Prompt Cache 支持<strong>显式缓存断点（explicit cache breakpoint）</strong>，缓存有效期至少 <strong>30 分钟</strong>，缓存读取享受 90% 费率优惠</li></ul><p>不只是同样的钱做同样的事，而是<strong>同样的钱做更多的事</strong>。</p><h2 id="四、软件工程：从生成到协作"><a href="#四、软件工程：从生成到协作" class="headerlink" title="四、软件工程：从生成到协作"></a>四、软件工程：从生成到协作</h2><h3 id="4-1-为什么软件工程是最佳试验场"><a href="#4-1-为什么软件工程是最佳试验场" class="headerlink" title="4.1 为什么软件工程是最佳试验场"></a>4.1 为什么软件工程是最佳试验场</h3><p>软件工程天然具备几个适合 Agent 发挥的特点：</p><ul><li><strong>任务目标明确</strong>：修 Bug、实现功能、优化性能——每一类都有清晰的完成标准</li><li><strong>环境反馈丰富</strong>：编译结果、测试输出、运行日志都会给出明确信号</li><li><strong>过程可以验证</strong>：单元测试、集成测试、Code Review、线上指标，都是验证手段</li></ul><p>软件工程是那种做对了就是对了，做错了立刻能发现的领域——这恰好是 Agent 最需要的环境特征。</p><h3 id="4-2-软件-Agent-的三层核心能力"><a href="#4-2-软件-Agent-的三层核心能力" class="headerlink" title="4.2 软件 Agent 的三层核心能力"></a>4.2 软件 Agent 的三层核心能力</h3><p>从技术角度拆解，一个能参与软件工程的 Agent 需要具备三层能力：</p><p><strong>第一层：Repository Understanding（代码库理解）</strong>。不是把整个项目塞进上下文，而是能像工程师一样探索代码——Code Search 定位关键符号、Symbol Graph 分析调用关系、Dependency Analysis 理解模块依赖。GPT-5.6 在可编程工具调用上的突破（前文已述）正是为这一层提供了基础：模型可以自己写程序来检索和分析代码，而不需要每一步都依赖外部系统。</p><p><strong>第二层：Execution Feedback（执行反馈）</strong>。修改完代码后，Agent 需要能运行测试、查看编译结果、分析日志，根据真实反馈调整策略。Terminal Agent 的能力直接决定了这一层的上限——这也是 Terminal-Bench 成为重要评测的原因。</p><p><strong>第三层：Change Management（变更管理）</strong>。一次软件变更不只是改代码，还需要 Diff Review 确认修改范围、Rollback 机制应对失败、Human Approval 把控关键决策。这层能力目前更多依赖工程框架而非模型本身，但模型需要能理解和配合这些流程。</p><p>GPT-5.6 在这三层上都有提升，但提升幅度不同——第一层和第二层进步最明显（可编程工具调用 + Terminal Agent），第三层仍然需要开发者在框架层面解决。</p><h3 id="4-3-Benchmark-数据"><a href="#4-3-Benchmark-数据" class="headerlink" title="4.3 Benchmark 数据"></a>4.3 Benchmark 数据</h3><div class="table-container"><table><thead><tr><th>评测</th><th>GPT-5.6 Sol</th><th>Claude Fable 5</th><th>GPT-5.5</th></tr></thead><tbody><tr><td><strong>Coding Agent Index v1.1</strong></td><td><strong>80</strong>（新 SOTA）</td><td>77.2</td><td>76.4</td></tr><tr><td><strong>Terminal-Bench 2.1</strong></td><td>88.8%（Ultra <strong>91.9%</strong>）</td><td>83.1%</td><td>85.6%</td></tr><tr><td><strong>DeepSWE v1.1</strong></td><td>72.7%</td><td>69.7%</td><td>67%</td></tr><tr><td><strong>SWE-Bench Pro</strong></td><td>64.6%</td><td><strong>80%</strong></td><td>59.4%</td></tr></tbody></table></div><p>值得注意：在 SWE-Bench Pro 上，Fable 5 仍然领先（80% vs 64.6%），说明不同 Benchmark 上各家模型互有胜负。但在 <strong>Coding Agent Index、Terminal-Bench、DeepSWE</strong> 这三项更侧重完整工程能力的评测上，GPT-5.6 Sol 都拿到了第一。</p><p>更重要的是效率：在 Coding Agent Index 上，Sol 以 80 分创下新 SOTA，同时实现了前文所述的效率提升——Token、耗时、成本均大幅下降。不只是更强，还更便宜更快。</p><h3 id="4-4-可编程工具调用：一个关键的技术突破"><a href="#4-4-可编程工具调用：一个关键的技术突破" class="headerlink" title="4.4 可编程工具调用：一个关键的技术突破"></a>4.4 可编程工具调用：一个关键的技术突破</h3><p>GPT-5.6 在编程场景下的一个重要变化是：<strong>模型能够编写并运行轻量级程序，在工作过程中协调工具、处理中间结果、监控进度并自主选择下一步操作</strong>。</p><p>传统的 Tool Calling 模式是调工具 → 拿结果 → 再调工具 → 再拿结果，每一步都需要模型参与决策，消耗大量 Token 和交互次数。而 GPT-5.6 可以直接在内存中写程序来处理中间数据、过滤噪音、只保留关键信息，然后在运行过程中动态调整工作流。</p><p>官方文档的表述是：开发人员无需编写每一个步骤的脚本，也不必将工具的每个响应都回传给模型。Responses API 中的可编程工具调用功能让重度依赖工具的任务能够以更少的 Token、更少的模型交互次数顺利推进。</p><h3 id="4-5-Terminal-Agent：让模型看到运行结果"><a href="#4-5-Terminal-Agent：让模型看到运行结果" class="headerlink" title="4.5 Terminal Agent：让模型看到运行结果"></a>4.5 Terminal Agent：让模型看到运行结果</h3><p>过去代码助手主要工作在 IDE 内，模型无法观察代码运行后的真实结果。而软件开发是一个高度依赖反馈的过程——模型生成了代码，但只有运行之后才知道对不对。</p><p>通过 Terminal，模型可以执行 <code>git status</code>、<code>mvn test</code>、<code>npm run build</code>、<code>docker logs</code> 等命令，观察编译错误、测试失败、运行异常。这让 AI 从代码生成器变成了<strong>开发环境中的参与者</strong>。前文 Terminal-Bench 2.1 的成绩也直接反映了这方面的能力。</p><h3 id="4-6-真实用户反馈"><a href="#4-6-真实用户反馈" class="headerlink" title="4.6 真实用户反馈"></a>4.6 真实用户反馈</h3><p>Benchmark 是实验室数据，真实用户的反馈更值得关注：</p><p>GPT-5.6 帮助用户以比前代模型减少约 <strong>25% 的步骤</strong>和 <strong>35-48% 的工具调用量</strong>完成任务，同时将项目成功率提升了 <strong>15%</strong>，并减少了运行卡顿的情况。<br>—— Fabian Hedin，Lovable 联合创始人</p><p>GPT-5.6 是我们在 CursorBench 上测试过的能力最强的模型之一，在早期评估中表现稳健。在任务持久性、智能水平及整体效率方面的提升，对开发者而言是令人振奋的一步。<br>—— Oskar Schulz，Cursor 总裁</p><h2 id="五、能力拓展：从代码到知识"><a href="#五、能力拓展：从代码到知识" class="headerlink" title="五、能力拓展：从代码到知识"></a>五、能力拓展：从代码到知识</h2><h3 id="5-1-设计能力跃升"><a href="#5-1-设计能力跃升" class="headerlink" title="5.1 设计能力跃升"></a>5.1 设计能力跃升</h3><p>官方文档专门用了一个章节介绍 GPT-5.6 在设计方面的进步：</p><p>仅凭高层次指导，GPT-5.6 就能创建美观、符合人体工学且功能完善的界面。凭借更强的计算机使用能力，它不再仅仅局限于生成底层代码或内容，而是<strong>能够检查并优化渲染后的结果</strong>——从而在交付最终作品前，主动捕获视觉与功能问题，并进行细节修饰。</p><p>这个能力转变值得关注——模型从生成完就结束变成了能自己看渲染结果、发现问题、自己修复。官方展示了多个 demo，包括 3D 航海游戏、博物馆网站、室内设计演示文稿、交互式万花尺等，均是仅凭自然语言描述生成。</p><p>在 ChatGPT Work 中，GPT-5.6 还能将自然语言请求转化为精美的、具有互动性的解释与可视化呈现——这对前端开发和教育场景有直接价值。</p><h3 id="5-2-知识型工作"><a href="#5-2-知识型工作" class="headerlink" title="5.2 知识型工作"></a>5.2 知识型工作</h3><p>GPT-5.6 在知识型工作方面也有显著提升。据官方文档描述，它能从用户的文档以及 Slack、Notion、Microsoft 365 和 Google Drive 等日常工作流中提取信息，将其转化为专家级、可共享的成果。</p><p>具体能力包括：</p><ul><li><strong>演示文稿</strong>：从零开始制作完全可编辑的 PPT，能够推断参考文档的设计体系（排版、字体、间距、色彩），并一致地应用到新材料中</li><li><strong>文档与电子表格</strong>：更准确地遵循复杂参考格式，在处理方程式与财务模型时更为精细</li><li><strong>信息检索与整合</strong>：在 BrowseComp（智能体网页浏览）上取得了前文所述的成绩，反映了其从多源信息中提取和整合知识的能力</li></ul><h2 id="六、安全机制：从防护到受信"><a href="#六、安全机制：从防护到受信" class="headerlink" title="六、安全机制：从防护到受信"></a>六、安全机制：从防护到受信</h2><h3 id="6-1-Agent-安全：从内容过滤到权限控制"><a href="#6-1-Agent-安全：从内容过滤到权限控制" class="headerlink" title="6.1 Agent 安全：从内容过滤到权限控制"></a>6.1 Agent 安全：从内容过滤到权限控制</h3><p>传统模型的安全问题相对简单——输入危险内容，模型拒绝回答，核心是内容过滤。但 Agent 的安全问题本质上变了：<strong>模型不再只是回答问题，而是拥有了执行动作的能力</strong>。</p><p>这意味着安全的焦点从内容安全转向了<strong>权限控制</strong>。一个 Agent 可能拥有文件读写权限、数据库操作权限、代码执行权限、企业系统访问权限——真正需要防范的不是模型回答错误，而是模型<strong>在错误的时间、对错误的资源、执行了错误的操作</strong>。</p><p>所以 Agent Security 更接近传统软件安全中的 Identity + Authorization + Audit + Sandbox，而不只是输入输出过滤。理解了这个背景，再看 GPT-5.6 的安全设计就更有针对性。</p><h3 id="6-2-分层保护架构"><a href="#6-2-分层保护架构" class="headerlink" title="6.2 分层保护架构"></a>6.2 分层保护架构</h3><p>随着 Agent 能力增强（能自主执行任务、操作文件、运行代码），安全问题变得更加突出。据官方文档，GPT-5.6 采用了<strong>分层保护架构</strong>：</p><ul><li><strong>模型内置防护</strong>：在训练阶段嵌入安全约束</li><li><strong>推理监控器（Reasoning Monitor）</strong>：审查对话内容，判断是否存在潜在危害——不再只靠分类器标记，而是用推理能力来理解上下文</li><li><strong>实时校验 + 持续监控 + 账户级干预</strong>：多层冗余，确保单层失效时系统仍然安全</li></ul><h3 id="6-3-关键数据"><a href="#6-3-关键数据" class="headerlink" title="6.3 关键数据"></a>6.3 关键数据</h3><ul><li>正式推出前进行了约 <strong>70 万个 A100 GPU 小时</strong>的黑盒自动化红队测试</li><li>网络安全防护拦截量比前代模型<strong>增加约十倍</strong></li><li>在生物学与网络安全领域，能力均超越前代，但<strong>未跨越严重（Critical）风险阈值</strong></li><li>最敏感的网络安全能力仅通过 <strong>Trusted Access 计划</strong>向经过验证的用户开放</li><li>个人成员需在 9 月 1 日前使用<strong>基于硬件的通行密钥</strong>启用高级账户安全</li></ul><h3 id="6-4-一个值得思考的权衡"><a href="#6-4-一个值得思考的权衡" class="headerlink" title="6.4 一个值得思考的权衡"></a>6.4 一个值得思考的权衡</h3><p>官方文档提到了一个有意思的观点：<strong>过度拦截本身也有安全风险</strong>。如果防护机制太严，防御者无法测试系统和部署补丁，而攻击者却在继续使用其他模型和现有黑客工具。官方的表述是：</p><p>有效的防护机制应充分考量请求的具体语境与可能产生的后果；在保护合规防御工作的同时，当有证据表明存在严重危害风险时，则应实施更严格的控制。</p><h2 id="七、开发影响：从工具到系统"><a href="#七、开发影响：从工具到系统" class="headerlink" title="七、开发影响：从工具到系统"></a>七、开发影响：从工具到系统</h2><h3 id="7-1-AI-辅助研发正在成为常态"><a href="#7-1-AI-辅助研发正在成为常态" class="headerlink" title="7.1 AI 辅助研发正在成为常态"></a>7.1 AI 辅助研发正在成为常态</h3><p>OpenAI 在官方文档中披露了内部使用数据：</p><ul><li>每位活跃研究人员的日均 Token 输出是 GPT-5.5 时期的 <strong>2 倍以上</strong></li><li>用于内部代码推理的计算资源占比增长了 <strong>100 倍</strong></li><li>内部 Agent Token 使用量增加了约 <strong>22 倍</strong></li></ul><p>官方也坦言：这些使用情况指标本身并不能衡量研究进展，但它们表明 AI 辅助在研究以及销售、营销、用户运营、财务等其他团队中的应用正在快速增长。</p><h3 id="7-2-开发重点正在转移"><a href="#7-2-开发重点正在转移" class="headerlink" title="7.2 开发重点正在转移"></a>7.2 开发重点正在转移</h3><p>从行业趋势来看，一个明显的变化是：开发者对怎么写 Prompt 的关注正在让位于怎么组织 Agent 的上下文和工具链。GPT-5.6 的产品设计也体现了这一点——可编程工具调用、multi-agent API、显式缓存断点、30 分钟缓存 TTL——这些都是在帮开发者解决<strong>工程层面的问题</strong>，而不只是提示词层面的问题。</p><p>（注：业界将这一趋势概括为从 Prompt Engineering 到 <strong>Context Engineering</strong> 的转变。这并非 OpenAI 官方术语，而是一个被广泛接受的行业观察。）</p><h3 id="7-3-AI-应用越来越像传统软件系统"><a href="#7-3-AI-应用越来越像传统软件系统" class="headerlink" title="7.3 AI 应用越来越像传统软件系统"></a>7.3 AI 应用越来越像传统软件系统</h3><p>早期 AI 应用可能只需要一个前端加一个 LLM API 调用。未来需要 Agent 层、Workflow 编排、Memory 管理、Tools 集成、监控体系……AI 应用开发会越来越接近后端系统开发。</p><p>RAG 不会消失，但定位会变化——从独立的 AI 应用范式变成 Agent 系统中的一个知识组件。MCP（Model Context Protocol）等协议的重要性也在提升——Agent 需要连接大量外部系统，统一工具协议是降本增效的关键。</p><h2 id="八、现实局限：从突破到边界"><a href="#八、现实局限：从突破到边界" class="headerlink" title="八、现实局限：从突破到边界"></a>八、现实局限：从突破到边界</h2><p>虽然 GPT-5.6 代表了 Agent 方向的重要进展，但距离完全自主工作的 AI 还有明显差距：</p><p><strong>可靠性</strong>：模型仍可能理解错误目标、做出错误决策、产生幻觉。在 SWE-Bench Pro 上，Fable 5 仍然以 80% 领先 Sol 的 64.6%——说明在纯代码理解和修复能力上，还有提升空间。</p><p><strong>成本</strong>：Sol 的输出价格 $30/M tokens 并不算便宜。复杂 Agent 需要多次模型调用，综合成本仍然不低。对于高频调用场景，Terra 和 Luna 是更务实的选择。</p><p><strong>抽象推理</strong>：Sol 在 ARC-AGI-3 上只拿到 <strong>7.78%</strong>（据官方数据）——距离通用人工智能还有很长的路。</p><p><strong>安全最佳实践缺失</strong>：让 Agent 自主执行任务、操作生产系统，安全边界如何划定？OpenAI 的分层防护方案是一个起点，但行业整体还没有成熟的标准。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>GPT-5.6 的意义不在于它是不是每个 Benchmark 上的第一名，而在于它代表了大模型应用方向的一次根本转变：</p><ul><li><strong>从聊天机器人到智能体</strong>——不再只是你说它答，而是你给目标，它完成任务</li><li><strong>从单智能体到多智能体</strong>——ultra 模式并行 4-16 个 Agent，从一个超级实习生变成一个小型团队</li><li><strong>从更贵到更高效</strong>——同样的钱做更多的事，Sol 编程场景成本降低 1/3、速度提升一倍</li><li><strong>从生成内容到交付成果</strong>——能自我检查、自我修复，直接交付可用的结果</li></ul><p>对于开发者来说，未来竞争的重点不会只是调用更强的模型，而是<strong>如何围绕模型构建可靠、高效、可维护的 AI 系统</strong>。GPT-5.6 是这个趋势中的一个重要节点，但真正决定 AI 应用价值的，仍然是系统工程能力。</p><p><strong>GPT-5.6 的真正意义，不是让模型替代软件工程，而是让软件工程开始具备智能执行层。未来的软件系统，将不再只是由代码定义行为，而是由代码、模型和环境反馈共同驱动。</strong></p><hr><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://openai.com/index/gpt-5-6/" target="_blank" rel="noopener">GPT-5.6: Frontier intelligence that scales with your ambition | OpenAI</a></li><li><a href="https://openai.com/index/previewing-gpt-5-6-sol/" target="_blank" rel="noopener">Previewing GPT-5.6 Sol: a next-generation model | OpenAI</a></li><li><a href="https://deploymentsafety.openai.com/gpt-5-6-preview" target="_blank" rel="noopener">GPT-5.6 Preview System Card</a></li><li><a href="https://help.openai.com/en/articles/20001325-a-preview-of-gpt-56-sol-terra-and-luna" target="_blank" rel="noopener">GPT-5.6 in ChatGPT – OpenAI Help Center</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;2026 年 7 月 10 日，OpenAI 发布了 GPT-5.6。&lt;/p&gt;
&lt;p&gt;这次发布的信息量很大，但如果只抓一个核心变化，那就是：</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="ChatGPT" scheme="https://donehub.github.io/tags/ChatGPT/"/>
    
  </entry>
  
  <entry>
    <title>JetCache 使用手册</title>
    <link href="https://donehub.github.io/2026/06/17/JetCache-%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/"/>
    <id>https://donehub.github.io/2026/06/17/JetCache-%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/</id>
    <published>2026-06-16T16:00:00.000Z</published>
    <updated>2026-07-09T07:06:13.306Z</updated>
    
    <content type="html"><![CDATA[<h1 id="一、组件介绍"><a href="#一、组件介绍" class="headerlink" title="一、组件介绍"></a>一、组件介绍</h1><p>JetCache 是阿里巴巴开源的通用缓存访问框架（<a href="https://github.com/alibaba/jetcache" target="_blank" rel="noopener">开源地址</a>），它做了一件事：用统一的 <code>Cache&lt;K, V&gt;</code>接口，把本地内存缓存和远程 Redis 缓存无缝组合起来，再通过注解、API 两种方式定义出标准的缓存协议接入层，让业务代码以最简洁的方式使用缓存。</p><p>和 Spring Cache 比，JetCache 的核心优势：</p><div class="table-container"><table><thead><tr><th>能力</th><th>Spring Cache</th><th>JetCache</th></tr></thead><tbody><tr><td>TTL（超时时间）</td><td>不原生支持，需自定义</td><td><strong>原生支持</strong>，注解上直接写 <code>expire</code></td></tr><tr><td>两级缓存</td><td>不支持</td><td><strong>原生支持</strong> <code>CacheType.BOTH</code>（本地 + 远程）</td></tr><tr><td>缓存自动刷新</td><td>不支持</td><td><strong>支持</strong> <code>@CacheRefresh</code>，分布式全局唯一刷新</td></tr><tr><td>穿透保护</td><td>不支持</td><td><strong>支持</strong> <code>@CachePenetrationProtect</code></td></tr><tr><td>分布式锁</td><td>不支持</td><td><strong>内置</strong> <code>tryLock</code> / <code>tryLockAndRun</code></td></tr><tr><td>异步 API</td><td>不支持</td><td><strong>支持</strong>（Lettuce 客户端下真正非阻塞）</td></tr><tr><td>统计监控</td><td>需第三方</td><td><strong>内置</strong> 命中率、加载次数等统计</td></tr><tr><td>更新/删除缓存注解</td><td>有但功能弱</td><td><code>@CacheUpdate</code> / <code>@CacheInvalidate</code> 支持 SpEL</td></tr></tbody></table></div><p><img data-src="/img/jetcache-final.png" alt="alt text"></p><h1 id="二、核心概念"><a href="#二、核心概念" class="headerlink" title="二、核心概念"></a>二、核心概念</h1><h2 id="2-1-Cache-接口"><a href="#2-1-Cache-接口" class="headerlink" title="2.1 Cache 接口"></a>2.1 Cache<K, V> 接口</h2><p>不管你底层用的是 Caffeine（本地内存）、Redis（远程）还是两级缓存组合，业务代码面对的都是同一个接口：</p><figure class="highlight java"><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">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Cache</span>&lt;<span class="title">K</span>, <span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    <span class="function">V <span class="title">get</span><span class="params">(K key)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">put</span><span class="params">(K key, V value)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">boolean</span> <span class="title">remove</span><span class="params">(K key)</span></span>;</span><br><span class="line">    <span class="function">V <span class="title">computeIfAbsent</span><span class="params">(K key, Function&lt;K, V&gt; loader)</span></span>;</span><br><span class="line">    <span class="comment">// ... 更多方法</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用起来就像一个 <code>Map</code>，非常直观。</p><h2 id="2-2-CacheType-—-缓存类型"><a href="#2-2-CacheType-—-缓存类型" class="headerlink" title="2.2 CacheType — 缓存类型"></a>2.2 CacheType — 缓存类型</h2><div class="table-container"><table><thead><tr><th>类型</th><th>含义</th><th>适用场景</th><th>备注</th></tr></thead><tbody><tr><td><code>CacheType.LOCAL</code></td><td>纯本地内存缓存<br>（Caffeine 或 LinkedHashMap）</td><td>字典数据、配置项等变化少的数据</td><td>目前没有使用本地缓存的诉求<br></td></tr><tr><td><code>CacheType.REMOTE</code></td><td>纯远程缓存（Redis）</td><td>一般业务场景，数据统一存 Redis</td><td>我们目前的使用场景<br></td></tr><tr><td><code>CacheType.BOTH</code></td><td>两级缓存：本地 + 远程</td><td>高频读取，本地扛量 + Redis 兜底</td><td>目前没有使用多级缓存的诉求</td></tr></tbody></table></div><h2 id="2-3-Key-的生成规则"><a href="#2-3-Key-的生成规则" class="headerlink" title="2.3 Key 的生成规则"></a>2.3 Key 的生成规则</h2><p>最终在 Redis 里存的 key 格式是：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Redis key = keyPrefix + keyConvertor(K)；</span><br></pre></td></tr></table></figure><ul><li><p><strong>keyPrefix</strong>：来自 <code>@Cached(name = &quot;toc:user:info:&quot;)</code> 中的 <code>name</code>，或 <code>QuickConfig.newBuilder(&quot;toc:user:info:&quot;)</code> 中的参数。它的作用就是给不同业务的缓存加个”门牌号”，避免 key 冲突。</p></li><li><p><strong>keyConvertor</strong>：把 Java 对象转成 String。默认用 <code>fastjson2</code>，对 String 类型的 key 直接透传，对复杂对象做 JSON 序列化。</p></li></ul><p>举个例子：<code>@Cached(name = &quot;toc:user:info:&quot;, key = &quot;#userId&quot;)</code> ，userId = 12345，最终 Redis 里的 key 就是 <code>toc:user:info:12345</code>。</p><h2 id="2-4-Area-—-缓存区域"><a href="#2-4-Area-—-缓存区域" class="headerlink" title="2.4 Area — 缓存区域"></a>2.4 Area — 缓存区域</h2><p>Area 是 JetCache 的多租户机制。默认有一个 <code>&quot;default&quot;</code> area，对应配置里的 <code>jetcache.local.default</code> 和 <code>jetcache.remote.default</code>。如果你的项目需要连多个 Redis 实例，可以配多个 area，然后在注解里通过 <code>area = &quot;otherArea&quot;</code> 指定。大多数场景用默认的就行。</p><hr><h1 id="三、快速接入（Spring-Boot）"><a href="#三、快速接入（Spring-Boot）" class="headerlink" title="三、快速接入（Spring Boot）"></a>三、快速接入（Spring Boot）</h1><h2 id="第一步：添加-Maven-依赖"><a href="#第一步：添加-Maven-依赖" class="headerlink" title="第一步：添加 Maven 依赖"></a>第一步：添加 Maven 依赖</h2><p>根据业务应用的 Redis 客户端，选择对应的 starter（<strong>三选一</strong>）：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 方式一：Lettuce（推荐，支持异步 API） --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alicp.jetcache<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jetcache-starter-redis-lettuce<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.8.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 方式二：Jedis（经典选择） --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alicp.jetcache<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jetcache-starter-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.8.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 方式三：Redisson（功能丰富） --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alicp.jetcache<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jetcache-starter-redisson<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.8.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>版本说明</strong>：JetCache 2.8+ 需要 <strong>JDK 17+</strong>、<strong>Spring Boot 3.x+</strong>、<strong>Spring Framework 6.x+</strong>。如果你的项目还在 JDK 8，请用 2.7.x 版本。</p><h2 id="第二步：配置-application-yml"><a href="#第二步：配置-application-yml" class="headerlink" title="第二步：配置 application.yml"></a>第二步：配置 application.yml</h2><figure class="highlight yaml"><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="attr">jetcache:</span></span><br><span class="line">  <span class="comment"># 统计间隔（分钟），0 表示不统计，生产建议 15</span></span><br><span class="line">  <span class="attr">statIntervalMinutes:</span> <span class="number">15</span></span><br><span class="line">  <span class="comment"># key 前缀是否包含 areaName，新项目建议 false</span></span><br><span class="line">  <span class="attr">areaInCacheName:</span> <span class="literal">false</span></span><br><span class="line">  <span class="comment"># 反序列化白名单（2.8+ 必须配置）</span></span><br><span class="line">  <span class="attr">decodeFilterAllowPatterns:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">com.remotecarter.</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># 本地缓存配置</span></span><br><span class="line">  <span class="attr">local:</span></span><br><span class="line">    <span class="attr">default:</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">caffeine</span>                        <span class="comment"># 推荐 caffeine，也可用 linkedhashmap</span></span><br><span class="line">      <span class="attr">limit:</span> <span class="number">100</span>                            <span class="comment"># 每个缓存实例最大元素数</span></span><br><span class="line">      <span class="attr">keyConvertor:</span> <span class="string">fastjson2</span>               <span class="comment"># key 转换方式</span></span><br><span class="line">      <span class="attr">expireAfterWriteInMillis:</span> <span class="number">60000</span>       <span class="comment"># 本地缓存默认超时（毫秒）</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># 远程缓存配置</span></span><br><span class="line">  <span class="attr">remote:</span></span><br><span class="line">    <span class="attr">default:</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">redis.redisson</span>                   <span class="comment"># redis / redis.lettuce / redis.redisson</span></span><br><span class="line">      <span class="attr">keyConvertor:</span> <span class="string">fastjson2</span></span><br><span class="line">      <span class="comment"># 广播 channel，用于两级缓存跨节点同步失效</span></span><br><span class="line">      <span class="comment"># 多个服务共用 Redis 时，不同服务用不同 channel，避免广播风暴</span></span><br><span class="line">      <span class="attr">broadcastChannel:</span> <span class="string">crm-user</span></span><br><span class="line">      <span class="attr">valueEncoder:</span> <span class="string">java</span>                    <span class="comment"># 序列化：java / kryo / kryo5</span></span><br><span class="line">      <span class="attr">valueDecoder:</span> <span class="string">java</span></span><br><span class="line">      <span class="attr">poolConfig:</span></span><br><span class="line">        <span class="attr">minIdle:</span> <span class="number">5</span></span><br><span class="line">        <span class="attr">maxIdle:</span> <span class="number">20</span></span><br><span class="line">        <span class="attr">maxTotal:</span> <span class="number">50</span></span><br><span class="line">      <span class="attr">host:</span> <span class="string">$&#123;REDIS_HOST:127.0.0.1&#125;</span></span><br><span class="line">      <span class="attr">port:</span> <span class="string">$&#123;REDIS_PORT:6379&#125;</span></span><br><span class="line">      <span class="comment"># 如果用 lettuce，也可以用 uri 方式</span></span><br><span class="line">      <span class="comment"># uri: redis://127.0.0.1:6379/0</span></span><br></pre></td></tr></table></figure><h2 id="第三步：启动类加注解"><a href="#第三步：启动类加注解" class="headerlink" title="第三步：启动类加注解"></a>第三步：启动类加注解</h2><figure class="highlight java"><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="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableMethodCache</span>(basePackages = <span class="string">"com.remotecarter"</span>)<span class="comment">// 激活 @Cached 等注解</span></span><br><span class="line"><span class="meta">@EnableCreateCacheAnnotation</span>                      <span class="comment">// 激活 @CreateCache 注解(2.7+ Deprecated可以不加）</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyApplication</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(MyApplication<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><strong><code>@EnableMethodCache(basePackages = &quot;...&quot;)</code></strong>：告诉 JetCache 去扫描哪些包下的 Spring Bean，对其中的 <code>@Cached</code>、<code>@CacheUpdate</code>、<code>@CacheInvalidate</code> 等注解做 AOP 代理。<strong>basePackages 要覆盖你所有用了缓存注解的包</strong>。</p></li><li><p><strong><code>@EnableCreateCacheAnnotation</code></strong>：激活 <code>@CreateCache</code> 注解支持，用于在字段上直接注入 Cache 实例，源码标记 Deprecated 了，可以不加。</p></li></ul><p>至此，接入完成。下面开始介绍怎么用。</p><h1 id="四、使用介绍"><a href="#四、使用介绍" class="headerlink" title="四、使用介绍"></a>四、使用介绍</h1><h2 id="4-1-注解驱动缓存（声明式）"><a href="#4-1-注解驱动缓存（声明式）" class="headerlink" title="4.1 注解驱动缓存（声明式）"></a>4.1 注解驱动缓存（声明式）</h2><p>这是最常用的方式。在 Service 接口（或实现类）的方法上加注解，JetCache 通过 Spring AOP 代理自动处理缓存的读、写、删。</p><p><strong>注意</strong>：注解可以加在接口方法上，也可以加在类方法上，但被注解的类必须是 <strong>Spring Bean</strong>。</p><h3 id="Cached-—-缓存读取"><a href="#Cached-—-缓存读取" class="headerlink" title="@Cached — 缓存读取"></a>@Cached — 缓存读取</h3><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 指定 name 和 key</span></span><br><span class="line">    <span class="meta">@Cached</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"#uuid"</span>, expire = <span class="number">3600</span>, cacheType = CacheType.REMOTE)</span><br><span class="line">    <span class="function">User <span class="title">getUserById</span><span class="params">(String uuid)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 缓存 null 值（防止缓存穿透）</span></span><br><span class="line">    <span class="meta">@Cached</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"#uuid"</span>, expire = <span class="number">300</span>, cacheNullValue = <span class="keyword">true</span>)</span><br><span class="line">    <span class="function">User <span class="title">getUserById</span><span class="params">(String uuid)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Cached-—-属性详解"><a href="#Cached-—-属性详解" class="headerlink" title="@Cached — 属性详解"></a>@Cached — 属性详解</h3><div class="table-container"><table><thead><tr><th>属性</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>area</code></td><td><code>&quot;default&quot;</code></td><td>缓存区域，一般不用改</td></tr><tr><td><code>name</code></td><td>自动生成（类名.方法名）</td><td>缓存唯一名称，<strong>会作为 Redis key 的前缀</strong></td></tr><tr><td><code>key</code></td><td>自动生成（根据所有参数）</td><td>SpEL 表达式指定 key，如 <code>&quot;#userId&quot;</code> 或 <code>&quot;args[0]&quot;</code></td></tr><tr><td><code>expire</code></td><td>跟随全局配置</td><td>超时时间</td></tr><tr><td><code>timeUnit</code></td><td><code>TimeUnit.SECONDS</code></td><td>expire 的时间单位</td></tr><tr><td><code>cacheType</code></td><td><code>CacheType.REMOTE</code></td><td>LOCAL / REMOTE / BOTH</td></tr><tr><td><code>localLimit</code></td><td>100</td><td>本地缓存最大元素数（LOCAL/BOTH 时生效）</td></tr><tr><td><code>localExpire</code></td><td>同 expire</td><td>本地缓存单独的超时时间（仅 BOTH 时生效）</td></tr><tr><td><code>syncLocal</code></td><td>false</td><td>更新时广播失效其他 JVM 的本地缓存（仅 BOTH 时生效）</td></tr><tr><td><code>serialPolicy</code></td><td><code>java</code></td><td>序列化方式：<code>SerialPolicy.JAVA</code> 或 <code>SerialPolicy.KRYO</code></td></tr><tr><td><code>keyConvertor</code></td><td><code>fastjson2</code></td><td>key 转换方式</td></tr><tr><td><code>enabled</code></td><td>true</td><td>是否启用缓存，false 时不走缓存，可通过 <code>CacheContext.enableCache</code> 临时激活</td></tr><tr><td><code>cacheNullValue</code></td><td>false</td><td>方法返回 null 时是否缓存</td></tr><tr><td><code>condition</code></td><td>无</td><td>SpEL 表达式，返回 true 才查缓存（方法执行前评估）</td></tr><tr><td><code>postCondition</code></td><td>无</td><td>SpEL 表达式，返回 true 才更新缓存（方法执行后评估，可用 <code>#result</code>）</td></tr></tbody></table></div><h3 id="CacheUpdate-—-更新缓存"><a href="#CacheUpdate-—-更新缓存" class="headerlink" title="@CacheUpdate — 更新缓存"></a>@CacheUpdate — 更新缓存</h3><p>当数据被修改时，用这个注解直接更新缓存，避免等 TTL 过期：</p><figure class="highlight java"><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"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Cached</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"#uuid"</span>, expire = <span class="number">3600</span>)</span><br><span class="line">    <span class="function">User <span class="title">getUserById</span><span class="params">(String uuid)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新缓存：key 和 name 必须和 @Cached 对应</span></span><br><span class="line">    <span class="meta">@CacheUpdate</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"#user.uuid"</span>, value = <span class="string">"#user"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">updateUser</span><span class="params">(User user)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="CacheInvalidate-—-删除缓存"><a href="#CacheInvalidate-—-删除缓存" class="headerlink" title="@CacheInvalidate — 删除缓存"></a>@CacheInvalidate — 删除缓存</h3><p>数据被删除时，从缓存中也移除：</p><figure class="highlight java"><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="meta">@CacheInvalidate</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"#uuid"</span>)</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">deleteUser</span><span class="params">(String uuid)</span></span>;</span><br></pre></td></tr></table></figure><p><strong>@CacheUpdate 和 @CacheInvalidate 的共同注意点</strong>：它们的 <code>name</code> 和 <code>area</code> 必须和对应的 <code>@Cached</code> 完全一致，这样 JetCache 才知道操作的是哪个缓存。</p><h3 id="CacheRefresh-—-自动刷新"><a href="#CacheRefresh-—-自动刷新" class="headerlink" title="@CacheRefresh — 自动刷新"></a>@CacheRefresh — 自动刷新</h3><p>这是 JetCache 的特色功能之一。对于加载开销大、实时性要求不高的数据（比如报表汇总），配置自动刷新，<strong>防止缓存过期瞬间的并发请求打爆数据库（缓存雪崩）</strong>：</p><figure class="highlight java"><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="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">SummaryService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Cached</span>(expire = <span class="number">3600</span>, cacheType = CacheType.REMOTE)</span><br><span class="line">    <span class="meta">@CacheRefresh</span>(refresh = <span class="number">1800</span>, stopRefreshAfterLastAccess = <span class="number">3600</span>, timeUnit = TimeUnit.SECONDS)</span><br><span class="line">    <span class="function">BigDecimal <span class="title">salesVolumeSummary</span><span class="params">(<span class="keyword">int</span> timeId, <span class="keyword">long</span> categoryId)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th>属性</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>refresh</code></td><td>无</td><td>刷新间隔</td></tr><tr><td><code>timeUnit</code></td><td><code>TimeUnit.SECONDS</code></td><td>时间单位</td></tr><tr><td><code>stopRefreshAfterLastAccess</code></td><td>无（一直刷新）</td><td>该 key 多久没访问就停止刷新</td></tr><tr><td><code>refreshLockTimeout</code></td><td>60 秒</td><td>刷新时在 Redis 放的分布式锁超时时间</td></tr></tbody></table></div><p><strong>关键特性</strong>：当 <code>cacheType</code> 为 REMOTE 或 BOTH 时，<strong>刷新行为是集群全局唯一的</strong>——不管有多少台服务器，同时只有一个节点在刷新某个 key，通过分布式锁实现。</p><h3 id="CachePenetrationProtect-—-穿透保护"><a href="#CachePenetrationProtect-—-穿透保护" class="headerlink" title="@CachePenetrationProtect — 穿透保护"></a>@CachePenetrationProtect — 穿透保护</h3><figure class="highlight java"><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="meta">@Cached</span>(expire = <span class="number">3600</span>, cacheType = CacheType.REMOTE)</span><br><span class="line"><span class="meta">@CachePenetrationProtect</span></span><br><span class="line"><span class="function">User <span class="title">getUserById</span><span class="params">(<span class="keyword">long</span> userId)</span></span>;</span><br></pre></td></tr></table></figure><p>当缓存未命中时，<strong>同一个 JVM 内同一个 key 只有一个线程去加载</strong>，其他线程等待结果。防止高并发场景下大量请求同时穿透到数据库。</p><p>当前实现是 <strong>单机的保护</strong>，不是分布式级别的。如果多个节点同时遇到同一个 key 的缓存未命中，各节点会各自加载一次。</p><p><strong>我们可以这样组合：自动刷新 + 穿透保护</strong></p><figure class="highlight java"><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="meta">@Cached</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"#uuid"</span>, expire = <span class="number">3600</span>)</span><br><span class="line"><span class="meta">@CacheRefresh</span>(refresh = <span class="number">1800</span>, stopRefreshAfterLastAccess = <span class="number">3600</span>, timeUnit = TimeUnit.SECONDS)</span><br><span class="line"><span class="meta">@CachePenetrationProtect</span></span><br><span class="line"><span class="function">User <span class="title">getUserById</span><span class="params">(String uuid)</span></span>;</span><br></pre></td></tr></table></figure><p>每 30 分钟自动刷新（集群唯一），30 分钟没人访问就停止刷新，万一缓存未命中还有穿透保护。</p><h2 id="4-2-编程式缓存（Cache-API）"><a href="#4-2-编程式缓存（Cache-API）" class="headerlink" title="4.2 编程式缓存（Cache API）"></a>4.2 编程式缓存（Cache API）</h2><p>注解方式虽然简洁，但灵活性有限——比如你需要在运行时动态决定 key，或者想在非 Spring 管理的类中使用缓存。这时候就用 <strong>Cache API</strong>。</p><h3 id="CacheManager-QuickConfig-创建缓存实例"><a href="#CacheManager-QuickConfig-创建缓存实例" class="headerlink" title="CacheManager + QuickConfig 创建缓存实例"></a>CacheManager + QuickConfig 创建缓存实例</h3><figure class="highlight java"><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="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">OrderService</span> <span class="keyword">implements</span> <span class="title">InitializingBean</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CacheManager cacheManager;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Cache&lt;String, OrderDO&gt; orderCache;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">afterPropertiesSet</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        QuickConfig qc = QuickConfig.newBuilder(<span class="string">"userCache"</span>)</span><br><span class="line">            .expire(Duration.ofSeconds(<span class="number">300</span>))</span><br><span class="line">            .cacheType(CacheType.BOTH)    <span class="comment">// 两级缓存</span></span><br><span class="line">            .syncLocal(<span class="keyword">true</span>)              <span class="comment">// 更新时广播失效其他节点本地缓存</span></span><br><span class="line">            .localLimit(<span class="number">200</span>)              <span class="comment">// 本地缓存最大元素数</span></span><br><span class="line">            .build();</span><br><span class="line">        orderCache = cacheManager.getOrCreateCache(qc);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>QuickConfig</code> 支持的配置项：</p><div class="table-container"><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>expire(Duration)</code></td><td>超时时间</td></tr><tr><td><code>localExpire(Duration)</code></td><td>本地缓存单独超时（BOTH 时）</td></tr><tr><td><code>localLimit(Integer)</code></td><td>本地缓存最大元素数</td></tr><tr><td><code>cacheType(CacheType)</code></td><td>LOCAL / REMOTE / BOTH</td></tr><tr><td><code>syncLocal(Boolean)</code></td><td>是否跨节点同步失效本地缓存</td></tr><tr><td><code>keyConvertor(Function)</code></td><td>key 转换器</td></tr><tr><td><code>valueEncoder / valueDecoder</code></td><td>序列化/反序列化</td></tr><tr><td><code>cacheNullValue(Boolean)</code></td><td>是否缓存 null</td></tr><tr><td><code>penetrationProtect(Boolean)</code></td><td>是否开启穿透保护</td></tr><tr><td><code>penetrationProtectTimeout(Duration)</code></td><td>穿透保护超时时间</td></tr><tr><td><code>refreshPolicy(RefreshPolicy)</code></td><td>自动刷新策略</td></tr><tr><td><code>loader(CacheLoader)</code></td><td>缓存未命中时的加载函数</td></tr></tbody></table></div><h3 id="基本操作"><a href="#基本操作" class="headerlink" title="基本操作"></a>基本操作</h3><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 读取</span></span><br><span class="line">UserDO user = userCache.get(<span class="string">"toc:user:info:12345"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写入</span></span><br><span class="line">userCache.put(<span class="string">"toc:user:info:12345"</span>, user);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写入并指定超时</span></span><br><span class="line">userCache.put(<span class="string">"toc:user:info:12345"</span>, user, <span class="number">10</span>, TimeUnit.MINUTES);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 删除</span></span><br><span class="line">userCache.remove(<span class="string">"toc:user:info:12345"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 批量读取</span></span><br><span class="line">Map&lt;String, UserDO&gt; users = userCache.getAll(Set.of(<span class="string">"toc:user:info:1"</span>, <span class="string">"toc:user:info:2"</span>, <span class="string">"toc:user:info:3"</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 批量写入</span></span><br><span class="line">userCache.putAll(Map.of(<span class="string">"toc:user:info:1"</span>, o1, <span class="string">"toc:user:info:2"</span>, o2));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 批量删除</span></span><br><span class="line">userCache.removeAll(Set.of(<span class="string">"toc:user:info:1"</span>, <span class="string">"toc:user:info:2"</span>));</span><br></pre></td></tr></table></figure><h3 id="computeIfAbsent-—-缓存未命中时自动加载"><a href="#computeIfAbsent-—-缓存未命中时自动加载" class="headerlink" title="computeIfAbsent — 缓存未命中时自动加载"></a>computeIfAbsent — 缓存未命中时自动加载</h3><p>这个方法非常实用，相当于 <code>get</code> + <code>put</code> 的原子操作：</p><figure class="highlight java"><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="comment">// 缓存命中直接返回，未命中则调用 loader 加载并写入缓存</span></span><br><span class="line">OrderDO order = userCache.computeIfAbsent(<span class="string">"toc:user:info:12345"</span>, key -&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> userMapper.selectById(key);  <span class="comment">// 从数据库加载</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>也可以在创建缓存时就设置好 loader，这样每次 <code>get</code> 都会自动加载：</p><figure class="highlight java"><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"><span class="comment">// 创建时设置 loader</span></span><br><span class="line">QuickConfig qc = QuickConfig.newBuilder(<span class="string">"userCache"</span>)</span><br><span class="line">    .expire(Duration.ofSeconds(<span class="number">300</span>))</span><br><span class="line">    .loader(key -&gt; userMapper.selectById(key))</span><br><span class="line">    .build();</span><br><span class="line">userCache = cacheManager.getOrCreateCache(qc);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 之后直接 get 就行，未命中会自动调 loader</span></span><br><span class="line">UserDO user = userCache.get(<span class="string">"toc:user:info:12345"</span>);</span><br></pre></td></tr></table></figure><h3 id="大写-API-—-带完整状态码的操作"><a href="#大写-API-—-带完整状态码的操作" class="headerlink" title="大写 API — 带完整状态码的操作"></a>大写 API — 带完整状态码的操作</h3><p>小写的 <code>get()</code> 返回 null 时，你分不清是”缓存中没有”还是”缓存出错了”。大写 API 返回 <code>CacheGetResult</code>，提供了完整的状态信息：</p><figure class="highlight java"><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">CacheGetResult&lt;UserDO&gt; r = userCache.GET(<span class="string">"toc:user:info:12345"</span>);</span><br><span class="line"><span class="keyword">if</span> (r.isSuccess()) &#123;</span><br><span class="line">    UserDO user = r.getValue();</span><br><span class="line">    <span class="comment">// 处理业务</span></span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (r.getResultCode() == CacheResultCode.NOT_EXISTS) &#123;</span><br><span class="line">    <span class="comment">// 缓存不存在</span></span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (r.getResultCode() == CacheResultCode.EXPIRED) &#123;</span><br><span class="line">    <span class="comment">// 缓存已过期</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 缓存访问出错（网络异常等）</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其他大写 API：<code>GET_ALL</code>、<code>PUT</code>、<code>PUT_ALL</code>、<code>REMOVE</code>、<code>REMOVE_ALL</code>、<code>PUT_IF_ABSENT</code>。</p><h3 id="异步-API"><a href="#异步-API" class="headerlink" title="异步 API"></a>异步 API</h3><p>当使用 <strong>Lettuce</strong> 客户端时，大写 API 支持真正的异步非阻塞：</p><figure class="highlight java"><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">CacheGetResult&lt;UserDO&gt; r = userCache.GET(<span class="string">"toc:user:info:12345"</span>);</span><br><span class="line"><span class="comment">// 此时操作可能还没完成</span></span><br><span class="line">CompletionStage&lt;ResultData&gt; future = r.future();</span><br><span class="line">future.thenRun(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (r.isSuccess()) &#123;</span><br><span class="line">        System.out.println(r.getValue());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>注意：小写的 <code>put()</code> 和 <code>removeAll()</code> 没有返回值，在 Lettuce 下会被自动优化为异步调用，减少 RT。但 <code>get()</code> 需要等待结果，所以仍然会阻塞。</p><h1 id="五、Key-类型与策略"><a href="#五、Key-类型与策略" class="headerlink" title="五、Key 类型与策略"></a>五、Key 类型与策略</h1><p>JetCache 的 key 是怎么生成和处理的，搞清楚这个才能在 Redis 里看到符合预期的 key。</p><h2 id="5-1-Redis-Key-的拼接规则"><a href="#5-1-Redis-Key-的拼接规则" class="headerlink" title="5.1 Redis Key 的拼接规则"></a>5.1 Redis Key 的拼接规则</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Redis 中的 key = keyPrefix + keyConvertor(Java Key 对象)</span><br></pre></td></tr></table></figure><p>举个例子：</p><ul><li><p><code>@Cached(name = &quot;``toc:user:info:``&quot;, key = &quot;#``userId``&quot;)</code> + userId= <code>12345</code>（long 类型）</p></li><li><p>keyConvertor 把 long 转成 <code>&quot;Long12345&quot;</code></p></li><li><p>最终 Redis key = <code>toc:user:info:Long12345</code></p></li></ul><p>如果 key 是 String 类型：</p><ul><li><p><code>@Cached(name = &quot;``toc:user:info:``&quot;, key = &quot;#uuid&quot;)</code> + uuid = <code>&quot;``X123456``&quot;</code></p></li><li><p>keyConvertor 对 String 直接透传</p></li><li><p>最终 Redis key = <code>toc:user:info:X123456</code></p></li></ul><h2 id="5-2-支持的-Key-类型"><a href="#5-2-支持的-Key-类型" class="headerlink" title="5.2 支持的 Key 类型"></a>5.2 支持的 Key 类型</h2><p>从 <code>ExternalKeyUtil.buildKeyAfterConvert</code> 源码可知，JetCache 支持以下 key 类型：</p><div class="table-container"><table><thead><tr><th>Java 类型</th><th>转换规则</th><th>示例</th></tr></thead><tbody><tr><td><code>String</code></td><td><strong>直接使用</strong>，不转换</td><td><code>&quot;abc&quot;</code> → <code>abc</code></td></tr><tr><td><code>Number</code>（Long、Integer 等）</td><td>类名 + 值</td><td><code>12345L</code> → <code>Long12345</code></td></tr><tr><td><code>Date</code></td><td>类名 + yyyyMMddHHmmss,SSS</td><td><code>new Date()</code> → <code>Date20260617100000,000</code></td></tr><tr><td><code>Boolean</code></td><td>toString</td><td><code>true</code> → <code>true</code></td></tr><tr><td><code>byte[]</code></td><td>直接使用</td><td>—</td></tr><tr><td>其他 <code>Serializable</code> 对象</td><td>Java 序列化</td><td>复杂对象 → 序列化字节</td></tr></tbody></table></div><p><strong>实践建议</strong>：<strong>推荐使用 String 类型的 key</strong>。如果你用 Long/Integer 类型的 key，最终 Redis 里会带个 <code>Long</code>/<code>Integer</code> 前缀，虽然不影响功能，但看起来不直观。在 SpEL 里做一下转换就行：<code>key = &quot;&#39;&#39; + #userId&quot;</code> 或 <code>key = &quot;#userId.toString()&quot;</code>。</p><h2 id="5-3-keyConvertor-机制"><a href="#5-3-keyConvertor-机制" class="headerlink" title="5.3 keyConvertor 机制"></a>5.3 keyConvertor 机制</h2><p>keyConvertor 负责把 Java 对象转成 Redis 能存的 String：</p><div class="table-container"><table><thead><tr><th>值</th><th>说明</th></tr></thead><tbody><tr><td><code>fastjson2</code></td><td><strong>默认推荐</strong>。String 直接透传，其他对象用 <code>JSON.toJSONString()</code> 转</td></tr><tr><td><code>jackson</code></td><td>用 Jackson 转 JSON</td></tr><tr><td><code>jackson3</code></td><td>Jackson 3.x 版本</td></tr><tr><td><code>none</code></td><td>不转换，直接 <code>equals</code> 比较。仅用于 <code>@CreateCache</code> 且 <code>cacheType = LOCAL</code> 的场景</td></tr></tbody></table></div><h2 id="5-4-SpEL-表达式指定-Key"><a href="#5-4-SpEL-表达式指定-Key" class="headerlink" title="5.4 SpEL 表达式指定 Key"></a>5.4 SpEL 表达式指定 Key</h2><p><code>@Cached</code> 的 <code>key</code> 属性支持 Spring 的 SpEL 表达式：</p><figure class="highlight java"><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"><span class="comment">// 直接用参数名（需 javac -parameters 编译）</span></span><br><span class="line"><span class="meta">@Cached</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"#uuid"</span>, expire = <span class="number">3600</span>)</span><br><span class="line"><span class="function">User <span class="title">getUserById</span><span class="params">(String uuid)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 按下标访问（不需要 -parameters）</span></span><br><span class="line"><span class="meta">@Cached</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"args[0]"</span>, expire = <span class="number">3600</span>)</span><br><span class="line"><span class="function">User <span class="title">getUserById</span><span class="params">(String uuid)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 访问对象属性</span></span><br><span class="line"><span class="meta">@Cached</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"#user.uuid"</span>, expire = <span class="number">3600</span>)</span><br><span class="line"><span class="function">User <span class="title">getUser</span><span class="params">(User user)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 字符串拼接</span></span><br><span class="line"><span class="meta">@Cached</span>(name = <span class="string">"toc:user:archives:"</span>, key = <span class="string">"#appId + ':' + #uuid"</span>, expire = <span class="number">1800</span>)</span><br><span class="line"><span class="function">List&lt;Archives&gt; <span class="title">getArchives</span><span class="params">(String appId, String uuid)</span></span>;</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：使用参数名（如 <code>#userId</code>）需要编译时加 <code>-parameters</code> 参数，否则只能用 <code>args[0]</code> 按下标访问。</p><p><strong>Maven 配置：</strong></p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">compilerArgument</span>&gt;</span>-parameters<span class="tag">&lt;/<span class="name">compilerArgument</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>IntelliJ IDEA 配置</strong>：Settings → Build → Compiler → Java Compiler → Additional command-line parameters，填入 <code>-parameters</code>。</p><h2 id="5-5-单值-vs-多值缓存场景"><a href="#5-5-单值-vs-多值缓存场景" class="headerlink" title="5.5 单值 vs 多值缓存场景"></a>5.5 单值 vs 多值缓存场景</h2><p>JetCache 是纯 KV 模型（底层用 Redis STRING 类型），<strong>不支持 Redis HASH 的子字段操作</strong>（HGET/HSET）。如果你之前用 Redisson 的 <code>RMap</code> 做过 Hash 缓存，迁移到 JetCache 时需要调整思路。</p><p><strong>场景：**</strong><code>toc:u</code><strong><strong><code>ser:archives:{uuid}</code></strong></strong> 一个用户对应多条 KYC 记录**</p><p>Redisson 的做法（Hash 粒度操作）：</p><figure class="highlight java"><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="comment">// Redisson：可以按 kycType 单独读写</span></span><br><span class="line">RMap&lt;String, String&gt; map = redissonClient.getMap(<span class="string">"user:archives:"</span> + uuid);</span><br><span class="line">map.put(<span class="string">"archive_real"</span>, jsonString);           <span class="comment">// HSET</span></span><br><span class="line">String json = map.get(<span class="string">"archive_real"</span>);         <span class="comment">// HGET</span></span><br></pre></td></tr></table></figure><p>JetCache 的做法（整体缓存）：</p><figure class="highlight java"><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="comment">// 方案一：整个 List 作为 value</span></span><br><span class="line"><span class="meta">@Cached</span>(name = <span class="string">"toc:user:archives:"</span>, key = <span class="string">"#appId + ':' + #uuid"</span>, expire = <span class="number">30</span>, timeUnit = TimeUnit.MINUTES)</span><br><span class="line"><span class="function">List&lt;ApiArchivesStatus&gt; <span class="title">getAllArchives</span><span class="params">(String appId, String uuid)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 按 kycType 查：整体取出后在内存中过滤</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> ApiArchivesStatus <span class="title">getByKycType</span><span class="params">(String appId, String uuid, String kycType)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> getAllArchives(appId, uuid).stream()</span><br><span class="line">        .filter(s -&gt; kycType.equals(s.getKycType()))</span><br><span class="line">        .findFirst().orElse(<span class="keyword">null</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方案二：Map 作为 value（kycType 作为 Map 的 key）</span></span><br><span class="line"><span class="meta">@Cached</span>(name = <span class="string">"toc:user:archives:"</span>, key = <span class="string">"#appId + ':' + #uuid"</span>, expire = <span class="number">30</span>, timeUnit = TimeUnit.MINUTES)</span><br><span class="line"><span class="function">Map&lt;String, ApiArchivesStatus&gt; <span class="title">getArchivesMap</span><span class="params">(String appId, String uuid)</span></span>;</span><br></pre></td></tr></table></figure><p>JetCache 无法做 Hash 字段级操作，需要把「uuid 对应的全部数据」作为一个完整的 value 来缓存。如果业务对子字段粒度读写要求很高，建议保留 Redisson RMap；如果整体读写为主，JetCache 的两级缓存、自动刷新等能力更有价值。</p><hr><h1 id="六、两级缓存（BOTH）"><a href="#六、两级缓存（BOTH）" class="headerlink" title="六、两级缓存（BOTH）"></a>六、两级缓存（BOTH）</h1><p>两级缓存是 JetCache 的一大亮点，虽然我们暂时用不到。简单来说就是：<strong>本地内存缓存（L1）+ Redis（L2）组合使用，读的时候先查 L1 再查 L2，写的时候两级都写。</strong></p><h2 id="6-1-工作原理"><a href="#6-1-工作原理" class="headerlink" title="6.1 工作原理"></a>6.1 工作原理</h2><figure class="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></pre></td><td class="code"><pre><span class="line">读取流程：</span><br><span class="line">  1. 查本地缓存（Caffeine/LinkedHashMap）</span><br><span class="line">  2. 本地命中 → 直接返回</span><br><span class="line">  3. 本地未命中 → 查 Redis</span><br><span class="line">  4. Redis 命中 → 回填本地缓存 → 返回</span><br><span class="line">  5. Redis 也未命中 → 返回 NOT_EXISTS</span><br><span class="line"></span><br><span class="line">写入流程：</span><br><span class="line">  1. 同时写入本地缓存和 Redis</span><br><span class="line"></span><br><span class="line">删除流程：</span><br><span class="line">  1. 同时删除本地缓存和 Redis 中的 key</span><br></pre></td></tr></table></figure><h2 id="6-2-配置使用"><a href="#6-2-配置使用" class="headerlink" title="6.2 配置使用"></a>6.2 配置使用</h2><h3 id="注解方式："><a href="#注解方式：" class="headerlink" title="注解方式："></a>注解方式：</h3><figure class="highlight java"><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="meta">@Cached</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"#uuid"</span>, expire = <span class="number">3600</span>,</span><br><span class="line">    cacheType = CacheType.BOTH,     <span class="comment">// 两级缓存</span></span><br><span class="line">    syncLocal = <span class="keyword">true</span>,               <span class="comment">// 更新时广播失效其他节点本地缓存</span></span><br><span class="line">    localLimit = <span class="number">100</span>,               <span class="comment">// 本地最大元素数</span></span><br><span class="line">    localExpire = <span class="number">60</span>                <span class="comment">// 本地缓存 60 秒超时（通常小于远程的 expire）</span></span><br><span class="line">)</span><br><span class="line"><span class="function">User <span class="title">getUserById</span><span class="params">(String uuid)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="编程方式："><a href="#编程方式：" class="headerlink" title="编程方式："></a>编程方式：</h3><figure class="highlight java"><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">QuickConfig qc = QuickConfig.newBuilder(<span class="string">"userCache"</span>)</span><br><span class="line">    .expire(Duration.ofSeconds(<span class="number">3600</span>))</span><br><span class="line">    .cacheType(CacheType.BOTH)</span><br><span class="line">    .syncLocal(<span class="keyword">true</span>)</span><br><span class="line">    .localLimit(<span class="number">100</span>)</span><br><span class="line">    .localExpire(Duration.ofSeconds(<span class="number">60</span>))</span><br><span class="line">    .build();</span><br><span class="line">Cache&lt;Long, User&gt; userCache = cacheManager.getOrCreateCache(qc);</span><br></pre></td></tr></table></figure><h2 id="6-3-syncLocal-—-跨节点同步失效"><a href="#6-3-syncLocal-—-跨节点同步失效" class="headerlink" title="6.3 syncLocal — 跨节点同步失效"></a>6.3 syncLocal — 跨节点同步失效</h2><p>这是两级缓存的关键配置。加入你有 3 台服务器，每台都有本地缓存。如果节点 A 更新了某个用户数据，节点 B 和 C 的本地缓存还是旧值，这就出现了不一致。</p><p><code>syncLocal = true</code> 的解决方式：</p><ol><li><p>节点 A 更新缓存时，向 Redis 的 <code>broadcastChannel</code> 发一条失效消息</p></li><li><p>节点 B 和 C 订阅了这个 channel，收到消息后清除本地对应的缓存</p></li><li><p>下次读取时，B 和 C 会从 Redis 拉取最新数据</p></li></ol><p><strong>前提条件</strong>：yml 中必须配置了 <code>broadcastChannel</code>。</p><figure class="highlight yaml"><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="attr">jetcache:</span></span><br><span class="line">  <span class="attr">remote:</span></span><br><span class="line">    <span class="attr">default:</span></span><br><span class="line">      <span class="attr">broadcastChannel:</span> <span class="string">crm-user</span>  <span class="comment"># 必须有这个配置</span></span><br></pre></td></tr></table></figure><p><strong>注意</strong>：多个服务共用同一个 Redis 时，不同服务请使用不同的 <code>broadcastChannel</code>，否则一个服务的缓存更新会触发其他服务的本地缓存全部失效，造成广播风暴。</p><h2 id="6-4-localExpire-—-本地和远程过期时间分离"><a href="#6-4-localExpire-—-本地和远程过期时间分离" class="headerlink" title="6.4 localExpire — 本地和远程过期时间分离"></a>6.4 localExpire — 本地和远程过期时间分离</h2><p>两级缓存场景下，本地缓存的过期时间通常应该 <strong>小于</strong> 远程缓存。比如远程设 1 小时，本地设 1 分钟，这样即使广播消息丢失，本地最多 1 分钟后也会自动过期重新从 Redis 拉取。</p><div class="table-container"><table><thead><tr><th>场景</th><th>推荐 CacheType</th><th>理由</th></tr></thead><tbody><tr><td>字典数据、配置项</td><td><code>LOCAL</code></td><td>变化少，本地内存就够了</td></tr><tr><td>一般业务数据</td><td><code>REMOTE</code></td><td>统一存 Redis，简单可靠</td></tr><tr><td>高频读 + 可接受秒级不一致</td><td><code>BOTH</code> + <code>syncLocal = true</code></td><td>本地扛读压力，Redis 兜底</td></tr><tr><td>高频读 + 数据量特别大</td><td><code>BOTH</code> + <code>localLimit</code> 控制大小</td><td>避免本地内存撑爆</td></tr></tbody></table></div><hr><h1 id="七、序列化配置"><a href="#七、序列化配置" class="headerlink" title="七、序列化配置"></a>七、序列化配置</h1><p>远程缓存（Redis）里的数据是字节流，存入时需要 <strong>序列化（encode）</strong>，取出时需要 <strong>反序列化（decode）</strong>。JetCache 提供了三种序列化方式：</p><h2 id="7-1-valueEncoder-valueDecoder-选择"><a href="#7-1-valueEncoder-valueDecoder-选择" class="headerlink" title="7.1 valueEncoder / valueDecoder 选择"></a>7.1 valueEncoder / valueDecoder 选择</h2><div class="table-container"><table><thead><tr><th>方式</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td><code>java</code>（默认）</td><td>兼容性最好，Java 原生</td><td>性能最差，字节数最大</td></tr><tr><td><code>kryo</code> / <code>kryo5</code></td><td>性能好，字节数小</td><td>需要注册类，升级时注意兼容</td></tr></tbody></table></div><figure class="highlight yaml"><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="attr">jetcache:</span></span><br><span class="line">  <span class="attr">remote:</span></span><br><span class="line">    <span class="attr">default:</span></span><br><span class="line">      <span class="attr">valueEncoder:</span> <span class="string">java</span>    <span class="comment"># 或 kryo / kryo5</span></span><br><span class="line">      <span class="attr">valueDecoder:</span> <span class="string">java</span></span><br></pre></td></tr></table></figure><p>这里不建议自定义编解码实现，存在造成多级缓存不一致的风险。因为编解码器不一致，会导致 jetcache 广播 start 异常。</p><h2 id="7-2-反序列化安全过滤器（2-8-）"><a href="#7-2-反序列化安全过滤器（2-8-）" class="headerlink" title="7.2 反序列化安全过滤器（2.8+）"></a>7.2 反序列化安全过滤器（2.8+）</h2><p>JetCache 2.8.x 默认开启了反序列化安全过滤器，<strong>只允许白名单中的类被反序列化</strong>。这是为了防止反序列化漏洞攻击。默认白名单包含：<code>java.lang</code>、<code>java.util.</code>、<code>java.time.</code>、<code>java.math</code>、<code>com.alicp.jetcache.</code>。</p><p>如果你的缓存值包含自定义类（比如 <code>UserDO</code>、<code>OrderDO</code>），<strong>必须添加白名单</strong>，否则反序列化会报错：</p><figure class="highlight yaml"><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="attr">jetcache:</span></span><br><span class="line">  <span class="attr">decodeFilterAllowPatterns:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">com.remotecarter.</span>                    <span class="comment"># 前缀匹配：该包及子包下所有类</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">com.remotecarter.UserDto</span>             <span class="comment"># 精确匹配：仅这一个类</span></span><br></pre></td></tr></table></figure><p><strong>模式匹配规则：</strong></p><div class="table-container"><table><thead><tr><th>模式</th><th>匹配方式</th><th>示例</th></tr></thead><tbody><tr><td><code>com.``remotecarter``.</code><br></td><td>前缀匹配（以 <code>.</code> 结尾）</td><td>匹配 <code>com.remotecarter.Foo</code>、<code>com.remotecarter.sub.Bar</code></td></tr><tr><td><code>com.``remotecarter</code></td><td>包名匹配（不以 <code>.</code> 结尾）</td><td>仅匹配 <code>com.remotecarter.Foo</code>，不含子包</td></tr><tr><td><code>com.``remotecarter``.``User``Dto</code></td><td>精确匹配（完整类名）</td><td>仅匹配 <code>com.remotecarter.UserDto</code></td></tr></tbody></table></div><p>拒绝列表（内置）包含已知反序列化攻击 gadget chain（Commons Collections、Spring AOP、Hibernate 等），以及 <code>Runtime</code>、<code>ProcessBuilder</code> 等危险类。<strong>拒绝列表不可被允许列表覆盖。</strong></p><p>也可以通过编程方式配置：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DecodeFilter.getDefault().addAllowPatterns(<span class="string">"com.yourcompany."</span>);</span><br></pre></td></tr></table></figure><h1 id="八、业务接入实战"><a href="#八、业务接入实战" class="headerlink" title="八、业务接入实战"></a>八、业务接入实战</h1><h2 id="场景一：单值缓存（用户信息）"><a href="#场景一：单值缓存（用户信息）" class="headerlink" title="场景一：单值缓存（用户信息）"></a>场景一：单值缓存（用户信息）</h2><p>最常见的场景——按 ID 查用户，缓存到 Redis。</p><figure class="highlight java"><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 class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Cached</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"#uuid"</span>, expire = <span class="number">3600</span>, cacheType = CacheType.REMOTE)</span><br><span class="line">    <span class="function">User <span class="title">getUserById</span><span class="params">(String uuid)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@CacheUpdate</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"#user.uuid"</span>, value = <span class="string">"#user"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">updateUser</span><span class="params">(User user)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@CacheInvalidate</span>(name = <span class="string">"toc:user:info:"</span>, key = <span class="string">"#uuid"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">deleteUser</span><span class="params">(String uuid)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Redis 里的 key 长这样：<code>toc:user:info:X12345</code>。</p><h2 id="场景二：多值缓存（Hash-替代方案）"><a href="#场景二：多值缓存（Hash-替代方案）" class="headerlink" title="场景二：多值缓存（Hash 替代方案）"></a>场景二：多值缓存（Hash 替代方案）</h2><p>一个用户对应多条 KYC 记录，之前在 Redisson 中用 <code>RMap</code>（Hash）实现，迁移到 JetCache 后用 <strong>整体缓存</strong> 替代：</p><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 整个 List 作为一条缓存</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ArchivesCacheService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Cached</span>(name = <span class="string">"toc:user:archives:"</span>, key = <span class="string">"#appId + ':' + #uuid"</span>,</span><br><span class="line">            expire = <span class="number">30</span>, timeUnit = TimeUnit.MINUTES, cacheType = CacheType.REMOTE)</span><br><span class="line">    <span class="function">List&lt;ApiArchivesStatus&gt; <span class="title">getAllArchives</span><span class="params">(String appId, String uuid)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@CacheUpdate</span>(name = <span class="string">"toc:user:archives:"</span>, key = <span class="string">"#appId + ':' + #uuid"</span>, value = <span class="string">"#list"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">saveAllArchives</span><span class="params">(String appId, String uuid, List&lt;ApiArchivesStatus&gt; list)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@CacheInvalidate</span>(name = <span class="string">"toc:user:archives:"</span>, key = <span class="string">"#appId + ':' + #uuid"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">deleteArchives</span><span class="params">(String appId, String uuid)</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 按 kycType 查询</span></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ArchivesServiceImpl</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ArchivesCacheService archivesCacheService;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ApiArchivesStatus <span class="title">getByKycType</span><span class="params">(String appId, String uuid, String kycType)</span> </span>&#123;</span><br><span class="line">        List&lt;ApiArchivesStatus&gt; list = archivesCacheService.getAllArchives(appId, uuid);</span><br><span class="line">        <span class="keyword">return</span> list.stream()</span><br><span class="line">            .filter(s -&gt; kycType.equals(s.getKycType()))</span><br><span class="line">            .findFirst()</span><br><span class="line">            .orElse(<span class="keyword">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 也可以使用 Map</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="场景三：高频读-自动刷新（报表汇总）"><a href="#场景三：高频读-自动刷新（报表汇总）" class="headerlink" title="场景三：高频读 + 自动刷新（报表汇总）"></a>场景三：高频读 + 自动刷新（报表汇总）</h2><figure class="highlight java"><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="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ReportService</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Cached</span>(expire = <span class="number">7200</span>, cacheType = CacheType.BOTH, syncLocal = <span class="keyword">true</span>)</span><br><span class="line">    <span class="meta">@CacheRefresh</span>(refresh = <span class="number">1800</span>, stopRefreshAfterLastAccess = <span class="number">3600</span>, timeUnit = TimeUnit.SECONDS)</span><br><span class="line">    <span class="meta">@CachePenetrationProtect</span></span><br><span class="line">    <span class="function">ReportSummary <span class="title">getReportSummary</span><span class="params">(String reportId)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>缓存 2 小时，每 30 分钟自动刷新一次（集群唯一），30 分钟没人访问就停止刷新。本地 + Redis 两级缓存，万一未命中还有穿透保护。</p><h2 id="场景四：条件缓存"><a href="#场景四：条件缓存" class="headerlink" title="场景四：条件缓存"></a>场景四：条件缓存</h2><p><strong>普通场景下需要根据条件决定是否使用缓存：</strong></p><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 只有 type 为 1 的时候才走缓存</span></span><br><span class="line"><span class="meta">@Cached</span>(name = <span class="string">"data-"</span>, key = <span class="string">"#id"</span>, expire = <span class="number">3600</span>, condition = <span class="string">"#type == 1"</span>)</span><br><span class="line"><span class="function">DataObject <span class="title">getData</span><span class="params">(<span class="keyword">long</span> id, <span class="keyword">int</span> type)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 只有结果不为空才缓存</span></span><br><span class="line"><span class="meta">@Cached</span>(name = <span class="string">"data-"</span>, key = <span class="string">"#id"</span>, expire = <span class="number">3600</span>, postCondition = <span class="string">"#result != null"</span>)</span><br><span class="line"><span class="function">DataObject <span class="title">getData</span><span class="params">(<span class="keyword">long</span> id)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 某个场景下临时禁用缓存（比如数据导出时不能用缓存）</span></span><br><span class="line"><span class="meta">@Cached</span>(name = <span class="string">"data-"</span>, key = <span class="string">"#id"</span>, expire = <span class="number">3600</span>, enabled = <span class="keyword">false</span>)</span><br><span class="line"><span class="function">DataObject <span class="title">getData</span><span class="params">(<span class="keyword">long</span> id)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在需要缓存的地方激活</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">exportData</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    CacheContext.enableCache(() -&gt; &#123;</span><br><span class="line">        <span class="comment">// 这里的 getData 会走缓存</span></span><br><span class="line">        DataObject data = getData(<span class="number">123L</span>);</span><br><span class="line">        <span class="keyword">return</span> data;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>如果需要通过配置热部署开启 / 关闭缓存：</strong></p><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.remotecarter.appuser.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Value;</span><br><span class="line"><span class="keyword">import</span> org.springframework.cloud.context.config.annotation.RefreshScope;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@RefreshScope</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SwitchCache</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> <span class="keyword">boolean</span> *CACHE_ON *= <span class="keyword">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;nacos.user.info.cacheOn:true&#125;"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setCacheOn</span><span class="params">(<span class="keyword">boolean</span> cacheOn)</span> </span>&#123;</span><br><span class="line">        *CACHE_ON *= cacheOn;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isCacheOn</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> *CACHE_ON*;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Cached</span>(name = <span class="string">"toc:user:info:"</span>,</span><br><span class="line">        key = <span class="string">"#uuid"</span>,</span><br><span class="line">        expire = <span class="number">3600</span>,</span><br><span class="line">        condition = <span class="string">"T(com.remotecarter.appuser.config.SwitchCache).isCacheOn()"</span>)</span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">UserInfoDetailDTO</span> <span class="title">queryUserDetail</span>(<span class="title">String</span> <span class="title">uuid</span>)</span>;</span><br></pre></td></tr></table></figure><h2 id="最佳实践与注意事项"><a href="#最佳实践与注意事项" class="headerlink" title="最佳实践与注意事项"></a>最佳实践与注意事项</h2><h3 id="1-TTL-必须设置"><a href="#1-TTL-必须设置" class="headerlink" title="1. TTL 必须设置"></a>1. TTL 必须设置</h3><p><code>@CacheUpdate</code> 和 <code>@CacheInvalidate</code> 可能因为网络波动失败。如果没有设置 TTL，失败的删除/更新操作就会导致缓存永远不一致。<strong>一定要设置合理的 expire 作为最终一致性的兜底</strong>。</p><h3 id="2-序列化选择"><a href="#2-序列化选择" class="headerlink" title="2. 序列化选择"></a>2. 序列化选择</h3><ul><li><p><strong>开发阶段 / 不确定选啥</strong>：用 <code>java</code>，兼容性最好;</p></li><li><p><strong>追求性能</strong>：用 <code>kryo</code>，体积小、速度快，但需要注册类;</p></li><li><p><strong>JSON 序列化</strong>：不推荐。JSON 不是专门的 Java 序列化工具，反射无法识别类型时会反序列化为 JSONObject，兼容性差;</p></li></ul><h3 id="3-broadcastChannel-隔离"><a href="#3-broadcastChannel-隔离" class="headerlink" title="3. broadcastChannel 隔离"></a>3. broadcastChannel 隔离</h3><p>多个服务共用同一个 Redis 实例时，不同服务一定要用不同的 <code>broadcastChannel</code>。否则 A 服务更新了缓存，广播消息会触发 B 服务的本地缓存失效——虽然看起来没啥问题，但当广播量大的时候就是灾难。</p><h3 id="4-AOP-代理陷阱"><a href="#4-AOP-代理陷阱" class="headerlink" title="4. AOP 代理陷阱"></a>4. AOP 代理陷阱</h3><p>JetCache 的注解通过 Spring AOP 代理实现。<strong>同一个类内部的方法调用不经过代理，缓存不会生效</strong>：</p><figure class="highlight java"><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="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserServiceImpl</span> <span class="keyword">implements</span> <span class="title">UserService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> User <span class="title">getUser</span><span class="params">(<span class="keyword">long</span> userId)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 这里调用了 getUserById，但缓存不会生效！</span></span><br><span class="line">        <span class="comment">// 因为 this.getUserById() 不经过代理</span></span><br><span class="line">        <span class="keyword">return</span> getUserById(userId);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Cached</span>(expire = <span class="number">3600</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> User <span class="title">getUserById</span><span class="params">(<span class="keyword">long</span> userId)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> userMapper.selectById(userId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>解决办法</strong>：通过 <code>@Autowired</code> 注入自己，用注入的实例调用：</p><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserServiceImpl</span> <span class="keyword">implements</span> <span class="title">UserService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserService self;  <span class="comment">// 注入代理实例</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> User <span class="title">getUser</span><span class="params">(<span class="keyword">long</span> userId)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> self.getUserById(userId);  <span class="comment">// 经过代理，缓存生效</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-parameters-编译参数"><a href="#5-parameters-编译参数" class="headerlink" title="5. -parameters 编译参数"></a>5. -parameters 编译参数</h3><p>如果想在 SpEL 中用参数名（如 <code>#uuid</code>），编译时必须加 <code>-parameters</code> 参数。否则只能用 <code>args[0]</code> 按下标访问。</p><h3 id="6-name-命名规范"><a href="#6-name-命名规范" class="headerlink" title="6. name 命名规范"></a>6. name 命名规范</h3><p><code>name</code> 会作为 Redis key 的前缀，建议：</p><ul><li><p>用业务含义明确的名称，如 <code>&quot;toc:user:info:&quot;</code>;</p></li><li><p>末尾加 <code>-</code> 或 <code>:</code> 作为分隔符，如 <code>&quot;userCache-12345&quot;</code></p></li><li><p>不要给不同的 <code>@Cached</code> 注解分配相同的 <code>name + area</code></p></li></ul><h3 id="7-本地缓存的内存控制"><a href="#7-本地缓存的内存控制" class="headerlink" title="7. 本地缓存的内存控制"></a>7. 本地缓存的内存控制</h3><p><code>localLimit</code> 是<strong>每个缓存实例</strong>的限制，不是全部。如果有 10 个 <code>@CreateCache</code> 创建的缓存实例，每个 limit 100，那本地总共可能有 1000 个元素。大对象场景下要注意控制。</p><h1 id="九、FAQ"><a href="#九、FAQ" class="headerlink" title="九、FAQ"></a>九、FAQ</h1><h2 id="Q-Cached-注解加在同类的另一个方法上，为什么没生效？"><a href="#Q-Cached-注解加在同类的另一个方法上，为什么没生效？" class="headerlink" title="Q: @Cached 注解加在同类的另一个方法上，为什么没生效？"></a>Q: @Cached 注解加在同类的另一个方法上，为什么没生效？</h2><p>Spring AOP 基于代理实现，同类内部的方法调用不经过代理。解决方案见上面”最佳实践”第 4 条。</p><h2 id="Q-用了参数名做-key，但缓存没生效？"><a href="#Q-用了参数名做-key，但缓存没生效？" class="headerlink" title="Q: 用了参数名做 key，但缓存没生效？"></a>Q: 用了参数名做 key，但缓存没生效？</h2><p>检查是否配置了 <code>-parameters</code> 编译参数。没有配置的话改用 <code>args[0]</code> 按下标访问。</p><h2 id="Q-升级到-2-8-后反序列化报错？"><a href="#Q-升级到-2-8-后反序列化报错？" class="headerlink" title="Q: 升级到 2.8 后反序列化报错？"></a>Q: 升级到 2.8 后反序列化报错？</h2><p>2.8+ 默认开启了反序列化安全过滤器。需要在 yml 中配置 <code>decodeFilterAllowPatterns</code> 添加你的自定义类所在的包。</p><h2 id="Q-如何同时连接多个-Redis-实例？"><a href="#Q-如何同时连接多个-Redis-实例？" class="headerlink" title="Q: 如何同时连接多个 Redis 实例？"></a>Q: 如何同时连接多个 Redis 实例？</h2><p>配置多个 area：</p><figure class="highlight yaml"><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">jetcache:</span></span><br><span class="line">  <span class="attr">remote:</span></span><br><span class="line">    <span class="attr">default:</span></span><br><span class="line">      <span class="attr">host:</span> <span class="string">redis-host-1</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">6379</span></span><br><span class="line">    <span class="attr">second:</span></span><br><span class="line">      <span class="attr">host:</span> <span class="string">redis-host-2</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">6380</span></span><br></pre></td></tr></table></figure><p>然后在注解中指定 area：<code>@Cached(area = &quot;second&quot;, ...)</code>。</p><h2 id="Q-CacheUpdate-CacheInvalidate-操作失败了怎么办？"><a href="#Q-CacheUpdate-CacheInvalidate-操作失败了怎么办？" class="headerlink" title="Q: @CacheUpdate / @CacheInvalidate 操作失败了怎么办？"></a>Q: @CacheUpdate / @CacheInvalidate 操作失败了怎么办？</h2><p>这两个操作可能因网络问题失败。JetCache 不会抛异常，只是静默失败。所以 <strong>设置合理的 TTL 是必须的</strong>——即使更新/删除失败，缓存也会在 TTL 后自动过期，从数据库重新加载。</p><h2 id="Q-本地缓存和-Redis-数据不一致怎么办？"><a href="#Q-本地缓存和-Redis-数据不一致怎么办？" class="headerlink" title="Q: 本地缓存和 Redis 数据不一致怎么办？"></a>Q: 本地缓存和 Redis 数据不一致怎么办？</h2><p>确保配置了 <code>syncLocal = true</code> 和 <code>broadcastChannel</code>。另外设置一个比 Redis expire 更小的 <code>localExpire</code>，作为兜底——即使广播消息丢失，本地缓存也会在 localExpire 后自动过期。</p><h2 id="Q-JetCache-的分布式锁能用吗？"><a href="#Q-JetCache-的分布式锁能用吗？" class="headerlink" title="Q: JetCache 的分布式锁能用吗？"></a>Q: JetCache 的分布式锁能用吗？</h2><p>JetCache 的锁是基于 Redis <code>SETNX</code> + TTL 实现的<strong>非严格分布式锁</strong>，适用于”防止重复执行”的场景。可以用。但目前了解到各域都有自己的分布式锁，建议还是用自己的吧，毕竟 JetCache 核心职责是定义缓存框架协议。</p><h2 id="Q：完整配置参考列表"><a href="#Q：完整配置参考列表" class="headerlink" title="Q：完整配置参考列表"></a>Q：完整配置参考列表</h2><figure class="highlight yaml"><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><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">jetcache:</span></span><br><span class="line">  <span class="comment"># ============ 全局配置 ============</span></span><br><span class="line">  <span class="attr">statIntervalMinutes:</span> <span class="number">15</span>                    <span class="comment"># 统计间隔（分钟），0 = 不统计</span></span><br><span class="line">  <span class="attr">areaInCacheName:</span> <span class="literal">false</span>                     <span class="comment"># key 前缀是否包含 area，新项目建议 false</span></span><br><span class="line">  <span class="attr">hidePackages:</span> <span class="string">com.remotecarter</span>                <span class="comment"># 自动生成 name 时截掉的包名前缀</span></span><br><span class="line">  <span class="attr">useDefaultLocalExpireInMultiLevelCache:</span> <span class="literal">false</span>  <span class="comment"># BOTH 时是否用本地 builder 的超时</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># ============ 反序列化安全（2.8+） ============</span></span><br><span class="line">  <span class="attr">decodeFilterEnabled:</span> <span class="literal">true</span>                  <span class="comment"># 总开关</span></span><br><span class="line">  <span class="attr">decodeFilterAllowPatterns:</span>                 <span class="comment"># 允许列表</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">com.remotecarter.</span></span><br><span class="line">  <span class="attr">decodeFilterDenyPatterns:</span>                  <span class="comment"># 拒绝列表（始终优先于允许列表）</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">com.dangerous.</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># ============ 本地缓存配置 ============</span></span><br><span class="line">  <span class="attr">local:</span></span><br><span class="line">    <span class="attr">default:</span>                                 <span class="comment"># area 名称</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">caffeine</span>                         <span class="comment"># caffeine 或 linkedhashmap</span></span><br><span class="line">      <span class="attr">limit:</span> <span class="number">100</span>                             <span class="comment"># 每个缓存实例最大元素数</span></span><br><span class="line">      <span class="attr">keyConvertor:</span> <span class="string">fastjson2</span>                <span class="comment"># fastjson2 / jackson / jackson3 / none</span></span><br><span class="line">      <span class="attr">expireAfterWriteInMillis:</span> <span class="number">60000</span>        <span class="comment"># 默认超时（毫秒）</span></span><br><span class="line">      <span class="attr">expireAfterAccessInMillis:</span> <span class="number">0</span>           <span class="comment"># 访问后超时（0 = 不使用）</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 可以配多个 area</span></span><br><span class="line">    <span class="attr">otherArea:</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">linkedhashmap</span></span><br><span class="line">      <span class="attr">limit:</span> <span class="number">50</span></span><br><span class="line">      <span class="attr">keyConvertor:</span> <span class="string">none</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># ============ 远程缓存配置 ============</span></span><br><span class="line">  <span class="attr">remote:</span></span><br><span class="line">    <span class="attr">default:</span>                                 <span class="comment"># area 名称</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">redis.redisson</span>                   <span class="comment"># redis / redis.lettuce / redis.redisson / redis.springdata</span></span><br><span class="line">      <span class="attr">keyConvertor:</span> <span class="string">fastjson2</span>                <span class="comment"># key 转换</span></span><br><span class="line">      <span class="attr">valueEncoder:</span> <span class="string">java</span>                     <span class="comment"># 序列化：java / kryo / kryo5</span></span><br><span class="line">      <span class="attr">valueDecoder:</span> <span class="string">java</span>                     <span class="comment"># 反序列化：java / kryo / kryo5</span></span><br><span class="line">      <span class="attr">broadcastChannel:</span> <span class="string">crm-user</span>             <span class="comment"># 两级缓存广播 channel（未配置则不开启）</span></span><br><span class="line"></span><br><span class="line">      <span class="comment"># --- Jedis / Redisson 连接池配置 ---</span></span><br><span class="line">      <span class="attr">poolConfig:</span></span><br><span class="line">        <span class="attr">minIdle:</span> <span class="number">5</span></span><br><span class="line">        <span class="attr">maxIdle:</span> <span class="number">20</span></span><br><span class="line">        <span class="attr">maxTotal:</span> <span class="number">50</span></span><br><span class="line">      <span class="attr">host:</span> <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">6379</span></span><br><span class="line">      <span class="comment"># password: xxx                       # 有密码时配置</span></span><br><span class="line"></span><br><span class="line">      <span class="comment"># --- Lettuce 连接（二选一） ---</span></span><br><span class="line">      <span class="comment"># uri: redis://127.0.0.1:6379/0</span></span><br><span class="line"></span><br><span class="line">      <span class="attr">expireAfterWriteInMillis:</span> <span class="number">300000</span>       <span class="comment"># 默认超时（毫秒）</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;一、组件介绍&quot;&gt;&lt;a href=&quot;#一、组件介绍&quot; class=&quot;headerlink&quot; title=&quot;一、组件介绍&quot;&gt;&lt;/a&gt;一、组件介绍&lt;/h1&gt;&lt;p&gt;JetCache 是阿里巴巴开源的通用缓存访问框架（&lt;a href=&quot;https://github.com/</summary>
      
    
    
    
    <category term="中间件" scheme="https://donehub.github.io/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
    
    
    <category term="JetCache" scheme="https://donehub.github.io/tags/JetCache/"/>
    
  </entry>
  
  <entry>
    <title>JD-HotKey 使用手册</title>
    <link href="https://donehub.github.io/2026/06/15/JD-HotKey-%E7%83%AD%E7%82%B9%E6%8E%A2%E6%B5%8B%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/"/>
    <id>https://donehub.github.io/2026/06/15/JD-HotKey-%E7%83%AD%E7%82%B9%E6%8E%A2%E6%B5%8B%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/</id>
    <published>2026-06-14T16:00:00.000Z</published>
    <updated>2026-07-09T07:06:17.256Z</updated>
    
    <content type="html"><![CDATA[<h1 id="一、组件介绍"><a href="#一、组件介绍" class="headerlink" title="一、组件介绍"></a>一、组件介绍</h1><p>JD-HotKey 是京东开源的<strong>实时热点 key 探测与缓存中间件</strong>（<a href="https://gitee.com/jd-platform-opensource/hotkey" target="_blank" rel="noopener">开源地址</a>），它做了一件事：在高并发场景下，<strong>自动发现热点 key，毫秒级推送到所有应用节点的 JVM 内存中</strong>，让热点请求直接在本地内存响应，不再打到 Redis 和数据库。</p><p>经典场景：某明星突然官宣，相关商品瞬间涌入海量请求。你事先根本不知道这个商品 ID 会变热，Redis 某个节点被这些请求打得 CPU 飙升——这就是典型的<strong>不可预知突发热点</strong>。</p><p>这时候你就需要 JD-HotKey：它能自动发现这类热点，把数据”镜像”到每台应用服务器的本地内存里，后续请求直接从内存读，<strong>RT 从几十毫秒降到 &lt; 1ms</strong>。</p><p>和直接用本地缓存（比如 Caffeine）比，JD-HotKey 的核心优势：</p><div class="table-container"><table><thead><tr><th>能力</th><th>Caffeine 本地缓存</th><th>JD-HotKey</th></tr></thead><tbody><tr><td>热点发现</td><td><strong>手动配置</strong>，你得提前知道哪些 key 要缓存</td><td><strong>自动探测</strong>，根据访问量实时判定</td></tr><tr><td>动态性</td><td>静态的，配了就一直在</td><td><strong>自动加入 / 退出热点</strong>，冷了自动释放内存</td></tr><tr><td>多节点一致性</td><td>各节点独立，互不知情</td><td><strong>全局统一判定</strong>，所有节点同步感知</td></tr><tr><td>适用场景</td><td>已知高频数据（如字典、配置）</td><td><strong>不可预知的突发热点</strong>（如秒杀、热搜）</td></tr><tr><td>部署复杂度</td><td>低（纯 SDK）</td><td>中等（需要 etcd + worker）</td></tr><tr><td>热用户 / 热接口探测</td><td>不支持</td><td><strong>支持</strong>（不只限 key，接口、用户也能探）</td></tr></tbody></table></div><p>简单总结：<strong>已知的热点用本地缓存就够了，不可预知的热点用 JD-HotKey</strong>。</p><h1 id="二、核心架构"><a href="#二、核心架构" class="headerlink" title="二、核心架构"></a>二、核心架构</h1><h2 id="2-1-整体架构"><a href="#2-1-整体架构" class="headerlink" title="2.1 整体架构"></a>2.1 整体架构</h2><h3 id="2-1-1-综合架构"><a href="#2-1-1-综合架构" class="headerlink" title="2.1.1 综合架构"></a>2.1.1 综合架构</h3><p><img data-src="/img/jdhotkey1.png" alt="alt text"></p><h3 id="2-1-2-分层架构"><a href="#2-1-2-分层架构" class="headerlink" title="2.1.2 分层架构"></a>2.1.2 分层架构</h3><p><img data-src="/img/jdhotkey2.png" alt="alt text"><br>系统由四个核心组件构成：</p><div class="table-container"><table><thead><tr><th>组件</th><th>职责</th><th>说明</th></tr></thead><tbody><tr><td><strong>etcd 集群</strong></td><td>配置中心 + 注册中心</td><td>存储热点规则、worker 节点注册、热点 key 的推送中转</td></tr><tr><td><strong>worker</strong></td><td>聚合计算节点</td><td>接收所有客户端的访问统计，执行热点判定，推送热点通知</td></tr><tr><td><strong>client SDK</strong></td><td>嵌入业务应用的客户端</td><td>收集访问数据、上报统计、接收热点通知、本地缓存热点数据</td></tr><tr><td><strong>dashboard</strong></td><td>可视化管理控制台</td><td>配置规则、查看热点、管理应用</td></tr></tbody></table></div><h2 id="2-2-数据流转过程"><a href="#2-2-数据流转过程" class="headerlink" title="2.2 数据流转过程"></a>2.2 数据流转过程</h2><p>整个热点探测的过程就像一条流水线：</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">1. 业务请求打到 app，client SDK 对 key 做访问计数</span><br><span class="line">        ↓</span><br><span class="line">   ┌─────────────────────────────────────────────┐</span><br><span class="line">   │  TurnKeyCollector（双 Map 无锁化收集）       │</span><br><span class="line">   │                                             │</span><br><span class="line">   │  Map[0]  ← 偶数次调用写入                    │</span><br><span class="line">   │  Map[1]  ← 奇数次调用写入                    │</span><br><span class="line">   │  AtomicLong 递增 → % 2 → 决定写哪个 Map     │</span><br><span class="line">   │  读写完全隔离，不阻塞业务线程                  │</span><br><span class="line">   └─────────────────────────────────────────────┘</span><br><span class="line">        ↓</span><br><span class="line">2. client 每隔 500ms 将其中一个 Map 的数据批量上报给 worker</span><br><span class="line">   （读一个 Map 时，写操作在另一个 Map 进行，互不影响）</span><br><span class="line">        ↓</span><br><span class="line">3. worker 汇聚所有 app 节点的数据，执行热点判定算法</span><br><span class="line">        ↓</span><br><span class="line">4. 某 key 在时间窗口内的总访问次数 &gt;&#x3D; 阈值 → 判定为热点</span><br><span class="line">        ↓</span><br><span class="line">5. worker 通过 etcd watch 机制，将热点 key 实时推送给所有客户端</span><br><span class="line">        ↓</span><br><span class="line">6. 客户端收到通知，将热点数据缓存到本地内存（Caffeine）</span><br><span class="line">        ↓</span><br><span class="line">7. 后续请求直接走本地缓存，不再访问 Redis &#x2F; DB</span><br><span class="line">        ↓</span><br><span class="line">8. 当 key 不再热门 → worker 通知客户端移除本地缓存，释放内存</span><br></pre></td></tr></table></figure><p><strong>关键设计：集中计算</strong>。你可能会想，为什么不直接在每个客户端本地判断热点？因为分布式场景下，单机请求是分散的——一个 key 在 100 台机器上每台只被访问了 5 次，单看任何一台都不算热，但汇总起来有 500 次。<strong>必须由 worker 集中汇总计算，才能准确识别全局热点</strong>。</p><p><strong>Worker 内部处理流程</strong>：worker 收到 client 上报的数据后，经过一条<strong>责任链</strong>处理：</p><figure class="highlight plain"><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">Netty 收到消息</span><br><span class="line">    ↓</span><br><span class="line">HeartBeatFilter（心跳消息直接处理）</span><br><span class="line">    ↓</span><br><span class="line">AppNameFilter（解析客户端 App 名称）</span><br><span class="line">    ↓</span><br><span class="line">HotKeyFilter（排除白名单 key，有效数据入队）</span><br><span class="line">    ↓</span><br><span class="line">KeyCounterFilter（统计数据处理）</span><br><span class="line">    ↓</span><br><span class="line">LinkedBlockingQueue（容量 200 万，削峰缓冲）</span><br><span class="line">    ↓</span><br><span class="line">KeyConsumer 多线程消费 → SlidingWindow.addCount() → 判定热点</span><br><span class="line">    ↓</span><br><span class="line">热点 key → 写入 etcd + 推送到所有 Client（每 10ms 批量推送）</span><br></pre></td></tr></table></figure><p>Worker 内部还有一个 <code>hotCache</code>（Caffeine 实现，<strong>5 秒 TTL</strong>），用来<strong>防抖</strong>——同一个 key 在 5 秒内不会重复推送，避免热点持续触发时产生大量无效推送。</p><h2 id="2-3-etcd-的角色"><a href="#2-3-etcd-的角色" class="headerlink" title="2.3 etcd 的角色"></a>2.3 etcd 的角色</h2><p>etcd 在这个系统里承担了三个职责：</p><ol><li><p><strong>规则存储</strong>：你在 dashboard 配置的热点规则（哪个 key 前缀、阈值多少、窗口多大），都持久化在 etcd 中。worker 和 client 通过 watch etcd 来感知规则变化。</p></li><li><p><strong>worker 注册</strong>：每个 worker 启动时把自己的 IP 注册到 etcd，client 通过读 etcd 知道该连哪些 worker。</p></li><li><p><strong>热点推送中转</strong>：worker 判定出热点 key 后，写入 etcd 并设置 TTL 过期时间；客户端 watch 对应的 etcd 路径，收到变更事件后立即更新本地缓存。</p></li></ol><p><strong>一个巧妙的设计</strong>：etcd 原生支持 key 的 TTL 自动过期删除。热点 key 写入 etcd 时设一个过期时间（比如 60 秒），过期后 etcd 自动删除，删除事件通过 watch 回调通知所有 client 清除本地缓存。这样就实现了<strong>热点 key 的自动淘汰</strong>，不需要额外的清理逻辑。</p><p><strong>为什么选 etcd 而不是 ZooKeeper？</strong> etcd 原生支持 key TTL 自动删除（ZooKeeper 不支持），性能更高，资源占用更少，API 风格也更现代（基于 gRPC）。</p><p><strong>etcd 版本要求</strong>：3.4.x 及以上。</p><h2 id="2-4-滑动窗口算法"><a href="#2-4-滑动窗口算法" class="headerlink" title="2.4 滑动窗口算法"></a>2.4 滑动窗口算法</h2><p>这是热点判定的核心算法。JD-HotKey 用的是<strong>双缓冲 Map</strong>实现滑动窗口：</p><figure class="highlight plain"><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">Worker 内部的双 Map 机制：</span><br><span class="line"></span><br><span class="line">  ┌─────────────────────────────────────────────────┐</span><br><span class="line">  │  Map[0]（写 Map） ← 当前时间片，接收新 key 上报   │</span><br><span class="line">  │  Map[1]（读 Map） ← 上一时间片，被消费线程读取统计 │</span><br><span class="line">  │                                                 │</span><br><span class="line">  │  AtomicLong 递增 → % 2 → 决定当前写哪个 Map      │</span><br><span class="line">  │  读写分离 → 永不阻塞 → 高并发吞吐                 │</span><br><span class="line">  └─────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><ul><li><strong>双 Map 交替</strong>：一个 Map 负责写入新上报的数据，另一个负责读取统计和清理。通过 <code>AtomicLong</code> 取模 2 切换读写对象，完全无锁设计</li><li><strong>读写不阻塞</strong>：消费线程在统计 Map[0] 时，写入线程往 Map[1] 写数据，互不干扰</li><li><strong>演进历史</strong>：最初用的是 Disruptor，但在实际高并发中发现会导致个别数据延迟且空耗 CPU，后来换成了 <code>LinkedBlockingQueue</code> + 读写分离锁的方案，性能反而更稳定</li></ul><p><strong>Key 的 Hash 分发</strong>：client 上报数据时，会对 key 做 Hash 计算（<code>hash(key) % workerCount</code>），<strong>同一个 key 始终路由到同一个 worker</strong>。这样保证了某个 key 的所有统计数据集中在一个 worker 上，不需要跨 worker 聚合。worker 之间也互不通信，各自独立计算。</p><h2 id="2-5-JdHotKeyStore-—-核心-API"><a href="#2-5-JdHotKeyStore-—-核心-API" class="headerlink" title="2.5 JdHotKeyStore — 核心 API"></a>2.5 JdHotKeyStore — 核心 API</h2><p><code>JdHotKeyStore</code> 是 client SDK 提供的核心操作类，只有 4 个静态方法：</p><div class="table-container"><table><thead><tr><th>方法</th><th>作用</th><th>使用场景</th></tr></thead><tbody><tr><td><code>isHotKey(key)</code></td><td>判断 key 是否热点，<strong>同时上报该 key 的访问</strong></td><td>最常用，适用于只需拦截或限流的场景</td></tr><tr><td><code>get(key)</code></td><td>从本地内存读取热点 key 缓存的<strong>值</strong></td><td>配合 <code>smartSet</code> 使用</td></tr><tr><td><code>smartSet(key, value)</code></td><td>为热点 key 设置本地缓存值</td><td>key 已被判定为热点时，存入真实数据</td></tr><tr><td><code>getValue(key)</code></td><td>综合查询：本地有值返回值，无值返回 null <strong>并自动上报</strong></td><td>一步到位的查询方式</td></tr></tbody></table></div><p><strong>注意区分这几个方法的行为差异</strong>，很多人刚接触时会搞混：</p><figure class="highlight plain"><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">isHotKey(key)  → 返回 true&#x2F;false，同时上报 key 的访问量（计数 +1）</span><br><span class="line">get(key)       → 只从本地缓存读值，不上报</span><br><span class="line">smartSet(key, value) → 只在该 key 是热点时才写入本地缓存（非热点写入会被忽略）</span><br><span class="line">getValue(key)  → 读值 + 上报（如果本地没值，返回 null，同时上报）</span><br></pre></td></tr></table></figure><p><strong>一个内部细节</strong>：当 worker 推送热点通知到 client 时，client 会先在 Caffeine 中存入一个<strong>魔术值</strong>（<code>0x12fcf76</code>）作为占位标记，表示”这个 key 已经是热点了，但还没填入实际业务数据”。所以 <code>get(key)</code> 可能返回这个魔术值或者 null——说明热点标记已生效，但数据还没被 <code>smartSet</code> 填进去，你需要自己加载数据并写入。</p><p><strong>Caffeine 分桶设计</strong>：client 内部不是用一个 Caffeine 实例存所有热点 key，而是按<strong>过期时间（duration）分桶</strong>——相同过期时间的 key 共享同一个 Caffeine 实例。这样不同规则下的热点 key 可以有不同的 TTL，互不干扰。</p><hr><h1 id="三、快速接入（Spring-Boot）"><a href="#三、快速接入（Spring-Boot）" class="headerlink" title="三、快速接入（Spring Boot）"></a>三、快速接入（Spring Boot）</h1><h2 id="第一步：部署基础环境"><a href="#第一步：部署基础环境" class="headerlink" title="第一步：部署基础环境"></a>第一步：部署基础环境</h2><p>JD-HotKey 依赖 etcd，部署前需要先准备好 etcd 集群。</p><h3 id="安装-etcd（开发环境）"><a href="#安装-etcd（开发环境）" class="headerlink" title="安装 etcd（开发环境）"></a>安装 etcd（开发环境）</h3><figure class="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><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="comment"># Docker 方式（快速启动单节点）</span></span><br><span class="line">docker run -d --name etcd \</span><br><span class="line">  -p 2379:2379 \</span><br><span class="line">  -e ALLOW_NONE_AUTHENTICATION=yes \</span><br><span class="line">  bitnami/etcd:3.4</span><br><span class="line"></span><br><span class="line"><span class="comment"># 验证连接</span></span><br><span class="line">docker <span class="built_in">exec</span> etcd etcdctl endpoint health</span><br></pre></td></tr></table></figure><h3 id="部署-worker"><a href="#部署-worker" class="headerlink" title="部署 worker"></a>部署 worker</h3><p>worker 是独立部署的 Java 进程，负责聚合计算。</p><figure class="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><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"><span class="comment"># 从源码编译</span></span><br><span class="line"><span class="built_in">cd</span> worker</span><br><span class="line">mvn clean package -DskipTests</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动 worker（关键参数）</span></span><br><span class="line">java -jar worker/target/worker.jar \</span><br><span class="line">  --etcd=http://127.0.0.1:2379 \</span><br><span class="line">  --threads=16 \</span><br><span class="line">  --workerPath=/jd/hotkey/worker</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th>参数</th><th>说明</th></tr></thead><tbody><tr><td><code>etcd</code></td><td>etcd 集群地址</td></tr><tr><td><code>threads</code></td><td>工作线程数，建议根据 CPU 核数调整</td></tr><tr><td><code>workerPath</code></td><td>worker 在 etcd 中的注册路径</td></tr></tbody></table></div><h3 id="部署-dashboard"><a href="#部署-dashboard" class="headerlink" title="部署 dashboard"></a>部署 dashboard</h3><p>dashboard 是 Web 管理控制台，需要连接 MySQL 和 etcd。</p><figure class="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><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 创建数据库，执行 db.sql 初始化脚本</span></span><br><span class="line"><span class="comment"># 2. 修改 application.yml 中的数据库和 etcd 配置</span></span><br><span class="line"><span class="comment"># 3. 启动</span></span><br><span class="line">java -jar dashboard.jar</span><br><span class="line"></span><br><span class="line"><span class="comment"># 访问 http://localhost:8081</span></span><br><span class="line"><span class="comment"># 默认管理员账号按 README 说明配置</span></span><br></pre></td></tr></table></figure><h2 id="第二步：引入-Maven-依赖"><a href="#第二步：引入-Maven-依赖" class="headerlink" title="第二步：引入 Maven 依赖"></a>第二步：引入 Maven 依赖</h2><figure class="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">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.jd.platform.hotkey<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>hotkey-client<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.4-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>依赖冲突注意</strong>：</p><ul><li>如果项目中有 guava，需要升级到 <strong>28.2-jre</strong> 以上</li><li>如果项目中有 fastjson，需要降到 <strong>1.2.70</strong>（hotkey-client 内部使用的版本）</li><li>通信协议用的 <strong>protobuf</strong>，注意版本兼容</li></ul><h2 id="第三步：初始化客户端"><a href="#第三步：初始化客户端" class="headerlink" title="第三步：初始化客户端"></a>第三步：初始化客户端</h2><p>在 Spring Boot 启动时初始化 client，连接到 etcd 并启动数据管道：</p><figure class="highlight java"><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 class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HotKeyConfig</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;etcd.server&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String etcdServer;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;spring.application.name&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String appName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        ClientStarter starter = <span class="keyword">new</span> ClientStarter.Builder()</span><br><span class="line">                .setAppName(appName)          <span class="comment">// 应用名，和 dashboard 中的配置对应</span></span><br><span class="line">                .setEtcdServer(etcdServer)    <span class="comment">// etcd 集群地址</span></span><br><span class="line">                .build();</span><br><span class="line">        starter.startPipeline();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>ClientStarter.Builder</code> 支持的配置项：</p><div class="table-container"><table><thead><tr><th>方法</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>setAppName</code></td><td>无（必填）</td><td>应用名称，用于匹配 dashboard 中的规则</td></tr><tr><td><code>setEtcdServer</code></td><td>无（必填）</td><td>etcd 集群地址，多个用逗号分隔</td></tr><tr><td><code>setCaffeineSize</code></td><td>200000</td><td>本地 Caffeine 缓存最大容量</td></tr><tr><td><code>setPushPeriod</code></td><td>500ms</td><td>批量上报统计数据的时间间隔（最小 50ms）</td></tr></tbody></table></div><h2 id="第四步：配置热点规则"><a href="#第四步：配置热点规则" class="headerlink" title="第四步：配置热点规则"></a>第四步：配置热点规则</h2><p>在 dashboard 中配置你的热点规则。规则以 JSON 格式存储，支持以下参数：</p><figure class="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">&#123;</span><br><span class="line">    <span class="attr">"desc"</span>: <span class="string">"商品信息热点规则"</span>,</span><br><span class="line">    <span class="attr">"key"</span>: <span class="string">"goods:"</span>,</span><br><span class="line">    <span class="attr">"prefix"</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">"threshold"</span>: <span class="number">100</span>,</span><br><span class="line">    <span class="attr">"duration"</span>: <span class="number">5</span>,</span><br><span class="line">    <span class="attr">"interval"</span>: <span class="number">1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th>字段</th><th>类型</th><th>说明</th></tr></thead><tbody><tr><td><code>key</code></td><td>String</td><td>key 匹配规则。<code>prefix = true</code> 时作为前缀匹配</td></tr><tr><td><code>prefix</code></td><td>boolean</td><td>是否前缀匹配。<code>true</code> 表示匹配 <code>key</code> 开头的所有 key</td></tr><tr><td><code>threshold</code></td><td>int</td><td>热点阈值——时间窗口内总访问次数超过此值判定为热点</td></tr><tr><td><code>duration</code></td><td>int</td><td>滑动窗口大小（秒）</td></tr><tr><td><code>interval</code></td><td>int</td><td>窗口滑动步长（秒）</td></tr><tr><td><code>desc</code></td><td>String</td><td>规则描述，方便管理</td></tr></tbody></table></div><p>上面的配置含义：<strong>以 <code>goods:</code> 开头的 key，在 5 秒内被访问超过 100 次，判定为热点</strong>。</p><hr><h1 id="四、使用介绍"><a href="#四、使用介绍" class="headerlink" title="四、使用介绍"></a>四、使用介绍</h1><h2 id="4-1-场景一：热点拦截-限流"><a href="#4-1-场景一：热点拦截-限流" class="headerlink" title="4.1 场景一：热点拦截 + 限流"></a>4.1 场景一：热点拦截 + 限流</h2><p>最简单的用法——判断是否热点，是的话做特殊处理（降级、限流或直接返回）：</p><figure class="highlight java"><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 class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GoodsController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/goods/&#123;id&#125;"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">getGoods</span><span class="params">(@PathVariable String id)</span> </span>&#123;</span><br><span class="line">        String key = <span class="string">"goods:"</span> + id;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// isHotKey 会同时上报 key 的访问量</span></span><br><span class="line">        <span class="keyword">if</span> (JdHotKeyStore.isHotKey(key)) &#123;</span><br><span class="line">            <span class="comment">// 热点 key 的降级处理：直接返回缓存数据或友好提示</span></span><br><span class="line">            <span class="keyword">return</span> <span class="string">"当前访问量大，请稍后再试"</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 非热点 key 走正常流程</span></span><br><span class="line">        <span class="keyword">return</span> goodsService.getGoodsDetail(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="4-2-场景二：热点数据本地缓存（推荐）"><a href="#4-2-场景二：热点数据本地缓存（推荐）" class="headerlink" title="4.2 场景二：热点数据本地缓存（推荐）"></a>4.2 场景二：热点数据本地缓存（推荐）</h2><p>更实用的方式——检测到热点后，把数据缓存到本地内存，后续请求直接从内存读：</p><figure class="highlight java"><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><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GoodsService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisTemplate&lt;String, Object&gt; redisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> GoodsMapper goodsMapper;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> GoodsDetail <span class="title">getGoodsDetail</span><span class="params">(String goodsId)</span> </span>&#123;</span><br><span class="line">        String key = <span class="string">"goods:"</span> + goodsId;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1. 判断是否热点</span></span><br><span class="line">        <span class="keyword">if</span> (JdHotKeyStore.isHotKey(key)) &#123;</span><br><span class="line">            <span class="comment">// 2. 先尝试从本地缓存读取</span></span><br><span class="line">            GoodsDetail cached = (GoodsDetail) JdHotKeyStore.get(key);</span><br><span class="line">            <span class="keyword">if</span> (cached != <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> cached;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 3. 本地没有，从 Redis 加载</span></span><br><span class="line">            GoodsDetail goods = loadFromRedis(goodsId);</span><br><span class="line">            <span class="keyword">if</span> (goods != <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="comment">// 4. 写入本地热点缓存（只有 key 是热点时才会写入成功）</span></span><br><span class="line">                JdHotKeyStore.smartSet(key, goods);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> goods;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 5. 非热点 key，正常走 Redis</span></span><br><span class="line">        <span class="keyword">return</span> loadFromRedis(goodsId);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> GoodsDetail <span class="title">loadFromRedis</span><span class="params">(String goodsId)</span> </span>&#123;</span><br><span class="line">        GoodsDetail goods = (GoodsDetail) redisTemplate.opsForValue().get(<span class="string">"goods:"</span> + goodsId);</span><br><span class="line">        <span class="keyword">if</span> (goods == <span class="keyword">null</span>) &#123;</span><br><span class="line">            goods = goodsMapper.selectById(goodsId);</span><br><span class="line">            <span class="keyword">if</span> (goods != <span class="keyword">null</span>) &#123;</span><br><span class="line">                redisTemplate.opsForValue().set(<span class="string">"goods:"</span> + goodsId, goods, <span class="number">300</span>, TimeUnit.SECONDS);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> goods;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>这里的 <code>smartSet</code> 很巧妙</strong>：它只在 key 已被判定为热点时才写入本地缓存。如果 key 不是热点，写入会被忽略——不用担心内存被非热点数据撑爆。</p><h2 id="4-3-场景三：用-getValue-简化逻辑"><a href="#4-3-场景三：用-getValue-简化逻辑" class="headerlink" title="4.3 场景三：用 getValue 简化逻辑"></a>4.3 场景三：用 getValue 简化逻辑</h2><p><code>getValue</code> 把”查本地缓存 + 上报”合并成了一步，代码更简洁：</p><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> GoodsDetail <span class="title">getGoodsDetail</span><span class="params">(String goodsId)</span> </span>&#123;</span><br><span class="line">    String key = <span class="string">"goods:"</span> + goodsId;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// getValue 自动处理：本地有值返回值 + 上报访问量</span></span><br><span class="line">    GoodsDetail cached = (GoodsDetail) JdHotKeyStore.getValue(key);</span><br><span class="line">    <span class="keyword">if</span> (cached != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> cached;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 本地没有（可能刚变热点还没缓存数据），从 Redis 加载并写入</span></span><br><span class="line">    GoodsDetail goods = loadFromRedis(goodsId);</span><br><span class="line">    <span class="keyword">if</span> (goods != <span class="keyword">null</span>) &#123;</span><br><span class="line">        JdHotKeyStore.smartSet(key, goods);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> goods;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="4-4-场景四：热用户-热接口探测"><a href="#4-4-场景四：热用户-热接口探测" class="headerlink" title="4.4 场景四：热用户 / 热接口探测"></a>4.4 场景四：热用户 / 热接口探测</h2><p>JD-HotKey 不只局限于数据 key，也能探测热用户和热接口：</p><figure class="highlight java"><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="comment">// 热用户探测（防爬虫 / 防刷子）</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleRequest</span><span class="params">(String userId)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (JdHotKeyStore.isHotKey(<span class="string">"hot:user:"</span> + userId)) &#123;</span><br><span class="line">        <span class="comment">// 该用户访问频率异常，触发限流或验证码</span></span><br><span class="line">        log.warn(<span class="string">"检测到异常高频用户: &#123;&#125;"</span>, userId);</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RateLimitException(<span class="string">"访问过于频繁，请稍后再试"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 正常业务逻辑...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 热接口探测</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleApiCall</span><span class="params">(String apiPath)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (JdHotKeyStore.isHotKey(<span class="string">"hot:api:"</span> + apiPath)) &#123;</span><br><span class="line">        <span class="comment">// 接口被大量请求，触发熔断或降级</span></span><br><span class="line">        log.warn(<span class="string">"接口被高频访问: &#123;&#125;"</span>, apiPath);</span><br><span class="line">        <span class="keyword">return</span> fallbackResponse;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 正常业务逻辑...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 dashboard 中配置对应的规则就行：<code>key = &quot;hot:user:&quot;</code> 和 <code>key = &quot;hot:api:&quot;</code>。</p><hr><h1 id="五、性能数据"><a href="#五、性能数据" class="headerlink" title="五、性能数据"></a>五、性能数据</h1><h2 id="5-1-Worker-性能演进"><a href="#5-1-Worker-性能演进" class="headerlink" title="5.1 Worker 性能演进"></a>5.1 Worker 性能演进</h2><p>JD-HotKey 的性能不是一蹴而就的，从初版到最终版经历了 <strong>17 倍</strong> 的提升：</p><div class="table-container"><table><thead><tr><th>版本</th><th>QPS</th><th>CPU</th><th>关键优化</th></tr></thead><tbody><tr><td>V1（初版）</td><td>~2 万</td><td>&gt;20%</td><td>Disruptor + Fastjson，遇到 JDK 线程 bug 导致大量线程创建</td></tr><tr><td>V2</td><td>~10 万</td><td>7-10%</td><td>换掉 Disruptor，改用 <code>LinkedBlockingQueue</code></td></tr><tr><td>V3</td><td>~16 万</td><td>~40%</td><td>8 核单机调优</td></tr><tr><td>V4</td><td>25-30 万</td><td>~70%</td><td>8 生产 + 8 消费线程</td></tr><tr><td><strong>V5（最终版）</strong></td><td><strong>稳定 30 万，极限 37 万</strong></td><td><strong>~50%</strong></td><td>序列化从 Fastjson 换为 <strong>Protobuf</strong>，16 核</td></tr></tbody></table></div><p><strong>最大的性能跃升来自序列化方案的切换</strong>：从 Fastjson 换为 Protobuf 后，序列化效率显著提升，CPU 使用率反而下降了。</p><h2 id="5-2-推送性能"><a href="#5-2-推送性能" class="headerlink" title="5.2 推送性能"></a>5.2 推送性能</h2><div class="table-container"><table><thead><tr><th>推送速率</th><th>延迟表现</th></tr></thead><tbody><tr><td>10-12 万/秒</td><td><strong>即时送达，无延迟</strong></td></tr><tr><td>20 万/秒</td><td>约 1 秒延迟</td></tr><tr><td>40-60 万/秒（8 IO 线程）</td><td>稳定推送</td></tr><tr><td><strong>70 万/秒（16 IO 线程）</strong></td><td><strong>稳定推送</strong></td></tr><tr><td>80 万/秒（极限）</td><td>频繁 GC，最终 OOM</td></tr></tbody></table></div><h2 id="5-3-生产实战数据"><a href="#5-3-生产实战数据" class="headerlink" title="5.3 生产实战数据"></a>5.3 生产实战数据</h2><div class="table-container"><table><thead><tr><th>指标</th><th>数据</th></tr></thead><tbody><tr><td>大促期间集群总吞吐</td><td><strong>1500 万/秒</strong></td></tr><tr><td>本地缓存命中占总流量</td><td><strong>&gt;50%</strong></td></tr><tr><td>日探测 Key 数量</td><td><strong>数十亿</strong></td></tr><tr><td>1 台 Worker（16 核）可支撑</td><td><strong>~1000 台业务服务</strong></td></tr><tr><td>扛住百万级热 key 所需 Worker</td><td><strong>~30 台</strong></td></tr></tbody></table></div><p><strong>对比一下</strong>：普通请求访问 Redis 的 RT 通常在 1-5ms，访问数据库 5-50ms。JD-HotKey 让热点请求直接从 JVM 内存读取，<strong>RT 降到纳秒级</strong>。</p><hr><h1 id="六、与同类方案对比"><a href="#六、与同类方案对比" class="headerlink" title="六、与同类方案对比"></a>六、与同类方案对比</h1><div class="table-container"><table><thead><tr><th>维度</th><th>JD-HotKey</th><th>Caffeine 本地缓存</th><th>Redis 热点 key 探测</th><th>JetCache BOTH</th></tr></thead><tbody><tr><td>热点发现</td><td><strong>自动探测</strong></td><td>手动配置</td><td><code>redis-cli --hotkeys</code>（采样）</td><td>手动配置</td></tr><tr><td>实时性</td><td>秒级自动感知</td><td>静态，需手动更新</td><td>实时但精度受限</td><td>广播通知</td></tr><tr><td>动态性</td><td><strong>自动加入 / 退出</strong></td><td>不变化，配了就一直有</td><td>需手动清理</td><td>广播失效</td></tr><tr><td>多节点一致性</td><td><strong>全局统一判定</strong></td><td>各节点独立</td><td>Redis 层面</td><td>广播通知</td></tr><tr><td>探测范围</td><td>key / 用户 / 接口</td><td>仅配置的 key</td><td>仅 Redis key</td><td>仅配置的 key</td></tr><tr><td>部署复杂度</td><td>中等（etcd + worker）</td><td>低（纯 SDK）</td><td>低（Redis 自带）</td><td>低（纯 SDK）</td></tr><tr><td>运维成本</td><td>中（etcd 集群维护）</td><td>无</td><td>无</td><td>无</td></tr><tr><td>适用场景</td><td><strong>不可预知的突发热点</strong></td><td>已知高频数据</td><td>Redis 热点节点排查</td><td>已知高频读数据</td></tr></tbody></table></div><h3 id="什么时候该用-JD-HotKey？"><a href="#什么时候该用-JD-HotKey？" class="headerlink" title="什么时候该用 JD-HotKey？"></a>什么时候该用 JD-HotKey？</h3><ul><li><strong>突发热点</strong>：热搜、突发新闻、秒杀商品——你没法提前知道哪些 key 会变热</li><li><strong>大促场景</strong>：618、双11，热点模式不可预测</li><li><strong>全站热点保护</strong>：不想人工分析哪些 key 是热点，交给系统自动发现</li><li><strong>热用户 / 热接口探测</strong>：防爬虫、防刷子</li></ul><h3 id="什么时候不需要-JD-HotKey？"><a href="#什么时候不需要-JD-HotKey？" class="headerlink" title="什么时候不需要 JD-HotKey？"></a>什么时候不需要 JD-HotKey？</h3><ul><li><strong>热点数据固定且已知</strong>：直接用 Caffeine 或 JetCache BOTH 就够了</li><li><strong>数据量不大，Redis 压力不大</strong>：没必要引入额外组件</li><li><strong>团队运维能力有限</strong>：etcd 集群的部署和维护有学习成本</li></ul><hr><h1 id="七、最佳实践与注意事项"><a href="#七、最佳实践与注意事项" class="headerlink" title="七、最佳实践与注意事项"></a>七、最佳实践与注意事项</h1><h2 id="7-1-阈值调优"><a href="#7-1-阈值调优" class="headerlink" title="7.1 阈值调优"></a>7.1 阈值调优</h2><p>阈值设太低 → 太多 key 被判定为热点 → 本地内存撑爆；阈值设太高 → 热点漏判 → Redis 被打。</p><p><strong>建议</strong>：</p><ul><li>核心业务（如商品详情）：阈值设低一点（如 5 秒内 50 次）</li><li>边缘业务：阈值设高一点（如 5 秒内 200 次）</li><li>通过 dashboard 配置<strong>分级阈值</strong>，不同业务用不同规则</li></ul><h2 id="7-2-内存控制"><a href="#7-2-内存控制" class="headerlink" title="7.2 内存控制"></a>7.2 内存控制</h2><p><code>smartSet</code> 存入的热点数据占用的是应用 JVM 内存。注意：</p><ul><li><code>CaffeineSize</code> 默认 200000，根据单机内存和数据大小调整</li><li>大对象场景下（如完整的商品详情 JSON），适当减小容量</li><li>热点 key 过期后会自动从本地缓存移除，不需要手动清理</li></ul><h2 id="7-3-etcd-集群容灾"><a href="#7-3-etcd-集群容灾" class="headerlink" title="7.3 etcd 集群容灾"></a>7.3 etcd 集群容灾</h2><p>etcd 是核心依赖，但 client 有容灾设计：</p><ul><li>etcd 短暂不可用时，<strong>已推送的热点数据仍然在本地缓存中正常工作</strong></li><li>只是新的热点判定和规则变更无法生效</li><li>建议 etcd 至少 3 节点部署，保证高可用</li></ul><h2 id="7-4-依赖冲突处理"><a href="#7-4-依赖冲突处理" class="headerlink" title="7.4 依赖冲突处理"></a>7.4 依赖冲突处理</h2><p>这是接入时最常踩的坑：</p><div class="table-container"><table><thead><tr><th>冲突依赖</th><th>解决方案</th></tr></thead><tbody><tr><td>guava</td><td>升级到 <strong>28.2-jre</strong> 以上</td></tr><tr><td>fastjson</td><td>降到 <strong>1.2.70</strong>（hotkey-client 内部使用的版本）</td></tr><tr><td>protobuf</td><td>注意版本兼容，参考 hotkey-client 的 pom</td></tr><tr><td>Netty</td><td>确保不与业务中的 Netty 版本冲突</td></tr><tr><td>JDK 版本</td><td>建议使用 <strong>JDK 1.8.0_191+</strong>（早期版本在容器环境下 <code>availableProcessors()</code> 返回宿主机核数而非容器限制核数，导致线程配置异常）</td></tr></tbody></table></div><p><strong>建议</strong>：引入依赖后先跑一遍单元测试，确认没有类冲突。特别注意 guava 和 fastjson 版本，这两个最容易出问题。</p><h2 id="7-5-配合降级策略"><a href="#7-5-配合降级策略" class="headerlink" title="7.5 配合降级策略"></a>7.5 配合降级策略</h2><p>热点探测不是万能的，建议配合降级策略：</p><figure class="highlight java"><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 class="function"><span class="keyword">public</span> GoodsDetail <span class="title">getGoodsDetail</span><span class="params">(String goodsId)</span> </span>&#123;</span><br><span class="line">    String key = <span class="string">"goods:"</span> + goodsId;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (JdHotKeyStore.isHotKey(key)) &#123;</span><br><span class="line">            GoodsDetail cached = (GoodsDetail) JdHotKeyStore.get(key);</span><br><span class="line">            <span class="keyword">if</span> (cached != <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> cached;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="comment">// hotkey 组件异常时降级到普通流程</span></span><br><span class="line">        log.warn(<span class="string">"hotkey 判断异常，降级处理"</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 正常流程：Redis → DB</span></span><br><span class="line">    <span class="keyword">return</span> loadFromRedis(goodsId);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="7-6-监控告警"><a href="#7-6-监控告警" class="headerlink" title="7.6 监控告警"></a>7.6 监控告警</h2><p>建议监控以下指标：</p><ul><li><strong>热点 key 数量</strong>：突增可能意味着异常流量</li><li><strong>热点 key 变化趋势</strong>：发现异常模式</li><li><strong>worker 健康状态</strong>：etcd 中 worker 注册是否正常</li><li><strong>client 上报延迟</strong>：是否因为网络问题导致上报积压</li></ul><hr><h1 id="八、FAQ"><a href="#八、FAQ" class="headerlink" title="八、FAQ"></a>八、FAQ</h1><h2 id="Q-worker-和-server-是什么关系？"><a href="#Q-worker-和-server-是什么关系？" class="headerlink" title="Q: worker 和 server 是什么关系？"></a>Q: worker 和 server 是什么关系？</h2><p>JD-HotKey 中，worker 就是实际干活的服务节点。有些文章叫它”server”，其实是一个东西。worker 接收 client 上报的数据，执行热点判定，推送结果。它独立部署，不依赖 Spring Boot，是个纯 Java 进程。</p><h2 id="Q-一个-etcd-集群可以支撑多少个应用？"><a href="#Q-一个-etcd-集群可以支撑多少个应用？" class="headerlink" title="Q: 一个 etcd 集群可以支撑多少个应用？"></a>Q: 一个 etcd 集群可以支撑多少个应用？</h2><p>理论上不限，但建议按业务域隔离。不同应用用不同的 <code>appName</code>，规则互不影响。大规模场景下建议 etcd 独立集群部署。</p><h2 id="Q-isHotKey-每次调用都会上报吗？"><a href="#Q-isHotKey-每次调用都会上报吗？" class="headerlink" title="Q: isHotKey 每次调用都会上报吗？"></a>Q: isHotKey 每次调用都会上报吗？</h2><p>是的，<code>isHotKey</code> 每次调用都会在 client 本地的双 Map 中计数 +1。client 每隔 <code>pushPeriod</code>（默认 500ms）将聚合后的数据批量上报给 worker。所以不是每次调用都发网络请求，是<strong>批量聚合后上报</strong>，开销很小。</p><h2 id="Q-worker-的吞吐量怎么评估？"><a href="#Q-worker-的吞吐量怎么评估？" class="headerlink" title="Q: worker 的吞吐量怎么评估？"></a>Q: worker 的吞吐量怎么评估？</h2><p>worker 内部用 <code>LinkedBlockingQueue</code>（容量 <strong>200 万</strong>）做消息缓冲，多线程消费。经验数据：<strong>1 台 16 核 worker 可以支撑约 1000 台业务服务</strong>。大促期间如果需要扛百万级热 key，大约需要 30 台 worker。</p><h2 id="Q-smartSet-和直接用-Caffeine-存有什么区别？"><a href="#Q-smartSet-和直接用-Caffeine-存有什么区别？" class="headerlink" title="Q: smartSet 和直接用 Caffeine 存有什么区别？"></a>Q: smartSet 和直接用 Caffeine 存有什么区别？</h2><p><code>smartSet</code> 内部用的也是 Caffeine，但它有个关键区别：<strong>只有 key 被判定为热点时才会写入成功</strong>。如果你直接用 Caffeine，所有 key 都会缓存，内存可能被非热点数据占满。<code>smartSet</code> 帮你做了这个过滤。</p><p>另外还有个 <code>forceSet</code> 方法可以强制设置缓存值（不管 key 是否热点），一般用不到。</p><h2 id="Q-热点-key-被判定后，数据怎么填充到本地缓存？"><a href="#Q-热点-key-被判定后，数据怎么填充到本地缓存？" class="headerlink" title="Q: 热点 key 被判定后，数据怎么填充到本地缓存？"></a>Q: 热点 key 被判定后，数据怎么填充到本地缓存？</h2><p>JD-HotKey <strong>只负责告诉你”这个 key 是热点”和”管理本地缓存的存取”</strong>，不负责帮你从数据库加载数据。你需要在代码中自己实现数据加载逻辑（见场景二的示例代码）。</p><p>完整流程是这样的：</p><ol><li>worker 判定热点 → 推送通知到所有 client</li><li>client 在 Caffeine 中存入<strong>魔术值</strong>（<code>0x12fcf76</code>）作为占位</li><li>业务代码调用 <code>isHotKey(key)</code> 返回 <code>true</code></li><li>业务代码调用 <code>get(key)</code> → 返回 null（因为是魔术值占位，还没填实际数据）</li><li>业务代码从 Redis / DB 加载真实数据</li><li>调用 <code>smartSet(key, value)</code> 写入本地缓存</li><li>后续请求直接命中本地缓存</li></ol><h2 id="Q-和-Redis-Cluster-的热点-key-问题有什么关系？"><a href="#Q-和-Redis-Cluster-的热点-key-问题有什么关系？" class="headerlink" title="Q: 和 Redis Cluster 的热点 key 问题有什么关系？"></a>Q: 和 Redis Cluster 的热点 key 问题有什么关系？</h2><p>Redis Cluster 的热点 key 问题是：某个 key 的访问量集中到一个分片节点，导致该节点 CPU / 带宽打满。JD-HotKey 的解决思路是<strong>在应用层就把热点请求拦截掉</strong>——数据缓存在 JVM 内存中，根本不会打到 Redis。两者是不同层面的解决方案。</p><h2 id="Q-生产环境怎么部署？"><a href="#Q-生产环境怎么部署？" class="headerlink" title="Q: 生产环境怎么部署？"></a>Q: 生产环境怎么部署？</h2><p>推荐架构：</p><ul><li><strong>etcd</strong>：3 节点集群（奇数，容忍 1 节点故障）</li><li><strong>worker</strong>：至少 2 个实例（worker 之间不通信，各自独立计算。client 通过 Hash 将不同 key 路由到不同 worker，天然负载均衡）</li><li><strong>dashboard</strong>：1-2 个实例（Web 控制台，不承载核心流量）</li><li><strong>client</strong>：嵌入每个业务应用实例</li></ul><h2 id="Q-和-JetCache-的两级缓存（BOTH）怎么配合？"><a href="#Q-和-JetCache-的两级缓存（BOTH）怎么配合？" class="headerlink" title="Q: 和 JetCache 的两级缓存（BOTH）怎么配合？"></a>Q: 和 JetCache 的两级缓存（BOTH）怎么配合？</h2><p>它们不冲突，可以一起用：</p><ul><li><strong>JetCache BOTH</strong>：适用于已知的高频读数据，提供 L1 + L2 两级缓存 + 自动刷新</li><li><strong>JD-HotKey</strong>：适用于不可预知的突发热点，自动发现并缓存</li></ul><p>配合方式：JetCache 负责常规缓存，JD-HotKey 负责突发热点保护。当 JD-HotKey 判定某 key 为热点时，数据直接走 JVM 内存，连 JetCache 的 L2（Redis）都不需要访问。</p><hr><h1 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h1><p>JD-HotKey 的核心价值在于<strong>自动发现热点</strong>——这是它区别于其他缓存方案的最大优势。你不需要提前知道哪些 key 会变热，系统会根据实际访问量自动判定、自动推送、自动过期释放。</p><div class="table-container"><table><thead><tr><th>场景</th><th>推荐方案</th></tr></thead><tbody><tr><td>已知的固定高频数据</td><td>Caffeine 或 JetCache BOTH</td></tr><tr><td>不可预知的突发热点</td><td><strong>JD-HotKey</strong></td></tr><tr><td>Redis 热点节点保护</td><td>JD-HotKey（应用层拦截）或 Redis 热点分片</td></tr><tr><td>热用户 / 热接口探测</td><td><strong>JD-HotKey</strong></td></tr></tbody></table></div><p>如果你的业务中存在”不知道什么时候会突然变热”的场景（电商秒杀、热搜话题、突发新闻），JD-HotKey 能帮你自动识别并保护这些热点 key，将请求拦截在 JVM 内存中，避免打垮 Redis 和数据库。</p><p>但它也不是万能的——etcd 的部署运维、依赖冲突的处理、阈值的调优，都需要一定的投入。建议先在非核心业务上试水，验证效果后再推广到核心链路。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;一、组件介绍&quot;&gt;&lt;a href=&quot;#一、组件介绍&quot; class=&quot;headerlink&quot; title=&quot;一、组件介绍&quot;&gt;&lt;/a&gt;一、组件介绍&lt;/h1&gt;&lt;p&gt;JD-HotKey 是京东开源的&lt;strong&gt;实时热点 key 探测与缓存中间件&lt;/strong&gt;（&lt;a </summary>
      
    
    
    
    <category term="中间件" scheme="https://donehub.github.io/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
    
    
    <category term="JD-HotKey" scheme="https://donehub.github.io/tags/JD-HotKey/"/>
    
  </entry>
  
  <entry>
    <title>Multi-Agent 通信深度解析</title>
    <link href="https://donehub.github.io/2026/05/30/multi-agent-communication-deep-dive/"/>
    <id>https://donehub.github.io/2026/05/30/multi-agent-communication-deep-dive/</id>
    <published>2026-05-29T16:00:00.000Z</published>
    <updated>2026-07-08T04:10:22.792Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>当系统从单个 Agent 进化到多个 Agent 协作时，一个核心问题就会浮出水面：Agent 之间怎么通信？</p><p>通信设计直接决定了你的 Multi-Agent 系统是高效协作还是混乱互怼。</p><p>打个比方：单个 Agent 像一个独立工作的程序员，能力再强也有天花板。Multi-Agent 就像一个开发团队——你需要设计好团队的沟通机制：谁向谁汇报？用什么格式交流？出了问题怎么兜底？这些决策决定了团队是 1+1&gt;2 还是 1+1&lt;0。</p><p>这篇文章会系统性地拆解 Multi-Agent 通信的三个核心维度：拓扑（谁跟谁聊）、机制（数据怎么流）、协议（聊什么格式）**，并深入分析工程落地中的失败模式和成本控制。</p><hr><h2 id="一、本质问题"><a href="#一、本质问题" class="headerlink" title="一、本质问题"></a>一、本质问题</h2><p>Multi-Agent 的通信，本质上不是”数据包交换”，而是”认知语义的传递”与”任务状态的对齐”。</p><p>传统分布式系统里，通信关心的是：HTTP 还是 gRPC？JSON 还是 Protobuf？超时时间设多少？这些当然重要，但在 Multi-Agent 系统里，它们只是基础设施层面的问题。</p><p>真正让 Multi-Agent 通信变得独特且困难的，是以下三个问题：</p><div class="table-container"><table><thead><tr><th>层次</th><th>传统分布式系统</th><th>Multi-Agent 系统</th></tr></thead><tbody><tr><td>语义层</td><td>结构化数据，格式固定</td><td>自然语言 + 推理链，语义模糊</td></tr><tr><td>状态层</td><td>无状态或简单状态机</td><td>复杂的认知状态（信念、意图、置信度）</td></tr><tr><td>容错层</td><td>重试 + 降级</td><td>语义漂移检测 + 认知纠偏</td></tr></tbody></table></div><p>比如：当 Agent A 告诉 Agent B “这个 Bug 很严重”时，B 需要理解的不只是”严重”这两个字，还包括 A 判断严重的依据、影响的范围、以及 A 对修复难度的预判。这种心智模型的传递，是传统 RPC 调用里不存在的。</p><p>基于这一点，我们再来看具体的通信设计。</p><hr><h2 id="二、通信拓扑：谁跟谁聊？"><a href="#二、通信拓扑：谁跟谁聊？" class="headerlink" title="二、通信拓扑：谁跟谁聊？"></a>二、通信拓扑：谁跟谁聊？</h2><p>拓扑设计是架构的第一个决策。它决定了系统的耦合度、容错性和扩展上限。</p><h3 id="2-1-中心化编排（Orchestrator-Supervisor）"><a href="#2-1-中心化编排（Orchestrator-Supervisor）" class="headerlink" title="2.1 中心化编排（Orchestrator / Supervisor）"></a>2.1 中心化编排（Orchestrator / Supervisor）</h3><p>这是最常见的模式，也最容易理解和实现。</p><figure class="highlight plain"><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">            │  Supervisor Agent│</span><br><span class="line">            │  （任务调度中心）  │</span><br><span class="line">            └───┬────┬────┬───┘</span><br><span class="line">                │    │    │</span><br><span class="line">       ┌────────┘    │    └────────┐</span><br><span class="line">       ▼             ▼             ▼</span><br><span class="line">┌────────────┐ ┌────────────┐ ┌────────────┐</span><br><span class="line">│ Researcher │ │  Coder     │ │  Reviewer  │</span><br><span class="line">│   Agent    │ │   Agent    │ │   Agent    │</span><br><span class="line">└────────────┘ └────────────┘ └────────────┘</span><br></pre></td></tr></table></figure><p><strong>核心逻辑</strong>：一个 Supervisor 负责接收任务、拆解子任务、分配给 Worker Agent、收集结果并汇总。Worker 之间不直接通信，所有信息流都经过 Supervisor 中转。</p><p><strong>生活类比</strong>：这就像一个项目经理带团队。需求先交给 PM，PM 拆分任务分给开发、测试、设计，最后由 PM 汇总交付。开发人员不需要直接跟测试沟通——PM 是信息枢纽。</p><p><strong>优势</strong>：</p><ul><li><strong>逻辑清晰</strong>：控制流一目了然，容易调试</li><li><strong>可观测性强</strong>：Supervisor 知道全局状态，方便监控和日志</li><li><strong>易于加入 Human-in-the-loop</strong>：人类可以在 Supervisor 层做审批</li></ul><p><strong>致命缺陷</strong>：</p><ul><li><strong>单点瓶颈</strong>：Supervisor 挂了，整个系统瘫痪</li><li><strong>性能天花板</strong>：所有通信都经过 Supervisor，吞吐量受限</li><li><strong>上下文爆炸</strong>：Supervisor 需要维护所有 Worker 的状态，上下文窗口压力巨大</li></ul><p><strong>适用场景</strong>：任务流程明确、Agent 数量较少（3-5 个）的场景。比如”搜索 → 分析 → 生成报告”这种线性流水线。</p><h3 id="2-2-去中心化协作（Peer-to-Peer-群聊模式）"><a href="#2-2-去中心化协作（Peer-to-Peer-群聊模式）" class="headerlink" title="2.2 去中心化协作（Peer-to-Peer / 群聊模式）"></a>2.2 去中心化协作（Peer-to-Peer / 群聊模式）</h3><p>所有 Agent 地位平等，直接互相通信，没有中心节点。</p><figure class="highlight plain"><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><br><span class="line">│  Agent A   │◄───►│  Agent B   │</span><br><span class="line">└─────┬──────┘     └─────┬──────┘</span><br><span class="line">      │                   │</span><br><span class="line">      │    ┌────────────┐ │</span><br><span class="line">      └───►│  Agent C   │◄┘</span><br><span class="line">           └────────────┘</span><br></pre></td></tr></table></figure><p><strong>核心逻辑</strong>：每个 Agent 都能发起对话、响应请求、推荐下一个处理者。没有谁是”老板”，大家通过协商决定谁来干活。</p><p><strong>生活类比</strong>：这像一个开源项目的维护者社区。每个人都能提 Issue、Review PR、Merge 代码，没有绝对的上下级关系。</p><p><strong>优势</strong>：</p><ul><li><strong>扩展性强</strong>：新增 Agent 不需要修改中心节点</li><li><strong>灵活路由</strong>：Agent 之间可以动态发现、动态协作</li><li><strong>无单点故障</strong>：某个 Agent 挂了，其他 Agent 可以继续工作</li></ul><p><strong>致命缺陷</strong>：</p><ul><li><strong>无限循环风险</strong>：A 让 B 做，B 觉得该 C 做，C 又踢回给 A → 死循环</li><li><strong>上下文爆炸</strong>：群聊消息指数级增长，每个 Agent 都要维护所有对话历史</li><li><strong>收敛性差</strong>：没有”裁判”，很难判断任务什么时候算完成</li></ul><p><strong>防循环的工程手段</strong>：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 设置最大轮次</span></span><br><span class="line">groupchat = GroupChat(max_round=<span class="number">10</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 加入"终止条件"检测</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">should_terminate</span><span class="params">(messages)</span>:</span></span><br><span class="line">    last_msgs = messages[<span class="number">-3</span>:]</span><br><span class="line">    <span class="comment"># 如果最近 3 条消息都在重复相同观点，终止</span></span><br><span class="line">    <span class="keyword">return</span> len(set(m[<span class="string">"content"</span>] <span class="keyword">for</span> m <span class="keyword">in</span> last_msgs)) == <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 引入"主持人"角色做仲裁</span></span><br><span class="line">moderator = AssistantAgent(</span><br><span class="line">    name=<span class="string">"Moderator"</span>,</span><br><span class="line">    system_message=<span class="string">"你是讨论主持人。当讨论陷入循环时，做出最终决策。"</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><p><strong>适用场景</strong>：头脑风暴、多角色辩论、代码审查等需要<strong>多视角碰撞</strong>的场景。</p><h3 id="2-3-层次化混合（Hierarchical）"><a href="#2-3-层次化混合（Hierarchical）" class="headerlink" title="2.3 层次化混合（Hierarchical）"></a>2.3 层次化混合（Hierarchical）</h3><p>现实中的大型系统，往往是两者的结合——分层。</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">      ┌───────────────┐</span><br><span class="line">      │   CEO Agent   │</span><br><span class="line">      │  （战略规划）   │</span><br><span class="line">      └───┬───────┬───┘</span><br><span class="line">          │       │</span><br><span class="line">┌─────────▼─┐   ┌─▼──────────┐</span><br><span class="line">│ VP-研发    │   │ VP-运营     │</span><br><span class="line">│ （中层管理）│   │ （中层管理） │</span><br><span class="line">└──┬────┬───┘   └──┬────┬────┘</span><br><span class="line">   │    │           │    │</span><br><span class="line"> ┌─▼─┐┌─▼─┐      ┌─▼─┐┌─▼─┐</span><br><span class="line"> │Dev││QA │       │Mkt││CS │</span><br><span class="line"> └───┘└───┘       └───┘└───┘</span><br></pre></td></tr></table></figure><p><strong>核心逻辑</strong>：顶层 Agent 做任务拆解和战略规划，中层 Agent 做子任务协调，底层 Agent 执行具体操作。每层只跟相邻层直接通信。</p><p><strong>生活类比</strong>：这就是公司的组织架构。CEO 定方向，VP 拆目标，一线执行。你不会希望 CEO 直接管到实习生——层级是管理复杂度的利器。</p><p><strong>关键设计原则</strong>：</p><div class="table-container"><table><thead><tr><th>原则</th><th>说明</th></tr></thead><tbody><tr><td><strong>信息压缩</strong></td><td>每层向上汇报时，要压缩信息。底层报”3 个 API 报错”，中层报”接口层有 3 个异常”，顶层只需要知道”系统存在稳定性风险”</td></tr><tr><td><strong>上下文隔离</strong></td><td>每层只维护自己需要的上下文，避免全局状态膨胀</td></tr><tr><td><strong>委托边界</strong></td><td>明确每层的决策权限。底层不需要请示中层就能做的小事，就不要上报</td></tr></tbody></table></div><p><strong>适用场景</strong>：大型企业级系统，如金融风控（合规层 → 策略层 → 执行层）、智能客服（路由层 → 业务层 → 工具层）。</p><h3 id="2-4-三种拓扑对比"><a href="#2-4-三种拓扑对比" class="headerlink" title="2.4 三种拓扑对比"></a>2.4 三种拓扑对比</h3><div class="table-container"><table><thead><tr><th>维度</th><th>中心化编排</th><th>去中心化协作</th><th>层次化混合</th></tr></thead><tbody><tr><td><strong>耦合度</strong></td><td>高（Worker 依赖 Supervisor）</td><td>低（Agent 独立）</td><td>中（层级内紧耦合，层级间松耦合）</td></tr><tr><td><strong>容错性</strong></td><td>差（单点故障）</td><td>好（无单点）</td><td>中（某层故障可降级）</td></tr><tr><td><strong>可观测性</strong></td><td>好（中心节点全局可见）</td><td>差（信息分散）</td><td>中（分层可见）</td></tr><tr><td><strong>扩展性</strong></td><td>差（中心节点是瓶颈）</td><td>好（动态加入）</td><td>好（横向+纵向扩展）</td></tr><tr><td><strong>实现复杂度</strong></td><td>低</td><td>高</td><td>中</td></tr><tr><td><strong>适用 Agent 数</strong></td><td>3-5 个</td><td>5-10 个</td><td>10+ 个</td></tr></tbody></table></div><hr><h2 id="三、通信机制：数据怎么流动？"><a href="#三、通信机制：数据怎么流动？" class="headerlink" title="三、通信机制：数据怎么流动？"></a>三、通信机制：数据怎么流动？</h2><p>拓扑解决的是”谁跟谁聊”，机制解决的是”数据怎么从 A 到 B”。</p><h3 id="3-1-基于共享状态（State-Based）"><a href="#3-1-基于共享状态（State-Based）" class="headerlink" title="3.1 基于共享状态（State-Based）"></a>3.1 基于共享状态（State-Based）</h3><p>这是 LangGraph 的核心设计哲学，也是目前最流行的方案。</p><p><strong>核心思想</strong>：Agent 之间不直接发送消息，而是共同读写一个全局状态对象。前一个 Agent 更新状态，后一个 Agent 读取状态变化，间接完成通信。</p><figure class="highlight plain"><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><br><span class="line">│ Agent A  │──写──►│  Global State │◄──读──│ Agent B  │</span><br><span class="line">└──────────┘       │  &#123;            │       └──────────┘</span><br><span class="line">                   │   task: &quot;...&quot;, │</span><br><span class="line">┌──────────┐       │   result: &quot;...&quot;,│      ┌──────────┐</span><br><span class="line">│ Agent C  │──写──►│   status: &quot;...&quot;│◄──读──│ Agent D  │</span><br><span class="line">└──────────┘       │  &#125;            │       └──────────┘</span><br><span class="line">                   └──────────────┘</span><br></pre></td></tr></table></figure><p><strong>生活类比</strong>：想象一个共享的 Google Docs。团队成员不需要开会讨论，直接打开文档看最新内容，需要修改就直接编辑。文档本身就是通信媒介。</p><p><strong>优势</strong>：</p><div class="table-container"><table><thead><tr><th>优势</th><th>说明</th></tr></thead><tbody><tr><td><strong>天然支持断点续传</strong></td><td>状态持久化到数据库，崩溃后可以从上次状态恢复</td></tr><tr><td><strong>Human-in-the-loop 友好</strong></td><td>人类可以查看和修改中间状态，再让 Agent 继续</td></tr><tr><td><strong>可观测性强</strong></td><td>每次状态变更都有记录，方便调试和审计</td></tr><tr><td><strong>避免消息丢失</strong></td><td>状态是幂等更新的，不存在”消息丢了”的问题</td></tr></tbody></table></div><p><strong>注意事项</strong>：</p><ul><li><strong>状态冲突</strong>：如果两个 Agent 并发修改同一字段，需要冲突解决策略（如 last-write-wins、merge 函数）</li><li><strong>状态膨胀</strong>：长时间运行的任务，状态会越来越大，需要定期压缩或归档</li></ul><h3 id="3-2-基于消息队列（Message-Queue-Event-Bus）"><a href="#3-2-基于消息队列（Message-Queue-Event-Bus）" class="headerlink" title="3.2 基于消息队列（Message Queue / Event Bus）"></a>3.2 基于消息队列（Message Queue / Event Bus）</h3><p>当你的 Agent 系统需要跨语言、跨进程、跨机器通信时，共享状态就不够用了。这时候需要引入消息中间件。</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">┌───────────┐                    ┌───────────┐</span><br><span class="line">│ Python     │                    │ Java       │</span><br><span class="line">│ Research   │                    │ Recommend  │</span><br><span class="line">│ Agent      │                    │ Agent      │</span><br><span class="line">└─────┬──────┘                    └─────┬──────┘</span><br><span class="line">      │ 发布                             │ 订阅</span><br><span class="line">      ▼                                 ▼</span><br><span class="line">┌───────────────────────────────────────────────┐</span><br><span class="line">│              消息队列 &#x2F; 事件总线                 │</span><br><span class="line">│         (Kafka &#x2F; RabbitMQ &#x2F; Redis Streams)    │</span><br><span class="line">│                                               │</span><br><span class="line">│  Topic: research.results                      │</span><br><span class="line">│  Topic: recommendation.requests               │</span><br><span class="line">│  Topic: code.generation.tasks                 │</span><br><span class="line">└───────────────────────────────────────────────┘</span><br><span class="line">      ▲                                 ▲</span><br><span class="line">      │ 订阅                             │ 发布</span><br><span class="line">┌─────┴──────┐                    ┌─────┴──────┐</span><br><span class="line">│ Python     │                    │ Python     │</span><br><span class="line">│ Coder      │                    │ Summarizer │</span><br><span class="line">│ Agent      │                    │ Agent      │</span><br><span class="line">└────────────┘                    └────────────┘</span><br></pre></td></tr></table></figure><p><strong>生活类比</strong>：这像公司的邮件系统 + 公告板。你需要别的团队配合时，发邮件（发布消息）到对方的收件箱（Topic），对方在自己方便的时候查看并处理（异步消费）。不需要面对面沟通。</p><p><strong>与共享状态的核心区别</strong>：</p><div class="table-container"><table><thead><tr><th>维度</th><th>共享状态</th><th>消息队列</th></tr></thead><tbody><tr><td><strong>通信模式</strong></td><td>隐式（通过读写状态）</td><td>显式（发送/接收消息）</td></tr><tr><td><strong>耦合方式</strong></td><td>数据耦合（共享同一份状态）</td><td>消息耦合（约定消息格式）</td></tr><tr><td><strong>时效性</strong></td><td>最终一致</td><td>可精确控制（同步/异步）</td></tr><tr><td><strong>跨语言</strong></td><td>困难（需要共享运行时）</td><td>天然支持（消息是通用格式）</td></tr><tr><td><strong>失败恢复</strong></td><td>状态快照恢复</td><td>消息重发（ACK 机制）</td></tr><tr><td><strong>适用场景</strong></td><td>单进程、强一致性</td><td>多进程/多语言、异步解耦</td></tr></tbody></table></div><h3 id="3-3-基于共享向量记忆（Shared-Memory-RAG）"><a href="#3-3-基于共享向量记忆（Shared-Memory-RAG）" class="headerlink" title="3.3 基于共享向量记忆（Shared Memory / RAG）"></a>3.3 基于共享向量记忆（Shared Memory / RAG）</h3><p>这是一种隐式通信方式：Agent 之间不直接交换数据，而是通过一个共享的向量知识库来间接协作。</p><figure class="highlight plain"><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">│ Agent A  │──向量化写入────────│ 向量数据库 │</span><br><span class="line">│(信息生产者)│                   │(共享记忆)  │</span><br><span class="line">└──────────┘                    └─────┬────┘</span><br><span class="line">                                      │</span><br><span class="line">                                语义检索 │</span><br><span class="line">                                      │</span><br><span class="line">                                ┌─────▼────┐</span><br><span class="line">                                │ Agent B  │</span><br><span class="line">                                │(信息消费者)│</span><br><span class="line">                                └──────────┘</span><br></pre></td></tr></table></figure><p><strong>核心逻辑</strong>：Agent A 将中间推理结果、观察到的事实、学到的经验向量化后存入向量库。Agent B 在需要时，通过语义检索主动”拉取”相关信息。</p><p><strong>生活类比</strong>：这像公司的知识库（Confluence / Notion）。前人在项目结束后把经验写成文档，后来的人遇到类似问题时去搜索，找到相关文档后借鉴。写文档的人和读文档的人不需要直接沟通。</p><p><strong>适用场景</strong>：</p><ul><li><strong>长期记忆</strong>：Agent 需要记住之前学到的东西</li><li><strong>跨任务知识复用</strong>：不同任务之间共享经验</li><li><strong>隐式协作</strong>：Agent 之间不需要实时交互，各自独立工作</li></ul><p><strong>关键设计点</strong>：</p><div class="table-container"><table><thead><tr><th>设计点</th><th>建议</th></tr></thead><tbody><tr><td><strong>元数据过滤</strong></td><td>一定要给向量加元数据（来源 Agent、时间戳、类型），否则检索结果会混入无关信息</td></tr><tr><td><strong>遗忘机制</strong></td><td>不能只增不减，需要定期清理过期或低相关度的记忆</td></tr><tr><td><strong>冲突检测</strong></td><td>不同 Agent 可能写入矛盾的信息，需要版本控制或置信度排序</td></tr></tbody></table></div><hr><h2 id="四、通信协议：聊什么格式？"><a href="#四、通信协议：聊什么格式？" class="headerlink" title="四、通信协议：聊什么格式？"></a>四、通信协议：聊什么格式？</h2><p>拓扑和机制解决的是”通道”问题，协议解决的是”内容”问题——Agent 之间传什么格式的消息，才能确保对方准确理解？</p><h3 id="4-1-纯文本通信（最原始，也最脆弱）"><a href="#4-1-纯文本通信（最原始，也最脆弱）" class="headerlink" title="4.1 纯文本通信（最原始，也最脆弱）"></a>4.1 纯文本通信（最原始，也最脆弱）</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># Agent A 发给 Agent B 的消息</span></span><br><span class="line">message = <span class="string">"帮我查一下这个 Bug，我觉得可能是数据库连接池的问题，你看看是不是 max_connections 设太小了"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Agent B 收到的就是这段自然语言，需要自己解析意图</span></span><br><span class="line"><span class="comment"># 问题：B 怎么知道 A 是要"查询"还是"修复"？"我觉得"说明 A 的置信度不高？</span></span><br></pre></td></tr></table></figure><p><strong>问题</strong>：</p><ul><li>意图不明确（是要查询？修复？还是只是讨论？）</li><li>没有结构，下游 Agent 解析困难</li><li>无法传递置信度、优先级等元信息</li></ul><h3 id="4-2-结构化工具调用（Structured-Tool-Call）"><a href="#4-2-结构化工具调用（Structured-Tool-Call）" class="headerlink" title="4.2 结构化工具调用（Structured Tool Call）"></a>4.2 结构化工具调用（Structured Tool Call）</h3><p>这是目前业界的主流做法。把 Agent 之间的通信标准化为函数调用格式，充分利用 LLM 原生的 Function Calling 能力。</p><figure class="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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"tool_call_id"</span>: <span class="string">"call_abc123"</span>,</span><br><span class="line">  <span class="attr">"name"</span>: <span class="string">"delegate_to_agent"</span>,</span><br><span class="line">  <span class="attr">"arguments"</span>: &#123;</span><br><span class="line">    <span class="attr">"target_agent"</span>: <span class="string">"code_reviewer"</span>,</span><br><span class="line">    <span class="attr">"task_type"</span>: <span class="string">"review"</span>,</span><br><span class="line">    <span class="attr">"payload"</span>: &#123;</span><br><span class="line">      <span class="attr">"code"</span>: <span class="string">"def calculate_price(...): ..."</span>,</span><br><span class="line">      <span class="attr">"review_focus"</span>: <span class="string">"security"</span>,</span><br><span class="line">      <span class="attr">"priority"</span>: <span class="string">"high"</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">"context"</span>: &#123;</span><br><span class="line">      <span class="attr">"parent_task"</span>: <span class="string">"修复价格计算的安全漏洞"</span>,</span><br><span class="line">      <span class="attr">"requester_agent"</span>: <span class="string">"code_generator"</span>,</span><br><span class="line">      <span class="attr">"deadline"</span>: <span class="string">"5min"</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>核心优势</strong>：</p><ul><li><strong>意图明确</strong>：<code>name</code> 字段直接说明要做什么</li><li><strong>参数结构化</strong>：下游 Agent 不需要”猜”参数</li><li><strong>可追踪</strong>：<code>tool_call_id</code> 让每次调用都可溯源</li><li><strong>LLM 原生支持</strong>：直接利用 Function Calling，不需要额外的解析层</li></ul><h3 id="4-3-心智模型传递（Theory-of-Mind-CoT-共享）"><a href="#4-3-心智模型传递（Theory-of-Mind-CoT-共享）" class="headerlink" title="4.3 心智模型传递（Theory-of-Mind / CoT 共享）"></a>4.3 心智模型传递（Theory-of-Mind / CoT 共享）</h3><p>这是进阶玩法。不只要传递”结果”，还要传递”推理过程”和”置信度”。</p><figure class="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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"from"</span>: <span class="string">"research_agent"</span>,</span><br><span class="line">  <span class="attr">"to"</span>: <span class="string">"decision_agent"</span>,</span><br><span class="line">  <span class="attr">"content"</span>: <span class="string">"推荐使用 Redis 作为缓存层"</span>,</span><br><span class="line">  <span class="attr">"reasoning"</span>: &#123;</span><br><span class="line">    <span class="attr">"steps"</span>: [</span><br><span class="line">      <span class="string">"分析了数据访问模式：读多写少，比例约 100:1"</span>,</span><br><span class="line">      <span class="string">"评估了数据一致性要求：允许秒级延迟"</span>,</span><br><span class="line">      <span class="string">"对比了 Redis 和 Memcached：Redis 支持更丰富的数据结构"</span>,</span><br><span class="line">      <span class="string">"考虑了团队技术栈：后端团队有 Redis 使用经验"</span></span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">"assumptions"</span>: [</span><br><span class="line">      <span class="string">"日活用户不超过 100 万"</span>,</span><br><span class="line">      <span class="string">"缓存数据不需要强一致性"</span></span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">"alternatives_considered"</span>: [</span><br><span class="line">      &#123;<span class="attr">"option"</span>: <span class="string">"Memcached"</span>, <span class="attr">"rejected_reason"</span>: <span class="string">"数据结构支持有限"</span>&#125;,</span><br><span class="line">      &#123;<span class="attr">"option"</span>: <span class="string">"本地缓存"</span>, <span class="attr">"rejected_reason"</span>: <span class="string">"多实例部署，本地缓存无法共享"</span>&#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">"confidence"</span>: <span class="number">0.82</span>,</span><br><span class="line">  <span class="attr">"risk_assessment"</span>: &#123;</span><br><span class="line">    <span class="attr">"level"</span>: <span class="string">"low"</span>,</span><br><span class="line">    <span class="attr">"details"</span>: <span class="string">"Redis 单点故障风险可通过 Sentinel 解决"</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>为什么这很重要？</strong></p><p>因为下游 Agent 需要根据置信度来决定下一步行动：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">decision_agent</span><span class="params">(received_message)</span>:</span></span><br><span class="line">    confidence = received_message[<span class="string">"confidence"</span>]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> confidence &gt;= <span class="number">0.8</span>:</span><br><span class="line">        <span class="comment"># 高置信度，直接采纳</span></span><br><span class="line">        <span class="keyword">return</span> execute(received_message[<span class="string">"content"</span>])</span><br><span class="line">    <span class="keyword">elif</span> confidence &gt;= <span class="number">0.5</span>:</span><br><span class="line">        <span class="comment"># 中等置信度，交给另一个 Agent 复核</span></span><br><span class="line">        <span class="keyword">return</span> delegate_to(<span class="string">"reviewer_agent"</span>, received_message)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="comment"># 低置信度，要求重新调研</span></span><br><span class="line">        <span class="keyword">return</span> delegate_to(<span class="string">"research_agent"</span>, &#123;</span><br><span class="line">            <span class="string">"action"</span>: <span class="string">"redo_research"</span>,</span><br><span class="line">            <span class="string">"feedback"</span>: received_message[<span class="string">"reasoning"</span>]</span><br><span class="line">        &#125;)</span><br></pre></td></tr></table></figure><p><strong>生活类比</strong>：这像医生会诊。一个医生不能只说”我觉得是肺炎”，他需要说”基于 X 光片和血常规结果（推理依据），我有 80% 的把握是肺炎（置信度），但也考虑了支气管炎的可能性（备选方案），建议再做 CT 确认（下一步建议）”。</p><h3 id="4-4-标准化-Agent-协议（A2A-MCP）"><a href="#4-4-标准化-Agent-协议（A2A-MCP）" class="headerlink" title="4.4 标准化 Agent 协议（A2A / MCP）"></a>4.4 标准化 Agent 协议（A2A / MCP）</h3><p>目前最重要的行业趋势之一：Agent 通信协议的标准化。</p><p>两个值得关注的协议：</p><div class="table-container"><table><thead><tr><th>协议</th><th>提出者</th><th>定位</th><th>核心概念</th></tr></thead><tbody><tr><td><strong>MCP</strong>（Model Context Protocol）</td><td>Anthropic</td><td>Agent ↔ 工具/数据源</td><td>Resource、Tool、Prompt</td></tr><tr><td><strong>A2A</strong>（Agent-to-Agent）</td><td>Google</td><td>Agent ↔ Agent</td><td>Task、Artifact、Message、StatusUpdate</td></tr></tbody></table></div><p><strong>MCP 解决的是</strong>：Agent 怎么调用外部工具和数据源。它定义了一套标准接口，让不同的 LLM 应用能以统一方式访问工具，类似于 USB-C 之于外设。</p><p><strong>A2A 解决的是</strong>：不同平台、不同供应商的 Agent 之间怎么互通。它定义了 Agent Card（能力描述）、Task（任务生命周期）、Artifact（产出物）等标准对象。</p><figure class="highlight plain"><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">A2A 协议核心对象：</span><br><span class="line"></span><br><span class="line">Agent Card:</span><br><span class="line">  - name: &quot;代码审查 Agent&quot;</span><br><span class="line">  - skills: [&quot;code_review&quot;, &quot;security_audit&quot;]</span><br><span class="line">  - endpoint: &quot;https:&#x2F;&#x2F;api.example.com&#x2F;a2a&quot;</span><br><span class="line"></span><br><span class="line">Task:</span><br><span class="line">  - id: &quot;task-123&quot;</span><br><span class="line">  - status: &quot;submitted&quot; → &quot;working&quot; → &quot;completed&quot; &#x2F; &quot;failed&quot;</span><br><span class="line">  - messages: [输入消息列表]</span><br><span class="line">  - artifacts: [输出产物列表]</span><br><span class="line"></span><br><span class="line">Artifact:</span><br><span class="line">  - type: &quot;code_review_result&quot;</span><br><span class="line">  - content: &#123;...&#125;</span><br><span class="line">  - metadata: &#123;...&#125;</span><br></pre></td></tr></table></figure><p><strong>为什么标准化很重要？</strong></p><p>想象一下这个场景：你的公司用 LangChain 写了 5 个 Agent，合作伙伴用 AutoGen 写了 3 个 Agent，供应商用自研框架写了 2 个 Agent。如果没有标准协议，每两个 Agent 之间都需要写一个”翻译层”——N 个 Agent 需要 N×(N-1)/2 个适配器。有了标准协议，所有 Agent 只需要实现协议接口，N 个 Agent 只需要 N 个适配器。</p><hr><h2 id="五、通信的失败模式：怎么确保”聊不崩”？"><a href="#五、通信的失败模式：怎么确保”聊不崩”？" class="headerlink" title="五、通信的失败模式：怎么确保”聊不崩”？"></a>五、通信的失败模式：怎么确保”聊不崩”？</h2><p>这是工程落地中最关键、也最容易被忽视的部分。你的系统在 Demo 里跑得很好，一上生产就炸，大概率就是通信失败处理没做好。</p><h3 id="5-1-语义漂移（Semantic-Drift）"><a href="#5-1-语义漂移（Semantic-Drift）" class="headerlink" title="5.1 语义漂移（Semantic Drift）"></a>5.1 语义漂移（Semantic Drift）</h3><p><strong>问题</strong>：Agent A 的意图在传递过程中被曲解，Agent B 理解的意思和 A 想表达的不一样。</p><figure class="highlight plain"><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">Agent A: &quot;优化这个函数的性能&quot;（意图：降低时间复杂度）</span><br><span class="line">    ↓ 传递</span><br><span class="line">Agent B: &quot;优化函数命名和代码风格&quot;（理解：改善可读性）</span><br><span class="line">    ↓ 结果</span><br><span class="line">返回了命名更规范但性能没变的代码</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><ul><li><strong>意图确认机制</strong>：Agent B 在执行前先回述自己对任务的理解，让 A 确认</li><li><strong>结构化任务描述</strong>：用 schema 定义任务的”验收标准”，而不是纯自然语言</li></ul><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 结构化的任务描述，避免语义漂移</span></span><br><span class="line">task = &#123;</span><br><span class="line">    <span class="string">"action"</span>: <span class="string">"optimize"</span>,</span><br><span class="line">    <span class="string">"target"</span>: <span class="string">"calculate_price"</span>,</span><br><span class="line">    <span class="string">"objective"</span>: <span class="string">"reduce_time_complexity"</span>,  <span class="comment"># 明确优化目标</span></span><br><span class="line">    <span class="string">"metric"</span>: <span class="string">"execution_time"</span>,</span><br><span class="line">    <span class="string">"current_value"</span>: <span class="string">"O(n^2)"</span>,</span><br><span class="line">    <span class="string">"target_value"</span>: <span class="string">"O(n log n)"</span>,</span><br><span class="line">    <span class="string">"constraint"</span>: <span class="string">"不改变函数签名和返回值"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-上下文溢出（Context-Overflow）"><a href="#5-2-上下文溢出（Context-Overflow）" class="headerlink" title="5.2 上下文溢出（Context Overflow）"></a>5.2 上下文溢出（Context Overflow）</h3><p><strong>问题</strong>：多轮通信导致消息列表越来越长，超出 LLM 的上下文窗口限制。</p><figure class="highlight plain"><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">Round 1: 2,000 tokens</span><br><span class="line">Round 2: 4,500 tokens</span><br><span class="line">Round 3: 9,000 tokens</span><br><span class="line">Round 4: 18,000 tokens</span><br><span class="line">Round 5: 35,000 tokens  ← 接近 GPT-4 的 32K 限制</span><br><span class="line">Round 6: 💥 超出限制</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><div class="table-container"><table><thead><tr><th>策略</th><th>做法</th><th>优缺点</th></tr></thead><tbody><tr><td><strong>滑动窗口</strong></td><td>只保留最近 N 轮消息</td><td>简单，但会丢失早期重要信息</td></tr><tr><td><strong>摘要压缩</strong></td><td>定期将历史消息压缩成摘要</td><td>保留关键信息，但摘要本身消耗 Token</td></tr><tr><td><strong>分层记忆</strong></td><td>短期记忆（最近消息）+ 长期记忆（向量化存储）</td><td>效果最好，但实现复杂</td></tr></tbody></table></div><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">compress_context</span><span class="params">(messages: list, max_tokens: int = <span class="number">4000</span>)</span>:</span></span><br><span class="line">    <span class="string">"""上下文压缩策略"""</span></span><br><span class="line">    <span class="keyword">if</span> count_tokens(messages) &lt;= max_tokens:</span><br><span class="line">        <span class="keyword">return</span> messages</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 1. 保留系统消息和最近 3 轮</span></span><br><span class="line">    system_msgs = [m <span class="keyword">for</span> m <span class="keyword">in</span> messages <span class="keyword">if</span> m[<span class="string">"role"</span>] == <span class="string">"system"</span>]</span><br><span class="line">    recent_msgs = messages[<span class="number">-6</span>:]  <span class="comment"># 最近 3 轮 = 6 条消息</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 2. 中间的历史消息压缩成摘要</span></span><br><span class="line">    old_msgs = messages[len(system_msgs):<span class="number">-6</span>]</span><br><span class="line">    summary = summarize(old_msgs)  <span class="comment"># 用 LLM 生成摘要</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> system_msgs + [&#123;<span class="string">"role"</span>: <span class="string">"system"</span>, <span class="string">"content"</span>: <span class="string">f"历史摘要：<span class="subst">&#123;summary&#125;</span>"</span>&#125;] + recent_msgs</span><br></pre></td></tr></table></figure><h3 id="5-3-死锁与活锁（Deadlock-Livelock）"><a href="#5-3-死锁与活锁（Deadlock-Livelock）" class="headerlink" title="5.3 死锁与活锁（Deadlock / Livelock）"></a>5.3 死锁与活锁（Deadlock / Livelock）</h3><p><strong>死锁</strong>：Agent A 等待 Agent B 的结果，Agent B 等待 Agent C 的结果，Agent C 等待 Agent A 的结果 → 三个都卡住。</p><p><strong>活锁</strong>：Agent A 和 B 互相传递任务，谁都不处理，一直踢皮球 → 系统一直在”忙”但没有进展。</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">死锁示例：</span><br><span class="line">Agent A: &quot;我需要 B 的分析结果才能给出建议&quot;</span><br><span class="line">Agent B: &quot;我需要 C 的数据才能分析&quot;</span><br><span class="line">Agent C: &quot;我需要 A 的建议才能确定数据范围&quot;</span><br><span class="line">→ 循环等待，无人能开始</span><br><span class="line"></span><br><span class="line">活锁示例：</span><br><span class="line">Agent A: &quot;这个任务应该交给 B&quot;</span><br><span class="line">Agent B: &quot;不，应该交给 A&quot;</span><br><span class="line">Agent A: &quot;还是交给 B 吧&quot;</span><br><span class="line">Agent B: &quot;不不不，A 更适合&quot;</span><br><span class="line">→ 无限循环，没有实际工作</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><figure class="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><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 全局超时机制</span></span><br><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">with_timeout</span><span class="params">(coro, timeout_seconds=<span class="number">30</span>)</span>:</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">await</span> asyncio.wait_for(coro, timeout=timeout_seconds)</span><br><span class="line">    <span class="keyword">except</span> asyncio.TimeoutError:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">"error"</span>: <span class="string">"timeout"</span>, <span class="string">"message"</span>: <span class="string">"任务执行超时"</span>&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 循环检测</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LoopDetector</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, max_repeats=<span class="number">3</span>)</span>:</span></span><br><span class="line">        self.history = []</span><br><span class="line">        self.max_repeats = max_repeats</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">check</span><span class="params">(self, message)</span>:</span></span><br><span class="line">        self.history.append(hash(message[<span class="string">"content"</span>]))</span><br><span class="line">        <span class="keyword">if</span> len(self.history) &gt;= self.max_repeats * <span class="number">2</span>:</span><br><span class="line">            <span class="comment"># 检查最近 N 条是否重复</span></span><br><span class="line">            recent = self.history[-self.max_repeats:]</span><br><span class="line">            <span class="keyword">if</span> len(set(recent)) == <span class="number">1</span>:</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">True</span>  <span class="comment"># 检测到循环！</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 强制降级</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">fallback_when_stuck</span><span class="params">(agents_in_loop)</span>:</span></span><br><span class="line">    <span class="string">"""当检测到死锁/活锁时，强制交给更高级的 Agent 处理"""</span></span><br><span class="line">    <span class="keyword">return</span> escalate_to_supervisor(</span><br><span class="line">        task=<span class="string">"检测到 Agent 间通信死锁，请人工介入"</span>,</span><br><span class="line">        context=agents_in_loop</span><br><span class="line">    )</span><br></pre></td></tr></table></figure><h3 id="5-4-错误传播（Error-Propagation）"><a href="#5-4-错误传播（Error-Propagation）" class="headerlink" title="5.4 错误传播（Error Propagation）"></a>5.4 错误传播（Error Propagation）</h3><p><strong>问题</strong>：一个 Agent 的输出错误，会导致下游所有 Agent 基于错误信息做出错误决策。</p><figure class="highlight plain"><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">Agent A（数据收集）: 错误地认为 API 限额是 1000 次&#x2F;小时（实际是 10000 次）</span><br><span class="line">    ↓</span><br><span class="line">Agent B（方案设计）: 基于&quot;1000 次限额&quot;设计了保守的缓存策略</span><br><span class="line">    ↓</span><br><span class="line">Agent C（代码实现）: 实现了不必要的复杂缓存逻辑</span><br><span class="line">    ↓</span><br><span class="line">结果: 过度设计，性能反而下降</span><br></pre></td></tr></table></figure><p><strong>解决方案</strong>：</p><ul><li><strong>置信度传递</strong>：每个 Agent 标注自己输出的置信度，下游根据置信度决定采信程度</li><li><strong>交叉验证</strong>：关键决策让多个 Agent 独立给出答案，投票决定</li><li><strong>溯源机制</strong>：保留完整的推理链，出问题时可以逐级回溯</li></ul><hr><h2 id="六、通信成本控制"><a href="#六、通信成本控制" class="headerlink" title="六、通信成本控制"></a>六、通信成本控制</h2><p>每次 Agent 间的通信都不是免费的。架构师需要在”充分通信”和”成本控制”之间找平衡。</p><h3 id="6-1-通信成本构成"><a href="#6-1-通信成本构成" class="headerlink" title="6.1 通信成本构成"></a>6.1 通信成本构成</h3><div class="table-container"><table><thead><tr><th>成本项</th><th>说明</th><th>量级</th></tr></thead><tbody><tr><td><strong>Token 消耗</strong></td><td>每次通信的输入 + 输出 Token</td><td>主要成本</td></tr><tr><td><strong>API 延迟</strong></td><td>每次 LLM 调用的网络延迟</td><td>0.5-5 秒/次</td></tr><tr><td><strong>错误代价</strong></td><td>一次错误通信导致的重做成本</td><td>可能数倍于正常成本</td></tr></tbody></table></div><h3 id="6-2-成本控制策略"><a href="#6-2-成本控制策略" class="headerlink" title="6.2 成本控制策略"></a>6.2 成本控制策略</h3><p><strong>策略一：按需通信，不要全程直播</strong></p><figure class="highlight plain"><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">❌ 差的做法：Agent A 的每一步都实时通知 Agent B</span><br><span class="line">✅ 好的做法：Agent A 完成阶段性成果后再通知 Agent B</span><br><span class="line"></span><br><span class="line">不是每想了一步就要同步，而是想清楚了一个完整方案再交流。</span><br><span class="line">就像高效的团队协作——不需要每分钟站会，有阶段性产出时再同步。</span><br></pre></td></tr></table></figure><p><strong>策略二：通信分级</strong></p><div class="table-container"><table><thead><tr><th>级别</th><th>方式</th><th>适用场景</th><th>Token 消耗</th></tr></thead><tbody><tr><td><strong>L1 轻量</strong></td><td>结构化信号（状态码/枚举值）</td><td>“任务完成”、”需要帮助”</td><td>极少</td></tr><tr><td><strong>L2 标准</strong></td><td>结果摘要 + 关键数据</td><td>阶段性汇报</td><td>中等</td></tr><tr><td><strong>L3 完整</strong></td><td>完整推理链 + 原始数据</td><td>关键决策、任务交接</td><td>高</td></tr></tbody></table></div><p>不是所有通信都需要完整的推理链。简单的状态同步，一个枚举值就够了。</p><p><strong>策略三：缓存复用</strong></p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 缓存其他 Agent 的历史输出，避免重复请求</span></span><br><span class="line">agent_output_cache = &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_agent_output</span><span class="params">(agent_id: str, task_hash: str)</span>:</span></span><br><span class="line">    cache_key = <span class="string">f"<span class="subst">&#123;agent_id&#125;</span>:<span class="subst">&#123;task_hash&#125;</span>"</span></span><br><span class="line">    <span class="keyword">if</span> cache_key <span class="keyword">in</span> agent_output_cache:</span><br><span class="line">        <span class="keyword">return</span> agent_output_cache[cache_key]  <span class="comment"># 命中缓存</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 未命中，实际调用</span></span><br><span class="line">    result = call_agent(agent_id, task_hash)</span><br><span class="line">    agent_output_cache[cache_key] = result</span><br><span class="line">    <span class="keyword">return</span> result</span><br></pre></td></tr></table></figure><hr><h2 id="七、实战选型指南"><a href="#七、实战选型指南" class="headerlink" title="七、实战选型指南"></a>七、实战选型指南</h2><h3 id="7-1-按场景选拓扑"><a href="#7-1-按场景选拓扑" class="headerlink" title="7.1 按场景选拓扑"></a>7.1 按场景选拓扑</h3><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">你的 Agent 系统是什么场景？</span><br><span class="line">│</span><br><span class="line">├── 线性流水线（A → B → C）</span><br><span class="line">│   └── 中心化编排（Supervisor 模式）</span><br><span class="line">│</span><br><span class="line">├── 多角色讨论&#x2F;辩论</span><br><span class="line">│   └── 去中心化协作（GroupChat 模式）</span><br><span class="line">│</span><br><span class="line">├── 大型复杂系统（&gt;10 个 Agent）</span><br><span class="line">│   └── 层次化混合（Hierarchical 模式）</span><br><span class="line">│</span><br><span class="line">└── 动态任务分配（不知道谁来做）</span><br><span class="line">    └── 去中心化 + 竞争机制（Contract Net 协议）</span><br></pre></td></tr></table></figure><h3 id="7-2-按需求选机制"><a href="#7-2-按需求选机制" class="headerlink" title="7.2 按需求选机制"></a>7.2 按需求选机制</h3><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">你的 Agent 在哪里运行？</span><br><span class="line">│</span><br><span class="line">├── 单进程 &#x2F; 同一运行时</span><br><span class="line">│   └── 共享状态（LangGraph State）</span><br><span class="line">│</span><br><span class="line">├── 多进程 &#x2F; 跨语言</span><br><span class="line">│   └── 消息队列（Redis Streams &#x2F; Kafka）</span><br><span class="line">│</span><br><span class="line">├── 需要长期记忆</span><br><span class="line">│   └── 共享向量记忆（Vector Store）</span><br><span class="line">│</span><br><span class="line">└── 以上都需要</span><br><span class="line">    └── 混合方案（状态 + 消息队列 + 向量记忆）</span><br></pre></td></tr></table></figure><h3 id="7-3-按阶段选协议"><a href="#7-3-按阶段选协议" class="headerlink" title="7.3 按阶段选协议"></a>7.3 按阶段选协议</h3><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">你的项目处于什么阶段？</span><br><span class="line">│</span><br><span class="line">├── MVP &#x2F; 原型阶段</span><br><span class="line">│   └── 结构化 JSON + Tool Call（够用了，别过度设计）</span><br><span class="line">│</span><br><span class="line">├── 生产环境</span><br><span class="line">│   └── 心智模型传递（CoT + 置信度）+ 完整的错误处理</span><br><span class="line">│</span><br><span class="line">└── 跨组织协作</span><br><span class="line">    └── A2A &#x2F; MCP 标准协议（互操作性优先）</span><br></pre></td></tr></table></figure><hr><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>Multi-Agent 通信设计，核心就是回答三个问题：</p><ol><li><strong>拓扑（谁跟谁聊）</strong>：中心化、去中心化、还是层次化？</li><li><strong>机制（数据怎么流）</strong>：共享状态、消息队列、还是向量记忆？</li><li><strong>协议（聊什么格式）</strong>：纯文本、结构化工具调用、还是心智模型传递？</li></ol><p>没有银弹，只有权衡。选择取决于你的<strong>Agent 数量</strong>、<strong>任务复杂度</strong>、<strong>性能要求</strong>和<strong>成本预算</strong>。</p><p>但有几个通用原则：</p><ul><li><strong>能少聊就少聊</strong>：每次通信都有成本，不要过度同步</li><li><strong>结构化优于自然语言</strong>：能用 JSON Schema 就不要用纯文本</li><li><strong>传递推理过程，不只传结果</strong>：置信度和推理链让下游决策更准确</li><li><strong>为失败而设计</strong>：超时、循环检测、降级策略缺一不可</li><li><strong>标准化是投资</strong>：现在多花一周做协议标准化，未来能省几个月适配成本</li></ul><p>Multi-Agent 系统还处于快速发展期，A2A 和 MCP 等标准协议正在逐步成熟。现在投入精力把通信架构设计好，未来接入更广泛的 Agent 生态时，就会觉得打地基阶段都是值得的。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;当系统从单个 Agent 进化到多个 Agent 协作时，一个核心问题就会浮出水面：Agent 之间怎么通信？&lt;/p&gt;
&lt;p&gt;通信设计直接决</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="Multi-Agent 通信" scheme="https://donehub.github.io/tags/Multi-Agent-%E9%80%9A%E4%BF%A1/"/>
    
  </entry>
  
  <entry>
    <title>Flipbook 深度解析：当浏览器不再需要 HTML</title>
    <link href="https://donehub.github.io/2026/05/23/Flipbook%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90-%E5%BD%93%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%8D%E5%86%8D%E9%9C%80%E8%A6%81HTML/"/>
    <id>https://donehub.github.io/2026/05/23/Flipbook%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90-%E5%BD%93%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%8D%E5%86%8D%E9%9C%80%E8%A6%81HTML/</id>
    <published>2026-05-22T16:00:00.000Z</published>
    <updated>2026-05-23T02:35:13.979Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>过去 50 年，人机交互经历了 CLI → GUI → Web 的演进。今天，一个名为 Flipbook 的实验性产品正在悄悄开启第四个时代——<strong>AI 生成界面（AGI，AI-Generated Interface）</strong>。每”页”都是一张 AI 实时生成的图片，没有 HTML，没有 CSS，没有 JavaScript。你看到的一切，都是像素。</p></blockquote><hr><h2 id="一、什么是-Flipbook？"><a href="#一、什么是-Flipbook？" class="headerlink" title="一、什么是 Flipbook？"></a>一、什么是 Flipbook？</h2><p>最近刷到 Shopify CEO Tobi Lütke 转发了一条动态，引起了我的注意。一个叫 <a href="https://flipbook.page/" target="_blank" rel="noopener">flipbook.page</a> 的平台，被描述为：</p><blockquote><p><strong>“An infinite visual browser generated entirely on demand in real time.”</strong><br>（一个完全按需实时生成的无限视觉浏览器）</p></blockquote><p>用一句话概括：</p><blockquote><p><strong>你在 Flipbook 里看到的每一”页”，都是一张 AI 实时生成的图片。点击图中的任何元素，就会生成一张新图片，带你深入探索那个方向。</strong></p></blockquote><p>听起来有点科幻？但它已经上线了。</p><hr><h2 id="二、它长什么样？"><a href="#二、它长什么样？" class="headerlink" title="二、它长什么样？"></a>二、它长什么样？</h2><p>传统浏览器的渲染链路是这样的：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">用户点击链接 → 服务器返回 HTML → 浏览器解析 DOM → CSS 渲染 → JS 执行 → 显示页面</span><br></pre></td></tr></table></figure><p>Flipbook 的链路则完全不同：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">用户点击图片某处 → AI 理解意图 → 实时生成一张新图片 → 显示为&quot;下一页&quot;</span><br></pre></td></tr></table></figure><p>具体来说：</p><ul><li>你打开 flipbook.page，输入一个你想探索的主题</li><li>AI 生成一张精美的信息图——包含文字、图标、图表、插图</li><li><strong>所有文字都是图片像素</strong>，不是 HTML 文本覆盖层</li><li>你点击图中的某个元素（比如一张图表中的某个数据点）</li><li>AI 理解你点击的内容，实时生成一张更深入的图</li><li>如此往复，没有终点</li></ul><p>这就像在探索一张<strong>无限展开的知识地图</strong>，而不是在浏览一个个独立的网页。</p><hr><h2 id="三、核心技术架构"><a href="#三、核心技术架构" class="headerlink" title="三、核心技术架构"></a>三、核心技术架构</h2><h3 id="3-1-两个模型的协同"><a href="#3-1-两个模型的协同" class="headerlink" title="3.1 两个模型的协同"></a>3.1 两个模型的协同</h3><p>Flipbook 背后有两套 AI 系统在协作：</p><div class="table-container"><table><thead><tr><th>系统</th><th>职责</th><th>类比</th></tr></thead><tbody><tr><td><strong>图像生成模型</strong></td><td>根据用户意图，实时绘制每一页</td><td>“画家”</td></tr><tr><td><strong>自定义视频模型</strong></td><td>在页面之间生成平滑过渡动画</td><td>“导演”</td></tr></tbody></table></div><p>用户开启”视频流”模式后，两系统合并为<strong>连续 1080p 视频流</strong>，页面切换不再是跳变，而是平滑的镜头运动。</p><h3 id="3-2-知识从哪来？"><a href="#3-2-知识从哪来？" class="headerlink" title="3.2 知识从哪来？"></a>3.2 知识从哪来？</h3><p>这不是一个”纯幻觉”的生成器。Flipbook 的内容来源有两个：</p><ol><li><strong>代理式网络搜索</strong>（agentic web search）—— 实时从互联网获取真实数据</li><li><strong>图像模型自身的知识库</strong> —— 模型训练时学到的世界知识</li></ol><p>官方自己也说：<em>“事实准确性大致等同于 ChatGPT/Gemini/Claude 的水平。”</em></p><h3 id="3-3-文字也是像素"><a href="#3-3-文字也是像素" class="headerlink" title="3.3 文字也是像素"></a>3.3 文字也是像素</h3><p>有一个细节很有意思：官方专门解释了文字渲染问题。</p><blockquote><p><em>“All text on the screen is rendered as pixels by the image model. There are no text overlays applied to the images.”</em></p></blockquote><p>这意味着图中的每一个字、每一行标题、每一个数字标注，都是图像模型”画”出来的。偶尔会出现文字不够清晰、位置偏移的问题——但这会随着模型迭代而改善。</p><p>换句话说，<strong>文字在这个系统中不再是可复制的文本节点，而是视觉元素的一部分</strong>。</p><hr><h2 id="四、谁在做这件事？"><a href="#四、谁在做这件事？" class="headerlink" title="四、谁在做这件事？"></a>四、谁在做这件事？</h2><p>Flipbook 的创始团队背景很有意思：</p><div class="table-container"><table><thead><tr><th>创始人</th><th>背景</th></tr></thead><tbody><tr><td><strong>Zain Shah</strong></td><td>前 OpenAI 研究员</td></tr><tr><td><strong>Eddie Jiao</strong></td><td>前 Humane、Slack</td></tr><tr><td><strong>Drew Carr</strong></td><td>前 Apple</td></tr></tbody></table></div><p>算力由 <strong>Modal</strong> 赞助，投资方是 <strong>South Park Commons</strong>（这家机构还投了 Notion、Figma 等知名产品）。</p><p>一个前 OpenAI 研究员 + 两个顶尖产品设计师的组合，解释了为什么这个项目既有技术深度，又有极强的交互直觉。</p><hr><h2 id="五、为什么这件事值得兴奋？"><a href="#五、为什么这件事值得兴奋？" class="headerlink" title="五、为什么这件事值得兴奋？"></a>五、为什么这件事值得兴奋？</h2><h3 id="5-1-它打破了一个根深蒂固的假设"><a href="#5-1-它打破了一个根深蒂固的假设" class="headerlink" title="5.1 它打破了一个根深蒂固的假设"></a>5.1 它打破了一个根深蒂固的假设</h3><p>过去 30 年，我们默认了一个前提：<strong>人机交互的界面是由工程师编写代码构建的</strong>。</p><p>无论是早期的 HTML 页面、Flash 动画，还是现在的 React 组件，本质上都是：工程师定义结构 → 浏览器渲染 → 用户交互。</p><p>Flipbook 打破了这个假设：</p><blockquote><p><strong>界面不再是”建造”出来的，而是”生成”出来的。</strong></p></blockquote><p>就像从”手工绘制每一帧动画”进化到”实时渲染引擎”——只不过这里的渲染引擎不是 GPU，而是<strong>大模型</strong>。</p><h3 id="5-2-信息表达的维度被彻底打开"><a href="#5-2-信息表达的维度被彻底打开" class="headerlink" title="5.2 信息表达的维度被彻底打开"></a>5.2 信息表达的维度被彻底打开</h3><p>官方有一段话很打动我：</p><blockquote><p><em>“一张图片价值千言万语，但我们的屏幕上却大多只是文字和彩色方块。”</em></p></blockquote><p>在传统 Web 上，如果你想解释一个复杂概念，你只有几种选择：</p><ul><li>写文字（读者需要理解）</li><li>放图片（需要设计师提前制作）</li><li>做动图/视频（成本高，不够灵活）</li></ul><p>在 Flipbook 里，<strong>如果最有效的表达方式是一个词，你会看到一个词；如果是一幅插图，你会看到一幅插图；如果是一个数据可视化，你会看到一个数据可视化</strong>——AI 会自动选择最适合当前语境的形式。</p><p>这不是在”展示信息”，而是在”选择最佳的信息传达方式”。</p><h3 id="5-3-灵感来自-HyperCard"><a href="#5-3-灵感来自-HyperCard" class="headerlink" title="5.3 灵感来自 HyperCard"></a>5.3 灵感来自 HyperCard</h3><p>Flipbook 被描述为 <strong>“AI 完全实现的 HyperCard”</strong>。</p><p>HyperCard 是 Apple 在 1987 年推出的一个软件，允许用户以卡片式的方式组织知识和导航。它的核心理念是：<strong>知识应该以空间方式探索，而不是线性搜索</strong>。</p><p>这个理念在当时太超前了，最终被万维网（WWW）取代。但 37 年后，AI 让”空间化知识探索”重新有了可能——而且这次不再需要用户手动制作卡片。</p><hr><h2 id="六、未来的惊艳方向"><a href="#六、未来的惊艳方向" class="headerlink" title="六、未来的惊艳方向"></a>六、未来的惊艳方向</h2><p>官方明确表示，Flipbook 目前是一个<strong>实验</strong>。但它规划的演进方向，每一条都足够让人兴奋。</p><h3 id="6-1-一站式交易闭环"><a href="#6-1-一站式交易闭环" class="headerlink" title="6.1 一站式交易闭环"></a>6.1 一站式交易闭环</h3><p>官方原话举例：</p><blockquote><p><em>“现在你用 Flipbook 研究旅行计划，但预订要去别的地方。未来整个过程都可以在 Flipbook 内完成。”</em></p></blockquote><p>想象一下：</p><ul><li>搜索”巴厘岛数字游民生活” → 生成精美信息图（签证、成本、社区）</li><li>点击”Co-working Space” → 生成实时价格表，包含可预订的空间</li><li>点击”预订” → <strong>在图内完成支付和确认</strong></li><li>不需要跳转到 Airbnb、Agoda 或任何第三方 App</li></ul><p><strong>从”信息探索”到”行动执行”的闭环。</strong></p><h3 id="6-2-实时数据流嵌入"><a href="#6-2-实时数据流嵌入" class="headerlink" title="6.2 实时数据流嵌入"></a>6.2 实时数据流嵌入</h3><p>当前页面是”快照式”的图片。但未来可能实现：</p><ul><li>股票价格、汇率、天气 <strong>实时渲染在图中</strong>，每帧都在更新</li><li>航班状态、快递追踪、赛事比分无需刷新，<strong>实时流动</strong></li><li>Flipbook 从”信息探索工具”变成 <strong>“实时动态仪表盘”</strong></li></ul><h3 id="6-3-真正的交互能力"><a href="#6-3-真正的交互能力" class="headerlink" title="6.3 真正的交互能力"></a>6.3 真正的交互能力</h3><p>官方提到了 <em>“more interactive”</em> 和 <em>“take actions and store their own data”</em>：</p><ul><li><strong>表单输入</strong>：直接在生成的图片上打字、选择、拖拽</li><li><strong>状态存储</strong>：Flipbook 拥有自己的”记忆”——购物车、收藏夹、项目草稿</li><li><strong>复杂操作</strong>：在图中直接编辑文档、调整设计</li></ul><p>这意味着<strong>交互能力将内嵌到像素生成的过程中</strong>，不再是 HTML 元素的专利。</p><h3 id="6-4-跨-App-的统一入口（操作系统的替代品）"><a href="#6-4-跨-App-的统一入口（操作系统的替代品）" class="headerlink" title="6.4 跨 App 的统一入口（操作系统的替代品）"></a>6.4 跨 App 的统一入口（操作系统的替代品）</h3><p>这是最大胆的方向。官方说：</p><blockquote><p><em>“我们想象一个世界，你使用的所有工具都像我们生活的世界一样丰富和可视化。”</em></p></blockquote><p>翻译成大白话：<strong>Flipbook 可能成为所有 App 的”元界面”。</strong></p><div class="table-container"><table><thead><tr><th>场景</th><th>现在</th><th>未来（Flipbook）</th></tr></thead><tbody><tr><td>打车</td><td>打开 Uber App → 输入地址 → 确认</td><td>在 Flipbook 里说”叫车去机场” → 生成选择图 → 点击确认</td></tr><tr><td>发邮件</td><td>打开 Gmail → 写邮件 → 发送</td><td>在 Flipbook 里说”给 Alice 发项目更新” → 生成预览图 → 点击发送</td></tr><tr><td>点外卖</td><td>打开外卖 App → 选餐厅 → 下单</td><td>在 Flipbook 里说”点份泰餐” → 生成推荐图 → 点击下单</td></tr></tbody></table></div><p><strong>所有功能都通过自然语言 + 视觉界面调度，不需要打开任何独立 App。</strong></p><p>这不就是 AI 一直在说的”无 App 的未来”吗？</p><h3 id="6-5-个性化实时生成"><a href="#6-5-个性化实时生成" class="headerlink" title="6.5 个性化实时生成"></a>6.5 个性化实时生成</h3><p>当前生成的是通用信息图。未来结合个人数据后：</p><ul><li>你的健康数据 + 运动目标 → <strong>专属于你的</strong>健身计划图</li><li>你的消费习惯 + 预算 → <strong>为你量身定制的</strong>理财建议图</li><li>你的学习进度 + 知识盲区 → <strong>针对你的薄弱点</strong>的教学图</li></ul><p><strong>每个人看到的内容完全不同</strong>，而且是当下即时生成的。</p><h3 id="6-6-多人协作探索"><a href="#6-6-多人协作探索" class="headerlink" title="6.6 多人协作探索"></a>6.6 多人协作探索</h3><ul><li>你探索到一张有价值的信息图 → 生成链接 → 朋友打开后<strong>从同一节点继续探索</strong></li><li>多人同时在同一个”视觉空间”中探索，各自走不同路径</li><li>类似”多人版维基百科”，但导航是视觉空间式的而非链接式的</li></ul><hr><h2 id="七、技术挑战"><a href="#七、技术挑战" class="headerlink" title="七、技术挑战"></a>七、技术挑战</h2><p>当然，Flipbook 要走的路还很长。以下是当前的核心瓶颈：</p><div class="table-container"><table><thead><tr><th>挑战</th><th>当前状态</th><th>突破方向</th></tr></thead><tbody><tr><td><strong>算力成本</strong></td><td>每页实时 AI 生成，极其昂贵</td><td>模型压缩、缓存热点页面、边缘计算</td></tr><tr><td><strong>文字渲染精度</strong></td><td>官方承认”偶尔不完美”</td><td>下一代图像模型的文本能力</td></tr><tr><td><strong>事实准确性</strong></td><td>类似 ChatGPT 水平，可能有幻觉</td><td>RAG + 实时搜索 + 引用溯源</td></tr><tr><td><strong>交互延迟</strong></td><td>生成需要等待时间</td><td>流式生成、预判意图提前生成</td></tr><tr><td><strong>商业化模式</strong></td><td>目前靠赞助算力</td><td>订阅制、按页面消耗计费、B2B</td></tr></tbody></table></div><hr><h2 id="八、个人感受"><a href="#八、个人感受" class="headerlink" title="八、个人感受"></a>八、个人感受</h2><p>说实话，第一次看到 Flipbook 的演示时，我的第一反应是：<strong>这东西真的能用吗？</strong></p><p>但仔细思考后，我发现它触及了一个本质问题——<strong>我们为什么需要浏览器？</strong></p><p>浏览器的核心功能是”获取信息并交互”。传统 Web 用 HTML/CSS/JS 实现了这个功能，但这只是<strong>一种实现方式</strong>，不是<strong>唯一的方式</strong>。</p><p>Flipbook 用 AI 重新定义了”获取信息”的界面形态：<strong>不再是工程师预先写好的页面，而是根据你的意图即时生成的视觉表达。</strong></p><p>这就像从”预先录制的电视节目”进化到”实时互动的直播”——内容不再是固定的，而是随观众需求变化的。</p><hr><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><p>Flipbook 目前可能只是一个实验性产品，但它代表了一个可能改变行业走向的信号：</p><blockquote><p><strong>界面，正在从”被构建”走向”被生成”。</strong></p></blockquote><p>如果说 HTML 定义了 Web 1.0 的界面范式，React 定义了 Web 2.0 的界面范式，那么 Flipbook 可能正在定义 Web 3.0 的界面范式——<strong>AI 生成的实时视觉界面</strong>。</p><p>这不是在取代 Web，而是在 Web 之上叠加了一层新的交互维度。</p><p>正如官方所说：</p><blockquote><p><em>“We wanted a computing experience full of rich beautiful visuals made just for us, generated just in time.”</em></p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;过去 50 年，人机交互经历了 CLI → GUI → Web 的演进。今天，一个名为 Flipbook 的实验性产品正在悄悄开启第四个时代——&lt;strong&gt;AI 生成界面（AGI，AI-Generated Interface）&lt;/strong&gt;</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="有意思的东西" scheme="https://donehub.github.io/tags/%E6%9C%89%E6%84%8F%E6%80%9D%E7%9A%84%E4%B8%9C%E8%A5%BF/"/>
    
  </entry>
  
  <entry>
    <title>OpenCLI 深度解析：让 AI Code Agent 操控任意网站</title>
    <link href="https://donehub.github.io/2026/05/22/opencli-deep-analysis/"/>
    <id>https://donehub.github.io/2026/05/22/opencli-deep-analysis/</id>
    <published>2026-05-21T16:00:00.000Z</published>
    <updated>2026-05-22T03:40:54.569Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、背景"><a href="#一、背景" class="headerlink" title="一、背景"></a>一、背景</h2><p>之前研究 Chrome DevTools MCP 的时候，解决的核心问题是<strong>让 AI 能操控浏览器</strong>。但那个方案有天然的局限性：必须配置 MCP Server、依赖 Chrome 调试端口、每个平台要单独写适配器。</p><p>OpenCLI 把这件事重新做了一遍，而且做得更彻底——它不只是让 AI 能操控浏览器，而是把<strong>任何网站变成标准化的命令行工具</strong>。GitHub 上 22K+ Stars，不是偶然。</p><p>这篇文章的目标很明确：讲清楚 OpenCLI 是什么、架构怎么设计的、以及最核心的部分——<strong>如何在 Claude Code 等 Code Agent 里用它</strong>。</p><hr><h2 id="二、OpenCLI-是什么"><a href="#二、OpenCLI-是什么" class="headerlink" title="二、OpenCLI 是什么"></a>二、OpenCLI 是什么</h2><p>一句话定义：OpenCLI 是一个 AI 原生的 CLI 运行时框架，把任意网站、浏览器会话、Electron 应用统一变成标准化的命令行接口。</p><p>打个比方理解它的定位：</p><div class="table-container"><table><thead><tr><th>场景</th><th>传统做法</th><th>OpenCLI 做法</th></tr></thead><tbody><tr><td>发一篇小红书</td><td>打开浏览器 → 登录 → 上传图片 → 写文案 → 发布</td><td><code>opencli xiaohongshu publish --title &quot;xxx&quot; --content &quot;xxx&quot;</code></td></tr><tr><td>看 B站播放量</td><td>打开 B站创作者中心 → 刷新 → 看数据</td><td><code>opencli bilibili stats</code></td></tr><tr><td>给 Claude Code 说”帮我发篇文章”</td><td>Claude Code 做不到</td><td>Claude Code 通过 OpenCLI 直接完成</td></tr></tbody></table></div><p>核心区别在于：传统做法每次都要手动操作网页，OpenCLI 把这些操作封装成<strong>确定性 CLI 命令</strong>，而且<strong>零 LLM 运行成本</strong>。</p><hr><h2 id="三、架构深度拆解"><a href="#三、架构深度拆解" class="headerlink" title="三、架构深度拆解"></a>三、架构深度拆解</h2><h3 id="3-1-整体架构图"><a href="#3-1-整体架构图" class="headerlink" title="3.1 整体架构图"></a>3.1 整体架构图</h3><figure class="highlight plain"><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">│                   用户 &#x2F; AI Agent                     │</span><br><span class="line">│              (Claude Code &#x2F; Cursor 等)                │</span><br><span class="line">└──────────────────────┬───────────────────────────────┘</span><br><span class="line">                       │ CLI 命令</span><br><span class="line">                       ▼</span><br><span class="line">┌──────────────────────────────────────────────────────┐</span><br><span class="line">│                   OpenCLI CLI 层                      │</span><br><span class="line">│  ┌─────────────┐  ┌──────────────┐  ┌─────────────┐  │</span><br><span class="line">│  │  Plugin     │  │  Adapter     │  │  Session    │  │</span><br><span class="line">│  │  Loader     │  │  Resolver    │  │  Manager    │  │</span><br><span class="line">│  └─────────────┘  └──────────────┘  └─────────────┘  │</span><br><span class="line">└──────────────────────┬───────────────────────────────┘</span><br><span class="line">                       │</span><br><span class="line">          ┌────────────┴────────────┐</span><br><span class="line">          ▼                         ▼</span><br><span class="line">┌────────────────────┐   ┌────────────────────────┐</span><br><span class="line">│  YAML Adapter 引擎  │   │  CDP 通信层             │</span><br><span class="line">│  (编译期智能)       │   │  Chrome DevTools        │</span><br><span class="line">│  生成确定性命令     │   │  Protocol 注入          │</span><br><span class="line">└────────────────────┘   └────────────┬───────────┘</span><br><span class="line">                                      │</span><br><span class="line">                                      ▼</span><br><span class="line">                          ┌───────────────────────┐</span><br><span class="line">                          │  Chrome Extension     │</span><br><span class="line">                          │  (Playwright MCP      │</span><br><span class="line">                          │   Bridge)             │</span><br><span class="line">                          └───────────┬───────────┘</span><br><span class="line">                                      │</span><br><span class="line">                                      ▼</span><br><span class="line">                          ┌───────────────────────┐</span><br><span class="line">                          │  已登录的 Chrome 浏览器 │</span><br><span class="line">                          │  (你的真实用户会话)     │</span><br><span class="line">                          └───────────────────────┘</span><br></pre></td></tr></table></figure><p>这个架构有几个关键设计点，值得拆开细说。</p><h3 id="3-2-核心理念：编译期智能-vs-运行期智能"><a href="#3-2-核心理念：编译期智能-vs-运行期智能" class="headerlink" title="3.2 核心理念：编译期智能 vs 运行期智能"></a>3.2 核心理念：编译期智能 vs 运行期智能</h3><p>这是 OpenCLI 架构里最重要的一个设计选择。</p><p><strong>运行期智能</strong>的意思是：每次执行命令时都让 LLM 实时理解页面结构、决定操作步骤。这种方式灵活，但每次都要消耗 token，而且结果不稳定。</p><p><strong>编译期智能</strong>是 OpenCLI 的选择：Adapter 在生成阶段只解析一次页面结构，产出一个确定性的 YAML 定义。后续所有 CLI 调用都走这个 YAML，<strong>零 LLM 成本，结果可预测</strong>。</p><figure class="highlight yaml"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 一个 Adapter 示例：获取 B站视频播放量</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">bilibili-stats</span></span><br><span class="line"><span class="attr">target:</span> <span class="string">https://member.bilibili.com/platform/home</span></span><br><span class="line"><span class="attr">steps:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">navigate</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">"https://member.bilibili.com/platform/home"</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">extract</span></span><br><span class="line">    <span class="attr">selector:</span> <span class="string">".data-overview .total-views"</span></span><br><span class="line">    <span class="attr">output:</span> <span class="string">total_views</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">extract</span></span><br><span class="line">    <span class="attr">selector:</span> <span class="string">".data-overview .total-fans"</span></span><br><span class="line">    <span class="attr">output:</span> <span class="string">total_fans</span></span><br></pre></td></tr></table></figure><p>这段 YAML 定义好之后，每次执行都是确定的 DOM 选择器匹配，不需要 LLM 参与。只有在 Adapter 开发阶段才需要一次智能解析。</p><h3 id="3-3-CDP-驱动层：为什么不走-Selenium-Playwright-路线"><a href="#3-3-CDP-驱动层：为什么不走-Selenium-Playwright-路线" class="headerlink" title="3.3 CDP 驱动层：为什么不走 Selenium/Playwright 路线"></a>3.3 CDP 驱动层：为什么不走 Selenium/Playwright 路线</h3><p>OpenCLI 选择基于 <strong>Chrome DevTools Protocol (CDP)</strong> 而不是传统的 Selenium 或 Playwright，原因很实际：</p><div class="table-container"><table><thead><tr><th>维度</th><th>Selenium/Playwright</th><th>CDP + Chrome 实例</th></tr></thead><tbody><tr><td>登录态</td><td>需要单独维护 cookies</td><td>直接复用你正在用的 Chrome</td></tr><tr><td>安全</td><td>需要存储账号密码</td><td>零凭证存储</td></tr><tr><td>真实性</td><td>无头浏览器可能被检测</td><td>真实浏览器实例</td></tr><tr><td>开发成本</td><td>要写完整的自动化脚本</td><td>YAML 声明式定义</td></tr></tbody></table></div><p>关键点在于<strong>复用登录态</strong>。你在浏览器里已经登录了知乎、B站、小红书，OpenCLI 直接通过 CDP 连上这个正在运行的实例，不需要重新登录、不需要 API Key、不需要存密码。</p><h3 id="3-4-浏览器扩展：Playwright-MCP-Bridge"><a href="#3-4-浏览器扩展：Playwright-MCP-Bridge" class="headerlink" title="3.4 浏览器扩展：Playwright MCP Bridge"></a>3.4 浏览器扩展：Playwright MCP Bridge</h3><p>CDP 本身只能从外部控制 Chrome，但 OpenCLI 需要双向通信——既要控制页面，也要把页面结构回传给 CLI。这个桥梁由一个 Chrome 扩展承担：</p><p><strong>Playwright MCP Bridge 扩展</strong>负责：</p><ol><li>接收 OpenCLI CLI 的指令（点击、输入、导航）</li><li>在页面中执行对应操作</li><li>返回结构化 DOM 快照（不是截图，是带语义的 DOM 树）</li><li>维护 Session 状态（bind/unbind 标签页）</li></ol><p>这个扩展是轻量级的 micro-daemon 模式，启动 OpenCLI 时自动加载。</p><h3 id="3-5-Session-管理：bind-机制"><a href="#3-5-Session-管理：bind-机制" class="headerlink" title="3.5 Session 管理：bind 机制"></a>3.5 Session 管理：bind 机制</h3><p>Session 是 OpenCLI 连接 CLI 命令和具体浏览器标签页的桥梁：</p><figure class="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><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="comment"># 把当前浏览器某个已登录的标签页绑定到 session</span></span><br><span class="line">opencli browser my-blog <span class="built_in">bind</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看已绑定的 session</span></span><br><span class="line">opencli browser list</span><br><span class="line"></span><br><span class="line"><span class="comment"># 解绑</span></span><br><span class="line">opencli browser my-blog unbind</span><br></pre></td></tr></table></figure><p>绑定之后，所有针对该平台的 CLI 命令都会在这个已登录的标签页里执行。</p><hr><h2 id="四、安装与快速上手"><a href="#四、安装与快速上手" class="headerlink" title="四、安装与快速上手"></a>四、安装与快速上手</h2><h3 id="4-1-环境要求"><a href="#4-1-环境要求" class="headerlink" title="4.1 环境要求"></a>4.1 环境要求</h3><ul><li>Node.js &gt;= 20.0.0</li><li>Chrome / Chromium / Brave / Edge 浏览器</li></ul><h3 id="4-2-安装步骤"><a href="#4-2-安装步骤" class="headerlink" title="4.2 安装步骤"></a>4.2 安装步骤</h3><figure class="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="comment"># 全局安装 OpenCLI</span></span><br><span class="line">npm install -g @jackwener/opencli</span><br><span class="line"></span><br><span class="line"><span class="comment"># 自动配置（检测 Chrome 实例、安装扩展）</span></span><br><span class="line">opencli setup</span><br></pre></td></tr></table></figure><p><code>opencli setup</code> 会做这几件事：</p><ol><li>检测本地 Chrome 调试端口</li><li>下载并加载 Playwright MCP Bridge 扩展</li><li>验证 CLI 与浏览器的连通性</li><li>初始化 <code>~/.opencli</code> 配置目录</li></ol><h3 id="4-3-运行第一个命令"><a href="#4-3-运行第一个命令" class="headerlink" title="4.3 运行第一个命令"></a>4.3 运行第一个命令</h3><figure class="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="comment"># 查看内置适配器列表</span></span><br><span class="line">opencli list</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行一个平台命令（以 B站 为例）</span></span><br><span class="line">opencli bilibili stats</span><br></pre></td></tr></table></figure><hr><h2 id="五、在-Code-Agent-中的使用（重点）"><a href="#五、在-Code-Agent-中的使用（重点）" class="headerlink" title="五、在 Code Agent 中的使用（重点）"></a>五、在 Code Agent 中的使用（重点）</h2><p>这部分是整篇文章的核心。OpenCLI 的真正威力不在于手动敲命令，而在于<strong>让 AI Code Agent 通过它操控任意网站</strong>。</p><h3 id="5-1-为什么-Code-Agent-需要-OpenCLI"><a href="#5-1-为什么-Code-Agent-需要-OpenCLI" class="headerlink" title="5.1 为什么 Code Agent 需要 OpenCLI"></a>5.1 为什么 Code Agent 需要 OpenCLI</h3><p>Claude Code、Cursor 这类 Code Agent 原生能力很强：能写代码、能读文件、能跑测试、能用 git。但有一个明确的边界——<strong>它们不能操作网页</strong>。</p><p>这个边界在实际工作中很要命。举个例子：</p><p>你要把一篇技术博客同步到知乎、公众号、小红书三个平台。用 Claude Code 写文章很快，但发布环节只能手动操作三个平台的后台。OpenCLI 把这个边界打通了。</p><h3 id="5-2-在-Claude-Code-中集成-OpenCLI"><a href="#5-2-在-Claude-Code-中集成-OpenCLI" class="headerlink" title="5.2 在 Claude Code 中集成 OpenCLI"></a>5.2 在 Claude Code 中集成 OpenCLI</h3><h4 id="方式一：直接作为-CLI-工具调用"><a href="#方式一：直接作为-CLI-工具调用" class="headerlink" title="方式一：直接作为 CLI 工具调用"></a>方式一：直接作为 CLI 工具调用</h4><p>Claude Code 本身就能执行 shell 命令，最直接的方式就是在对话中让它执行 <code>opencli</code> 命令：</p><figure class="highlight plain"><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">你: 帮我查看 B站最近的视频数据</span><br><span class="line">Claude Code: 执行 opencli bilibili stats，返回结果...</span><br></pre></td></tr></table></figure><p>这种方式不需要额外配置，只要本机装了 OpenCLI 就行。</p><h4 id="方式二：安装-OpenCLI-Skill"><a href="#方式二：安装-OpenCLI-Skill" class="headerlink" title="方式二：安装 OpenCLI Skill"></a>方式二：安装 OpenCLI Skill</h4><p>OpenCLI 提供了适配 AI Agent 的 Skill 定义，安装后 Agent 能更准确地理解和使用 OpenCLI：</p><figure class="highlight bash"><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="comment"># 在 Claude Code 中安装 OpenCLI skill</span></span><br><span class="line">npx skills add jackwener/opencli</span><br></pre></td></tr></table></figure><p>安装之后，Claude Code 会在执行浏览器相关任务时自动识别 OpenCLI 的可用命令，而不是每次都让你手动指定。</p><h4 id="方式三：在-claude-commands-中自定义命令"><a href="#方式三：在-claude-commands-中自定义命令" class="headerlink" title="方式三：在 .claude/commands 中自定义命令"></a>方式三：在 .claude/commands 中自定义命令</h4><p>结合 Claude Code 的自定义命令系统，可以把常用的 OpenCLI 操作封装成斜杠命令：</p><figure class="highlight yaml"><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"><span class="comment"># .claude/commands/publish-blog.md</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">发布博客到多平台</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"></span><br><span class="line"><span class="string">执行以下命令发布博客：</span></span><br><span class="line"><span class="number">1</span><span class="string">.</span> <span class="string">opencli</span> <span class="string">zhihu</span> <span class="string">publish</span> <span class="string">--title</span> <span class="string">"<span class="template-variable">&#123;&#123;title&#125;&#125;</span>"</span> <span class="string">--content</span> <span class="string">"<span class="template-variable">&#123;&#123;content&#125;&#125;</span>"</span></span><br><span class="line"><span class="number">2</span><span class="string">.</span> <span class="string">opencli</span> <span class="string">weixin</span> <span class="string">publish</span> <span class="string">--title</span> <span class="string">"<span class="template-variable">&#123;&#123;title&#125;&#125;</span>"</span> <span class="string">--content</span> <span class="string">"<span class="template-variable">&#123;&#123;content&#125;&#125;</span>"</span></span><br><span class="line"><span class="number">3</span><span class="string">.</span> <span class="string">opencli</span> <span class="string">xiaohongshu</span> <span class="string">publish</span> <span class="string">--title</span> <span class="string">"<span class="template-variable">&#123;&#123;title&#125;&#125;</span>"</span> <span class="string">--content</span> <span class="string">"<span class="template-variable">&#123;&#123;content&#125;&#125;</span>"</span></span><br></pre></td></tr></table></figure><p>之后在 Claude Code 中只需输入 <code>/publish-blog</code> 就能一键发布。</p><h3 id="5-3-实际工作流示例"><a href="#5-3-实际工作流示例" class="headerlink" title="5.3 实际工作流示例"></a>5.3 实际工作流示例</h3><h4 id="场景：AI-驱动的博客同步工作流"><a href="#场景：AI-驱动的博客同步工作流" class="headerlink" title="场景：AI 驱动的博客同步工作流"></a>场景：AI 驱动的博客同步工作流</h4><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">你: 把 source&#x2F;_posts&#x2F;opencli-deep-analysis.md 这篇文章发到知乎、小红书、B站</span><br><span class="line"></span><br><span class="line">Claude Code 的执行流程:</span><br><span class="line">1. 读取 Markdown 文件内容</span><br><span class="line">2. 根据各平台特点调整格式（知乎支持 Markdown，</span><br><span class="line">   小红书需要短文案+图片，B站专栏有特定结构）</span><br><span class="line">3. 执行 opencli zhihu publish --title &quot;...&quot; --content &quot;...&quot;</span><br><span class="line">4. 执行 opencli xiaohongshu publish --title &quot;...&quot; --content &quot;...&quot;</span><br><span class="line">5. 执行 opencli bilibili publish --title &quot;...&quot; --content &quot;...&quot;</span><br><span class="line">6. 返回各平台的发布结果</span><br></pre></td></tr></table></figure><p>整个过程不需要你打开任何一个浏览器页面。</p><h4 id="场景：数据监控面板"><a href="#场景：数据监控面板" class="headerlink" title="场景：数据监控面板"></a>场景：数据监控面板</h4><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">你: 帮我看看各平台最近一周的数据</span><br><span class="line"></span><br><span class="line">Claude Code:</span><br><span class="line">&gt; opencli bilibili stats --period 7d</span><br><span class="line">&gt; opencli zhihu stats --period 7d</span><br><span class="line">&gt; opencli xiaohongshu stats --period 7d</span><br><span class="line"></span><br><span class="line">汇总输出:</span><br><span class="line">| 平台   | 阅读量  | 点赞 | 评论 |</span><br><span class="line">|--------|--------|------|------|</span><br><span class="line">| B站    | 12,340 | 456  | 89   |</span><br><span class="line">| 知乎   | 8,920  | 312  | 56   |</span><br><span class="line">| 小红书 | 15,600 | 890  | 123  |</span><br></pre></td></tr></table></figure><h4 id="场景：社区运营自动化"><a href="#场景：社区运营自动化" class="headerlink" title="场景：社区运营自动化"></a>场景：社区运营自动化</h4><p>对于数字游民社区的运营工作，OpenCLI 能大幅减少重复劳动：</p><figure class="highlight plain"><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"></span><br><span class="line">Claude Code:</span><br><span class="line">&gt; opencli nomad-community pending-reviews</span><br><span class="line"></span><br><span class="line">返回 3 条待审核内容，逐条展示...</span><br><span class="line"></span><br><span class="line">你: 全部通过</span><br><span class="line"></span><br><span class="line">Claude Code:</span><br><span class="line">&gt; opencli nomad-community approve --all</span><br></pre></td></tr></table></figure><h3 id="5-4-Agent-操作浏览器的技术细节"><a href="#5-4-Agent-操作浏览器的技术细节" class="headerlink" title="5.4 Agent 操作浏览器的技术细节"></a>5.4 Agent 操作浏览器的技术细节</h3><p>理解 Claude Code 通过 OpenCLI 操作浏览器的底层流程，有助于你排查问题和编写自定义插件。</p><p><strong>完整的调用链路</strong>：</p><figure class="highlight plain"><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">Claude Code 发起对话</span><br><span class="line">  → OpenCLI CLI 解析命令</span><br><span class="line">    → 查找对应 Adapter 定义（YAML&#x2F;TS）</span><br><span class="line">      → 通过 CDP 连接 Chrome 实例</span><br><span class="line">        → 扩展在页面中执行操作</span><br><span class="line">          → 返回结构化 DOM 快照</span><br><span class="line">            → Adapter 解析快照提取数据</span><br><span class="line">              → CLI 返回结果给 Claude Code</span><br></pre></td></tr></table></figure><p><strong>结构化 DOM 快照</strong> 是理解这一切的关键。OpenCLI 不是截图给 AI 看，而是把页面转成一个带语义的结构化文本：</p><figure class="highlight plain"><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">[button] &quot;发布文章&quot; (clickable, enabled)</span><br><span class="line">[textbox] &quot;标题&quot; value&#x3D;&quot;OpenCLI 深度解析&quot; (editable)</span><br><span class="line">[textarea] &quot;正文内容&quot; (editable, placeholder&#x3D;&quot;输入正文...&quot;)</span><br><span class="line">[link] &quot;预览&quot; href&#x3D;&quot;&#x2F;preview&quot;</span><br></pre></td></tr></table></figure><p>这种格式对 LLM 极其友好：token 消耗远低于截图，信息密度更高，而且可以直接定位到可交互元素。</p><hr><h2 id="六、插件开发实战"><a href="#六、插件开发实战" class="headerlink" title="六、插件开发实战"></a>六、插件开发实战</h2><p>内置适配器覆盖了主流平台，但你自己的网站或者小众平台需要写自定义插件。</p><h3 id="6-1-插件目录结构"><a href="#6-1-插件目录结构" class="headerlink" title="6.1 插件目录结构"></a>6.1 插件目录结构</h3><figure class="highlight plain"><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">~&#x2F;.opencli&#x2F;plugins&#x2F;</span><br><span class="line">└── my-custom-plugin&#x2F;</span><br><span class="line">    ├── plugin.yaml        # 插件元信息</span><br><span class="line">    ├── adapters&#x2F;</span><br><span class="line">    │   ├── list.yaml      # 列表命令</span><br><span class="line">    │   ├── publish.yaml   # 发布命令</span><br><span class="line">    │   └── stats.yaml     # 统计命令</span><br><span class="line">    └── README.md</span><br></pre></td></tr></table></figure><h3 id="6-2-编写一个完整的-Adapter"><a href="#6-2-编写一个完整的-Adapter" class="headerlink" title="6.2 编写一个完整的 Adapter"></a>6.2 编写一个完整的 Adapter</h3><p>以”从某个网站后台提取文章列表”为例：</p><figure class="highlight yaml"><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"># plugin.yaml</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">my-blog-admin</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">1.0</span><span class="number">.0</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">个人博客后台管理</span> <span class="string">CLI</span></span><br><span class="line"><span class="attr">base_url:</span> <span class="string">https://myblog.com/admin</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># adapters/list.yaml</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">articles</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">列出所有文章</span></span><br><span class="line"><span class="attr">target:</span> <span class="string">"https://myblog.com/admin/articles"</span></span><br><span class="line"><span class="attr">steps:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">navigate</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">"https://myblog.com/admin/articles"</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">wait</span></span><br><span class="line">    <span class="attr">selector:</span> <span class="string">".article-table tbody tr"</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">extract</span></span><br><span class="line">    <span class="attr">selector:</span> <span class="string">".article-table tbody tr"</span></span><br><span class="line">    <span class="attr">fields:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">title</span></span><br><span class="line">        <span class="attr">selector:</span> <span class="string">".article-title"</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">status</span></span><br><span class="line">        <span class="attr">selector:</span> <span class="string">".article-status"</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">published_at</span></span><br><span class="line">        <span class="attr">selector:</span> <span class="string">".published-date"</span></span><br><span class="line">    <span class="attr">output:</span> <span class="string">articles</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">format</span></span><br><span class="line">    <span class="attr">template:</span> <span class="string">"<span class="template-variable">&#123;&#123;title&#125;&#125;</span> | <span class="template-variable">&#123;&#123;status&#125;&#125;</span> | <span class="template-variable">&#123;&#123;published_at&#125;&#125;</span>"</span></span><br><span class="line">    <span class="attr">output:</span> <span class="string">articles</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># adapters/publish.yaml</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">publish</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">发布新文章</span></span><br><span class="line"><span class="attr">target:</span> <span class="string">"https://myblog.com/admin/articles/new"</span></span><br><span class="line"><span class="attr">inputs:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">title</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">string</span></span><br><span class="line">    <span class="attr">required:</span> <span class="literal">true</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">content</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">string</span></span><br><span class="line">    <span class="attr">required:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">steps:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">navigate</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">"https://myblog.com/admin/articles/new"</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">input</span></span><br><span class="line">    <span class="attr">selector:</span> <span class="string">"#article-title"</span></span><br><span class="line">    <span class="attr">value:</span> <span class="string">"<span class="template-variable">&#123;&#123;title&#125;&#125;</span>"</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">input</span></span><br><span class="line">    <span class="attr">selector:</span> <span class="string">"#article-content"</span></span><br><span class="line">    <span class="attr">value:</span> <span class="string">"<span class="template-variable">&#123;&#123;content&#125;&#125;</span>"</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">click</span></span><br><span class="line">    <span class="attr">selector:</span> <span class="string">"#publish-button"</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">wait</span></span><br><span class="line">    <span class="attr">selector:</span> <span class="string">".publish-success"</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">action:</span> <span class="string">extract</span></span><br><span class="line">    <span class="attr">selector:</span> <span class="string">".success-message"</span></span><br><span class="line">    <span class="attr">output:</span> <span class="string">result</span></span><br></pre></td></tr></table></figure><p>写好之后，通过 symlink 链接到 OpenCLI 插件目录：</p><figure class="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="comment"># Linux/Mac</span></span><br><span class="line">ln -s /path/to/my-custom-plugin ~/.opencli/plugins/my-blog-admin</span><br><span class="line"></span><br><span class="line"><span class="comment"># Windows (PowerShell)</span></span><br><span class="line">New-Item -ItemType Junction -Path <span class="string">"<span class="variable">$env</span>:USERPROFILE\.opencli\plugins\my-blog-admin"</span> -Target <span class="string">"D:\path\to\my-custom-plugin"</span></span><br></pre></td></tr></table></figure><p>之后就能直接使用：</p><figure class="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="comment"># 列出文章</span></span><br><span class="line">opencli my-blog-admin articles</span><br><span class="line"></span><br><span class="line"><span class="comment"># 发布文章</span></span><br><span class="line">opencli my-blog-admin publish --title <span class="string">"OpenCLI 深度解析"</span> --content <span class="string">"..."</span></span><br></pre></td></tr></table></figure><h3 id="6-3-在-Claude-Code-中使用自定义插件"><a href="#6-3-在-Claude-Code-中使用自定义插件" class="headerlink" title="6.3 在 Claude Code 中使用自定义插件"></a>6.3 在 Claude Code 中使用自定义插件</h3><p>自定义插件写好后，Claude Code 同样能调用。你可以在 Claude 的对话中直接说：</p><figure class="highlight plain"><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">你: 用 my-blog-admin 插件列出所有已发布的文章</span><br><span class="line">Claude Code: 执行 opencli my-blog-admin articles，返回...</span><br><span class="line"></span><br><span class="line">你: 帮我把标题为 &quot;xxx&quot; 的文章同步到知乎</span><br><span class="line">Claude Code:</span><br><span class="line">1. 执行 opencli my-blog-admin articles 获取文章内容</span><br><span class="line">2. 提取目标文章内容</span><br><span class="line">3. 执行 opencli zhihu publish 发布到知乎</span><br></pre></td></tr></table></figure><hr><h2 id="七、OpenCLI-vs-其他方案对比"><a href="#七、OpenCLI-vs-其他方案对比" class="headerlink" title="七、OpenCLI vs 其他方案对比"></a>七、OpenCLI vs 其他方案对比</h2><p>市面上让 AI 操作网页的方案不少，简单对比一下：</p><div class="table-container"><table><thead><tr><th>方案</th><th>登录态</th><th>LLM 成本</th><th>开发门槛</th><th>稳定性</th></tr></thead><tbody><tr><td><strong>OpenCLI</strong></td><td>复用 Chrome</td><td>零运行成本</td><td>YAML 声明式</td><td>确定性执行</td></tr><tr><td>Chrome DevTools MCP</td><td>复用 Chrome</td><td>每次消耗 token</td><td>需 MCP 配置</td><td>LLM 实时判断</td></tr><tr><td>Playwright 脚本</td><td>需要维护 cookies</td><td>无</td><td>完整编程</td><td>最高但开发成本大</td></tr><tr><td>Browser Use 框架</td><td>需要单独登录</td><td>每次消耗 token</td><td>Python 代码</td><td>依赖 LLM 判断</td></tr></tbody></table></div><p>OpenCLI 的 sweet spot 很明确：<strong>当你需要一个确定性的、零运行成本的、能复用浏览器登录态的网站操作方案时</strong>，它是最优选择。</p><hr><h2 id="八、注意事项与限制"><a href="#八、注意事项与限制" class="headerlink" title="八、注意事项与限制"></a>八、注意事项与限制</h2><p>任何工具都有边界，OpenCLI 也不例外。</p><h3 id="8-1-不适合的场景"><a href="#8-1-不适合的场景" class="headerlink" title="8.1 不适合的场景"></a>8.1 不适合的场景</h3><ul><li><strong>高频交易或实时性要求极高的操作</strong>：CDP 通信有延迟，不如直接调 API</li><li><strong>需要无头浏览器批量爬取的场景</strong>：OpenCLI 依赖真实 Chrome 实例，不适合大规模并发</li><li><strong>对稳定性要求 100% 的生产环境</strong>：网站改版后 Adapter 需要更新选择器</li></ul><h3 id="8-2-安全考量"><a href="#8-2-安全考量" class="headerlink" title="8.2 安全考量"></a>8.2 安全考量</h3><p>OpenCLI 复用浏览器登录态，意味着它能操作你已登录的任何网站。建议：</p><ul><li>只在受信任的 AI Agent（如本地 Claude Code）中使用</li><li>不要在云端或共享环境中使用</li><li>发布类操作可以先让 Agent 生成预览，人工确认后再执行</li></ul><h3 id="8-3-平台反爬"><a href="#8-3-平台反爬" class="headerlink" title="8.3 平台反爬"></a>8.3 平台反爬</h3><p>部分平台会检测自动化行为。OpenCLI 走的是真实浏览器实例，比无头浏览器好一些，但如果操作频率过高仍可能触发风控。控制节奏、避免短时间大量操作。</p><hr><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><p>OpenCLI 把”让 AI 操作网页”这件事从实验阶段拉到了生产可用阶段。它的核心价值可以概括为三点：</p><ol><li><strong>统一接口</strong>：任何网站变成 CLI，不用管底层是网页、Electron 还是本地工具</li><li><strong>零运行成本</strong>：Adapter 编译后确定性执行，不消耗 LLM token</li><li><strong>安全复用登录态</strong>：不存密码、不要 API Key、走真实浏览器</li></ol><p>对于写博客、运营社区、管理多平台的创作者来说，配合 Claude Code 这样的 Code Agent，OpenCLI 能省掉大量重复的浏览器操作。对于开发者来说，YAML 声明式的插件开发门槛远低于写完整的自动化脚本。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一、背景&quot;&gt;&lt;a href=&quot;#一、背景&quot; class=&quot;headerlink&quot; title=&quot;一、背景&quot;&gt;&lt;/a&gt;一、背景&lt;/h2&gt;&lt;p&gt;之前研究 Chrome DevTools MCP 的时候，解决的核心问题是&lt;strong&gt;让 AI 能操控浏览器&lt;/stro</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="OpenCLI" scheme="https://donehub.github.io/tags/OpenCLI/"/>
    
  </entry>
  
  <entry>
    <title>TrendRadar：告别无效刷屏，只看真正关心的新闻</title>
    <link href="https://donehub.github.io/2026/05/16/trendradar-hot-news-aggregator-guide/"/>
    <id>https://donehub.github.io/2026/05/16/trendradar-hot-news-aggregator-guide/</id>
    <published>2026-05-15T16:00:00.000Z</published>
    <updated>2026-05-16T04:37:38.232Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、背景"><a href="#一、背景" class="headerlink" title="一、背景"></a>一、背景</h2><p>每天打开手机，十几个 APP 轮番刷一遍，微博热搜、知乎热榜、抖音热点、今日头条……刷完一圈下来，两个小时过去了，真正有用的信息可能就三五条。剩下的是什么？震惊体标题党、营销软文、明星八卦、各种算法硬塞给你的”你可能感兴趣”。</p><p>更气人的是，明明只想看看科技圈今天发生了什么，却被”某明星离婚”霸占了热搜第一。平台算法绑架了我们的注意力，想看的内容找不到，不想看的铺天盖地。</p><p>有没有一种工具，能帮你从”被动接收”变成”主动获取”？<strong>TrendRadar</strong> 就是这么一个开源项目——聚合全网热点，按你的关键词筛选，定时推送到你的手机。更重要的是，它还能让 AI 帮你分析这些热点背后的趋势和情绪。</p><h2 id="二、TrendRadar-是什么"><a href="#二、TrendRadar-是什么" class="headerlink" title="二、TrendRadar 是什么"></a>二、TrendRadar 是什么</h2><p>一句话概括：<strong>TrendRadar 是一个开源的热点新闻聚合分析工具</strong>。</p><p>它的核心思路很简单——把全网 50+ 个平台的热榜抓过来，按你设定的关键词过滤，把真正关心的内容推给你。推送渠道也很丰富：飞书、钉钉、企业微信、Telegram、邮件、Bark（iOS）、Slack，甚至自定义 Webhook。</p><p>更厉害的是，它内置了 <strong>AI 分析功能</strong>。不仅是聚合热点，还能让 AI 帮你：</p><ul><li>分析热点趋势走向</li><li>判断舆论情绪（正面/负面/争议）</li><li>跨平台关联分析</li><li>生成洞察报告</li></ul><p>这就像雇了一个私人新闻助理，每天帮你从海量信息中提炼出真正有价值的干货。</p><h2 id="三、数据是怎么来的"><a href="#三、数据是怎么来的" class="headerlink" title="三、数据是怎么来的"></a>三、数据是怎么来的</h2><p>TrendRadar 的数据来源是另一个开源项目 <strong>NewsNow</strong>。这个项目聚合了全网 50+ 个平台的热榜数据，包括：</p><div class="table-container"><table><thead><tr><th>国内综合</th><th>科技平台</th><th>金融平台</th><th>国际媒体</th></tr></thead><tbody><tr><td>知乎、微博</td><td>IT之家、36氪</td><td>华尔街见闻</td><td>Hacker News</td></tr><tr><td>百度热搜</td><td>稀土掘金</td><td>财联社</td><td>GitHub Trending</td></tr><tr><td>抖音、今日头条</td><td>V2EX</td><td>雪球</td><td>Product Hunt</td></tr><tr><td>澎湃新闻、凤凰网</td><td>酷安</td><td>金十数据</td><td>联合早报</td></tr><tr><td>虎扑、贴吧</td><td>少数派</td><td>格隆汇</td><td>卫星通讯社</td></tr></tbody></table></div><p>NewsNow 通过调用各平台的官方 API 或爬取页面来获取热榜数据，然后统一输出成标准格式。TrendRadar 直接调用 NewsNow 的公开 API：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https:&#x2F;&#x2F;newsnow.busiyi.world&#x2F;api&#x2F;s?id&#x3D;zhihu&amp;latest</span><br></pre></td></tr></table></figure><p>返回的数据格式是这样的：</p><figure class="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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"status"</span>: <span class="string">"success"</span>,</span><br><span class="line">  <span class="attr">"items"</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">"title"</span>: <span class="string">"如何评价DeepSeek新模型?"</span>,</span><br><span class="line">      <span class="attr">"url"</span>: <span class="string">"https://zhuanlan.zhihu.com/p/xxx"</span>,</span><br><span class="line">      <span class="attr">"extra"</span>: &#123;</span><br><span class="line">        <span class="attr">"info"</span>: <span class="string">"1234万热度"</span>,</span><br><span class="line">        <span class="attr">"hover"</span>: <span class="string">"摘要描述..."</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>所以 TrendRadar 不需要自己去啃各平台的反爬机制，数据源维护这个苦活儿由 NewsNow 项目负责。万一某个平台接口变了，NewsNow 更一下就行，TrendRadar 用户完全不用操心。</p><h2 id="四、核心功能一览"><a href="#四、核心功能一览" class="headerlink" title="四、核心功能一览"></a>四、核心功能一览</h2><h3 id="热榜聚合"><a href="#热榜聚合" class="headerlink" title="热榜聚合"></a>热榜聚合</h3><p>默认支持 11 个主流平台：知乎、微博、百度热搜、抖音、今日头条、B站热搜、华尔街见闻、财联社、澎湃新闻、凤凰网、贴吧。想加更多平台？直接在配置文件里加就行。</p><h3 id="关键词筛选"><a href="#关键词筛选" class="headerlink" title="关键词筛选"></a>关键词筛选</h3><p>这是核心功能。你在 <code>frequency_words.txt</code> 里写上关心的关键词，系统就只推送包含这些词的新闻。语法很灵活：</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line"># 最简单的：直接写关键词</span><br><span class="line">华为</span><br><span class="line"></span><br><span class="line"># 多个关键词归为一组（空行分隔）</span><br><span class="line">华为</span><br><span class="line">鸿蒙</span><br><span class="line">任正非</span><br><span class="line"></span><br><span class="line"># 给词组起个名字</span><br><span class="line">[科技巨头]</span><br><span class="line">华为</span><br><span class="line">腾讯</span><br><span class="line">字节</span><br><span class="line"></span><br><span class="line"># 正则匹配（精确匹配英文单词，避免误匹配）</span><br><span class="line">&#x2F;\bAI\b&#x2F; &#x3D;&gt; AI相关</span><br><span class="line">人工智能</span><br><span class="line"></span><br><span class="line"># 排除不想看的</span><br><span class="line">[苹果公司]</span><br><span class="line">苹果</span><br><span class="line">!水果        # 排除&quot;水果&quot;相关的</span><br><span class="line"></span><br><span class="line"># 限制显示条数</span><br><span class="line">特斯拉</span><br><span class="line">@10          # 最多显示10条</span><br><span class="line"></span><br><span class="line"># 必须同时包含多个词</span><br><span class="line">+发布会</span><br><span class="line">+新品        # 必须同时出现&quot;发布会&quot;和&quot;新品&quot;</span><br></pre></td></tr></table></figure><h3 id="AI-智能筛选（新功能）"><a href="#AI-智能筛选（新功能）" class="headerlink" title="AI 智能筛选（新功能）"></a>AI 智能筛选（新功能）</h3><p>如果你不想自己写关键词，可以用 <strong>自然语言描述</strong> 你关注的方向。在 <code>ai_interests.txt</code> 里写：</p><figure class="highlight plain"><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"></span><br><span class="line">1. 中国科技与互联网公司：重点关注 DeepSeek、华为、腾讯...</span><br><span class="line">2. 大模型与 AI 产品：关注 OpenAI、Claude、ChatGPT...</span><br><span class="line">3. AI 基础设施与云算力：关注英伟达、AMD...</span><br><span class="line">4. 芯片与半导体制造：关注芯片、光刻机...</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"># 标题质量要求</span><br><span class="line">- 不要标题党&#x2F;震惊体</span><br><span class="line">- 不要营销软文</span><br></pre></td></tr></table></figure><p>AI 会自动理解你的兴趣，给每条新闻打分，只推送高相关度的内容。这个功能需要配置 AI API（支持 DeepSeek、OpenAI、Gemini 等）。</p><h3 id="三种推送模式"><a href="#三种推送模式" class="headerlink" title="三种推送模式"></a>三种推送模式</h3><div class="table-container"><table><thead><tr><th>模式</th><th>说明</th><th>适用人群</th></tr></thead><tbody><tr><td><strong>daily（当日汇总）</strong></td><td>每天定时推送当天所有匹配新闻</td><td>企业管理者、普通用户</td></tr><tr><td><strong>current（当前榜单）</strong></td><td>每次推送当前榜单匹配新闻</td><td>自媒体人、内容创作者</td></tr><tr><td><strong>incremental（增量监控）</strong></td><td>只推送新出现的内容，零重复</td><td>投资者、交易员</td></tr></tbody></table></div><p>举个例子：你监控”特斯拉”，每小时执行一次。如果选择 <code>incremental</code> 模式，只有第一次出现的新闻才会推送给你，后续重复出现的就不打扰了。适合高频监控场景。</p><h3 id="调度系统（时间线）"><a href="#调度系统（时间线）" class="headerlink" title="调度系统（时间线）"></a>调度系统（时间线）</h3><p>你可以精细控制”什么时间做什么事”。比如：</p><ul><li>工作日：早上9点速览、中午看热点、晚上7点汇总</li><li>周末：睡到自然醒，10点开始推送，有新增就推</li></ul><p>预设了 5 种模板：<code>always_on</code>（全天候）、<code>morning_evening</code>（早晚汇总）、<code>office_hours</code>（办公时间）、<code>night_owl</code>（夜猫子）、<code>custom</code>（完全自定义）。</p><h3 id="AI-分析推送"><a href="#AI-分析推送" class="headerlink" title="AI 分析推送"></a>AI 分析推送</h3><p>开启后，每次推送都会附带一份 AI 生成的洞察报告，包含：</p><ul><li>核心热点态势</li><li>舆论风向争议</li><li>异动与弱信号</li><li>研判策略建议</li></ul><p>AI 还能分析每条新闻的排名变化轨迹、热度持续时间、跨平台表现。比如某条新闻在微博排第3，知乎排第5，抖音排第8——AI 能告诉你这个话题的”全网热度分布”。</p><h3 id="AI-多语言翻译"><a href="#AI-多语言翻译" class="headerlink" title="AI 多语言翻译"></a>AI 多语言翻译</h3><p>如果你订阅了海外 RSS（如 Hacker News），AI 可以帮你把英文标题翻译成中文。反过来，如果你想用英文读国内热点，也可以翻译成英文。</p><h3 id="MCP-智能分析（进阶功能）"><a href="#MCP-智能分析（进阶功能）" class="headerlink" title="MCP 智能分析（进阶功能）"></a>MCP 智能分析（进阶功能）</h3><p>这是给深度用户准备的。TrendRadar 实现了 <strong>MCP (Model Context Protocol)</strong> 协议，可以接入 Claude Desktop、Cherry Studio、Cursor 等 AI 客户端。</p><p>你可以用自然语言跟新闻数据”对话”：</p><figure class="highlight plain"><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">&quot;分析过去一周 DeepSeek 的热度变化&quot;</span><br><span class="line">&quot;对比知乎和微博今天的热点差异&quot;</span><br><span class="line">&quot;生成一份今天的科技热点摘要，推送到飞书&quot;</span><br><span class="line">&quot;搜索特斯拉相关新闻，分析情感倾向&quot;</span><br></pre></td></tr></table></figure><p>AI 会自动调用 TrendRadar 的 21 个分析工具，帮你做深度数据挖掘。</p><h2 id="五、部署方式"><a href="#五、部署方式" class="headerlink" title="五、部署方式"></a>五、部署方式</h2><h3 id="GitHub-Actions（零服务器）"><a href="#GitHub-Actions（零服务器）" class="headerlink" title="GitHub Actions（零服务器）"></a>GitHub Actions（零服务器）</h3><p>适合没有服务器的用户。流程是：</p><ol><li>Fork TrendRadar 仓库到自己的 GitHub</li><li>配置 GitHub Secrets（填推送渠道的 webhook URL）</li><li>GitHub Actions 定时运行，自动抓取并推送</li></ol><p>缺点是每次运行完环境就销毁，数据没法本地存。需要配置云存储（如 Cloudflare R2）来持久化数据。</p><h3 id="Docker（推荐）"><a href="#Docker（推荐）" class="headerlink" title="Docker（推荐）"></a>Docker（推荐）</h3><p>适合有服务器、NAS 或长期运行电脑的用户。数据本地存储，更稳定。</p><figure class="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><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 克隆项目</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/sansan0/TrendRadar.git</span><br><span class="line"><span class="built_in">cd</span> TrendRadar</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置</span></span><br><span class="line">cp config/config.yaml.example config/config.yaml</span><br><span class="line"><span class="comment"># 编辑 config.yaml 和 frequency_words.txt</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动</span></span><br><span class="line">docker compose up -d</span><br></pre></td></tr></table></figure><p>Docker 部署还有个好处：可以同时跑两个容器——一个做新闻推送，一个做 MCP AI 分析服务。</p><h3 id="本地运行"><a href="#本地运行" class="headerlink" title="本地运行"></a>本地运行</h3><p>Windows/Mac/Linux 直接跑：</p><figure class="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><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="comment"># Windows</span></span><br><span class="line">setup-windows.bat</span><br><span class="line"></span><br><span class="line"><span class="comment"># Mac/Linux</span></span><br><span class="line">./setup-mac.sh</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行</span></span><br><span class="line">python main.py</span><br></pre></td></tr></table></figure><h2 id="六、配置要点"><a href="#六、配置要点" class="headerlink" title="六、配置要点"></a>六、配置要点</h2><h3 id="config-yaml-主配置"><a href="#config-yaml-主配置" class="headerlink" title="config.yaml 主配置"></a>config.yaml 主配置</h3><p>这是核心配置文件，结构如下：</p><figure class="highlight yaml"><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><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">app:</span></span><br><span class="line">  <span class="attr">timezone:</span> <span class="string">"Asia/Shanghai"</span>        <span class="comment"># 时区</span></span><br><span class="line"></span><br><span class="line"><span class="attr">schedule:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">preset:</span> <span class="string">"morning_evening"</span>        <span class="comment"># 调度模板</span></span><br><span class="line"></span><br><span class="line"><span class="attr">platforms:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">sources:</span>                         <span class="comment"># 监控平台列表</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">id:</span> <span class="string">"zhihu"</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">"知乎"</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">id:</span> <span class="string">"weibo"</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">"微博"</span></span><br><span class="line"></span><br><span class="line"><span class="attr">report:</span></span><br><span class="line">  <span class="attr">mode:</span> <span class="string">"incremental"</span>              <span class="comment"># 推送模式</span></span><br><span class="line">  <span class="attr">display_mode:</span> <span class="string">"keyword"</span>          <span class="comment"># 显示方式</span></span><br><span class="line"></span><br><span class="line"><span class="attr">filter:</span></span><br><span class="line">  <span class="attr">method:</span> <span class="string">"keyword"</span>                <span class="comment"># keyword | ai</span></span><br><span class="line"></span><br><span class="line"><span class="attr">notification:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">channels:</span></span><br><span class="line">    <span class="attr">feishu:</span></span><br><span class="line">      <span class="attr">webhook_url:</span> <span class="string">""</span></span><br><span class="line">    <span class="attr">telegram:</span></span><br><span class="line">      <span class="attr">bot_token:</span> <span class="string">""</span></span><br><span class="line">      <span class="attr">chat_id:</span> <span class="string">""</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ai:</span></span><br><span class="line">  <span class="attr">model:</span> <span class="string">"deepseek/deepseek-chat"</span>  <span class="comment"># AI 模型</span></span><br><span class="line">  <span class="attr">api_key:</span> <span class="string">""</span>                      <span class="comment"># API Key</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ai_analysis:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span>                    <span class="comment"># 开启 AI 分析</span></span><br><span class="line">  <span class="attr">max_news_for_analysis:</span> <span class="number">50</span>        <span class="comment"># 分析数量上限</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ai_translation:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">language:</span> <span class="string">"中文"</span></span><br></pre></td></tr></table></figure><h3 id="frequency-words-txt-关键词配置"><a href="#frequency-words-txt-关键词配置" class="headerlink" title="frequency_words.txt 关键词配置"></a>frequency_words.txt 关键词配置</h3><p>前面已经介绍过语法，这里补充几个实用技巧：</p><p><strong>技巧1：从宽到严，逐步调整</strong></p><p>刚开始可以写宽泛的关键词，观察几天后再加过滤词：</p><figure class="highlight plain"><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"># 第一版：先测试</span><br><span class="line">AI</span><br><span class="line">ChatGPT</span><br><span class="line"></span><br><span class="line"># 第二版：发现太多广告，加过滤</span><br><span class="line">AI</span><br><span class="line">ChatGPT</span><br><span class="line">!培训</span><br><span class="line">!课程</span><br><span class="line">!广告</span><br><span class="line"></span><br><span class="line"># 第三版：只想看技术相关，加必须词</span><br><span class="line">AI</span><br><span class="line">ChatGPT</span><br><span class="line">+技术</span><br></pre></td></tr></table></figure><p><strong>技巧2：正则表达式精确匹配英文</strong></p><p>英文容易误匹配，比如 <code>ai</code> 会匹配到 <code>training</code> 里的 <code>ai</code>。用正则解决：</p><figure class="highlight plain"><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><br><span class="line">&#x2F;\bAI\b&#x2F;i &#x3D;&gt; AI相关</span><br><span class="line"></span><br><span class="line"># 匹配开头或结尾</span><br><span class="line">&#x2F;^breaking&#x2F;     # 只匹配开头是 breaking 的</span><br><span class="line">&#x2F;发布$&#x2F;         # 只匹配结尾是&quot;发布&quot;的</span><br></pre></td></tr></table></figure><p>不会写正则？直接问 ChatGPT：”帮我写一个正则表达式，精确匹配英文单词 AI，不匹配 training 里的 ai，格式是 /正则/ =&gt; 别名”</p><p><strong>技巧3：全局过滤不想看的</strong></p><p>有些内容不管什么关键词都不想看，用 <code>[GLOBAL_FILTER]</code>：</p><figure class="highlight plain"><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">[GLOBAL_FILTER]</span><br><span class="line">震惊</span><br><span class="line">刚刚</span><br><span class="line">竟然</span><br><span class="line">广告</span><br><span class="line">推广</span><br><span class="line"></span><br><span class="line">[WORD_GROUPS]</span><br><span class="line">你的关键词配置...</span><br></pre></td></tr></table></figure><h3 id="推送渠道配置"><a href="#推送渠道配置" class="headerlink" title="推送渠道配置"></a>推送渠道配置</h3><p><strong>企业微信（最简单）</strong>：</p><ol><li>打开企业微信，进入目标群聊</li><li>点击右上角”…”，选择”群机器人”</li><li>添加机器人，复制 Webhook URL</li><li>填入配置或 GitHub Secrets</li></ol><p><strong>飞书</strong>：</p><ol><li>访问 <a href="https://botbuilder.feishu.cn/home/my-command" target="_blank" rel="noopener">https://botbuilder.feishu.cn/home/my-command</a></li><li>新建机器人指令</li><li>选择”Webhook 触发”，复制 URL</li><li>配置参数模板：<figure class="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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"message_type"</span>: <span class="string">"text"</span>,</span><br><span class="line">  <span class="attr">"content"</span>: &#123; <span class="attr">"text"</span>: <span class="string">"&#123;&#123;内容&#125;&#125;"</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><p><strong>Telegram</strong>：</p><p>需要两个配置：<code>bot_token</code> 和 <code>chat_id</code>。</p><ol><li>在 Telegram 搜索 @BotFather，发送 <code>/newbot</code> 创建机器人</li><li>获取 Bot Token</li><li>向你的机器人发一条消息</li><li>访问 <code>https://api.telegram.org/bot&lt;Token&gt;/getUpdates</code></li><li>从返回 JSON 找到 <code>chat.id</code></li></ol><p><strong>邮件</strong>：</p><p>支持 Gmail、QQ邮箱、163、Outlook 等。QQ邮箱需要用授权码（不是密码），在邮箱设置里开启 SMTP 服务后生成。</p><h2 id="七、实际使用体验"><a href="#七、实际使用体验" class="headerlink" title="七、实际使用体验"></a>七、实际使用体验</h2><p>我部署了一套配置，关键词设为：AI、DeepSeek、华为、特斯拉、芯片、大模型。推送模式选 <code>incremental</code>，调度选 <code>morning_evening</code>。</p><p>效果是这样的：</p><p><strong>早上 9 点</strong>：收到推送，包含昨晚到今早新出现的 15 条相关热点。AI 分析报告附在最后，告诉我”AI 领域今天舆论偏正面，DeepSeek 新模型发布引发热议，华为鸿蒙讨论度上升”。</p><p><strong>晚上 8 点</strong>：收到当日汇总，包含全天所有匹配新闻（去重后约 30 条）。AI 给了一份更完整的趋势分析，包括”哪些话题持续在榜”、”哪些是新爆发点”。</p><p><strong>好处</strong>：</p><ol><li><strong>不用刷 APP 了</strong>。之前每天刷微博知乎抖音至少两小时，现在 5 分钟看完推送就行。</li><li><strong>信息密度高</strong>。一条推送包含 11 个平台的热点，跨平台对比一目了然。</li><li><strong>AI 分析有价值</strong>。不是简单的汇总，而是告诉你趋势、情绪、关联。比如”特斯拉降价”这个话题，AI 能分析出”微博讨论偏负面（吐槽割韭菜），知乎讨论偏中性（分析影响），抖音讨论偏正面（喊降价真香）”。</li></ol><p><strong>注意点</strong>：</p><ol><li><strong>关键词不要太多</strong>。我刚开始写了 30 多个关键词，结果每次推送 100 多条，信息过载。后来精简到 6 个核心关键词，效果好多了。</li><li><strong>AI 分析有成本</strong>。默认模型是 DeepSeek，很便宜。按官方估算，每小时推送一次，每天约 0.1 元。如果想省钱，可以把 <code>max_news_for_analysis</code> 从 150 降到 50。</li><li><strong>GitHub Actions 有延迟</strong>。定时任务触发时间不稳定，可能有 ±15 分钟偏差。如果需要精准推送，建议用 Docker 部署到自己的服务器。</li></ol><h2 id="八、MCP-功能进阶用法"><a href="#八、MCP-功能进阶用法" class="headerlink" title="八、MCP 功能进阶用法"></a>八、MCP 功能进阶用法</h2><p>如果你想深度挖掘新闻数据，MCP 功能很有价值。</p><h3 id="配置-MCP-客户端"><a href="#配置-MCP-客户端" class="headerlink" title="配置 MCP 客户端"></a>配置 MCP 客户端</h3><p>以 Cherry Studio 为例（推荐，有 GUI）：</p><ol><li><p>运行 TrendRadar 的 MCP 服务：</p><figure class="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="comment"># Windows</span></span><br><span class="line">start-http.bat</span><br><span class="line"></span><br><span class="line"><span class="comment"># Mac/Linux</span></span><br><span class="line">./start-http.sh</span><br></pre></td></tr></table></figure></li><li><p>在 Cherry Studio 设置里添加 MCP 服务器：</p><ul><li>类型：<code>streamableHttp</code></li><li>URL：<code>http://127.0.0.1:3333/mcp</code></li></ul></li><li><p>开始对话。</p></li></ol><h3 id="MCP-可以做什么"><a href="#MCP-可以做什么" class="headerlink" title="MCP 可以做什么"></a>MCP 可以做什么</h3><p><strong>趋势分析</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;分析最近 7 天 DeepSeek 的热度变化&quot;</span><br></pre></td></tr></table></figure><p>AI 会调用 <code>analyze_topic_trend</code> 工具，返回：</p><ul><li>首次出现时间、持续时间</li><li>排名变化曲线（第3→第1→第5）</li><li>热度峰值、爆火判断</li><li>趋势预测</li></ul><p><strong>平台对比</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;对比知乎和微博今天关于 AI 的讨论差异&quot;</span><br></pre></td></tr></table></figure><p>AI 会对比两个平台的热点分布、情绪倾向、讨论角度差异。</p><p><strong>情感分析</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;分析特斯拉最近新闻的情感倾向&quot;</span><br></pre></td></tr></table></figure><p>返回正面/负面/中性分布，以及典型情感关键词。</p><p><strong>生成报告并推送</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;写一份今天的科技热点摘要，推送到飞书&quot;</span><br></pre></td></tr></table></figure><p>AI 会调用 <code>generate_summary_report</code> 生成报告，然后调用 <code>send_notification</code> 推送。自动处理格式转换（Markdown → 飞书格式）。</p><h3 id="MCP-工具列表"><a href="#MCP-工具列表" class="headerlink" title="MCP 工具列表"></a>MCP 工具列表</h3><div class="table-container"><table><thead><tr><th>分类</th><th>工具</th><th>功能</th></tr></thead><tbody><tr><td>基础</td><td><code>get_latest_news</code></td><td>获取最新新闻</td></tr><tr><td></td><td><code>get_news_by_date</code></td><td>按日期查询</td></tr><tr><td></td><td><code>get_trending_topics</code></td><td>热点统计</td></tr><tr><td>RSS</td><td><code>get_latest_rss</code></td><td>RSS 内容</td></tr><tr><td></td><td><code>search_rss</code></td><td>RSS 搜索</td></tr><tr><td>搜索</td><td><code>search_news</code></td><td>统一搜索</td></tr><tr><td></td><td><code>find_related_news</code></td><td>相似新闻</td></tr><tr><td>分析</td><td><code>analyze_topic_trend</code></td><td>趋势分析</td></tr><tr><td></td><td><code>analyze_sentiment</code></td><td>情感分析</td></tr><tr><td></td><td><code>aggregate_news</code></td><td>跨平台聚合</td></tr><tr><td></td><td><code>compare_periods</code></td><td>时期对比</td></tr><tr><td></td><td><code>generate_summary_report</code></td><td>生成报告</td></tr><tr><td>通知</td><td><code>send_notification</code></td><td>推送消息</td></tr><tr><td>文章</td><td><code>read_article</code></td><td>读取正文</td></tr></tbody></table></div><p>总共 21 个工具，覆盖了从查询到分析到推送的全流程。</p><h2 id="九、数据存储"><a href="#九、数据存储" class="headerlink" title="九、数据存储"></a>九、数据存储</h2><p>TrendRadar 的数据存储在 SQLite 数据库，按日期分库：</p><figure class="highlight plain"><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">output&#x2F;</span><br><span class="line">├── news&#x2F;</span><br><span class="line">│   ├── 2025-05-16.db    # 当天热榜数据</span><br><span class="line">│   ├── 2025-05-15.db    # 历史数据</span><br><span class="line">├── rss&#x2F;</span><br><span class="line">│   ├── 2025-05-16.db    # RSS 数据</span><br><span class="line">└── html&#x2F;</span><br><span class="line">    └── 当日汇总.html     # HTML 报告</span><br></pre></td></tr></table></figure><p>数据库表结构设计得很好：</p><ul><li><code>news_items</code>：存储新闻条目（标题、URL、排名）</li><li><code>rank_history</code>：记录排名变化历史（每次抓取的排名）</li><li><code>crawl_records</code>：记录抓取时间和数量</li></ul><p>这样设计的好处是可以追踪热度变化轨迹。比如某条新闻早上排第 5，中午排第 3，晚上掉到第 10——这些变化都会被记录下来，供 AI 分析。</p><h2 id="十、与其他工具对比"><a href="#十、与其他工具对比" class="headerlink" title="十、与其他工具对比"></a>十、与其他工具对比</h2><div class="table-container"><table><thead><tr><th>工具</th><th>TrendRadar</th><th>RSS 阅读器</th><th>热榜网站</th></tr></thead><tbody><tr><td>数据源</td><td>50+ 平台热榜</td><td>RSS订阅源</td><td>单一或少量平台</td></tr><tr><td>筛选方式</td><td>关键词+AI</td><td>手动订阅</td><td>无筛选</td></tr><tr><td>推送</td><td>多渠道</td><td>需额外工具</td><td>无推送</td></tr><tr><td>AI 分析</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></tbody></table></div><p>TrendRadar 的优势在于：<strong>聚合 + 筛选 + 分析 + 推送</strong> 一条龙。RSS 阅读器适合订阅特定博客，热榜网站适合快速浏览，但都没有 AI 分析和自动推送。</p><h2 id="十一、项目地址和资源"><a href="#十一、项目地址和资源" class="headerlink" title="十一、项目地址和资源"></a>十一、项目地址和资源</h2><ul><li><strong>GitHub</strong>：<a href="https://github.com/sansan0/TrendRadar" target="_blank" rel="noopener">https://github.com/sansan0/TrendRadar</a></li><li><strong>可视化配置编辑器</strong>：<a href="https://sansan0.github.io/TrendRadar/" target="_blank" rel="noopener">https://sansan0.github.io/TrendRadar/</a></li><li><strong>NewsNow 数据源</strong>：<a href="https://github.com/ourongxing/newsnow" target="_blank" rel="noopener">https://github.com/ourongxing/newsnow</a></li></ul><p>项目维护得很活跃，版本迭代快（从 v1.0 到 v6.7），文档也很详细。有问题可以去 GitHub Issues 提，作者回复很及时。</p><h2 id="十二、总结"><a href="#十二、总结" class="headerlink" title="十二、总结"></a>十二、总结</h2><p>TrendRadar 解决的问题是：<strong>如何从信息洪流中高效获取有价值的内容</strong>。</p><p>它不是简单的热榜聚合，而是：</p><ul><li>用关键词/AI筛选过滤噪音</li><li>用多渠道推送直达手机</li><li>用AI分析提供深度洞察</li><li>用MCP协议支持自定义数据挖掘</li></ul><p>如果你每天花大量时间刷 APP 看热点，却总觉得信息过载、抓不住重点——试试 TrendRadar。部署一次，配置好关键词，之后就等着推送敲门，看完推送就完事。</p><p>从”被动接收算法推荐”变成”主动获取关心内容”，这才是高效的信息消费方式。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一、背景&quot;&gt;&lt;a href=&quot;#一、背景&quot; class=&quot;headerlink&quot; title=&quot;一、背景&quot;&gt;&lt;/a&gt;一、背景&lt;/h2&gt;&lt;p&gt;每天打开手机，十几个 APP 轮番刷一遍，微博热搜、知乎热榜、抖音热点、今日头条……刷完一圈下来，两个小时过去了，真正有用的</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="AI 开源工具" scheme="https://donehub.github.io/tags/AI-%E5%BC%80%E6%BA%90%E5%B7%A5%E5%85%B7/"/>
    
  </entry>
  
  <entry>
    <title>存储芯片双雄：DRAM与NAND Flash全景解析</title>
    <link href="https://donehub.github.io/2026/05/14/dram-nand-flash-deep-analysis/"/>
    <id>https://donehub.github.io/2026/05/14/dram-nand-flash-deep-analysis/</id>
    <published>2026-05-13T16:00:00.000Z</published>
    <updated>2026-05-16T04:36:30.927Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、背景"><a href="#一、背景" class="headerlink" title="一、背景"></a>一、背景</h2><p>2026年，AI浪潮席卷全球，最近都被这些新闻刷频了：</p><blockquote><p>“三星市值突破新高，HBM订单排到明年”<br>“海力士股价暴涨，成英伟达最大HBM供应商”<br>“美光宣布HBM3e量产，AI存储竞争白热化”<br>“长鑫存储融资成功，中国DRAM再获突破”</p></blockquote><p>作为科技爱好者，你可能会有很多困惑：</p><ul><li>内存条、固态硬盘、HBM… 到底都是什么？</li><li>为什么AI训练需要HBM，而不是普通内存条？</li><li>三星、海力士、美光、长鑫、长江存储… 各家到底做什么？</li></ul><p>这篇文章，用<strong>两个视角</strong>帮你彻底理清：<strong>DRAM</strong>和<strong>NAND Flash</strong>——存储芯片世界的两大支柱。</p><hr><h2 id="二、第一视角：DRAM"><a href="#二、第一视角：DRAM" class="headerlink" title="二、第一视角：DRAM"></a>二、第一视角：DRAM</h2><h3 id="一、DRAM是什么？"><a href="#一、DRAM是什么？" class="headerlink" title="一、DRAM是什么？"></a>一、DRAM是什么？</h3><p><strong>DRAM = Dynamic Random Access Memory</strong>，中文叫”动态随机存取存储器”。</p><p>通俗理解：<strong>DRAM就是你电脑上的”临时工作台”</strong>。</p><figure class="highlight plain"><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">你在用Word写文档：</span><br><span class="line">硬盘（SSD）── 长期保存文件，断电后还在</span><br><span class="line">     ↓ 打开文件时加载</span><br><span class="line">内存条（DRAM）── 临时存放正在编辑的内容，断电就没了</span><br><span class="line">     ↓ CPU随时读取</span><br><span class="line">CPU ── 处理文字、格式、排版</span><br></pre></td></tr></table></figure><p>忘记保存就断电？文件没了。因为DRAM里的数据瞬间清空。</p><h3 id="二、为什么叫”动态”？"><a href="#二、为什么叫”动态”？" class="headerlink" title="二、为什么叫”动态”？"></a>二、为什么叫”动态”？</h3><p>这是DRAM与其他内存技术最大的区别：<strong>数据需要不断”刷新”才能保持</strong>。</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">DRAM的存储单元 &#x3D; 1个电容 + 1个晶体管</span><br><span class="line"></span><br><span class="line">电容充电 &#x3D; 存储&quot;1&quot;</span><br><span class="line">电容放电 &#x3D; 存储&quot;0&quot;</span><br><span class="line"></span><br><span class="line">问题：电容会自然漏电，电荷慢慢流失</span><br><span class="line">      几毫秒后，数据就没了</span><br><span class="line"></span><br><span class="line">解决：每隔几毫秒&quot;刷新&quot;一次</span><br><span class="line">      把电荷补回去，数据才能保持</span><br></pre></td></tr></table></figure><p>这就是”动态”的含义：数据不是静态保存的，需要<strong>动态、持续地刷新</strong>。</p><p>对比一下SRAM（静态随机存取存储器）：</p><div class="table-container"><table><thead><tr><th>特性</th><th>DRAM</th><th>SRAM</th></tr></thead><tbody><tr><td>存储单元</td><td>1电容+1晶体管</td><td>6个晶体管</td></tr><tr><td>需要刷新</td><td>✅ 必须周期刷新</td><td>❌ 不需要</td></tr><tr><td>密度</td><td>高（结构简单）</td><td>低（结构复杂）</td></tr><tr><td>容量</td><td>大（单芯片可达16GB）</td><td>小（通常几KB到几MB）</td></tr><tr><td>成本</td><td>便宜</td><td>贵</td></tr><tr><td>应用</td><td>内存条、手机内存</td><td>CPU缓存（L1/L2/L3）</td></tr></tbody></table></div><p>一句话：<strong>DRAM性价比高，适合做大容量内存；SRAM性能好但贵，只做CPU内部的小缓存</strong>。</p><h3 id="三、DRAM的核心特点"><a href="#三、DRAM的核心特点" class="headerlink" title="三、DRAM的核心特点"></a>三、DRAM的核心特点</h3><div class="table-container"><table><thead><tr><th>特点</th><th>说明</th></tr></thead><tbody><tr><td><strong>易失性</strong></td><td>断电后数据立即丢失</td></tr><tr><td><strong>需要刷新</strong></td><td>每隔几毫秒必须刷新，否则数据消失</td></tr><tr><td><strong>速度快</strong></td><td>读写速度远快于硬盘</td></tr><tr><td><strong>密度高</strong></td><td>单芯片可存储大量数据</td></tr><tr><td><strong>成本低</strong></td><td>每GB价格相对便宜</td></tr></tbody></table></div><h3 id="四、DRAM产品家族"><a href="#四、DRAM产品家族" class="headerlink" title="四、DRAM产品家族"></a>四、DRAM产品家族</h3><p>都属于DRAM技术，但针对不同场景优化：</p><div class="table-container"><table><thead><tr><th>产品</th><th>特点</th><th>用途</th><th>带宽</th><th>代表厂商</th></tr></thead><tbody><tr><td><strong>DDR4/DDR5</strong></td><td>标准内存条</td><td>PC、服务器</td><td>~25GB/s</td><td>三星/海力士/美光/长鑫</td></tr><tr><td><strong>LPDDR4/LPDDR5</strong></td><td>低功耗版</td><td>手机、平板</td><td>~60GB/s</td><td>三星/海力士/美光/长鑫</td></tr><tr><td><strong>HBM/HBM3e</strong></td><td>堆叠高带宽</td><td>AI训练GPU</td><td>~1TB/s+</td><td>三星/海力士/美光</td></tr><tr><td><strong>GDDR6/GDDR7</strong></td><td>显卡显存</td><td>游戏显卡</td><td>~160GB/s</td><td>三星/海力士/美光</td></tr><tr><td><strong>Server DRAM</strong></td><td>服务器专用</td><td>数据中心</td><td>~50GB/s</td><td>三星/海力士/美光/长鑫</td></tr><tr><td><strong>Mobile DRAM</strong></td><td>移动端定制</td><td>智能穿戴、IoT</td><td>~10GB/s</td><td>各厂商均有</td></tr></tbody></table></div><h3 id="五、重点解读：HBM为什么是AI的命门"><a href="#五、重点解读：HBM为什么是AI的命门" class="headerlink" title="五、重点解读：HBM为什么是AI的命门"></a>五、重点解读：HBM为什么是AI的命门</h3><p>普通内存条有个致命瓶颈：<strong>带宽不够</strong>。</p><figure class="highlight plain"><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">普通内存条：单通道带宽约 25GB&#x2F;s（DDR5-6400）</span><br><span class="line">AI训练需求：几百 GB&#x2F;s 甚至 TB&#x2F;s 级别</span><br></pre></td></tr></table></figure><p>GPU（如英伟达H100）算力极强，但数据喂不进去——传统内存条成了瓶颈。</p><p><strong>HBM的解决方案：垂直堆叠</strong></p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">传统内存条：</span><br><span class="line">┌───┐</span><br><span class="line">│芯片│ ← 平铺在PCB上，信号要走很远</span><br><span class="line">└───┘</span><br><span class="line"></span><br><span class="line">HBM：</span><br><span class="line">┌─────┐</span><br><span class="line">│芯片8│ ↑</span><br><span class="line">├─────┤ │ 垂直堆叠8-12层</span><br><span class="line">│芯片7│ │ 通过TSV（硅通孔）连接</span><br><span class="line">│ ... │ │ 路径极短，速度极快</span><br><span class="line">└──┬──┘ ↓</span><br><span class="line">   └── GPU芯片（紧邻封装）</span><br></pre></td></tr></table></figure><p><strong>核心技术</strong>：</p><div class="table-container"><table><thead><tr><th>技术</th><th>作用</th></tr></thead><tbody><tr><td><strong>TSV（硅通孔）</strong></td><td>在芯片上打微孔，垂直导通</td></tr><tr><td><strong>3D堆叠</strong></td><td>8层、12层DRAM芯片叠在一起</td></tr><tr><td><strong>CoWoS封装</strong></td><td>把HBM和GPU封装在同一块硅中介层上</td></tr></tbody></table></div><p><strong>性能对比</strong>：</p><div class="table-container"><table><thead><tr><th>类型</th><th>带宽</th><th>应用</th></tr></thead><tbody><tr><td>DDR5内存条</td><td>~25GB/s</td><td>电脑、服务器</td></tr><tr><td>HBM3</td><td>~1TB/s</td><td>AI训练GPU</td></tr><tr><td>HBM3e</td><td>~1.5TB/s+</td><td>最先进AI芯片</td></tr></tbody></table></div><p>这就是为什么英伟达H100价格3万美元起步——<strong>HBM成本占了很大比例</strong>。</p><h3 id="六、DRAM厂商格局"><a href="#六、DRAM厂商格局" class="headerlink" title="六、DRAM厂商格局"></a>六、DRAM厂商格局</h3><p><strong>全球市场份额（2025年Q1）</strong>：</p><div class="table-container"><table><thead><tr><th style="text-align:center">排名</th><th>厂商</th><th>国家</th><th style="text-align:center">份额</th><th style="text-align:center">趋势</th><th>技术水平</th></tr></thead><tbody><tr><td style="text-align:center">1</td><td><strong>三星电子</strong></td><td>韩国</td><td style="text-align:center">~40-41%</td><td style="text-align:center">↓略降</td><td>最领先</td></tr><tr><td style="text-align:center">2</td><td><strong>SK海力士</strong></td><td>韩国</td><td style="text-align:center">~28-29%</td><td style="text-align:center">↑上升</td><td>HBM领先</td></tr><tr><td style="text-align:center">3</td><td><strong>美光科技</strong></td><td>美国</td><td style="text-align:center">~23-24%</td><td style="text-align:center">稳定</td><td>一流</td></tr><tr><td style="text-align:center">4</td><td><strong>长鑫存储</strong></td><td>中国</td><td style="text-align:center">~5-6%</td><td style="text-align:center">↑上升</td><td>追赶中</td></tr><tr><td style="text-align:center">5</td><td><strong>南亚科技</strong></td><td>中国台湾</td><td style="text-align:center">~2%</td><td style="text-align:center">稳定</td><td>中端</td></tr><tr><td style="text-align:center">6</td><td><strong>华邦电子</strong></td><td>中国台湾</td><td style="text-align:center">~1%</td><td style="text-align:center">稳定</td><td>中低端</td></tr><tr><td style="text-align:center">7</td><td><strong>力积电</strong></td><td>中国台湾</td><td style="text-align:center">&lt;1%</td><td style="text-align:center">稳定</td><td>代工</td></tr></tbody></table></div><blockquote><p><strong>三巨头控制93%+市场</strong></p></blockquote><p><strong>2025年关键变化</strong>：</p><ul><li><strong>海力士份额上升</strong>：HBM业务驱动，成英伟达最大HBM供应商</li><li><strong>长鑫份额上升</strong>：从3-5%提升到5-6%，国产替代加速</li><li><strong>三星份额略降</strong>：战略转向高利润HBM，减少低端产能</li></ul><p><strong>技术能力矩阵</strong>：</p><div class="table-container"><table><thead><tr><th>厂商</th><th style="text-align:center">DDR5</th><th style="text-align:center">LPDDR5X</th><th style="text-align:center">HBM3e</th><th style="text-align:center">GDDR7</th><th style="text-align:center">制程</th></tr></thead><tbody><tr><td>三星</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">12nm</td></tr><tr><td>海力士</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅领先</td><td style="text-align:center">✅</td><td style="text-align:center">12nm</td></tr><tr><td>美光</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">12nm</td></tr><tr><td>长鑫</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">17nm</td></tr><tr><td>南亚科</td><td style="text-align:center">✅</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">20nm</td></tr><tr><td>华邦</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">25nm</td></tr></tbody></table></div><p><strong>中国现状</strong>：长鑫存储是中国大陆唯一的DRAM厂商，2016年成立，填补了国内空白。目前主攻DDR4/DDR5、LPDDR4/LPDDR5，HBM仍在研发阶段。</p><p><strong>日本现状</strong>：日本已无DRAM厂商。曾经的霸主尔必达2012年破产被海力士收购，日本DRAM产业终结。</p><hr><h2 id="三、第二视角：NAND-Flash"><a href="#三、第二视角：NAND-Flash" class="headerlink" title="三、第二视角：NAND Flash"></a>三、第二视角：NAND Flash</h2><h3 id="一、NAND-Flash是什么？"><a href="#一、NAND-Flash是什么？" class="headerlink" title="一、NAND Flash是什么？"></a>一、NAND Flash是什么？</h3><p><strong>NAND Flash</strong>，中文叫”NAND闪存”，是一种<strong>非易失性存储器</strong>。</p><p>通俗理解：<strong>NAND Flash就是你电脑的”永久仓库”</strong>。</p><figure class="highlight plain"><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><br><span class="line">RAM（DRAM）8GB ── 运行APP时临时用，断电清空</span><br><span class="line">存储（NAND）128GB ── 保存照片、APP、文件，断电后还在</span><br><span class="line"></span><br><span class="line">你的电脑：</span><br><span class="line">内存条（DRAM）16GB ── 正在运行的程序</span><br><span class="line">固态硬盘（NAND）512GB ── 系统、软件、所有文件</span><br></pre></td></tr></table></figure><p><strong>核心特点</strong>：断电后数据<strong>不会丢失</strong>，可以长期保存。</p><h3 id="二、为什么叫”NAND”和”闪存”"><a href="#二、为什么叫”NAND”和”闪存”" class="headerlink" title="二、为什么叫”NAND”和”闪存”"></a>二、为什么叫”NAND”和”闪存”</h3><p><strong>NAND</strong>是逻辑门电路的名字（Not AND），用这种结构的晶体管阵列存储数据，所以叫NAND Flash。</p><p><strong>“闪存”的由来</strong>：</p><figure class="highlight plain"><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">传统EEPROM：擦除需要几秒</span><br><span class="line">NAND Flash：擦除只需几毫秒</span><br><span class="line"></span><br><span class="line">像&quot;闪光&quot;一样快 → Flash Memory（闪存）</span><br></pre></td></tr></table></figure><h3 id="三、工作原理"><a href="#三、工作原理" class="headerlink" title="三、工作原理"></a>三、工作原理</h3><figure class="highlight plain"><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">NAND存储单元 &#x3D; 浮栅晶体管</span><br><span class="line"></span><br><span class="line">┌─────────────────┐</span><br><span class="line">│ 浮栅            │ ← 电子被困在这里</span><br><span class="line">│  有电子 &#x3D; 存储&quot;0&quot;│    不会跑掉</span><br><span class="line">│  无电子 &#x3D; 存储&quot;1&quot;│</span><br><span class="line">└─────────────────┘</span><br><span class="line"></span><br><span class="line">写入：把电子注入浮栅（高压）</span><br><span class="line">擦除：把电子从浮栅拉出来（更高电压）</span><br><span class="line">读取：检测浮栅是否有电子</span><br></pre></td></tr></table></figure><p><strong>关键点</strong>：电子被困在浮栅里，没有电源也能长期保存——这就是”非易失性”的原因。</p><h3 id="四、NAND-Flash的核心特点"><a href="#四、NAND-Flash的核心特点" class="headerlink" title="四、NAND Flash的核心特点"></a>四、NAND Flash的核心特点</h3><div class="table-container"><table><thead><tr><th>特点</th><th>说明</th></tr></thead><tbody><tr><td><strong>非易失性</strong></td><td>断电后数据保留</td></tr><tr><td><strong>不需要刷新</strong></td><td>与DRAM不同，写入后自然保持</td></tr><tr><td><strong>密度高</strong></td><td>单芯片容量远超DRAM</td></tr><tr><td><strong>有擦写寿命</strong></td><td>每个单元可擦写几千到几万次</td></tr><tr><td><strong>速度较慢</strong></td><td>比DRAM慢，但比机械硬盘快很多</td></tr></tbody></table></div><h3 id="五、NAND分类（按每单元存储位数）"><a href="#五、NAND分类（按每单元存储位数）" class="headerlink" title="五、NAND分类（按每单元存储位数）"></a>五、NAND分类（按每单元存储位数）</h3><div class="table-container"><table><thead><tr><th>类型</th><th>全称</th><th style="text-align:center">每单元存储</th><th>特点</th><th>寿命</th><th>应用</th></tr></thead><tbody><tr><td><strong>SLC</strong></td><td>Single-Level Cell</td><td style="text-align:center">1 bit</td><td>最快、最耐用、最贵</td><td>10万次</td><td>企业/军工</td></tr><tr><td><strong>MLC</strong></td><td>Multi-Level Cell</td><td style="text-align:center">2 bit</td><td>平衡性能与成本</td><td>3000-1万次</td><td>高端消费</td></tr><tr><td><strong>TLC</strong></td><td>Triple-Level Cell</td><td style="text-align:center">3 bit</td><td>主流选择，性价比高</td><td>500-3000次</td><td>消费级SSD</td></tr><tr><td><strong>QLC</strong></td><td>Quad-Level Cell</td><td style="text-align:center">4 bit</td><td>便宜但慢</td><td>100-1000次</td><td>大容量存储</td></tr></tbody></table></div><figure class="highlight plain"><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><br><span class="line">SLC &#x3D; 每格只放1个东西，空间利用率低，但耐用快速</span><br><span class="line">TLC &#x3D; 每格塞3个东西，空间利用率高，但慢一些</span><br><span class="line">QLC &#x3D; 每格塞4个东西，最便宜，但寿命最短</span><br></pre></td></tr></table></figure><h3 id="六、3D-NAND：垂直堆叠技术"><a href="#六、3D-NAND：垂直堆叠技术" class="headerlink" title="六、3D NAND：垂直堆叠技术"></a>六、3D NAND：垂直堆叠技术</h3><p>传统NAND是平铺的，容量有限。现代技术把存储单元垂直堆叠：</p><figure class="highlight plain"><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">传统2D NAND：</span><br><span class="line">┌──┐ ┌──┐ ┌──┐ ┌──┐ ← 平铺在一层</span><br><span class="line"></span><br><span class="line">3D NAND：</span><br><span class="line">┌──┐</span><br><span class="line">│232│ ↑ 垂直堆叠</span><br><span class="line">├──┤ │ 像盖楼房一样</span><br><span class="line">│...│ │ 同样面积，容量翻倍</span><br><span class="line">└──┘ ↓</span><br></pre></td></tr></table></figure><p>主流3D NAND层数：</p><div class="table-container"><table><thead><tr><th>厂商</th><th style="text-align:center">最高层数</th></tr></thead><tbody><tr><td>三星</td><td style="text-align:center">236层</td></tr><tr><td>海力士</td><td style="text-align:center">238层</td></tr><tr><td>美光</td><td style="text-align:center">232层</td></tr><tr><td>长江存储</td><td style="text-align:center">232层（Xtacking技术）</td></tr></tbody></table></div><h3 id="七、长江存储的独创技术：Xtacking"><a href="#七、长江存储的独创技术：Xtacking" class="headerlink" title="七、长江存储的独创技术：Xtacking"></a>七、长江存储的独创技术：Xtacking</h3><figure class="highlight plain"><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">传统3D NAND：</span><br><span class="line">┌─────────────────┐</span><br><span class="line">│ 存储单元+外围电路 │ ← 同一片晶圆上制造</span><br><span class="line">│    叠在一起      │    层数增加会互相干扰</span><br><span class="line">└─────────────────┘</span><br><span class="line"></span><br><span class="line">Xtacking架构：</span><br><span class="line">┌──────────┐   ┌──────────┐</span><br><span class="line">│ 存储单元  │ ←→ │ 外围电路  │ ← 两片晶圆分别制造</span><br><span class="line">│（垂直堆叠）│   │（高速逻辑）│   再键合在一起</span><br><span class="line">└──────────┘   └──────────┘</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：存储密度更高、I/O速度更快、制造效率更高。</p><h3 id="八、NAND-Flash产品家族"><a href="#八、NAND-Flash产品家族" class="headerlink" title="八、NAND Flash产品家族"></a>八、NAND Flash产品家族</h3><div class="table-container"><table><thead><tr><th>产品</th><th>特点</th><th>用途</th><th>速度</th></tr></thead><tbody><tr><td><strong>SSD固态硬盘</strong></td><td>大容量高速存储</td><td>电脑硬盘</td><td>3-7GB/s</td></tr><tr><td><strong>UFS</strong></td><td>高速嵌入式存储</td><td>中高端手机</td><td>~4GB/s</td></tr><tr><td><strong>eMMC</strong></td><td>集成控制器，成本低</td><td>低端手机/IoT</td><td>~400MB/s</td></tr><tr><td><strong>SD卡/TF卡</strong></td><td>可插拔便携</td><td>相机/无人机</td><td>~100MB/s</td></tr><tr><td><strong>USB闪存盘</strong></td><td>便携通用</td><td>数据传输</td><td>~100MB/s</td></tr></tbody></table></div><h3 id="九、NAND-Flash厂商格局"><a href="#九、NAND-Flash厂商格局" class="headerlink" title="九、NAND Flash厂商格局"></a>九、NAND Flash厂商格局</h3><p><strong>全球市场份额（2025年）</strong>：</p><div class="table-container"><table><thead><tr><th style="text-align:center">排名</th><th>厂商</th><th>国家</th><th style="text-align:center">份额</th><th style="text-align:center">趋势</th><th>技术水平</th></tr></thead><tbody><tr><td style="text-align:center">1</td><td><strong>三星电子</strong></td><td>韩国</td><td style="text-align:center">~35-38%</td><td style="text-align:center">稳定</td><td>最领先</td></tr><tr><td style="text-align:center">2</td><td><strong>凯侠</strong></td><td>日本</td><td style="text-align:center">~15-18%</td><td style="text-align:center">稳定</td><td>一流（2024年IPO）</td></tr><tr><td style="text-align:center">3</td><td><strong>西部数据</strong></td><td>美国</td><td style="text-align:center">~12-15%</td><td style="text-align:center">稳定</td><td>一流（与凯侠合资）</td></tr><tr><td style="text-align:center">4</td><td><strong>SK海力士</strong></td><td>韩国</td><td style="text-align:center">~12-15%</td><td style="text-align:center">↑上升</td><td>一流（含Solidigm）</td></tr><tr><td style="text-align:center">5</td><td><strong>美光科技</strong></td><td>美国</td><td style="text-align:center">~10-12%</td><td style="text-align:center">稳定</td><td>一流</td></tr><tr><td style="text-align:center">6</td><td><strong>长江存储</strong></td><td>中国</td><td style="text-align:center">~5-8%</td><td style="text-align:center">受限</td><td>接近一流</td></tr></tbody></table></div><blockquote><p><strong>三星+凯侠+西数+海力士控制80%+市场</strong></p></blockquote><p><strong>2025年关键变化</strong>：</p><ul><li><strong>凯侠完成IPO</strong>：2024年底上市，获得资金扩张</li><li><strong>西部数据拆分</strong>：计划将SanDisk业务独立分拆</li><li><strong>长江存储受限</strong>：美国制裁持续，全球份额增长受阻，但国内市场稳步发展</li></ul><p><strong>中国现状</strong>：长江存储是中国最大的NAND Flash厂商，技术水平与国际差距较小，232层Xtacking技术已接近第一梯队。</p><p><strong>日本现状</strong>：凯侠（原东芝存储）专注NAND Flash，是日本唯一的存储芯片厂商。日本已无DRAM厂商。</p><hr><h2 id="四、两视角对比：DRAM-vs-NAND-Flash"><a href="#四、两视角对比：DRAM-vs-NAND-Flash" class="headerlink" title="四、两视角对比：DRAM vs NAND Flash"></a>四、两视角对比：DRAM vs NAND Flash</h2><h3 id="核心区别"><a href="#核心区别" class="headerlink" title="核心区别"></a>核心区别</h3><div class="table-container"><table><thead><tr><th>维度</th><th>DRAM</th><th>NAND Flash</th></tr></thead><tbody><tr><td><strong>断电后</strong></td><td>❌ 数据丢失</td><td>✅ 数据保留</td></tr><tr><td><strong>需要刷新</strong></td><td>✅ 必须周期刷新</td><td>❌ 不需要</td></tr><tr><td><strong>速度</strong></td><td>很快（几十GB/s）</td><td>较慢（几GB/s）</td></tr><tr><td><strong>容量</strong></td><td>小（8-16GB/芯片）</td><td>大（256GB-4TB/芯片）</td></tr><tr><td><strong>擦写寿命</strong></td><td>无限（理论上）</td><td>有（几千到几万次）</td></tr><tr><td><strong>成本/GB</strong></td><td>较贵</td><td>便宜</td></tr><tr><td><strong>典型产品</strong></td><td>内存条、HBM</td><td>固态硬盘、手机存储</td></tr><tr><td><strong>生活比喻</strong></td><td>“临时工作台”</td><td>“永久仓库”</td></tr></tbody></table></div><h3 id="用一个场景理解"><a href="#用一个场景理解" class="headerlink" title="用一个场景理解"></a>用一个场景理解</h3><figure class="highlight plain"><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">┌─────────────────────────────────────┐</span><br><span class="line">│ 固态硬盘（NAND）                     │</span><br><span class="line">│ 存着游戏的所有文件（50GB）           │</span><br><span class="line">│ 断电后还在                           │</span><br><span class="line">└─────────────────────────────────────┘</span><br><span class="line">         ↓ 启动游戏时加载</span><br><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│ 内存条（DRAM）                       │</span><br><span class="line">│ 临时存放正在运行的游戏数据（8GB）     │</span><br><span class="line">│ 断电就没了                           │</span><br><span class="line">└─────────────────────────────────────┘</span><br><span class="line">         ↓ GPU随时读取渲染</span><br><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│ GPU + HBM                            │</span><br><span class="line">│ AI时代：GPU算力强，HBM喂得快          │</span><br><span class="line">│ 传统内存条喂不饱GPU                  │</span><br><span class="line">└─────────────────────────────────────┘</span><br></pre></td></tr></table></figure><hr><h2 id="五、中国存储产业现状"><a href="#五、中国存储产业现状" class="headerlink" title="五、中国存储产业现状"></a>五、中国存储产业现状</h2><h3 id="两家企业，两条路线"><a href="#两家企业，两条路线" class="headerlink" title="两家企业，两条路线"></a>两家企业，两条路线</h3><div class="table-container"><table><thead><tr><th>企业</th><th>技术领域</th><th>定位</th><th>国际差距</th></tr></thead><tbody><tr><td><strong>长鑫存储</strong></td><td>DRAM</td><td>中国唯一内存厂商</td><td>差距较大（落后约2代）</td></tr><tr><td><strong>长江存储</strong></td><td>NAND Flash</td><td>中国最大闪存厂商</td><td>差距较小（接近第一梯队）</td></tr></tbody></table></div><h3 id="为什么差距不同？"><a href="#为什么差距不同？" class="headerlink" title="为什么差距不同？"></a>为什么差距不同？</h3><p><strong>DRAM差距较大</strong>：</p><ul><li>三星、海力士、美光有40年技术积累</li><li>DRAM工艺极其复杂，专利壁垒高</li><li>设备（光刻机、刻蚀机）受国外限制</li></ul><p><strong>NAND差距较小</strong>：</p><ul><li>NAND技术路线相对灵活</li><li>长江存储Xtacking架构实现”弯道超车”</li><li>层数堆叠更多依赖工艺创新，而非单纯制程</li></ul><hr><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><div class="table-container"><table><thead><tr><th>新闻里提到的</th><th>属于哪个领域</th><th>哪家厂商在做</th></tr></thead><tbody><tr><td><strong>内存条涨价</strong></td><td>DRAM</td><td>三星/海力士/美光/长鑫</td></tr><tr><td><strong>HBM供不应求</strong></td><td>DRAM（高端）</td><td>三星/海力士/美光</td></tr><tr><td><strong>固态硬盘新品</strong></td><td>NAND Flash</td><td>三星/凯侠/西部数据/长江存储</td></tr><tr><td><strong>长鑫融资成功</strong></td><td>DRAM</td><td>中国唯一DRAM厂商</td></tr><tr><td><strong>长江存储突破</strong></td><td>NAND Flash</td><td>中国NAND厂商</td></tr></tbody></table></div><hr><p><strong>DRAM</strong>：三巨头（三星+海力士+美光）控制90%市场，长鑫是中国唯一希望，日本已无厂商。</p><p><strong>NAND Flash</strong>：六强格局（三星/海力士/凯侠/西数/美光/长江存储），长江存储技术水平接近一流。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一、背景&quot;&gt;&lt;a href=&quot;#一、背景&quot; class=&quot;headerlink&quot; title=&quot;一、背景&quot;&gt;&lt;/a&gt;一、背景&lt;/h2&gt;&lt;p&gt;2026年，AI浪潮席卷全球，最近都被这些新闻刷频了：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“三星市值突破新高，HBM订</summary>
      
    
    
    
    <category term="存储芯片" scheme="https://donehub.github.io/categories/%E5%AD%98%E5%82%A8%E8%8A%AF%E7%89%87/"/>
    
    
    <category term="概念解读" scheme="https://donehub.github.io/tags/%E6%A6%82%E5%BF%B5%E8%A7%A3%E8%AF%BB/"/>
    
  </entry>
  
  <entry>
    <title>RAG 准确率优化</title>
    <link href="https://donehub.github.io/2026/05/05/rag-accuracy-optimization/"/>
    <id>https://donehub.github.io/2026/05/05/rag-accuracy-optimization/</id>
    <published>2026-05-04T16:00:00.000Z</published>
    <updated>2026-07-06T16:23:19.861Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>做 RAG 系统的开发者，大概都会经历过这样一个过程：</p><p>Demo 阶段跑几个测试用例，效果惊艳。一上真实业务数据，准确率直接掉到 60% 甚至更低，用户投诉不断，自己也说不清问题出在哪。</p><p>我曾经连续三周每天晚上对着 Bad Case 分析表发呆，改了 Prompt 没用，换了大模型没用，调了 TopK 还是没用。最后发现，问题根本不在大模型那一环，而是在大模型之前的整条链路上。</p><p>这篇文章通过深度讲解四步优化，每一步都能量化地拉高准确率，最终从 60% 做到 85%。</p><a id="more"></a><h2 id="先看全局：RAG-全链路优化地图"><a href="#先看全局：RAG-全链路优化地图" class="headerlink" title="先看全局：RAG 全链路优化地图"></a>先看全局：RAG 全链路优化地图</h2><p>在动手之前，先把整个链路摊开来看。RAG 系统不是”检索 + 生成”两个黑盒，而是一条精密的流水线，任何一个环节出问题，最终输出都会崩。</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">用户输入</span><br><span class="line">  │</span><br><span class="line">  ▼</span><br><span class="line">┌──────────────────────┐</span><br><span class="line">│  ① Query 预处理       │  ← 第二环：改写 + 校验</span><br><span class="line">└──────────┬───────────┘</span><br><span class="line">           │</span><br><span class="line">           ▼</span><br><span class="line">┌──────────────────────┐</span><br><span class="line">│  ② 混合检索          │  ← 第三环：向量 + BM25 + LambdaMART 重排</span><br><span class="line">│     (向量 + 关键词)    │</span><br><span class="line">└──────────┬───────────┘</span><br><span class="line">           │</span><br><span class="line">           ▼</span><br><span class="line">┌──────────────────────┐</span><br><span class="line">│  ③ 上下文组装         │  ← 检索结果拼装，喂给大模型</span><br><span class="line">└──────────┬───────────┘</span><br><span class="line">           │</span><br><span class="line">           ▼</span><br><span class="line">┌──────────────────────┐</span><br><span class="line">│  ④ 大模型生成         │  ← Prompt + 生成策略</span><br><span class="line">└──────────────────────┘</span><br><span class="line"></span><br><span class="line">        ↕ 贯穿全链路 ↕</span><br><span class="line"></span><br><span class="line">┌──────────────────────┐</span><br><span class="line">│  ⑤ 文档分块 &amp; 索引    │  ← 第一环：地基，离线阶段</span><br><span class="line">└──────────────────────┘</span><br></pre></td></tr></table></figure><p>下面按优先级从高到低，逐环拆解。</p><h2 id="第一环：文档分块-——-全链路性价比最高的优化"><a href="#第一环：文档分块-——-全链路性价比最高的优化" class="headerlink" title="第一环：文档分块 —— 全链路性价比最高的优化"></a>第一环：文档分块 —— 全链路性价比最高的优化</h2><h3 id="为什么文档分块是地基"><a href="#为什么文档分块是地基" class="headerlink" title="为什么文档分块是地基"></a>为什么文档分块是地基</h3><p>很多人做文档切分图省事，直接固定 Token 数一刀切——每 500 Token 切一段，简单粗暴。这是典型的实验室玩法，上线必出问题。因为你一刀下去，很可能把一件完整的事情切成两半：</p><ul><li>一个完整的知识点被拦腰截断</li><li>一张结构化表格被切成上半截和下半截</li><li>一段”因为…所以…”的因果关系，”因为”在上一个 chunk，”所以”在下一个 chunk</li></ul><p>检索的时候，召回的全是碎片化的残缺信息。大模型连完整上下文都看不到，更没法指望它给出正确答案。</p><p>这就好比你要查字典找一个词的完整释义，结果字典被人从中间撕开了，你只看到前半句——“此药适用于”——后面的内容没了。你知道它适用，但你不知道适用于什么。</p><h3 id="工业界怎么做：语义感知的动态切分"><a href="#工业界怎么做：语义感知的动态切分" class="headerlink" title="工业界怎么做：语义感知的动态切分"></a>工业界怎么做：语义感知的动态切分</h3><p>我们不用死板的 Token 计数器，而是用NLP 语义感知的方式做动态切分。核心思路是：绝不让一个完整的语义单元被拆到两个 chunk 里。</p><p>具体怎么做？分三步走。</p><h4 id="第一步：文档结构解析"><a href="#第一步：文档结构解析" class="headerlink" title="第一步：文档结构解析"></a>第一步：文档结构解析</h4><p>在切分之前，先解析文档的骨架结构。这一步要用专业的解析模型，而不是简单的正则匹配。</p><figure class="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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> langchain.text_splitter <span class="keyword">import</span> RecursiveCharacterTextSplitter</span><br><span class="line"><span class="keyword">import</span> spacy</span><br><span class="line"></span><br><span class="line"><span class="comment"># 加载中文 NLP 模型</span></span><br><span class="line">nlp = spacy.load(<span class="string">"zh_core_web_sm"</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">parse_document_structure</span><span class="params">(raw_text)</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">    识别标题层级、段落边界、表格区域等。</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    doc = nlp(raw_text)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 按句子边界切分，保留完整句子</span></span><br><span class="line">    sentences = [sent.text <span class="keyword">for</span> sent <span class="keyword">in</span> doc.sents]</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 识别标题（这里简化处理，实际场景需要更复杂的规则或模型）</span></span><br><span class="line">    sections = []</span><br><span class="line">    current_section = &#123;<span class="string">"title"</span>: <span class="string">"default"</span>, <span class="string">"paragraphs"</span>: []&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> sent <span class="keyword">in</span> sentences:</span><br><span class="line">        <span class="keyword">if</span> is_heading(sent):  <span class="comment"># 判断是否是标题</span></span><br><span class="line">            <span class="keyword">if</span> current_section[<span class="string">"paragraphs"</span>]:</span><br><span class="line">                sections.append(current_section)</span><br><span class="line">            current_section = &#123;<span class="string">"title"</span>: sent, <span class="string">"paragraphs"</span>: []&#125;</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            current_section[<span class="string">"paragraphs"</span>].append(sent)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> current_section[<span class="string">"paragraphs"</span>]:</span><br><span class="line">        sections.append(current_section)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> sections</span><br></pre></td></tr></table></figure><h4 id="第二步：语义完整性保护"><a href="#第二步：语义完整性保护" class="headerlink" title="第二步：语义完整性保护"></a>第二步：语义完整性保护</h4><p>解析完结构后，切分时必须遵循语义完整性原则：</p><div class="table-container"><table><thead><tr><th>规则</th><th>说明</th></tr></thead><tbody><tr><td>标题和正文不分离</td><td>标题必须和它下面的段落绑定在同一个 chunk</td></tr><tr><td>因果不断裂</td><td>“因为…所以…”、”如果…那么…”必须在同一 chunk</td></tr><tr><td>表格整体化</td><td>结构化表格要么整体作为一个 chunk，要么按行/列做结构化拆分</td></tr><tr><td>列表不割裂</td><td>一个有序列表或无序列表尽量保持在同一 chunk</td></tr></tbody></table></div><h4 id="第三步：上下文重叠窗口"><a href="#第三步：上下文重叠窗口" class="headerlink" title="第三步：上下文重叠窗口"></a>第三步：上下文重叠窗口</h4><p>即使做了语义感知切分，相邻 chunk 之间也可能存在语义断层。所以必须加重叠窗口：每个 chunk 保留头部和尾部 10%~20% 的内容作为重叠区。</p><p>打个比方，就像接力赛的交棒区：前一个选手和后一个选手有一段距离是共同持有的，这样交接的时候不会掉棒。</p><figure class="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><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><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SemanticChunker</span>:</span></span><br><span class="line">    <span class="string">"""语义感知的文档分块器"""</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">        self,</span></span></span><br><span class="line"><span class="function"><span class="params">        chunk_size: int = <span class="number">512</span>,        <span class="comment"># 目标 chunk 大小（Token 数）</span></span></span></span><br><span class="line"><span class="function"><span class="params">        overlap_ratio: float = <span class="number">0.15</span>,  <span class="comment"># 重叠比例 15%</span></span></span></span><br><span class="line"><span class="function"><span class="params">        min_chunk_size: int = <span class="number">100</span>,    <span class="comment"># 最小 chunk 大小</span></span></span></span><br><span class="line"><span class="function"><span class="params">    )</span>:</span></span><br><span class="line">        self.chunk_size = chunk_size</span><br><span class="line">        self.overlap_tokens = int(chunk_size * overlap_ratio)  <span class="comment"># ~77 tokens</span></span><br><span class="line">        self.min_chunk_size = min_chunk_size</span><br><span class="line">        self.nlp = spacy.load(<span class="string">"zh_core_web_sm"</span>)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">chunk_document</span><span class="params">(self, text: str)</span> -&gt; list[dict]:</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">        返回 chunk 列表，每个 chunk 包含内容和元数据。</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        <span class="comment"># 1. 解析文档结构</span></span><br><span class="line">        sections = self._parse_structure(text)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 2. 按结构边界进行初步切分</span></span><br><span class="line">        raw_chunks = self._split_by_structure(sections)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 3. 对过长的 chunk 做二次切分（以句子为最小单位）</span></span><br><span class="line">        refined_chunks = self._refine_chunks(raw_chunks)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 4. 添加重叠窗口</span></span><br><span class="line">        final_chunks = self._add_overlap(refined_chunks)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> final_chunks</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_split_by_structure</span><span class="params">(self, sections: list)</span> -&gt; list[str]:</span></span><br><span class="line">        <span class="string">"""按文档结构边界切分，保证标题-段落完整性"""</span></span><br><span class="line">        chunks = []</span><br><span class="line">        <span class="keyword">for</span> section <span class="keyword">in</span> sections:</span><br><span class="line">            <span class="comment"># 标题 + 所属段落绑定为一个单元</span></span><br><span class="line">            section_text = <span class="string">f"<span class="subst">&#123;section[<span class="string">'title'</span>]&#125;</span>\n"</span> + <span class="string">"\n"</span>.join(</span><br><span class="line">                section[<span class="string">"paragraphs"</span>]</span><br><span class="line">            )</span><br><span class="line">            chunks.append(section_text)</span><br><span class="line">        <span class="keyword">return</span> chunks</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_refine_chunks</span><span class="params">(self, raw_chunks: list[str])</span> -&gt; list[str]:</span></span><br><span class="line">        <span class="string">"""对超长 chunk 按句子边界二次切分"""</span></span><br><span class="line">        refined = []</span><br><span class="line">        <span class="keyword">for</span> chunk <span class="keyword">in</span> raw_chunks:</span><br><span class="line">            doc = self.nlp(chunk)</span><br><span class="line">            sentences = [sent.text <span class="keyword">for</span> sent <span class="keyword">in</span> doc.sents]</span><br><span class="line"></span><br><span class="line">            current_chunk = <span class="string">""</span></span><br><span class="line">            <span class="keyword">for</span> sent <span class="keyword">in</span> sentences:</span><br><span class="line">                <span class="comment"># 如果加上这句话超过 chunk_size，且当前已有内容</span></span><br><span class="line">                <span class="keyword">if</span> (</span><br><span class="line">                    len(self._tokenize(current_chunk + sent))</span><br><span class="line">                    &gt; self.chunk_size</span><br><span class="line">                    <span class="keyword">and</span> current_chunk</span><br><span class="line">                ):</span><br><span class="line">                    refined.append(current_chunk.strip())</span><br><span class="line">                    current_chunk = sent</span><br><span class="line">                <span class="keyword">else</span>:</span><br><span class="line">                    current_chunk += sent</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> current_chunk <span class="keyword">and</span> len(current_chunk.strip()) &gt; self.min_chunk_size:</span><br><span class="line">                refined.append(current_chunk.strip())</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> refined</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_add_overlap</span><span class="params">(self, chunks: list[str])</span> -&gt; list[dict]:</span></span><br><span class="line">        <span class="string">"""为相邻 chunk 添加头尾重叠窗口"""</span></span><br><span class="line">        result = []</span><br><span class="line">        <span class="keyword">for</span> i, chunk <span class="keyword">in</span> enumerate(chunks):</span><br><span class="line">            <span class="comment"># 头部重叠：取上一个 chunk 的尾部</span></span><br><span class="line">            head_overlap = <span class="string">""</span></span><br><span class="line">            <span class="keyword">if</span> i &gt; <span class="number">0</span>:</span><br><span class="line">                prev_tokens = self._tokenize(chunks[i - <span class="number">1</span>])</span><br><span class="line">                head_overlap = self._detokenize(</span><br><span class="line">                    prev_tokens[-self.overlap_tokens:]</span><br><span class="line">                )</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 尾部重叠：取下一个 chunk 的头部</span></span><br><span class="line">            tail_overlap = <span class="string">""</span></span><br><span class="line">            <span class="keyword">if</span> i &lt; len(chunks) - <span class="number">1</span>:</span><br><span class="line">                next_tokens = self._tokenize(chunks[i + <span class="number">1</span>])</span><br><span class="line">                tail_overlap = self._detokenize(</span><br><span class="line">                    next_tokens[:self.overlap_tokens]</span><br><span class="line">                )</span><br><span class="line"></span><br><span class="line">            result.append(&#123;</span><br><span class="line">                <span class="string">"content"</span>: chunk,</span><br><span class="line">                <span class="string">"metadata"</span>: &#123;</span><br><span class="line">                    <span class="string">"head_overlap"</span>: head_overlap,</span><br><span class="line">                    <span class="string">"tail_overlap"</span>: tail_overlap,</span><br><span class="line">                    <span class="string">"chunk_index"</span>: i,</span><br><span class="line">                &#125;,</span><br><span class="line">            &#125;)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> result</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_tokenize</span><span class="params">(self, text: str)</span> -&gt; list[str]:</span></span><br><span class="line">        <span class="string">"""简单的 Token 化（实际场景用 tiktoken 等专业工具）"""</span></span><br><span class="line">        <span class="keyword">return</span> text.split()</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_detokenize</span><span class="params">(self, tokens: list[str])</span> -&gt; str:</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">" "</span>.join(tokens)</span><br></pre></td></tr></table></figure><h3 id="这一步的效果"><a href="#这一步的效果" class="headerlink" title="这一步的效果"></a>这一步的效果</h3><p>在固定 Token 切分的基线上，不用动大模型分毫，光是把切分策略从一刀切换成语义感知动态切分 + 重叠窗口，准确率就能直接拉升 10~15 个百分点。</p><p>这就是为什么我说它是全链路性价比最高的优化——投入产出比太高了。</p><h2 id="第二环：Query-预处理-——-上线后准确率跳水的主因"><a href="#第二环：Query-预处理-——-上线后准确率跳水的主因" class="headerlink" title="第二环：Query 预处理 —— 上线后准确率跳水的主因"></a>第二环：Query 预处理 —— 上线后准确率跳水的主因</h2><h3 id="真实用户的提问会比较离谱"><a href="#真实用户的提问会比较离谱" class="headerlink" title="真实用户的提问会比较离谱"></a>真实用户的提问会比较离谱</h3><p>实验室里测试的时候，习惯用完整的问句：”请问这个产品的退款政策是什么？”</p><p>但真实用户可能是这样问的：</p><ul><li>“怎么退费”</li><li>“开票规则”</li><li>“有效期”</li><li>“能不能退”</li></ul><p>两三个字，语义极度模糊。你拿”开票规则”四个字去做向量检索，embedding 模型能给你算出一个向量，但这个向量在语义空间里指向的方向是极其不确定的——它可能匹配到”如何开发票”，也可能匹配到”发票开具的时间规定”，甚至匹配到”开票系统的使用说明”。</p><p>直接检索，准确率不可能高。</p><h3 id="标准做法：Query-扩写"><a href="#标准做法：Query-扩写" class="headerlink" title="标准做法：Query 扩写"></a>标准做法：Query 扩写</h3><p>常规的解法是用一个小模型对用户原始 Query 做扩写，生成几个同义或近义的问句，然后分别检索，最后合并结果。</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">expand_query</span><span class="params">(original_query: str)</span> -&gt; list[str]:</span></span><br><span class="line">    <span class="string">"""用 LLM 对用户 Query 进行扩写"""</span></span><br><span class="line">    prompt = <span class="string">f"""</span></span><br><span class="line"><span class="string">    用户输入了一个简短的问题："<span class="subst">&#123;original_query&#125;</span>"</span></span><br><span class="line"><span class="string">    请生成 3 个语义相近但表述不同的改写版本，</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">    要求：</span></span><br><span class="line"><span class="string">    1. 改写后的问题必须和原问题语义一致</span></span><br><span class="line"><span class="string">    2. 覆盖不同的表述角度</span></span><br><span class="line"><span class="string">    3. 每个改写独立一行，用数字编号</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">    用户输入："怎么退费"</span></span><br><span class="line"><span class="string">    改写：</span></span><br><span class="line"><span class="string">    1. 如何申请退款，退费的流程是什么？</span></span><br><span class="line"><span class="string">    2. 退款需要满足什么条件，多久能到账？</span></span><br><span class="line"><span class="string">    3. 退费申请在哪里提交，需要哪些材料？</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    response = llm.generate(prompt)</span><br><span class="line">    <span class="keyword">return</span> parse_expansions(response)</span><br></pre></td></tr></table></figure><p>思路没错，但这里藏着一个非常容易踩到的巨坑。</p><h3 id="巨坑：改写模型的幻觉"><a href="#巨坑：改写模型的幻觉" class="headerlink" title="巨坑：改写模型的幻觉"></a>巨坑：改写模型的幻觉</h3><p>扩写模型一旦出现幻觉，你的整个检索就会被带偏。</p><p>举个例子：</p><div class="table-container"><table><thead><tr><th>原始 Query</th><th>正确扩写</th><th>幻觉扩写</th></tr></thead><tbody><tr><td>怎么退费</td><td>如何申请退款</td><td>如何收费 ← <strong>反义了！</strong></td></tr><tr><td>开票规则</td><td>发票开具的规定</td><td>不开票的流程</td></tr><tr><td>有效期</td><td>产品的有效期限</td><td>过期了怎么办 ← <strong>语义偏移了</strong></td></tr></tbody></table></div><p>“怎么退费”被扩写成”怎么收费”——一个是问退款，一个是问收费，语义完全相反。这种扩写结果参与检索，不仅不会帮忙，反而会引入大量噪声，把正确答案从 Top-K 里挤出去。</p><p>你花大力气做的检索系统，被自己加的 Query 扩写模块给废了。</p><h3 id="兜底机制：余弦相似度校验"><a href="#兜底机制：余弦相似度校验" class="headerlink" title="兜底机制：余弦相似度校验"></a>兜底机制：余弦相似度校验</h3><p>这里必须加一道防线：对所有扩写后的 Query，做语义相似度校验。</p><figure class="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><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><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> sentence_transformers <span class="keyword">import</span> SentenceTransformer, util</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">QueryRewriter</span>:</span></span><br><span class="line">    <span class="string">"""带语义校验的 Query 扩写器"""</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 相似度阈值：通用场景的黄金值</span></span><br><span class="line">    SIMILARITY_THRESHOLD = <span class="number">0.8</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.embed_model = SentenceTransformer(<span class="string">"BAAI/bge-large-zh-v1.5"</span>)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">safe_expand</span><span class="params">(self, original_query: str)</span> -&gt; list[str]:</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">        """</span></span><br><span class="line">        <span class="comment"># 1. 用 LLM 生成扩写结果</span></span><br><span class="line">        expansions = self._llm_expand(original_query)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 2. 计算原始 Query 的 embedding</span></span><br><span class="line">        original_embedding = self.embed_model.encode(</span><br><span class="line">            original_query, convert_to_tensor=<span class="literal">True</span></span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 3. 逐条校验</span></span><br><span class="line">        valid_expansions = []</span><br><span class="line">        <span class="keyword">for</span> exp <span class="keyword">in</span> expansions:</span><br><span class="line">            exp_embedding = self.embed_model.encode(</span><br><span class="line">                exp, convert_to_tensor=<span class="literal">True</span></span><br><span class="line">            )</span><br><span class="line">            cosine_sim = util.cos_sim(</span><br><span class="line">                original_embedding, exp_embedding</span><br><span class="line">            ).item()</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> cosine_sim &gt;= self.SIMILARITY_THRESHOLD:</span><br><span class="line">                <span class="comment"># 相似度达标，保留</span></span><br><span class="line">                valid_expansions.append(exp)</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                <span class="comment"># 相似度不达标，说明改写偏离了原意，废弃</span></span><br><span class="line">                print(</span><br><span class="line">                    <span class="string">f"[过滤] '<span class="subst">&#123;exp&#125;</span>' 与原 Query 相似度仅 <span class="subst">&#123;cosine_sim:<span class="number">.3</span>f&#125;</span>，"</span></span><br><span class="line">                    <span class="string">f"低于阈值 <span class="subst">&#123;self.SIMILARITY_THRESHOLD&#125;</span>，已丢弃"</span></span><br><span class="line">                )</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 4. 兜底：如果所有扩写都被过滤了，至少用原始 Query 检索</span></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> valid_expansions:</span><br><span class="line">            valid_expansions = [original_query]</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> valid_expansions</span><br></pre></td></tr></table></figure><p>0.8 这个阈值是怎么来的？</p><p>这是通用场景下经过大量实验验证的经验值：</p><ul><li><strong>≥ 0.8</strong>：改写基本保持了原意，可以安全使用</li><li><strong>0.6 ~ 0.8</strong>：有一定语义偏移，需要根据具体业务判断</li><li><strong>&lt; 0.6</strong>：基本可以认为改写偏离了原意</li></ul><p>当然，你的业务场景不同，这个阈值需要微调。建议拿 100 条真实用户 Query，人工标注扩写质量，画出相似度分布图，找到最佳切分点。</p><h3 id="这一步的价值"><a href="#这一步的价值" class="headerlink" title="这一步的价值"></a>这一步的价值</h3><p>这一步的核心价值不在于”提升上限”，而在于守住下限：从源头杜绝我们自己给系统引入噪声。不加这道防线，你的 Query 扩写模块就是一个随机的噪声注入器，系统表现会极不稳定。</p><h2 id="第三环：混合检索与重排序-——-解决量纲冲突"><a href="#第三环：混合检索与重排序-——-解决量纲冲突" class="headerlink" title="第三环：混合检索与重排序 —— 解决量纲冲突"></a>第三环：混合检索与重排序 —— 解决量纲冲突</h2><h3 id="问题：两个维度的分数怎么合并"><a href="#问题：两个维度的分数怎么合并" class="headerlink" title="问题：两个维度的分数怎么合并"></a>问题：两个维度的分数怎么合并</h3><p>大家都知道 RAG 要做混合检索：向量检索（Dense Retrieval）+ 关键词检索（BM25 Sparse Retrieval）。向量检索擅长语义匹配，BM25 擅长精确关键词匹配，两者互补。</p><p>但问题来了——</p><div class="table-container"><table><thead><tr><th>检索方式</th><th>打分范围</th><th>示例分数</th></tr></thead><tbody><tr><td>向量检索（余弦相似度）</td><td>[0, 1]</td><td>0.87</td></tr><tr><td>BM25</td><td>[0, +∞)</td><td>15.3</td></tr></tbody></table></div><p>向量检索的打分是 0~1 之间的余弦相似度，BM25 的打分可能是十几甚至几十。两个维度的量纲完全不一样，我们怎么合并？</p><p>最朴素的做法是归一化，然后加权求和：</p><figure class="highlight python"><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="comment"># 朴素做法（不推荐）</span></span><br><span class="line">final_score = <span class="number">0.7</span> * normalize(vector_score) + <span class="number">0.3</span> * normalize(bm25_score)</span><br></pre></td></tr></table></figure><p>0.7 和 0.3 都是拍脑袋定的。</p><p>不同的 Query 类型，最优权重完全不一样：</p><ul><li>对于”合同违约条款第几条”这种精确关键词查询，BM25 的权重应该更高</li><li>对于”这个产品适合什么人用”这种语义模糊查询，向量检索的权重应该更高</li></ul><p>拍脑袋定一个固定权重，等于用一种策略应对所有场景，效果不可能好。</p><h3 id="工业界解法：LambdaMART-排序学习"><a href="#工业界解法：LambdaMART-排序学习" class="headerlink" title="工业界解法：LambdaMART 排序学习"></a>工业界解法：LambdaMART 排序学习</h3><p>工业界的标准做法是用排序学习（Learning to Rank）模型，其中 LambdaMART 是最成熟、应用最广泛的算法之一。</p><p>LambdaMART 的核心思想是：不靠人拍脑袋定权重，而是让模型自己学。</p><figure class="highlight plain"><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">                  ┌──────────────┐</span><br><span class="line">向量检索分数 ─────→│              │</span><br><span class="line">                  │  LambdaMART  │──→ 统一打分</span><br><span class="line">BM25 分数 ───────→│   模型       │</span><br><span class="line">                  │              │</span><br><span class="line">其他特征 ────────→│              │</span><br><span class="line">(文档长度、       └──────────────┘</span><br><span class="line"> 标题匹配度、</span><br><span class="line"> 位置信息等)</span><br></pre></td></tr></table></figure><p>它做的事情是：把所有检索通道的特征（向量分数、BM25 分数、文档长度、标题匹配度、位置信息等）统一映射到同一个打分维度，输出一个科学合理的综合排序分数。</p><h4 id="为什么是-LambdaMART"><a href="#为什么是-LambdaMART" class="headerlink" title="为什么是 LambdaMART"></a>为什么是 LambdaMART</h4><p>排序学习有三大类方法：</p><div class="table-container"><table><thead><tr><th>方法类别</th><th>代表算法</th><th>特点</th></tr></thead><tbody><tr><td>Pointwise</td><td>线性回归、逻辑回归</td><td>逐条打分，不考虑文档间的相对顺序</td></tr><tr><td>Pairwise</td><td>RankSVM、RankNet</td><td>优化文档对的相对顺序</td></tr><tr><td>Listwise</td><td>LambdaMART、LambdaRank</td><td>直接优化整个排序列表的指标（如 NDCG）</td></tr></tbody></table></div><p>LambdaMART 属于 Listwise 方法，它直接优化 NDCG 这样的排序质量指标，而不是逐条或逐对优化。这在信息检索场景下效果最好，因为用户关心的是整个结果列表的质量，而不是某一条结果的绝对分数。</p><h4 id="实操代码"><a href="#实操代码" class="headerlink" title="实操代码"></a>实操代码</h4><figure class="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><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><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> lightgbm <span class="keyword">as</span> lgb</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">HybridRetriever</span>:</span></span><br><span class="line">    <span class="string">"""基于 LambdaMART 的混合检索重排序器"""</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.lambdamart_model = <span class="literal">None</span></span><br><span class="line">        self.vector_retriever = VectorRetriever()</span><br><span class="line">        self.bm25_retriever = BM25Retriever()</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">retrieve</span><span class="params">(self, query: str, top_k: int = <span class="number">10</span>)</span> -&gt; list[dict]:</span></span><br><span class="line">        <span class="string">"""混合检索 + LambdaMART 重排序"""</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 1. 双路召回</span></span><br><span class="line">        vector_results = self.vector_retriever.search(query, top_k=<span class="number">50</span>)</span><br><span class="line">        bm25_results = self.bm25_retriever.search(query, top_k=<span class="number">50</span>)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 2. 合并候选集（去重）</span></span><br><span class="line">        candidates = self._merge_candidates(vector_results, bm25_results)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 3. 构建特征矩阵</span></span><br><span class="line">        features = self._build_features(query, candidates)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 4. LambdaMART 重排序</span></span><br><span class="line">        reranked_scores = self.lambdamart_model.predict(features)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 5. 按重排序分数排序，返回 Top-K</span></span><br><span class="line">        sorted_indices = np.argsort(-reranked_scores)</span><br><span class="line">        <span class="keyword">return</span> [candidates[i] <span class="keyword">for</span> i <span class="keyword">in</span> sorted_indices[:top_k]]</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_build_features</span><span class="params">(self, query: str, candidates: list)</span> -&gt; np.ndarray:</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">        这些特征就是 LambdaMART 的输入。</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        features = []</span><br><span class="line">        <span class="keyword">for</span> doc <span class="keyword">in</span> candidates:</span><br><span class="line">            feature = [</span><br><span class="line">                doc[<span class="string">"vector_score"</span>],           <span class="comment"># 向量相似度分数</span></span><br><span class="line">                doc[<span class="string">"bm25_score"</span>],             <span class="comment"># BM25 分数</span></span><br><span class="line">                doc[<span class="string">"title_match_score"</span>],      <span class="comment"># 标题匹配度</span></span><br><span class="line">                doc[<span class="string">"doc_length"</span>],             <span class="comment"># 文档长度</span></span><br><span class="line">                doc[<span class="string">"query_doc_overlap"</span>],      <span class="comment"># Query 与文档的关键词重叠率</span></span><br><span class="line">                doc[<span class="string">"position_in_doc"</span>],        <span class="comment"># 匹配片段在文档中的位置</span></span><br><span class="line">                doc[<span class="string">"section_level"</span>],          <span class="comment"># 所在章节层级</span></span><br><span class="line">            ]</span><br><span class="line">            features.append(feature)</span><br><span class="line">        <span class="keyword">return</span> np.array(features)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">train</span><span class="params">(self, train_data: lgb.Dataset, params: dict)</span>:</span></span><br><span class="line">        <span class="string">"""训练 LambdaMART 模型"""</span></span><br><span class="line">        params.update(&#123;</span><br><span class="line">            <span class="string">"objective"</span>: <span class="string">"lambdarank"</span>,     <span class="comment"># 排序学习目标</span></span><br><span class="line">            <span class="string">"metric"</span>: <span class="string">"ndcg"</span>,              <span class="comment"># 优化 NDCG 指标</span></span><br><span class="line">            <span class="string">"ndcg_eval_at"</span>: [<span class="number">5</span>, <span class="number">10</span>],       <span class="comment"># 在 Top-5 和 Top-10 处评估</span></span><br><span class="line">            <span class="string">"learning_rate"</span>: <span class="number">0.05</span>,</span><br><span class="line">            <span class="string">"num_leaves"</span>: <span class="number">31</span>,</span><br><span class="line">            <span class="string">"max_depth"</span>: <span class="number">6</span>,</span><br><span class="line">            <span class="string">"min_data_in_leaf"</span>: <span class="number">50</span>,</span><br><span class="line">        &#125;)</span><br><span class="line">        self.lambdamart_model = lgb.train(params, train_data)</span><br></pre></td></tr></table></figure><p>训练数据的标注也不复杂：对于一批真实的 Query，标注每个候选文档的相关性等级（0=不相关，1=部分相关，2=完全相关），构造成排序学习的训练格式即可。</p><h3 id="这一步的效果-1"><a href="#这一步的效果-1" class="headerlink" title="这一步的效果"></a>这一步的效果</h3><p>相比固定权重的线性融合，LambdaMART 重排序通常能把 Recall@10 提升 5~10 个百分点，NDCG@10 提升更明显。而且它是轻量模型，推理延迟在毫秒级，不会影响线上性能。</p><h2 id="第四环：评估指标拆解-——-不做”笼统评分”"><a href="#第四环：评估指标拆解-——-不做”笼统评分”" class="headerlink" title="第四环：评估指标拆解 —— 不做”笼统评分”"></a>第四环：评估指标拆解 —— 不做”笼统评分”</h2><h3 id="为什么”准确率-85-”没有说服力"><a href="#为什么”准确率-85-”没有说服力" class="headerlink" title="为什么”准确率 85%”没有说服力"></a>为什么”准确率 85%”没有说服力</h3><p>老板：系统准确率多少？你说：85%。</p><p>老板：那剩下的 15% 是什么问题？你说：呃…</p><p>只说整体准确率，完全没有意义。 因为你不知道问题出在哪——是检索没找到正确答案？还是检索找到了，但大模型没用好？还是大模型瞎编了？</p><p>不同环节的问题，优化方向完全不同。你必须把指标拆开看。</p><h3 id="两个核心指标"><a href="#两个核心指标" class="headerlink" title="两个核心指标"></a>两个核心指标</h3><p>RAG 系统的评估，核心盯两个指标就够了：</p><h4 id="指标一：Context-Recall（上下文召回率）"><a href="#指标一：Context-Recall（上下文召回率）" class="headerlink" title="指标一：Context Recall（上下文召回率）"></a>指标一：Context Recall（上下文召回率）</h4><blockquote><p><strong>定义</strong>：用户问题的正确答案，是否出现在检索回来的 Top-N 切片里</p></blockquote><p>换句话说：检索环节是否把”正确答案的线索”给找回来了？</p><ul><li>如果正确答案不在 Top-N 里 → 检索环节出了问题 → 去优化文档分块、检索策略</li><li>如果正确答案在 Top-N 里但最终回答错了 → 检索没问题，问题在生成环节</li></ul><figure class="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><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">compute_context_recall</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">    question: str,</span></span></span><br><span class="line"><span class="function"><span class="params">    ground_truth: str,</span></span></span><br><span class="line"><span class="function"><span class="params">    retrieved_chunks: list[str],</span></span></span><br><span class="line"><span class="function"><span class="params">    evaluator_llm</span></span></span><br><span class="line"><span class="function"><span class="params">)</span> -&gt; float:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    计算 Context Recall。</span></span><br><span class="line"><span class="string">    核心思路：检查 ground_truth 中的关键信息是否出现在 retrieved_chunks 中。</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    prompt = <span class="string">f"""</span></span><br><span class="line"><span class="string">    问题：<span class="subst">&#123;question&#125;</span></span></span><br><span class="line"><span class="string">    标准答案：<span class="subst">&#123;ground_truth&#125;</span></span></span><br><span class="line"><span class="string">    检索到的上下文：</span></span><br><span class="line"><span class="string">    <span class="subst">&#123;<span class="string">""</span>.join([<span class="string">f"[<span class="subst">&#123;i+<span class="number">1</span>&#125;</span>] <span class="subst">&#123;chunk&#125;</span>"</span> <span class="keyword">for</span> i, chunk <span class="keyword">in</span> enumerate(retrieved_chunks)])&#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">    请返回一个 0~1 之间的分数。</span></span><br><span class="line"><span class="string">    评分标准：</span></span><br><span class="line"><span class="string">    - 1.0：所有关键信息都能在上下文中找到</span></span><br><span class="line"><span class="string">    - 0.5：部分关键信息能找到</span></span><br><span class="line"><span class="string">    - 0.0：关键信息完全找不到</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    score = evaluator_llm.generate(prompt)</span><br><span class="line">    <span class="keyword">return</span> float(score)</span><br></pre></td></tr></table></figure><h4 id="指标二：Faithfulness（忠实度）"><a href="#指标二：Faithfulness（忠实度）" class="headerlink" title="指标二：Faithfulness（忠实度）"></a>指标二：Faithfulness（忠实度）</h4><blockquote><p><strong>定义</strong>：大模型生成的回答，是否忠实于检索到的资料？有没有脱离资料瞎编？</p></blockquote><p>这就是我们常说的幻觉率。检索回来的资料里明明写的是 A，大模型偏偏说是 B，这就是不忠实。</p><figure class="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><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="function"><span class="keyword">def</span> <span class="title">compute_faithfulness</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">    answer: str,</span></span></span><br><span class="line"><span class="function"><span class="params">    retrieved_chunks: list[str],</span></span></span><br><span class="line"><span class="function"><span class="params">    evaluator_llm</span></span></span><br><span class="line"><span class="function"><span class="params">)</span> -&gt; float:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    计算 Faithfulness。</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="comment"># 1. 把回答拆解成独立的声明（claims）</span></span><br><span class="line">    claims_prompt = <span class="string">f"""</span></span><br><span class="line"><span class="string">    请将以下回答拆解为独立的事实声明：</span></span><br><span class="line"><span class="string">    <span class="subst">&#123;answer&#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">    claims = evaluator_llm.generate(claims_prompt).strip().split(<span class="string">"\n"</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 2. 逐条检查每个声明是否有资料支撑</span></span><br><span class="line">    context = <span class="string">"\n"</span>.join(retrieved_chunks)</span><br><span class="line">    supported_count = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> claim <span class="keyword">in</span> claims:</span><br><span class="line">        check_prompt = <span class="string">f"""</span></span><br><span class="line"><span class="string">        声明：<span class="subst">&#123;claim&#125;</span></span></span><br><span class="line"><span class="string">        上下文资料：</span></span><br><span class="line"><span class="string">        <span class="subst">&#123;context&#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">        回答 "yes" 或 "no"。</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        result = evaluator_llm.generate(check_prompt).strip().lower()</span><br><span class="line">        <span class="keyword">if</span> result == <span class="string">"yes"</span>:</span><br><span class="line">            supported_count += <span class="number">1</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 3. Faithfulness = 有依据的声明数 / 总声明数</span></span><br><span class="line">    <span class="keyword">return</span> supported_count / len(claims) <span class="keyword">if</span> claims <span class="keyword">else</span> <span class="number">0.0</span></span><br></pre></td></tr></table></figure><h3 id="用指标拆解驱动优化"><a href="#用指标拆解驱动优化" class="headerlink" title="用指标拆解驱动优化"></a>用指标拆解驱动优化</h3><p>把这两个指标监控起来之后，你就能精准定位问题了：</p><div class="table-container"><table><thead><tr><th style="text-align:center">Context Recall</th><th style="text-align:center">Faithfulness</th><th>诊断</th><th>优化方向</th></tr></thead><tbody><tr><td style="text-align:center">高</td><td style="text-align:center">高</td><td>✅ 系统健康</td><td>维持现状</td></tr><tr><td style="text-align:center"><strong>低</strong></td><td style="text-align:center">高</td><td>检索没找对，但生成没问题</td><td>优化文档分块、Query 扩写、检索策略</td></tr><tr><td style="text-align:center">高</td><td style="text-align:center"><strong>低</strong></td><td>检索找对了，但大模型瞎编</td><td>优化 Prompt、加引用约束、换模型</td></tr><tr><td style="text-align:center"><strong>低</strong></td><td style="text-align:center"><strong>低</strong></td><td>全链路都有问题</td><td>从头到尾逐步排查</td></tr></tbody></table></div><p><strong>这才是工程化的做法</strong>——不是看着一个笼统的分数盲目调参，而是用指标拆解精确定位问题环节，针对性优化。</p><h2 id="效果总结：四步优化的累计收益"><a href="#效果总结：四步优化的累计收益" class="headerlink" title="效果总结：四步优化的累计收益"></a>效果总结：四步优化的累计收益</h2><p>把四步优化串起来，看一个典型的收益曲线：</p><figure class="highlight plain"><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">  │</span><br><span class="line">  │                                          ● 85%</span><br><span class="line">  │                                    ●─────┘</span><br><span class="line">  │                              ●─────┘</span><br><span class="line">  │  ● 60%               ●─────┘</span><br><span class="line">  │  │  基线        ●─────┘</span><br><span class="line">  │  │              │</span><br><span class="line">  │  │    +15pp     │ +5pp</span><br><span class="line">  │  │  (文档分块)  │(混合重排)</span><br><span class="line">  │  │              │</span><br><span class="line">  │  ●────────●─────●</span><br><span class="line">  │           │</span><br><span class="line">  │      守住下限</span><br><span class="line">  │     (Query校验)</span><br><span class="line">  └────────────────────────────────→</span><br><span class="line">     基线    第一步   第二步   第三步   第四步</span><br><span class="line">                                 (指标拆解)</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th>优化步骤</th><th>核心动作</th><th>典型收益</th></tr></thead><tbody><tr><td>第一步：文档分块</td><td>语义感知动态切分 + 重叠窗口</td><td><strong>+10~15pp</strong></td></tr><tr><td>第二步：Query 校验</td><td>扩写 + 余弦相似度 ≥ 0.8 兜底</td><td>守住下限，防退化</td></tr><tr><td>第三步：混合重排</td><td>LambdaMART 统一打分</td><td><strong>+5~10pp</strong>（Recall@10）</td></tr><tr><td>第四步：指标拆解</td><td>Context Recall + Faithfulness 分开监控</td><td>精准定位，持续迭代</td></tr></tbody></table></div><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><blockquote><p><strong>切分</strong>不搞一刀切，动态语义加重叠。<br><strong>改写</strong>不丢原语义，相似度校验做兜底。<br><strong>排序</strong>不拍脑袋定，LambdaMART 来统一。<br><strong>优化</strong>不看笼统分，召回忠实拆分明。</p></blockquote><p>RAG 系统从 Demo 到工业级，不是靠换一个更大的模型就能解决的。它是一条精密的流水线，每一个环节都需要工程化的思维和可量化的优化。把上面这四步做透，RAG 系统就能真正扛住线上流量的考验。</p><hr><p><strong>参考资源</strong>：</p><ul><li><a href="https://www.anthropic.com/news/contextual-retrieval" target="_blank" rel="noopener">Anthropic Contextual Retrieval</a></li><li><a href="https://github.com/explodinggradients/ragas" target="_blank" rel="noopener">RAGAS — RAG 评估框架</a></li><li><a href="https://lightgbm.readthedocs.io/en/latest/parameters.html#learning-rate-parameters" target="_blank" rel="noopener">LightGBM LambdaRank 文档</a></li><li><a href="https://www.sbert.net/" target="_blank" rel="noopener">Sentence Transformers</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;做 RAG 系统的开发者，大概都会经历过这样一个过程：&lt;/p&gt;
&lt;p&gt;Demo 阶段跑几个测试用例，效果惊艳。一上真实业务数据，准确率直接掉到 60% 甚至更低，用户投诉不断，自己也说不清问题出在哪。&lt;/p&gt;
&lt;p&gt;我曾经连续三周每天晚上对着 Bad Case 分析表发呆，改了 Prompt 没用，换了大模型没用，调了 TopK 还是没用。最后发现，问题根本不在大模型那一环，而是在大模型之前的整条链路上。&lt;/p&gt;
&lt;p&gt;这篇文章通过深度讲解四步优化，每一步都能量化地拉高准确率，最终从 60% 做到 85%。&lt;/p&gt;</summary>
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="RAG" scheme="https://donehub.github.io/tags/RAG/"/>
    
  </entry>
  
  <entry>
    <title>多Agent系统的设计与评估</title>
    <link href="https://donehub.github.io/2026/04/30/multi-agent-decision-and-evaluation/"/>
    <id>https://donehub.github.io/2026/04/30/multi-agent-decision-and-evaluation/</id>
    <published>2026-04-29T16:00:00.000Z</published>
    <updated>2026-04-30T15:19:12.023Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>“任务越复杂越该用多Agent”——听起来好像没毛病，但这句话背后藏着一个巨大的陷阱。很多人一拍脑袋就上多Agent，结果延迟爆表、成本失控、日志一团浆糊，最后发现单Agent加个工具调用就能搞定。上一篇我们聊了 Multi-Agent 系统的设计原理，今天换个角度：什么时候该用多Agent？用完之后，怎么评判这套设计到底好不好？</p></blockquote><hr><h2 id="一、先破一个误区：复杂度不是决策标准"><a href="#一、先破一个误区：复杂度不是决策标准" class="headerlink" title="一、先破一个误区：复杂度不是决策标准"></a>一、先破一个误区：复杂度不是决策标准</h2><p>很多人一听到”多Agent”，脑子里的画面是这样的：一个主控 Agent 坐镇中央，下面挂着写代码的、做测试的、写文档的，各司其职，特别壮观。</p><p>但壮观不等于好用。</p><p><strong>复杂度从来不是选择多Agent的理由。</strong> 真正的决策标准只有两个维度：<strong>并发收益</strong>和<strong>上下文约束</strong>。</p><p>用一张表说清楚：</p><div class="table-container"><table><thead><tr><th>场景特征</th><th>单Agent够用吗</th><th>要不要上多Agent</th></tr></thead><tbody><tr><td>步骤严格串行，后一步依赖前一步结果</td><td>够用</td><td>不要，纯浪费</td></tr><tr><td>多个子任务互相独立，可以同时跑</td><td>不够</td><td>要，并行收益明显</td></tr><tr><td>单个任务的上下文撑爆模型窗口</td><td>不够</td><td>要，必须切分</td></tr><tr><td>子任务之间需要频繁交换中间状态</td><td>勉强</td><td>谨慎，通信开销可能吃掉收益</td></tr></tbody></table></div><p>但这张表太粗了，真实决策远比这复杂。下面我们拆开来讲。</p><hr><h2 id="二、决策框架：四把尺子量出答案"><a href="#二、决策框架：四把尺子量出答案" class="headerlink" title="二、决策框架：四把尺子量出答案"></a>二、决策框架：四把尺子量出答案</h2><h3 id="2-1-第一把尺子：任务依赖图是线性的还是扇出的？"><a href="#2-1-第一把尺子：任务依赖图是线性的还是扇出的？" class="headerlink" title="2.1 第一把尺子：任务依赖图是线性的还是扇出的？"></a>2.1 第一把尺子：任务依赖图是线性的还是扇出的？</h3><p>这是最核心的判断依据。</p><p><strong>线性依赖</strong>是指任务之间有严格的先后顺序：A 的输出是 B 的输入，B 的输出是 C 的输入。比如用户问”我的退款到哪一步了”——先识别意图，再查订单库，最后组织回复。这三步必须串行，中间插不进任何并行操作。</p><p><strong>扇出依赖</strong>是指一个任务可以拆成多个互不相关的子任务同时执行。比如”帮我审查这个代码仓库的安全性”——注入漏洞扫描、内存泄漏检测、代码风格检查，这三件事之间没有任何数据依赖，完全可以同时开工。</p><p>画成图就是这样的区别：</p><figure class="highlight plain"><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">线性依赖（单Agent）：</span><br><span class="line">意图识别 → 查数据库 → 组织回复</span><br><span class="line">  A    →    B     →    C</span><br><span class="line"></span><br><span class="line">扇出依赖（多Agent）：</span><br><span class="line">            ┌→ 安全检测 ──┐</span><br><span class="line">主控规划 ──→├→ 性能分析 ──├→ 汇总合并</span><br><span class="line">            └→ 规范检查 ──┘</span><br></pre></td></tr></table></figure><p><strong>判断标准</strong>：如果你的任务依赖图画出来是一条直线，用单Agent。如果画出来像一把扇子，考虑多Agent。</p><h3 id="2-2-第二把尺子：上下文窗口是不是硬瓶颈？"><a href="#2-2-第二把尺子：上下文窗口是不是硬瓶颈？" class="headerlink" title="2.2 第二把尺子：上下文窗口是不是硬瓶颈？"></a>2.2 第二把尺子：上下文窗口是不是硬瓶颈？</h3><p>有些任务看起来是线性的，但数据量大到单个模型根本吃不下。这时候即使任务是串行的，你也得想办法切分。</p><p>举个例子：给你一个 50 万行的代码仓库，要求生成完整的 API 文档。这不是并行任务——你需要理解全局架构才能写好文档。但问题是，50 万行代码塞不进任何模型的上下文窗口。</p><p>这时候多Agent的价值不是并行加速，而是<strong>上下文切片</strong>：</p><figure class="highlight plain"><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">Agent-1：扫描 controller 层，提取接口定义</span><br><span class="line">Agent-2：扫描 service 层，提取业务逻辑</span><br><span class="line">Agent-3：扫描 entity 层，提取数据模型</span><br><span class="line">主控 Agent：拿到三份切片报告，合并生成文档</span><br></pre></td></tr></table></figure><p>每个 Agent 只需要处理自己那一层的代码，上下文压力骤降。最后主控 Agent 拿到的是三份精炼过的报告，而不是几十万行原始代码。</p><p><strong>判断标准</strong>：估算一下任务需要的上下文 token 量。如果超过模型窗口的 60%，就要考虑切分了。留 40% 的余量给系统 Prompt、工具调用和中间推理。</p><h3 id="2-3-第三把尺子：延迟预算够不够？"><a href="#2-3-第三把尺子：延迟预算够不够？" class="headerlink" title="2.3 第三把尺子：延迟预算够不够？"></a>2.3 第三把尺子：延迟预算够不够？</h3><p>这是最容易被忽略的一把尺子。</p><p>多Agent系统的延迟公式非常残酷：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">总延迟 &#x3D; max(各子Agent延迟) + 主控规划延迟 + 主控汇总延迟 + 通信开销</span><br></pre></td></tr></table></figure><p>注意这里用的是 <code>max</code> 不是 <code>sum</code>——因为子 Agent 是并行的，总延迟取决于最慢的那个。听起来不错对吧？但别忘了后面还有三项固定开销。</p><p>在真实的线上环境里，大模型的一次调用延迟通常在 2-8 秒。一个三层架构的多Agent系统，光是规划和汇总就要各调一次模型，加上子 Agent 的执行时间，端到端延迟很容易突破 15 秒。</p><p>对比一下单Agent方案：</p><figure class="highlight plain"><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">单Agent：意图识别(2s) → 工具调用(1s) → 总结回复(2s) &#x3D; 5s</span><br><span class="line">多Agent：规划(3s) + max(子Agent×3)(4s) + 汇总(3s) + 通信(1s) &#x3D; 11s</span><br></pre></td></tr></table></figure><p><strong>判断标准</strong>：如果你的场景是面向用户的实时交互（聊天机器人、客服系统），延迟预算通常在 5 秒以内。这种场景下多Agent几乎一定会超时，单Agent + 工具调用是更务实的选择。如果是后台批处理任务（代码审计、报告生成），延迟预算宽松，多Agent才有发挥空间。</p><h3 id="2-4-第四把尺子：Token-预算能不能扛得住？"><a href="#2-4-第四把尺子：Token-预算能不能扛得住？" class="headerlink" title="2.4 第四把尺子：Token 预算能不能扛得住？"></a>2.4 第四把尺子：Token 预算能不能扛得住？</h3><p>多Agent系统的 Token 消耗不是线性增长，而是<strong>阶梯式跳涨</strong>。</p><p>每一次 Agent 调用，你都要支付：</p><ul><li>系统 Prompt（每次都一样，但每次都要算钱）</li><li>上下文注入（任务描述、历史信息）</li><li>模型推理（输出 token）</li></ul><p>假设你有一个主控 Agent + 5 个子 Agent，每个 Agent 的系统 Prompt 是 2000 token，任务描述平均 1000 token，输出平均 1500 token。那么一轮完整执行的 Token 消耗大约是：</p><figure class="highlight plain"><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">主控：2000 + 1000 + 1500 &#x3D; 4500</span><br><span class="line">子Agent×5：(2000 + 1000 + 1500) × 5 &#x3D; 22500</span><br><span class="line">汇总：2000 + 3000(子Agent结果) + 2000 &#x3D; 7000</span><br><span class="line">总计：约 34000 token</span><br></pre></td></tr></table></figure><p>而单Agent完成同样任务，可能只需要 5000-8000 token。</p><p><strong>判断标准</strong>：如果你的系统要处理高并发请求（比如每秒几百个），Token 成本的差异会被放大几百倍。在做架构选型时，先算一笔账：多Agent方案的 Token 成本是单Agent的几倍？这个倍数你的预算扛不扛得住？</p><hr><h2 id="三、四个典型场景的决策推演"><a href="#三、四个典型场景的决策推演" class="headerlink" title="三、四个典型场景的决策推演"></a>三、四个典型场景的决策推演</h2><p>光讲理论不过瘾，我们拿四个真实场景来跑一遍决策流程。</p><h3 id="3-1-场景一：智能客服（结论：单Agent）"><a href="#3-1-场景一：智能客服（结论：单Agent）" class="headerlink" title="3.1 场景一：智能客服（结论：单Agent）"></a>3.1 场景一：智能客服（结论：单Agent）</h3><p>用户问：”我的订单 #12345 退款到哪一步了？”</p><p><strong>依赖图分析</strong>：识别意图 → 查订单 → 回复。严格线性，没有并行空间。</p><p><strong>上下文分析</strong>：一次对话的上下文很小，远不到窗口极限。</p><p><strong>延迟分析</strong>：用户在等回复，延迟预算 3 秒。</p><p><strong>Token 分析</strong>：高并发场景（成千上万用户同时咨询），成本敏感。</p><p>四把尺子量完，全部指向单Agent。上多Agent就是过度设计。</p><h3 id="3-2-场景二：企业级代码安全审计（结论：多Agent）"><a href="#3-2-场景二：企业级代码安全审计（结论：多Agent）" class="headerlink" title="3.2 场景二：企业级代码安全审计（结论：多Agent）"></a>3.2 场景二：企业级代码安全审计（结论：多Agent）</h3><p>需求：扫描一个 10 万行代码仓库，输出安全漏洞报告。</p><p><strong>依赖图分析</strong>：注入扫描、内存泄漏检测、依赖漏洞检查、代码规范审查——四个子任务完全独立。</p><p><strong>上下文分析</strong>：10 万行代码远超单模型窗口，必须切片。</p><p><strong>延迟分析</strong>：后台批处理，用户可以等几分钟，延迟预算宽松。</p><p><strong>Token 分析</strong>：低并发（一天可能就跑几次），成本可控。</p><p>四把尺子量完，全部指向多Agent。</p><h3 id="3-3-场景三：长文档摘要生成（结论：伪多Agent-单Agent-分段策略）"><a href="#3-3-场景三：长文档摘要生成（结论：伪多Agent-单Agent-分段策略）" class="headerlink" title="3.3 场景三：长文档摘要生成（结论：伪多Agent / 单Agent + 分段策略）"></a>3.3 场景三：长文档摘要生成（结论：伪多Agent / 单Agent + 分段策略）</h3><p>需求：把一份 200 页的技术文档浓缩成 5 页摘要。</p><p>乍一看，200 页文档肯定塞不进上下文，应该用多Agent切分对吧？</p><p>但仔细想想：文档摘要是<strong>有全局连贯性要求</strong>的。你不能让 Agent-A 总结第 1-50 页、Agent-B 总结第 51-100 页，然后拼在一起——这样出来的摘要会有大量重复、遗漏和逻辑断裂。</p><p>更好的方案是<strong>单Agent + 分段迭代策略</strong>：</p><figure class="highlight plain"><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">第一轮：单Agent处理第1-50页，产出中间摘要A</span><br><span class="line">第二轮：单Agent带着摘要A处理第51-100页，产出中间摘要B</span><br><span class="line">第三轮：单Agent带着摘要B处理第101-150页，产出中间摘要C</span><br><span class="line">第四轮：单Agent带着摘要C处理第151-200页，产出最终摘要</span><br></pre></td></tr></table></figure><p>每一轮都保留了前序上下文的精华，保证了连贯性。这比多Agent并行切分效果好得多。</p><p><strong>教训</strong>：上下文超限不等于必须上多Agent。有时候<strong>串行分段 + 状态传递</strong>比并行切分更适合有连贯性要求的任务。</p><h3 id="3-4-场景四：多语言翻译流水线（结论：看情况）"><a href="#3-4-场景四：多语言翻译流水线（结论：看情况）" class="headerlink" title="3.4 场景四：多语言翻译流水线（结论：看情况）"></a>3.4 场景四：多语言翻译流水线（结论：看情况）</h3><p>需求：把一份技术文档翻译成英、日、韩三种语言。</p><p><strong>表面分析</strong>：三种语言互不依赖，可以并行——看起来应该用多Agent。</p><p><strong>深层分析</strong>：翻译的前置步骤（术语提取、风格统一）是共享的。如果三个 Agent 各自提取术语，出来的译文风格会不一致。</p><p><strong>最优方案</strong>：混合架构。</p><figure class="highlight plain"><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">阶段一（单Agent）：提取术语表 + 定义翻译风格指南</span><br><span class="line">阶段二（多Agent并行）：三个Agent分别翻译三种语言，共享术语表</span><br><span class="line">阶段三（单Agent）：审校三份译文的一致性</span><br></pre></td></tr></table></figure><p><strong>教训</strong>：很多任务不是纯粹的”该用”或”不该用”多Agent，而是<strong>在流水线的某些阶段用、某些阶段不用</strong>。混合架构才是最常见的生产形态。</p><hr><h2 id="四、怎么评估一个多Agent系统设计得好不好？"><a href="#四、怎么评估一个多Agent系统设计得好不好？" class="headerlink" title="四、怎么评估一个多Agent系统设计得好不好？"></a>四、怎么评估一个多Agent系统设计得好不好？</h2><p>选对了场景只是第一步。就算你决策正确，多Agent系统仍然可能设计得很烂。下面是一套评估框架，从五个维度给系统打分。</p><h3 id="4-1-评估维度一：任务分解的合理性"><a href="#4-1-评估维度一：任务分解的合理性" class="headerlink" title="4.1 评估维度一：任务分解的合理性"></a>4.1 评估维度一：任务分解的合理性</h3><p>这是最基础也最关键的维度。分解不合理，后面的一切都是空中楼阁。</p><p><strong>好的分解</strong>有三个特征：</p><div class="table-container"><table><thead><tr><th>特征</th><th>说明</th><th>反例</th></tr></thead><tbody><tr><td>子任务之间低耦合</td><td>每个子Agent能独立完成自己的工作，不需要等别人的结果</td><td>Agent-A 需要 Agent-B 的输出才能开始</td></tr><tr><td>子任务粒度适中</td><td>不太粗（一个Agent干不完）也不太细（拆太碎通信开销吃掉收益）</td><td>把”查数据库”拆成”建立连接””发送SQL””解析结果”三个Agent</td></tr><tr><td>子任务边界清晰</td><td>每个Agent的职责范围明确，不会出现两个Agent干同一件事</td><td>安全Agent和性能Agent都在扫描同一段代码的同一个函数</td></tr></tbody></table></div><p><strong>快速检验法</strong>：拿一张纸，把每个子Agent的任务写下来。如果你发现两个子Agent的任务描述有超过 30% 的重叠，分解就有问题。</p><h3 id="4-2-评估维度二：通信开销占比"><a href="#4-2-评估维度二：通信开销占比" class="headerlink" title="4.2 评估维度二：通信开销占比"></a>4.2 评估维度二：通信开销占比</h3><p>多Agent系统的总成本 = 计算成本 + 通信成本。</p><p>通信成本包括：</p><ul><li>主控 Agent 向子 Agent 传递任务描述的 Token</li><li>子 Agent 向主控 Agent 返回结果的 Token</li><li>主控 Agent 汇总所有结果的 Token</li><li>如果子 Agent 之间需要通信（不推荐），还有交叉通信的 Token</li></ul><p><strong>健康的系统</strong>：通信开销占总 Token 消耗的 20% 以内。</p><p><strong>有问题的系统</strong>：通信开销超过 40%。这意味着你在花大量 Token 让 Agent 之间”传纸条”，而不是干实事。</p><p>计算公式：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">通信开销占比 &#x3D; (任务传递Token + 结果回传Token + 汇总Token) &#x2F; 总Token消耗 × 100%</span><br></pre></td></tr></table></figure><p>如果你算出来这个比例很高，说明要么子任务拆得太碎（太多小Agent在传话），要么结果压缩做得不好（子Agent带回来的信息太冗余）。</p><h3 id="4-3-评估维度三：并行效率"><a href="#4-3-评估维度三：并行效率" class="headerlink" title="4.3 评估维度三：并行效率"></a>4.3 评估维度三：并行效率</h3><p>多Agent的核心价值是并行。如果并行效率低，用多Agent就没有意义。</p><p><strong>并行效率</strong>的定义：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">并行效率 &#x3D; 单Agent串行完成时间 &#x2F; 多Agent并行完成时间</span><br></pre></td></tr></table></figure><p>理论上，N 个 Agent 并行，效率应该接近 N。但实际上很难达到，因为：</p><ul><li>子 Agent 的任务量不均衡（最慢的那个决定了总时间）</li><li>主控 Agent 的规划和汇总有固定延迟</li><li>通信有网络开销</li></ul><div class="table-container"><table><thead><tr><th>并行效率</th><th>评价</th><th>建议</th></tr></thead><tbody><tr><td>&gt; 2.0x</td><td>优秀</td><td>多Agent方案值得投入</td></tr><tr><td>1.5x - 2.0x</td><td>一般</td><td>看场景，如果成本敏感可以考虑单Agent</td></tr><tr><td>&lt; 1.5x</td><td>差</td><td>多Agent带来的收益不够覆盖额外开销，回退单Agent</td></tr></tbody></table></div><p><strong>提升并行效率的关键</strong>：让每个子 Agent 的工作量尽量均衡。如果一个子 Agent 1 秒就跑完了，另一个要跑 10 秒，那你的并行效率就被拖后腿了。</p><h3 id="4-4-评估维度四：容错与可观测性"><a href="#4-4-评估维度四：容错与可观测性" class="headerlink" title="4.4 评估维度四：容错与可观测性"></a>4.4 评估维度四：容错与可观测性</h3><p>这是工程落地时最容易翻车的维度。</p><p><strong>容错能力</strong>评估清单：</p><ul><li>一个子 Agent 超时或失败了，系统会怎样？是直接整体失败，还是能降级处理？</li><li>子 Agent 返回了错误结果（幻觉），主控 Agent 能不能识别出来？</li><li>有没有设置最大重试次数？重试的 Token 成本有没有上限？</li></ul><p><strong>可观测性</strong>评估清单：</p><ul><li>出了问题，你能在日志里定位到是哪个 Agent 在哪个环节出错的吗？</li><li>每个 Agent 的输入输出有没有完整记录？</li><li>你能不能回放一次完整的执行过程来做 Debug？</li></ul><p>一个残酷的现实：大部分多Agent系统的日志都是一团浆糊。主控 Agent 调了 5 个子 Agent，每个子 Agent 又调了若干工具，出了问题你只能看到一个笼统的”执行失败”，根本不知道是哪一步在胡说八道。</p><p><strong>好的设计</strong>：给每个 Agent 执行分配一个唯一的 trace ID，所有日志都带上这个 ID，方便链路追踪。</p><h3 id="4-5-评估维度五：边际收益递减点"><a href="#4-5-评估维度五：边际收益递减点" class="headerlink" title="4.5 评估维度五：边际收益递减点"></a>4.5 评估维度五：边际收益递减点</h3><p>这是最需要工程直觉的一个维度。</p><p>多Agent系统的子 Agent 数量不是越多越好。存在一个<strong>边际收益递减点</strong>——超过这个点，增加 Agent 带来的并行收益小于增加的通信开销和协调成本。</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">    收益</span><br><span class="line">     ↑</span><br><span class="line">     │        ╭───────── 收益曲线</span><br><span class="line">     │      ╱</span><br><span class="line">     │    ╱</span><br><span class="line">     │  ╱</span><br><span class="line">     │╱</span><br><span class="line">     ├──────────────────→ Agent数量</span><br><span class="line">     ↑</span><br><span class="line">边际收益递减点</span><br></pre></td></tr></table></figure><p><strong>经验值</strong>：在大部分场景下，子 Agent 数量控制在 3-8 个是比较合理的区间。超过 10 个，协调成本会急剧上升。</p><p><strong>怎么找到这个点？</strong> 最靠谱的方法是跑基准测试：</p><ol><li>从 2 个子 Agent 开始，记录执行时间和 Token 消耗</li><li>逐步增加到 3、4、5… 个</li><li>画出”Agent数量 vs 端到端延迟”和”Agent数量 vs Token成本”两条曲线</li><li>找到延迟不再显著下降、但 Token 成本还在上升的那个拐点</li></ol><p>那个拐点就是你的最优 Agent 数量。</p><hr><h2 id="五、一个完整的评估-Checklist"><a href="#五、一个完整的评估-Checklist" class="headerlink" title="五、一个完整的评估 Checklist"></a>五、一个完整的评估 Checklist</h2><p>把上面五个维度浓缩成一份可执行的检查清单。拿到任何一套多Agent系统设计方案，对照这张表打分：</p><figure class="highlight plain"><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">├─ 子任务之间是否低耦合？               □ 是    □ 否</span><br><span class="line">├─ 粒度是否适中？                       □ 是    □ 否</span><br><span class="line">└─ 边界是否清晰？                       □ 是    □ 否</span><br><span class="line"></span><br><span class="line">通信开销占比                            □ 通过  □ 不通过</span><br><span class="line">├─ 通信Token占比 &lt; 20%？               □ 是    □ 否</span><br><span class="line">└─ 子Agent结果是否经过压缩？            □ 是    □ 否</span><br><span class="line"></span><br><span class="line">并行效率                                □ 通过  □ 不通过</span><br><span class="line">├─ 并行加速比 &gt; 1.5x？                 □ 是    □ 否</span><br><span class="line">└─ 子Agent工作量是否均衡？              □ 是    □ 否</span><br><span class="line"></span><br><span class="line">容错与可观测性                          □ 通过  □ 不通过</span><br><span class="line">├─ 子Agent失败是否可降级？              □ 是    □ 否</span><br><span class="line">├─ 每个Agent是否有trace ID？            □ 是    □ 否</span><br><span class="line">└─ 能否回放完整执行过程？               □ 是    □ 否</span><br><span class="line"></span><br><span class="line">边际收益                                □ 通过  □ 不通过</span><br><span class="line">├─ 子Agent数量是否在合理区间(3-8)？     □ 是    □ 否</span><br><span class="line">└─ 是否做过基准测试找最优解？           □ 是    □ 否</span><br></pre></td></tr></table></figure><p>5 个维度全部通过，这是一套合格的多Agent系统设计。有 1-2 个不通过，需要针对性优化。3 个以上不通过——回去重新考虑一下，这个场景是不是真的需要多Agent。</p><hr><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><p>回到文章开头的问题：什么场景下用多Agent系统？</p><p>答案不是”任务越复杂越好”，而是<strong>当你同时撞上了两个天花板时</strong>：</p><ol><li><strong>单一模型的上下文天花板</strong>——数据量大到一个模型吃不下</li><li><strong>串行执行的延迟天花板</strong>——子任务之间没有依赖，串行跑太慢</li></ol><p>只撞上第一个天花板，用单Agent + 分段策略就能解决。只撞上第二个天花板，用单Agent + 异步并发工具调用也能凑合。两个同时撞上，才是多Agent真正不可替代的场景。</p><p>而在评估一套多Agent系统设计时，不要只看架构图好不好看，要拿数据说话：通信开销占比多少？并行效率多少？边际收益递减点在哪？</p><p><strong>用工程的刚性去约束架构的浪漫，这才是做系统设计的正确姿势。</strong></p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;“任务越复杂越该用多Agent”——听起来好像没毛病，但这句话背后藏着一个巨大的陷阱。很多人一拍脑袋就上多Agent，结果延迟爆表、成本失控、日志一团浆糊，最后发现单Agent加个工具调用就能搞定。上一篇我们聊了 Multi-Agent 系统的设计</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="Multi-Agent" scheme="https://donehub.github.io/tags/Multi-Agent/"/>
    
  </entry>
  
  <entry>
    <title>Agent 记忆系统设计规范</title>
    <link href="https://donehub.github.io/2026/04/27/agent-memory-what-to-store/"/>
    <id>https://donehub.github.io/2026/04/27/agent-memory-what-to-store/</id>
    <published>2026-04-26T16:00:00.000Z</published>
    <updated>2026-04-27T09:56:03.740Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>如果你在构建 Agent，一定想过这个问题：怎么让 Agent 跨会话记住用户的偏好、项目的背景、之前犯过的错误？大部分人的做法是搞一个文件，把所有「需要记住的东西」往里塞。文件越来越大，token 成本越来越高，而且大部分内容跟当前对话根本没关系。Claude Code 的记忆系统给出了一个反直觉的设计原则：工程量最大的部分不是「怎么存」，也不是「怎么取」，而是「什么该存、什么不该存」。</p></blockquote><hr><h2 id="一、核心哲学：记忆是代码的补集"><a href="#一、核心哲学：记忆是代码的补集" class="headerlink" title="一、核心哲学：记忆是代码的补集"></a>一、核心哲学：记忆是代码的补集</h2><p>这套记忆系统的设计哲学可以用一句话概括：</p><blockquote><p><strong>记忆是代码的补集。</strong></p></blockquote><p>听起来简单，但这是整个设计里最有启发性的原则。具体来说：</p><div class="table-container"><table><thead><tr><th>维度</th><th>代码/工具擅长</th><th>记忆系统擅长</th></tr></thead><tbody><tr><td>时效性</td><td>实时查询、权威来源</td><td>跨会话沉淀、经验积累</td></tr><tr><td>可变性</td><td>随代码更新自动失效</td><td>需要主动维护、可能过时</td></tr><tr><td>粒度</td><td>精确到函数/文件</td><td>模糊的意图、偏好、上下文</td></tr></tbody></table></div><p><strong>该存的</strong>：全部是「关于人和上下文」的信息——人的偏好、纠正、动机、外部资源指针。这些藏在代码之外，不查记忆就无从得知。</p><p><strong>不该存的</strong>：全部是「关于代码和项目状态」的信息——代码是实时的、可查的、权威的。代码能回答的问题，不要让记忆来回答。</p><p>这个分界线一旦清晰，你会发现很多之前觉得「应该记住」的东西，其实根本不该存。</p><hr><h2 id="二、四种该存的记忆"><a href="#二、四种该存的记忆" class="headerlink" title="二、四种该存的记忆"></a>二、四种该存的记忆</h2><h3 id="2-1-用户记忆：记用户是谁"><a href="#2-1-用户记忆：记用户是谁" class="headerlink" title="2.1 用户记忆：记用户是谁"></a>2.1 用户记忆：记用户是谁</h3><p>用户记忆记录的是用户的角色、技术背景、工作习惯、知识水平。</p><p><strong>好的例子</strong>：</p><blockquote><p>「这个用户是数据科学家，目前在做日志系统的调研。」</p><p>「这个用户写了十年 Go，但第一次碰 React。」</p></blockquote><p>这类记忆的设计意图是让 Agent 能调整沟通方式和工作策略。面对一个资深后端工程师，Agent 不需要解释基础概念，可以直接用技术术语；面对一个初学者，Agent 需要更耐心地铺垫背景。</p><p><strong>关键约束</strong>：记忆的目的是「怎么更好地帮这个人」，不是「给这个人画像」。不要记录对用户的负面评价，也不要记录跟工作无关的个人信息。</p><figure class="highlight plain"><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><br><span class="line">❌ 不要记：「这个用户喜欢喝咖啡」</span><br><span class="line">✅ 要记：「这个用户偏好简洁回复，不需要末尾总结」</span><br></pre></td></tr></table></figure><h3 id="2-2-反馈记忆：记纠正和肯定"><a href="#2-2-反馈记忆：记纠正和肯定" class="headerlink" title="2.2 反馈记忆：记纠正和肯定"></a>2.2 反馈记忆：记纠正和肯定</h3><p>这是四种记忆里设计最精细的一种。源码里对它有三个关键要求。</p><p><strong>要求一：规则 + 原因 + 适用场景</strong></p><p>每条反馈记忆必须包含三个部分：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">规则是什么 → Why → 什么时候该应用</span><br></pre></td></tr></table></figure><p>举个实际例子。用户说：</p><blockquote><p>「测试不要 mock 数据库，上个季度我们就是因为 mock 测试通过了但生产环境迁移失败才出的事故。」</p></blockquote><p>如果只记「不要 mock 数据库」，Agent 在所有测试里都不敢 mock，包括那些跟数据库迁移完全无关的单元测试。</p><p>但如果它知道原因是「mock 和生产环境行为不一致导致迁移失败」，它就能判断：集成测试不该 mock，但纯逻辑的单元测试 mock 是没问题的。</p><p><strong>记原因，是为了让 Agent 能在新场景下做判断，而不是机械地执行规则。</strong></p><p><strong>要求二：不要只记纠正，也要记肯定</strong></p><p>源码注释里说得很直白：</p><blockquote><p>如果你只记录用户说「不要这样做」的时刻，它只知道什么是错的，不知道什么是对的。时间长了，它会回避一切不确定的做法，变得畏手畏脚。</p></blockquote><p>但肯定信号比纠正信号更难捕捉。用户说「不要这样做」很明显，但用户说「对，就是这样」或者默默接受了一个不寻常的方案，需要主动注意这些肯定信号。</p><p><strong>例子</strong>：</p><blockquote><p>用户说：「对，这次用一个大 PR 是对的，拆开反而是无意义的工作量。」</p></blockquote><p>这条记忆的价值是：下次遇到类似的重构场景，Agent 知道这个用户倾向于合并提交，而不是拆成很多小 PR。这不是纠正，是一个被验证过的判断。</p><p><strong>要求三：区分个人偏好和项目规范</strong></p><blockquote><p>「不要在回复末尾加总结」→ 个人偏好，只对这个用户有效</p><p>「集成测试必须用真实数据库」→ 项目规范，对所有协作者有效</p></blockquote><p>源码里用 scope 来区分这两种。个人偏好存在私有目录，项目规范存在团队共享目录。</p><h3 id="2-3-项目记忆：记正在发生什么"><a href="#2-3-项目记忆：记正在发生什么" class="headerlink" title="2.3 项目记忆：记正在发生什么"></a>2.3 项目记忆：记正在发生什么</h3><p>项目记忆记录的是当前项目里正在发生的事：谁在做什么、为什么要做、截止日期是什么。</p><p><strong>例子</strong>：</p><blockquote><p>「本周四之后冻结所有非关键合并，移动端团队要切发布分支。」</p><p>「正在重写认证中间件，原因是法务团队指出旧的 token 存储方式不符合合规要求，所以做决策的时候要优先考虑合规性而不是技术优雅。」</p></blockquote><p><strong>关键规则：相对日期必须转换成绝对日期</strong></p><p>用户说「周四冻结」，记忆里存的是具体的年月日，比如「2026-03-07」。</p><p>为什么？因为记忆是跨会话的。如果存「周四」，下周再看这条记忆就不知道是哪个周四了。</p><p><strong>另一个特点：衰减得很快</strong></p><p>一个月前的项目状态大概率已经过时了。所以源码要求项目记忆必须记录「为什么」。即使事实过时了，背后的动机仍然有参考价值。</p><figure class="highlight plain"><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">动机仍有效：「合规要求优先于技术优雅」→ 这个决策原则长期有效</span><br></pre></td></tr></table></figure><h3 id="2-4-引用记忆：记外部资源在哪里"><a href="#2-4-引用记忆：记外部资源在哪里" class="headerlink" title="2.4 引用记忆：记外部资源在哪里"></a>2.4 引用记忆：记外部资源在哪里</h3><p>引用记忆记录的是外部资源的位置：bug 在哪个系统里追踪、监控面板的地址是什么、设计文档在哪个平台。</p><p><strong>例子</strong>：</p><blockquote><p>「流水线相关的 bug 都在 Linear 的 INGEST 项目里追踪。」</p><p>「API 延迟的监控面板在 grafana.internal/d/api-latency，值班的时候看这个。」</p></blockquote><p>这是四种里最简单的，但也是最实用的。它本质上是一个「去哪里找信息」的索引。</p><hr><h2 id="三、五种不该存的东西"><a href="#三、五种不该存的东西" class="headerlink" title="三、五种不该存的东西"></a>三、五种不该存的东西</h2><p>这部分才是整个设计里最有启发性的。很多人做记忆系统的第一件事，就是把不该存的东西全存了。</p><h3 id="3-1-代码模式、架构、文件路径、项目结构"><a href="#3-1-代码模式、架构、文件路径、项目结构" class="headerlink" title="3.1 代码模式、架构、文件路径、项目结构"></a>3.1 代码模式、架构、文件路径、项目结构</h3><p>这是最反直觉的。很多人觉得 Agent 应该记住「项目用了什么框架、目录怎么组织、哪个文件负责什么」。</p><p><strong>Claude Code 说：不要存这些。</strong></p><p>为什么？因为这些信息可以直接从代码里读出来。Agent 随时可以通过读代码和搜索来获取当前的项目结构。</p><p>把这些存进记忆有两个问题：</p><figure class="highlight plain"><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><br><span class="line">每次对话都要加载一堆本来可以实时查的信息</span><br><span class="line"></span><br><span class="line">问题二：过时风险</span><br><span class="line">代码改了但记忆没更新 → Agent 基于过时信息决策 → 很难发现</span><br></pre></td></tr></table></figure><p><strong>背后的原则</strong>：如果一个信息可以从当前项目状态推导出来，就不要存进记忆。记忆只存那些「看代码看不出来」的东西。</p><h3 id="3-2-版本管理历史"><a href="#3-2-版本管理历史" class="headerlink" title="3.2 版本管理历史"></a>3.2 版本管理历史</h3><p>谁改了什么、最近的提交记录——这些用版本管理工具查就行了。</p><p>Git 是实时的、权威的，不需要记忆来存一份可能过时的副本。</p><h3 id="3-3-调试方案和修复方法"><a href="#3-3-调试方案和修复方法" class="headerlink" title="3.3 调试方案和修复方法"></a>3.3 调试方案和修复方法</h3><p>修复已经在代码里了，提交信息里有上下文。存「怎么修的」没有意义，因为代码本身就是最好的参考。</p><h3 id="3-4-配置文件里已经写过的东西"><a href="#3-4-配置文件里已经写过的东西" class="headerlink" title="3.4 配置文件里已经写过的东西"></a>3.4 配置文件里已经写过的东西</h3><p>如果你的项目里有 CLAUDE.md 或其他配置文件已经定义了编码规范，记忆系统不需要再存一份。</p><p>重复存储不仅浪费空间，还会在两份内容不一致的时候制造混乱。</p><figure class="highlight plain"><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">配置文件说：「使用 pnpm」</span><br><span class="line">记忆文件说：「使用 npm」</span><br><span class="line">→ Agent 该听谁的？</span><br></pre></td></tr></table></figure><h3 id="3-5-临时性的任务细节"><a href="#3-5-临时性的任务细节" class="headerlink" title="3.5 临时性的任务细节"></a>3.5 临时性的任务细节</h3><p>当前正在做什么、对话里的中间状态——这些是短期的，属于当前会话的上下文，不该进入长期记忆。</p><hr><h2 id="四、一条特别重要的规则"><a href="#四、一条特别重要的规则" class="headerlink" title="四、一条特别重要的规则"></a>四、一条特别重要的规则</h2><p>即使用户明确要求你记住某些东西，如果它属于上面五类，也不该记。</p><p><strong>例子</strong>：</p><blockquote><p>用户：「记住这周的 PR 列表」</p></blockquote><p>Agent 不应该直接存 PR 列表，而应该反问：</p><blockquote><p>「这些 PR 里有什么让你意外的或者不明显的？那个部分才值得记。」</p></blockquote><p><strong>活动日志不是记忆，从活动中提炼出的洞察才是。</strong></p><p>用户要求存不该存的内容时，正确的做法是提炼价值点。PR 列表本身不该存，但如果某个 PR 的处理方式让用户觉得「这样做是对的」，那个判断才值得存成反馈记忆。</p><hr><h2 id="五、信息来源决策树"><a href="#五、信息来源决策树" class="headerlink" title="五、信息来源决策树"></a>五、信息来源决策树</h2><p>把上面的原则整理成一个判断流程：</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">这条信息能从代码&#x2F;工具实时获取吗？</span><br><span class="line">         │</span><br><span class="line">         ├── 能 → 不存，实时查</span><br><span class="line">         │</span><br><span class="line">         └── 不能 → 属于哪种类型？</span><br><span class="line">                         │</span><br><span class="line">                         ├── 用户特征 → 用户记忆</span><br><span class="line">                         ├── 纠正&#x2F;肯定 → 反馈记忆</span><br><span class="line">                         ├── 项目动态 → 项目记忆</span><br><span class="line">                         └── 外部资源 → 引用记忆</span><br></pre></td></tr></table></figure><hr><h2 id="六、实践中的常见错误"><a href="#六、实践中的常见错误" class="headerlink" title="六、实践中的常见错误"></a>六、实践中的常见错误</h2><h3 id="错误一：把记忆当成项目文档"><a href="#错误一：把记忆当成项目文档" class="headerlink" title="错误一：把记忆当成项目文档"></a>错误一：把记忆当成项目文档</h3><figure class="highlight plain"><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">❌ 存储：「src&#x2F;auth 目录负责认证逻辑，包含 middleware.ts 和 token.ts」</span><br><span class="line">✅ 实时查：Glob + Read 工具</span><br></pre></td></tr></table></figure><h3 id="错误二：把记忆当成聊天记录"><a href="#错误二：把记忆当成聊天记录" class="headerlink" title="错误二：把记忆当成聊天记录"></a>错误二：把记忆当成聊天记录</h3><figure class="highlight plain"><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">❌ 存储：「用户昨天问怎么配置 Redis，我回答了...」</span><br><span class="line">✅ 不存：这是会话上下文，不是长期记忆</span><br></pre></td></tr></table></figure><h3 id="错误三：记了规则但没记原因"><a href="#错误三：记了规则但没记原因" class="headerlink" title="错误三：记了规则但没记原因"></a>错误三：记了规则但没记原因</h3><figure class="highlight plain"><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">❌ 存储：「不要用 forEach」</span><br><span class="line">✅ 存储：「避免在 async 函数里用 forEach，原因是 forEach 不等待 Promise 完成，</span><br><span class="line">         曾导致批量写入只执行了一半，适用场景是异步批量操作」</span><br></pre></td></tr></table></figure><h3 id="错误四：只记纠正不记肯定"><a href="#错误四：只记纠正不记肯定" class="headerlink" title="错误四：只记纠正不记肯定"></a>错误四：只记纠正不记肯定</h3><figure class="highlight plain"><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">✅ 也要存：「用户确认：重构时用一个大PR是对的，拆开反而增加工作量」</span><br></pre></td></tr></table></figure><hr><h2 id="七、记忆系统的质量检验清单"><a href="#七、记忆系统的质量检验清单" class="headerlink" title="七、记忆系统的质量检验清单"></a>七、记忆系统的质量检验清单</h2><p>在写入一条记忆之前，问自己这些问题：</p><figure class="highlight plain"><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><br><span class="line">□ 是否包含「为什么」？→ 补充动机</span><br><span class="line">□ 是否指定适用范围？→ 补充项目&#x2F;模块边界</span><br><span class="line">□ 相对时间是否转绝对时间？→ 如「明天」→「2026-03-16」</span><br><span class="line">□ 是否存在相似记忆？→ 合并去重</span><br><span class="line">□ 敏感信息是否过滤？→ 拒绝存储密码、密钥、PII</span><br></pre></td></tr></table></figure><hr><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>Claude Code 记忆系统的核心设计可以概括为一句话：<strong>少而精</strong>。</p><p>记忆的价值不在于数量，而在于每条记忆都能在关键时刻减少认知负担。</p><p>如果你在给自己的 Agent 做记忆系统，这个分类框架可以直接拿来用：</p><p><strong>四种该存</strong>：</p><ul><li>用户是谁（角色、背景、习惯）</li><li>用户纠正和肯定过什么（含规则、原因、适用场景）</li><li>项目背后的动机和时间线（相对日期→绝对日期）</li><li>外部资源在哪里（索引而非内容）</li></ul><p><strong>五种不该存</strong>：</p><ul><li>代码能告诉你的一切</li><li>版本历史能告诉你的一切</li><li>提交记录能告诉你的一切</li><li>配置文件已经说过的一切</li><li>临时性的中间状态</li></ul><p>这样做的好处是：你的记忆文件会非常精简，每一条都是高价值的、代码里找不到的信息。模型每次加载记忆的时候，看到的全是有用的东西，没有噪声。</p><hr><p><strong>相关文章</strong>：</p><ul><li><a href="/claude-code-memory-system/">Memory 系统：跨会话持久化知识库</a> — Claude Code 记忆系统的技术实现细节</li><li><a href="/categories/Claude-Code/">Claude Code 源码深度解析系列</a> — 更多 Claude Code 架构分析</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;如果你在构建 Agent，一定想过这个问题：怎么让 Agent 跨会话记住用户的偏好、项目的背景、之前犯过的错误？大部分人的做法是搞一个文件，把所有「需要记住的东西」往里塞。文件越来越大，token 成本越来越高，而且大部分内容跟当前对话根本没关系</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="AI Agent Memeory" scheme="https://donehub.github.io/tags/AI-Agent-Memeory/"/>
    
  </entry>
  
  <entry>
    <title>DeepSeek V4 技术解读</title>
    <link href="https://donehub.github.io/2026/04/24/deepseek-v4-technical-review/"/>
    <id>https://donehub.github.io/2026/04/24/deepseek-v4-technical-review/</id>
    <published>2026-04-24T08:30:00.000Z</published>
    <updated>2026-04-24T08:30:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>2026年4月24日，DeepSeek 正式发布了 V4 系列模型。这不是一次普通的版本迭代——它解决了一个困扰 AI 行业多年的根本问题：<strong>长上下文的效率瓶颈</strong>。</p><p>本文将深入解读 DeepSeek V4 的核心技术创新，帮助你理解这次发布为何值得关注。</p><hr><h2 id="一、模型规格：更大但不更贵"><a href="#一、模型规格：更大但不更贵" class="headerlink" title="一、模型规格：更大但不更贵"></a>一、模型规格：更大但不更贵</h2><p>DeepSeek V4 发布了两个版本：</p><div class="table-container"><table><thead><tr><th>模型</th><th>总参数量</th><th>激活参数量</th><th>上下文长度</th></tr></thead><tbody><tr><td><strong>DeepSeek-V4-Pro</strong></td><td>1.6T</td><td>49B</td><td>100万 tokens</td></tr><tr><td><strong>DeepSeek-V4-Flash</strong></td><td>284B</td><td>13B</td><td>100万 tokens</td></tr></tbody></table></div><p>对比上一代 V3.2（671B 总参数，37B 激活），V4-Pro 参数量翻了 2.4 倍，但激活参数仅增加 32%。更重要的是，<strong>两者都原生支持 100万 token 上下文</strong>——这是之前任何开源模型都做不到的。</p><h3 id="为什么”更大但不更贵”？"><a href="#为什么”更大但不更贵”？" class="headerlink" title="为什么”更大但不更贵”？"></a>为什么”更大但不更贵”？</h3><p>得益于 MoE（Mixture-of-Experts）架构，每次推理只激活一小部分参数。V4-Pro 的激活率仅为 <strong>3%</strong>（49B/1.6T），这意味着：</p><ul><li>推理成本接近一个 50B 参数的稠密模型</li><li>但拥有 1.6T 参数的知识容量和表达能力</li></ul><p>这是 DeepSeek 从 V2 开始就坚持的技术路线，V4 把这个策略推向了新高度。</p><hr><h2 id="二、核心架构创新：打破-O-n²-的魔咒"><a href="#二、核心架构创新：打破-O-n²-的魔咒" class="headerlink" title="二、核心架构创新：打破 O(n²) 的魔咒"></a>二、核心架构创新：打破 O(n²) 的魔咒</h2><p>Transformer 的标准注意力机制计算复杂度是 O(n²)——序列长度翻倍，计算量翻四倍。当上下文达到百万级别时，这变成了不可承受之重。</p><p>DeepSeek V4 用<strong>混合注意力架构</strong>彻底解决了这个问题。</p><h3 id="2-1-CSA（Compressed-Sparse-Attention）"><a href="#2-1-CSA（Compressed-Sparse-Attention）" class="headerlink" title="2.1 CSA（Compressed Sparse Attention）"></a>2.1 CSA（Compressed Sparse Attention）</h3><p>CSA 的核心思路是：<strong>压缩 + 稀疏选择</strong>。</p><figure class="highlight plain"><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">原始序列：n 个 token</span><br><span class="line">     ↓ 压缩（每 m 个 token 合成一个 KV entry）</span><br><span class="line">压缩序列：n&#x2F;m 个 compressed KV entry</span><br><span class="line">     ↓ 稀疏选择（Lightning Indexer 选 top-k）</span><br><span class="line">参与计算的：k 个 compressed KV entry</span><br></pre></td></tr></table></figure><p>具体流程：</p><ol><li><strong>KV Cache 压缩</strong>：将每 m 个 token 的 KV entry 通过加权聚合压缩成一个条目，序列长度降到 1/m</li><li><strong>Lightning Indexer</strong>：为每个 query token 生成 indexer queries，与压缩后的 KV 偂相似度计算，选出 top-k 个最相关的压缩块</li><li><strong>Core Attention</strong>：只在选出的 k 个压缩块上做完整的 attention 计算</li></ol><p>关键参数（V4-Pro）：</p><ul><li>压缩率 m = 4（每 4 个 token 压缩成 1 个）</li><li>Indexer head 数 = 64，head 维度 = 128</li><li>Top-k = 1024（每个 query 只关注 1024 个压缩块）</li></ul><h3 id="2-2-HCA（Heavily-Compressed-Attention）"><a href="#2-2-HCA（Heavily-Compressed-Attention）" class="headerlink" title="2.2 HCA（Heavily Compressed Attention）"></a>2.2 HCA（Heavily Compressed Attention）</h3><p>HCA 是更激进的压缩策略，用于处理”不需要精细关注的历史信息”：</p><figure class="highlight plain"><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">压缩率 m&#39; &#x3D; 128（每 128 个 token 合成一个 KV entry）</span><br><span class="line">     ↓</span><br><span class="line">直接对压缩后的 KV 做完整 attention（不做稀疏选择）</span><br></pre></td></tr></table></figure><p>HCA 的哲学是：<strong>远处的信息可以”模糊处理”，近处的信息才需要精细关注</strong>。</p><h3 id="2-3-混合架构设计"><a href="#2-3-混合架构设计" class="headerlink" title="2.3 混合架构设计"></a>2.3 混合架构设计</h3><p>V4 不是全用 CSA 或全用 HCA，而是<strong>交替使用</strong>：</p><ul><li><strong>前 2 层</strong>：纯滑动窗口 attention（保留近期信息的精细度）</li><li><strong>后续层</strong>：CSA 和 HCA 交替，形成”粗细结合”的信息处理</li></ul><p>这种设计让模型既能高效处理长上下文，又能保持对关键信息的精确检索能力。</p><h3 id="2-4-效率提升有多夸张？"><a href="#2-4-效率提升有多夸张？" class="headerlink" title="2.4 效率提升有多夸张？"></a>2.4 效率提升有多夸张？</h3><p>官方给出了硬核数据（100万 token 上下文场景）：</p><div class="table-container"><table><thead><tr><th>指标</th><th>V4-Pro vs V3.2</th><th>V4-Flash vs V3.2</th></tr></thead><tbody><tr><td>单 token FLOPs</td><td><strong>27%</strong>（节省 3.7×）</td><td><strong>10%</strong>（节省 10×）</td></tr><tr><td>KV Cache 大小</td><td><strong>10%</strong>（节省 9.5×）</td><td><strong>7%</strong>（节省 13.7×）</td></tr></tbody></table></div><p>这意味着：以前跑不起的百万级上下文任务，现在<strong>可以在单卡上跑了</strong>。</p><hr><h2 id="三、mHC：残差连接的”数学升级版”"><a href="#三、mHC：残差连接的”数学升级版”" class="headerlink" title="三、mHC：残差连接的”数学升级版”"></a>三、mHC：残差连接的”数学升级版”</h2><p>残差连接 <code>x + F(x)</code> 是 Transformer 的基石，但深层堆叠时会遇到问题：</p><ul><li>信号可能逐层放大 → 数值爆炸</li><li>信号可能逐层衰减 →梯度消失</li></ul><p>DeepSeek V4 引入了 <strong>Manifold-Constrained Hyper-Connections (mHC)</strong>，用数学约束解决这个问题。</p><h3 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h3><p>传统残差连接：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">X_next &#x3D; X + F(X)  &#x2F;&#x2F; 简单加法</span><br></pre></td></tr></table></figure></p><p>mHC：<br><figure class="highlight plain"><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">X_next &#x3D; B·X + C·F(A·X)  &#x2F;&#x2F; A、B、C 是线性映射矩阵</span><br><span class="line">         ↑</span><br><span class="line">      B 约束在双随机矩阵流形上（行和&#x3D;1，列和&#x3D;1，元素≥0）</span><br></pre></td></tr></table></figure></p><p>关键约束：<strong>B 的谱范数 ≤ 1</strong>，这意味着信号传播是”非膨胀的”，不会爆炸。</p><h3 id="为什么叫”流形约束”？"><a href="#为什么叫”流形约束”？" class="headerlink" title="为什么叫”流形约束”？"></a>为什么叫”流形约束”？</h3><p>双随机矩阵构成的空间是一个<strong>流形（Manifold）</strong>——Birkhoff Polytope。mHC 通过 Sinkhorn-Knopp 算法，把矩阵 B 投影到这个流形上：</p><figure class="highlight plain"><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">1. 对 B 取 exponential（保证正元素）</span><br><span class="line">2. 迭代做行归一化、列归一化</span><br><span class="line">3. 收敛到一个双随机矩阵</span><br></pre></td></tr></table></figure><p>这套数学确保了深层堆叠时的稳定性，同时保留了模型的表达能力。</p><hr><h2 id="四、Muon-优化器：万亿参数训练的新配方"><a href="#四、Muon-优化器：万亿参数训练的新配方" class="headerlink" title="四、Muon 优化器：万亿参数训练的新配方"></a>四、Muon 优化器：万亿参数训练的新配方</h2><p>训练万亿参数模型，AdamW 已经不够稳了。V4 引入了 <strong>Muon</strong> 优化器。</p><h3 id="核心算法"><a href="#核心算法" class="headerlink" title="核心算法"></a>核心算法</h3><figure class="highlight plain"><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">G &#x3D; gradient</span><br><span class="line">M &#x3D; momentum_buffer</span><br><span class="line">M &#x3D; μ·M + G  &#x2F;&#x2F; 动量累积</span><br><span class="line">O &#x3D; HybridNewtonSchulz(μ·M + G)  &#x2F;&#x2F; Nesterov trick + 正交化</span><br><span class="line">W &#x3D; W·(1 - ηλ) - η·O  &#x2F;&#x2F; weight decay + update</span><br></pre></td></tr></table></figure><p>关键步骤是 <strong>Hybrid Newton-Schulz 迭代</strong>，把梯度矩阵正交化：</p><figure class="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"><span class="comment"># 10 步迭代，分两阶段</span></span><br><span class="line"><span class="comment"># Stage 1（前 8 步）：快速收敛</span></span><br><span class="line">M_k = <span class="number">3.4445</span>·M_&#123;k<span class="number">-1</span>&#125; - <span class="number">4.7750</span>·(M·M^T)·M + <span class="number">2.0315</span>·(M·M^T)^<span class="number">2</span>·M</span><br><span class="line"></span><br><span class="line"><span class="comment"># Stage 2（后 2 步）：精确定位到正交矩阵</span></span><br><span class="line">M_k = <span class="number">2</span>·M_&#123;k<span class="number">-1</span>&#125; - <span class="number">1.5</span>·(M·M^T)·M + <span class="number">0.5</span>·(M·M^T)^<span class="number">2</span>·M</span><br></pre></td></tr></table></figure><p>正交化的好处：</p><ul><li>避免”跑偏”——梯度方向更明确</li><li>避免”数值爆炸”——矩阵谱范数被约束</li><li>收敛更快——不需要 Adam 的二阶矩估计</li></ul><h3 id="配合稳定性技术"><a href="#配合稳定性技术" class="headerlink" title="配合稳定性技术"></a>配合稳定性技术</h3><p>V4 还用了两招来防止 loss spike：</p><ol><li><strong>Anticipatory Routing</strong>：路由决策用”历史参数”而非”当前参数”，打破 MoE 路由的恶性循环</li><li><strong>SwiGLU Clamping</strong>：把 SwiGLU 的线性分量 clamp 到 [-10, 10]，直接压制异常值</li></ol><hr><h2 id="五、FP4-量化感知训练：天生适应低精度"><a href="#五、FP4-量化感知训练：天生适应低精度" class="headerlink" title="五、FP4 量化感知训练：天生适应低精度"></a>五、FP4 量化感知训练：天生适应低精度</h2><p>以往的量化是”训练后补救”——模型在高精度下训练，推理时强行降精度，性能必然下降。</p><p>V4 的创新：<strong>训练时就让模型适应 FP4</strong>。</p><h3 id="应用范围"><a href="#应用范围" class="headerlink" title="应用范围"></a>应用范围</h3><ul><li><strong>MoE 专家权重</strong>：占模型大部分参数，FP4 压缩节省大量显存</li><li><strong>QK 路径</strong>（Lightning Indexer 的 indexer 部分）：长上下文检索的核心计算，FP4 加速</li></ul><h3 id="关键技术点"><a href="#关键技术点" class="headerlink" title="关键技术点"></a>关键技术点</h3><p><strong>FP4 → FP8 的无损反量化</strong>：</p><figure class="highlight plain"><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">FP4 (E2M1) → FP8 (E4M3)</span><br><span class="line">          ↑</span><br><span class="line">FP8 多 2 个 exponent bit，动态范围更大</span><br><span class="line">只要 block 内的 scale factor 差异不超过阈值，信息完全保留</span><br></pre></td></tr></table></figure><p>这意味着：</p><ul><li>训练时用 FP8 做计算（模拟 FP4）</li><li>推理时直接用 FP4 权重，零性能损失</li><li>整个 pipeline 可以复用现有的 FP8 训练框架</li></ul><hr><h2 id="六、训练基础设施：工程硬核"><a href="#六、训练基础设施：工程硬核" class="headerlink" title="六、训练基础设施：工程硬核"></a>六、训练基础设施：工程硬核</h2><p>V4 的基础设施投入展现了”长期主义”的工程思维。</p><h3 id="6-1-TileLang：Kernel-开发的-DSL"><a href="#6-1-TileLang：Kernel-开发的-DSL" class="headerlink" title="6.1 TileLang：Kernel 开发的 DSL"></a>6.1 TileLang：Kernel 开发的 DSL</h3><p>传统 CUDA Kernel 开发效率低、难迭代。V4 用 <strong>TileLang</strong> 这个 DSL：</p><ul><li>用声明式语法描述 Kernel 逻辑</li><li>Z3 SMT Solver 做形式化分析（证明正确性）</li><li>自动生成高性能 CUDA 代码</li></ul><p>开发效率 + 运行效率，两者兼得。</p><h3 id="6-2-确定性训练"><a href="#6-2-确定性训练" class="headerlink" title="6.2 确定性训练"></a>6.2 确定性训练</h3><p>V4 的 Kernel 全程<strong>批不变（Batch-Invariant）</strong>：</p><ul><li>同一 token 无论在 batch 哪个位置，输出 bitwise 一致</li><li>用特殊设计避免了原子加法带来的不确定性</li><li>训练过程可复现，调试有据可查</li></ul><p>这对大规模训练调试、定位问题至关重要。</p><h3 id="6-3-MoE-EP-的细粒度重叠"><a href="#6-3-MoE-EP-的细粒度重叠" class="headerlink" title="6.3 MoE EP 的细粒度重叠"></a>6.3 MoE EP 的细粒度重叠</h3><p>Expert Parallelism 的通信开销大。V4 把 MoE 层拆成 <strong>4 个阶段</strong>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Dispatch (通信) → Linear-1 (计算) → Activation → Linear-2 (计算) → Combine (通信)</span><br></pre></td></tr></table></figure><p>关键洞察：<strong>计算时间 &gt; 通信时间</strong>，所以通信可以被计算掩盖。</p><p>V4 把专家分成”wave”，每个 wave 的通信和计算流水线化，实现 <strong>1.5-1.96× 加速</strong>。</p><hr><h2 id="七、性能基准：开源模型的新标杆"><a href="#七、性能基准：开源模型的新标杆" class="headerlink" title="七、性能基准：开源模型的新标杆"></a>七、性能基准：开源模型的新标杆</h2><h3 id="知识任务"><a href="#知识任务" class="headerlink" title="知识任务"></a>知识任务</h3><div class="table-container"><table><thead><tr><th>Benchmark</th><th>V4-Pro-Max</th><th>K2.6</th><th>GLM-5.1</th><th>Gemini 3.1 Pro</th></tr></thead><tbody><tr><td>SimpleQA Verified</td><td><strong>57.9</strong></td><td>36.9</td><td>38.1</td><td>75.6</td></tr><tr><td>Chinese-SimpleQA</td><td><strong>84.4</strong></td><td>75.9</td><td>75.0</td><td>85.9</td></tr></tbody></table></div><p>V4-Pro-Max 在知识任务上<strong>领先开源对手 20+ 百分点</strong>，但距离 Gemini 3.1 Pro 还有一段差距。</p><h3 id="Agent-能力：开源最佳"><a href="#Agent-能力：开源最佳" class="headerlink" title="Agent 能力：开源最佳"></a>Agent 能力：开源最佳</h3><p>这是 V4 最重要的能力跃升之一。官方披露：</p><ul><li><strong>Agentic Coding</strong>：V4-Pro 达到当前开源模型最佳水平</li><li><strong>内部实测</strong>：已成为 DeepSeek 公司内部员工使用的 Agentic Coding 首选模型</li><li><strong>体验对比</strong>：优于 Claude Sonnet 4.5，交付质量接近 Claude Opus 4.6 非思考模式</li></ul><p>V4 针对 <strong>Claude Code、OpenClaw、OpenCode、CodeBuddy</strong> 等主流 Agent 产品进行了专项适配优化，在代码任务、文档生成等场景表现显著提升。</p><h3 id="推理与代码"><a href="#推理与代码" class="headerlink" title="推理与代码"></a>推理与代码</h3><div class="table-container"><table><thead><tr><th>Benchmark</th><th>V4-Pro-Max</th><th>GPT-5.4</th><th>Gemini 3.1 Pro</th></tr></thead><tbody><tr><td>Codeforces Rating</td><td><strong>3206</strong></td><td>3168</td><td>3052</td></tr><tr><td>Apex Shortlist</td><td><strong>90.2</strong></td><td>78.1</td><td>89.1</td></tr></tbody></table></div><p><strong>这是开源模型首次在代码竞赛上追平闭源模型</strong>。V4-Pro-Max 在 Codeforces 排名第 23 位（人类选手中）。</p><h3 id="长上下文"><a href="#长上下文" class="headerlink" title="长上下文"></a>长上下文</h3><div class="table-container"><table><thead><tr><th>Benchmark</th><th>V4-Pro-Max</th><th>Claude Opus 4.6</th><th>Gemini 3.1 Pro</th></tr></thead><tbody><tr><td>MRCR 1M (MMR)</td><td>83.5</td><td><strong>92.9</strong></td><td>76.3</td></tr><tr><td>CorpusQA 1M</td><td><strong>62.0</strong></td><td>71.7</td><td>53.8</td></tr></tbody></table></div><p>V4-Pro 在真实场景的 CorpusQA 上超越 Gemini 3.1 Pro，在 MRCR 上接近 Claude Opus 4.6。</p><hr><h2 id="八、V4-Flash：经济高效的选择"><a href="#八、V4-Flash：经济高效的选择" class="headerlink" title="八、V4-Flash：经济高效的选择"></a>八、V4-Flash：经济高效的选择</h2><p>V4-Flash 是一个重要的补充版本，让不同需求的用户都能找到合适的方案。</p><h3 id="与-V4-Pro-的对比"><a href="#与-V4-Pro-的对比" class="headerlink" title="与 V4-Pro 的对比"></a>与 V4-Pro 的对比</h3><div class="table-container"><table><thead><tr><th>维度</th><th>V4-Flash</th><th>V4-Pro</th></tr></thead><tbody><tr><td>激活参数</td><td>13B</td><td>49B</td></tr><tr><td>推理速度</td><td>更快</td><td>较慢</td></tr><tr><td>API 成本</td><td>更低</td><td>较高</td></tr><tr><td>世界知识</td><td>稍逊</td><td>大幅领先开源</td></tr><tr><td>推理能力</td><td>接近 Pro</td><td>开源最佳</td></tr><tr><td>Agent 简单任务</td><td>旗鼓相当</td><td>更优</td></tr><tr><td>Agent 高难度任务</td><td>有差距</td><td>最佳</td></tr></tbody></table></div><p><strong>适用场景</strong>：</p><ul><li><strong>V4-Flash</strong>：日常对话、简单代码任务、成本敏感场景</li><li><strong>V4-Pro</strong>：复杂 Agent 任务、深度推理、高质量输出需求</li></ul><hr><h2 id="九、三种推理模式：灵活的推理成本"><a href="#九、三种推理模式：灵活的推理成本" class="headerlink" title="九、三种推理模式：灵活的推理成本"></a>九、三种推理模式：灵活的推理成本</h2><p>V4 支持三种推理模式，让用户按需求选择成本：</p><div class="table-container"><table><thead><tr><th>模式</th><th>特点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>Non-Think</strong></td><td>快速直觉响应，无 thinking tokens</td><td>日常对话、低风险决策</td></tr><tr><td><strong>Think</strong></td><td>逻辑分析，中等 thinking budget</td><td>复杂问题、规划任务</td></tr><tr><td><strong>Think Max</strong></td><td>极限推理，长 thinking budget</td><td>数学证明、高难度任务</td></tr></tbody></table></div><p>Think Max 模式会在系统 prompt 里注入特殊指令：</p><figure class="highlight plain"><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">Reasoning Effort: Absolute maximum with no shortcuts permitted.</span><br><span class="line">You MUST be very thorough in your thinking...</span><br></pre></td></tr></table></figure><p>这让模型”把推理推到极限”，在 HLE、IMO 等高难度任务上表现最优。</p><hr><h2 id="十、API-使用指南"><a href="#十、API-使用指南" class="headerlink" title="十、API 使用指南"></a>十、API 使用指南</h2><h3 id="模型调用"><a href="#模型调用" class="headerlink" title="模型调用"></a>模型调用</h3><p>DeepSeek API 已同步上线 V4-Pro 与 V4-Flash，支持 OpenAI ChatCompletions 接口与 Anthropic 接口：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># OpenAI 格式</span></span><br><span class="line"><span class="keyword">from</span> openai <span class="keyword">import</span> OpenAI</span><br><span class="line"></span><br><span class="line">client = OpenAI(</span><br><span class="line">    api_key=<span class="string">"your-api-key"</span>,</span><br><span class="line">    base_url=<span class="string">"https://api.deepseek.com"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">response = client.chat.completions.create(</span><br><span class="line">    model=<span class="string">"deepseek-v4-pro"</span>,  <span class="comment"># 或 deepseek-v4-flash</span></span><br><span class="line">    messages=[&#123;<span class="string">"role"</span>: <span class="string">"user"</span>, <span class="string">"content"</span>: <span class="string">"你好"</span>&#125;]</span><br><span class="line">)</span><br></pre></td></tr></table></figure><h3 id="关键参数"><a href="#关键参数" class="headerlink" title="关键参数"></a>关键参数</h3><div class="table-container"><table><thead><tr><th>参数</th><th>说明</th></tr></thead><tbody><tr><td><code>model</code></td><td><code>deepseek-v4-pro</code> 或 <code>deepseek-v4-flash</code></td></tr><tr><td><code>max_tokens</code></td><td>最大输出长度，默认 8K</td></tr><tr><td><code>reasoning_effort</code></td><td>思考强度：<code>high</code> 或 <code>max</code>（仅思考模式）</td></tr></tbody></table></div><h3 id="思考模式"><a href="#思考模式" class="headerlink" title="思考模式"></a>思考模式</h3><p>对于复杂的 Agent 场景，建议使用思考模式并设置强度为 <code>max</code>：</p><figure class="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></pre></td><td class="code"><pre><span class="line">response = client.chat.completions.create(</span><br><span class="line">    model=<span class="string">"deepseek-v4-pro"</span>,</span><br><span class="line">    messages=[&#123;<span class="string">"role"</span>: <span class="string">"user"</span>, <span class="string">"content"</span>: <span class="string">"复杂任务..."</span>&#125;],</span><br><span class="line">    reasoning_effort=<span class="string">"max"</span>  <span class="comment"># 极限推理</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><h3 id="⚠️-重要提示"><a href="#⚠️-重要提示" class="headerlink" title="⚠️ 重要提示"></a>⚠️ 重要提示</h3><p>旧 API 模型名 <code>deepseek-chat</code> 和 <code>deepseek-reasoner</code> 将于 <strong>2026年7月24日</strong> 停止使用：</p><ul><li>当前阶段 <code>deepseek-chat</code> → 指向 V4-Flash 非思考模式</li><li>当前阶段 <code>deepseek-reasoner</code> → 指向 V4-Flash 思考模式</li></ul><p>请尽快迁移到新的模型名称。</p><hr><h2 id="十一、开源与本地部署"><a href="#十一、开源与本地部署" class="headerlink" title="十一、开源与本地部署"></a>十一、开源与本地部署</h2><h3 id="权重下载"><a href="#权重下载" class="headerlink" title="权重下载"></a>权重下载</h3><div class="table-container"><table><thead><tr><th>平台</th><th>链接</th></tr></thead><tbody><tr><td>HuggingFace</td><td><a href="https://huggingface.co/collections/deepseek-ai/deepseek-v4" target="_blank" rel="noopener">https://huggingface.co/collections/deepseek-ai/deepseek-v4</a></td></tr><tr><td>ModelScope</td><td><a href="https://modelscope.cn/collections/deepseek-ai/DeepSeek-V4" target="_blank" rel="noopener">https://modelscope.cn/collections/deepseek-ai/DeepSeek-V4</a></td></tr></tbody></table></div><h3 id="本地部署建议"><a href="#本地部署建议" class="headerlink" title="本地部署建议"></a>本地部署建议</h3><p>由于 V4-Pro 参数量达 1.6T，本地部署需要：</p><ul><li><strong>多卡推理</strong>：至少 8× A100 80GB 或同等显存</li><li><strong>量化推理</strong>：FP4 量化后可显著降低显存需求</li><li><strong>V4-Flash</strong>：单卡 A100 80GB 可运行</li></ul><h3 id="技术报告"><a href="#技术报告" class="headerlink" title="技术报告"></a>技术报告</h3><p>完整技术细节请参考官方技术报告：</p><ul><li><a href="https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro/blob/main/DeepSeek_V4.pdf" target="_blank" rel="noopener">DeepSeek V4 技术报告（PDF）</a></li></ul><hr><h2 id="十二、行业启示：V4-带来的新范式"><a href="#十二、行业启示：V4-带来的新范式" class="headerlink" title="十二、行业启示：V4 带来的新范式"></a>十二、行业启示：V4 带来的新范式</h2><h3 id="12-1-长上下文不再是奢侈品"><a href="#12-1-长上下文不再是奢侈品" class="headerlink" title="12.1 长上下文不再是奢侈品"></a>12.1 长上下文不再是奢侈品</h3><p>以前，百万级上下文是”理论上可行但经济上不行”。V4 把成本降到 <strong>原来的 10-30%</strong>，让以下场景变得可行：</p><ul><li><strong>Test-time Scaling</strong>：推理阶段可以长时间思考，不受上下文限制</li><li><strong>长 horizon Agent</strong>：复杂多轮任务（如软件工程流水线）有足够”记忆空间”</li><li><strong>在线学习</strong>：持续吸收新信息，无需全量重训练</li></ul><h3 id="12-2-开源-vs-闭源的格局变化"><a href="#12-2-开源-vs-闭源的格局变化" class="headerlink" title="12.2 开源 vs 闭源的格局变化"></a>12.2 开源 vs 闭源的格局变化</h3><p>V4 是一个信号：<strong>开源模型不仅追上了能力，还追上了效率性价比</strong>。</p><ul><li>V4-Flash 用 13B 激活参数，就能达到接近 GPT-5.2 的推理水平</li><li>在代码任务上，开源首次追平闭源</li></ul><p>这意味着闭源模型的”护城河”正在缩小。</p><h3 id="12-3-架构创新的长期价值"><a href="#12-3-架构创新的长期价值" class="headerlink" title="12.3 架构创新的长期价值"></a>12.3 架构创新的长期价值</h3><p>V4 的创新不是”刷榜技巧”，而是<strong>架构层面的根本改进</strong>：</p><ul><li>CSA/HCA 解决了 Transformer 的 O(n²) 瓶颈</li><li>mHC 让残差连接更稳定、可堆叠更深</li><li>Muon 优化器可能成为万亿参数训练的新标配</li></ul><p>这些创新会启发更多研究，推动整个行业向前。</p><hr><h2 id="十三、局限与展望"><a href="#十三、局限与展望" class="headerlink" title="十三、局限与展望"></a>十三、局限与展望</h2><p>官方坦承了几个局限：</p><ol><li><strong>架构相对复杂</strong>：为了降低风险，保留了 V3 的很多验证过的组件，未来会精简</li><li><strong>训练稳定性原理未完全理解</strong>：Anticipatory Routing 和 SwiGLU Clamping 有效，但数学原理还在探索</li><li><strong>多模态尚未集成</strong>：未来版本会加入视觉能力</li></ol><p>展望方向：</p><ul><li>进一步的稀疏化探索（如稀疏 embedding）</li><li>低延迟架构优化（让长上下文交互更流畅）</li><li>长 horizon Agent 的深度优化</li></ul><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>DeepSeek V4 的意义不在于某个具体指标的提升，而在于它<strong>解决了长上下文效率这个根本问题</strong>。</p><p>通过 CSA/HCA 混合注意力、mHC 残差升级、Muon 优化器、FP4 量化训练等一系列创新，V4 让百万级上下文从”理论上可行”变成”经济上可行”。</p><p>这为 AI 的下一阶段——更深的 test-time scaling、更长的 Agent 任务、更灵活的在线学习——铺好了基础设施。</p><p>开源模型第一次在效率和能力的综合维度上，追上了闭源前沿。这是整个行业值得关注的里程碑。</p><hr><p><strong>参考资源</strong>：</p><ul><li><a href="https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro/blob/main/DeepSeek_V4.pdf" target="_blank" rel="noopener">DeepSeek V4 技术报告（PDF）</a></li><li><a href="https://huggingface.co/collections/deepseek-ai/deepseek-v4" target="_blank" rel="noopener">模型权重（HuggingFace）</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;2026年4月24日，DeepSeek 正式发布了 V4 系列模型。这不是一次普通的版本迭代——它解决了一个困扰 AI 行业多年的根本问题：</summary>
      
    
    
    
    <category term="DeepSeek" scheme="https://donehub.github.io/categories/DeepSeek/"/>
    
    
    <category term="AI" scheme="https://donehub.github.io/tags/AI/"/>
    
  </entry>
  
  <entry>
    <title>奇技淫巧：Java / Python 应用调用阿里百炼 Coding Plan 服务</title>
    <link href="https://donehub.github.io/2026/04/19/bailian_coding_plan_usage_java_or_python/"/>
    <id>https://donehub.github.io/2026/04/19/bailian_coding_plan_usage_java_or_python/</id>
    <published>2026-04-18T16:00:00.000Z</published>
    <updated>2026-04-19T01:37:15.224Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>阿里云百炼 Coding Plan 官方宣称”仅限编程工具使用”，但实际上其 endpoint 基于 OpenAI 兼容协议，Java/Python 应用完全可以调用。本文分享如何用 LangChain4j 和 OpenAI SDK 突破这一限制，直接消耗 Coding Plan 额度。</p></blockquote><hr><h2 id="一、背景：一个被”误解”的服务"><a href="#一、背景：一个被”误解”的服务" class="headerlink" title="一、背景：一个被”误解”的服务"></a>一、背景：一个被”误解”的服务</h2><p>阿里云百炼的 <strong>Coding Plan</strong> 是一项面向 AI 编程助手的服务套餐，提供专门的模型调用额度。官方客服的说法是：</p><blockquote><p>“Coding Plan 的专属 API Key（格式为 <code>sk-sp-xxxxx</code>）仅限在支持的编程工具（如 Claude Code、OpenClaw 等）中使用，不能用于 Java 应用直接调用大模型。若您的 Java 应用需要调用百炼大模型，请使用百炼通用 API Key（格式为 <code>sk-xxxxx</code>），该 Key 支持调用包括 Coding 模型在内的所有百炼模型，并按量计费。”</p></blockquote><p>这意味着如果你想在 Java 应用中使用百炼大模型，需要：</p><ol><li>额外开通百炼通用 API Key（格式为 <code>sk-xxxxx</code>）</li><li>按量付费，产生额外费用</li></ol><p><strong>但实际上，Coding Plan 的额度完全可以在 Java/Python 应用中使用！</strong> 本文将分享这个”奇技淫巧”。</p><hr><h2 id="二、问题发现：为什么-Coding-Plan-Key-在-Java-中”失效”？"><a href="#二、问题发现：为什么-Coding-Plan-Key-在-Java-中”失效”？" class="headerlink" title="二、问题发现：为什么 Coding Plan Key 在 Java 中”失效”？"></a>二、问题发现：为什么 Coding Plan Key 在 Java 中”失效”？</h2><h3 id="2-1-错误的调用方式"><a href="#2-1-错误的调用方式" class="headerlink" title="2.1 错误的调用方式"></a>2.1 错误的调用方式</h3><p>很多开发者（包括我）最初使用阿里云官方的 <code>dashscope-sdk-java</code> 调用百炼：</p><figure class="highlight java"><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"><span class="comment">// pom.xml</span></span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;com.alibaba&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;dashscope-sdk-java&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.22.15&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Java 代码</span></span><br><span class="line">Generation gen = <span class="keyword">new</span> Generation();</span><br><span class="line">GenerationParam param = GenerationParam.builder()</span><br><span class="line">    .apiKey(<span class="string">"sk-sp-xxxxx"</span>)  <span class="comment">// Coding Plan API Key</span></span><br><span class="line">    .model(<span class="string">"qwen-plus"</span>)</span><br><span class="line">    .messages(messages)</span><br><span class="line">    .build();</span><br><span class="line">GenerationResult result = gen.call(param);</span><br></pre></td></tr></table></figure><p><strong>结果：API 返回 InvalidApiKey 错误，或者即使调通了，消耗的是通用额度而非 Coding Plan 额度！</strong></p><h3 id="2-2-根本原因分析"><a href="#2-2-根本原因分析" class="headerlink" title="2.2 根本原因分析"></a>2.2 根本原因分析</h3><p>Coding Plan 的 API Key 使用的是 <strong>OpenAI 兼容协议</strong>，endpoint 地址不同于通用百炼服务：</p><div class="table-container"><table><thead><tr><th>服务类型</th><th>API Key 格式</th><th>Endpoint</th><th>模型名称</th></tr></thead><tbody><tr><td><strong>通用百炼</strong></td><td><code>sk-xxxxx</code></td><td><code>https://dashscope.aliyuncs.com/compatible-mode/v1</code></td><td><code>qwen-plus</code>, <code>qwen-max</code></td></tr><tr><td><strong>Coding Plan</strong></td><td><code>sk-sp-xxxxx</code></td><td><code>https://coding.dashscope.aliyuncs.com/v1</code></td><td><code>多个模型</code> 等</td></tr></tbody></table></div><p>官方的 <code>dashscope-sdk-java</code> 只支持通用百炼 endpoint，无法连接 Coding Plan 的 endpoint！</p><hr><h2 id="三、解决方案：使用-OpenAI-兼容模式"><a href="#三、解决方案：使用-OpenAI-兼容模式" class="headerlink" title="三、解决方案：使用 OpenAI 兼容模式"></a>三、解决方案：使用 OpenAI 兼容模式</h2><h3 id="3-1-技术原理"><a href="#3-1-技术原理" class="headerlink" title="3.1 技术原理"></a>3.1 技术原理</h3><p>Coding Plan 的 endpoint 基于 <strong>OpenAI API 兼容协议</strong>，任何支持 OpenAI API 的客户端都可以调用：</p><ol><li><strong>Python</strong>: 使用 <code>openai</code> SDK</li><li><strong>Java</strong>: 使用 LangChain4j 的 <code>langchain4j-open-ai</code> 模块</li></ol><p>只要将 <code>base_url</code> 指向 Coding Plan 的 endpoint，API Key 就能正常工作！</p><hr><h2 id="四、Java-实现：LangChain4j-Coding-Plan"><a href="#四、Java-实现：LangChain4j-Coding-Plan" class="headerlink" title="四、Java 实现：LangChain4j + Coding Plan"></a>四、Java 实现：LangChain4j + Coding Plan</h2><h3 id="4-1-添加依赖"><a href="#4-1-添加依赖" class="headerlink" title="4.1 添加依赖"></a>4.1 添加依赖</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- pom.xml --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">langchain4j.version</span>&gt;</span>0.35.0<span class="tag">&lt;/<span class="name">langchain4j.version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- LangChain4j OpenAI 兼容模块 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>dev.langchain4j<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>langchain4j<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;langchain4j.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>dev.langchain4j<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>langchain4j-open-ai<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;langchain4j.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="4-2-配置文件"><a href="#4-2-配置文件" class="headerlink" title="4.2 配置文件"></a>4.2 配置文件</h3><figure class="highlight properties"><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="comment"># application.properties - Coding Plan 配置</span></span><br><span class="line"><span class="meta">langchain4j.open-ai.chat-model.base-url</span>=<span class="string">https://coding.dashscope.aliyuncs.com/v1</span></span><br><span class="line"><span class="meta">langchain4j.open-ai.chat-model.api-key</span>=<span class="string">sk-sp-xxxxx</span></span><br><span class="line"><span class="meta">langchain4j.open-ai.chat-model.model-name</span>=<span class="string">kimi-k2.5</span></span><br><span class="line"><span class="meta">langchain4j.open-ai.chat-model.temperature</span>=<span class="string">0.3</span></span><br><span class="line"><span class="meta">langchain4j.open-ai.chat-model.max-tokens</span>=<span class="string">4096</span></span><br></pre></td></tr></table></figure><p><strong>关键点：<code>base-url</code> 必须是 <code>coding.dashscope.aliyuncs.com/v1</code>，不是通用的 <code>dashscope.aliyuncs.com</code>！</strong></p><h3 id="4-3-Java-代码实现"><a href="#4-3-Java-代码实现" class="headerlink" title="4.3 Java 代码实现"></a>4.3 Java 代码实现</h3><figure class="highlight java"><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><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> dev.langchain4j.data.message.AiMessage;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.data.message.ChatMessage;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.data.message.SystemMessage;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.data.message.UserMessage;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.model.chat.ChatLanguageModel;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.model.openai.OpenAiChatModel;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.model.output.Response;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.time.Duration;</span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CodingPlanExample</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatLanguageModel chatModel;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">CodingPlanExample</span><span class="params">(String baseUrl, String apiKey, String modelName)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 使用 OpenAI 兼容模式构建 ChatModel</span></span><br><span class="line">        <span class="keyword">this</span>.chatModel = OpenAiChatModel.builder()</span><br><span class="line">                .baseUrl(baseUrl)  <span class="comment">// Coding Plan endpoint</span></span><br><span class="line">                .apiKey(apiKey)    <span class="comment">// Coding Plan API Key</span></span><br><span class="line">                .modelName(modelName)</span><br><span class="line">                .temperature(<span class="number">0.3</span>)</span><br><span class="line">                .maxTokens(<span class="number">4096</span>)</span><br><span class="line">                .timeout(Duration.ofSeconds(<span class="number">60</span>))</span><br><span class="line">                .build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">chat</span><span class="params">(String systemPrompt, String userMessage)</span> </span>&#123;</span><br><span class="line">        List&lt;ChatMessage&gt; messages = Arrays.asList(</span><br><span class="line">                SystemMessage.from(systemPrompt),</span><br><span class="line">                UserMessage.from(userMessage)</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        Response&lt;AiMessage&gt; response = chatModel.generate(messages);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (response == <span class="keyword">null</span> || response.content() == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"模型返回结果为空"</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> response.content().text();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        CodingPlanExample example = <span class="keyword">new</span> CodingPlanExample(</span><br><span class="line">                <span class="string">"https://coding.dashscope.aliyuncs.com/v1"</span>,</span><br><span class="line">                <span class="string">"sk-sp-xxxxx"</span>,</span><br><span class="line">                <span class="string">"kimi-k2.5"</span></span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        String result = example.chat(</span><br><span class="line">                <span class="string">"你是一位专业的翻译助手"</span>,</span><br><span class="line">                <span class="string">"将以下内容翻译为英文：你好世界"</span></span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        System.out.println(result);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-4-Spring-Boot-集成示例"><a href="#4-4-Spring-Boot-集成示例" class="headerlink" title="4.4 Spring Boot 集成示例"></a>4.4 Spring Boot 集成示例</h3><figure class="highlight java"><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><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LangChain4jConfig</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;langchain4j.open-ai.chat-model.base-url&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String baseUrl;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;langchain4j.open-ai.chat-model.api-key&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String apiKey;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;langchain4j.open-ai.chat-model.model-name&#125;"</span>)</span><br><span class="line">    <span class="keyword">private</span> String modelName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ChatLanguageModel <span class="title">chatLanguageModel</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> OpenAiChatModel.builder()</span><br><span class="line">                .baseUrl(baseUrl)</span><br><span class="line">                .apiKey(apiKey)</span><br><span class="line">                .modelName(modelName)</span><br><span class="line">                .timeout(Duration.ofSeconds(<span class="number">60</span>))</span><br><span class="line">                .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TranslationService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatLanguageModel chatModel;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">TranslationService</span><span class="params">(ChatLanguageModel chatModel)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.chatModel = chatModel;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">translate</span><span class="params">(String content, String sourceLang, String targetLang)</span> </span>&#123;</span><br><span class="line">        String systemPrompt = String.format(</span><br><span class="line">                <span class="string">"你是翻译专家，将内容从%s翻译为%s，直接输出结果"</span>,</span><br><span class="line">                sourceLang, targetLang</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        List&lt;ChatMessage&gt; messages = Arrays.asList(</span><br><span class="line">                SystemMessage.from(systemPrompt),</span><br><span class="line">                UserMessage.from(content)</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> chatModel.generate(messages).content().text();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="五、Python-实现：OpenAI-SDK-Coding-Plan"><a href="#五、Python-实现：OpenAI-SDK-Coding-Plan" class="headerlink" title="五、Python 实现：OpenAI SDK + Coding Plan"></a>五、Python 实现：OpenAI SDK + Coding Plan</h2><h3 id="5-1-安装依赖"><a href="#5-1-安装依赖" class="headerlink" title="5.1 安装依赖"></a>5.1 安装依赖</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install openai</span><br></pre></td></tr></table></figure><h3 id="5-2-Python-代码"><a href="#5-2-Python-代码" class="headerlink" title="5.2 Python 代码"></a>5.2 Python 代码</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> openai <span class="keyword">import</span> OpenAI</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 Coding Plan endpoint</span></span><br><span class="line">client = OpenAI(</span><br><span class="line">    base_url=<span class="string">"https://coding.dashscope.aliyuncs.com/v1"</span>,</span><br><span class="line">    api_key=<span class="string">"sk-sp-xxxxx"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">response = client.chat.completions.create(</span><br><span class="line">    model=<span class="string">"kimi-k2.5"</span>,</span><br><span class="line">    messages=[</span><br><span class="line">        &#123;<span class="string">"role"</span>: <span class="string">"system"</span>, <span class="string">"content"</span>: <span class="string">"你是一位专业的翻译助手"</span>&#125;,</span><br><span class="line">        &#123;<span class="string">"role"</span>: <span class="string">"user"</span>, <span class="string">"content"</span>: <span class="string">"将以下内容翻译为英文：你好世界"</span>&#125;</span><br><span class="line">    ],</span><br><span class="line">    temperature=<span class="number">0.3</span>,</span><br><span class="line">    max_tokens=<span class="number">4096</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">print(response.choices[<span class="number">0</span>].message.content)</span><br></pre></td></tr></table></figure><h3 id="5-3-异步调用示例"><a href="#5-3-异步调用示例" class="headerlink" title="5.3 异步调用示例"></a>5.3 异步调用示例</h3><figure class="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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> openai <span class="keyword">import</span> AsyncOpenAI</span><br><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"></span><br><span class="line">async_client = AsyncOpenAI(</span><br><span class="line">    base_url=<span class="string">"https://coding.dashscope.aliyuncs.com/v1"</span>,</span><br><span class="line">    api_key=<span class="string">"sk-sp-xxxxx"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">translate_async</span><span class="params">(content: str)</span> -&gt; str:</span></span><br><span class="line">    response = <span class="keyword">await</span> async_client.chat.completions.create(</span><br><span class="line">        model=<span class="string">"kimi-k2.5"</span>,</span><br><span class="line">        messages=[</span><br><span class="line">            &#123;<span class="string">"role"</span>: <span class="string">"system"</span>, <span class="string">"content"</span>: <span class="string">"你是翻译专家"</span>&#125;,</span><br><span class="line">            &#123;<span class="string">"role"</span>: <span class="string">"user"</span>, <span class="string">"content"</span>: content&#125;</span><br><span class="line">        ]</span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> response.choices[<span class="number">0</span>].message.content</span><br><span class="line"></span><br><span class="line"><span class="comment"># 批量翻译</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">batch_translate</span><span class="params">(contents: list[str])</span> -&gt; list[str]:</span></span><br><span class="line">    tasks = [translate_async(c) <span class="keyword">for</span> c <span class="keyword">in</span> contents]</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">await</span> asyncio.gather(*tasks)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行示例</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line">    results = <span class="keyword">await</span> batch_translate([<span class="string">"你好世界"</span>, <span class="string">"人工智能"</span>])</span><br><span class="line">    print(results)</span><br><span class="line"></span><br><span class="line">asyncio.run(main())</span><br></pre></td></tr></table></figure><hr><h2 id="六、实际应用场景"><a href="#六、实际应用场景" class="headerlink" title="六、实际应用场景"></a>六、实际应用场景</h2><h3 id="6-1-职位内容翻译"><a href="#6-1-职位内容翻译" class="headerlink" title="6.1 职位内容翻译"></a>6.1 职位内容翻译</h3><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JobTranslationService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatLanguageModel chatModel;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">translateJobDescription</span><span class="params">(String content, String sourceLang, String targetLang)</span> </span>&#123;</span><br><span class="line">        String systemPrompt = buildTranslationPrompt(sourceLang, targetLang);</span><br><span class="line"></span><br><span class="line">        List&lt;ChatMessage&gt; messages = Arrays.asList(</span><br><span class="line">                SystemMessage.from(systemPrompt),</span><br><span class="line">                UserMessage.from(content)</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> chatModel.generate(messages).content().text();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> String <span class="title">buildTranslationPrompt</span><span class="params">(String sourceLang, String targetLang)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> String.format(<span class="string">""</span><span class="string">"</span></span><br><span class="line"><span class="string">            你是一位专业的职位内容翻译专家。</span></span><br><span class="line"><span class="string">            请将用户输入的职位描述从%s翻译为%s。</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. 保持原文的段落结构和格式</span></span><br><span class="line"><span class="string">            2. 专业术语使用行业标准翻译</span></span><br><span class="line"><span class="string">            3. 直接输出翻译结果</span></span><br><span class="line"><span class="string">            "</span><span class="string">""</span>, sourceLang, targetLang);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-文档内容生成"><a href="#6-2-文档内容生成" class="headerlink" title="6.2 文档内容生成"></a>6.2 文档内容生成</h3><figure class="highlight java"><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 class="function"><span class="keyword">public</span> String <span class="title">generateDocumentOutline</span><span class="params">(String topic)</span> </span>&#123;</span><br><span class="line">    String prompt = <span class="string">""</span><span class="string">"</span></span><br><span class="line"><span class="string">        根据以下主题，生成一份技术文档大纲：</span></span><br><span class="line"><span class="string">        主题：%s</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. 结构清晰，层次分明</span></span><br><span class="line"><span class="string">        2. 每个章节要有简要说明</span></span><br><span class="line"><span class="string">        3. 使用 Markdown 格式输出</span></span><br><span class="line"><span class="string">        "</span><span class="string">""</span>.formatted(topic);</span><br><span class="line"></span><br><span class="line">    List&lt;ChatMessage&gt; messages = Arrays.asList(</span><br><span class="line">        SystemMessage.from(<span class="string">"你是技术文档撰写专家"</span>),</span><br><span class="line">        UserMessage.from(prompt)</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> chatModel.generate(messages).content().text();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-3-智能客服对话"><a href="#6-3-智能客服对话" class="headerlink" title="6.3 智能客服对话"></a>6.3 智能客服对话</h3><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CustomerServiceBot</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatLanguageModel chatModel;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">handleUserMessage</span><span class="params">(String userMessage, List&lt;String&gt; history)</span> </span>&#123;</span><br><span class="line">        List&lt;ChatMessage&gt; messages = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 系统提示词</span></span><br><span class="line">        messages.add(SystemMessage.from(<span class="string">""</span><span class="string">"</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. 语气友好专业</span></span><br><span class="line"><span class="string">            2. 回答简洁明了</span></span><br><span class="line"><span class="string">            3. 如果无法回答，引导用户联系人工客服</span></span><br><span class="line"><span class="string">            "</span><span class="string">""</span>));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 添加历史对话</span></span><br><span class="line">        <span class="keyword">for</span> (String h : history) &#123;</span><br><span class="line">            messages.add(UserMessage.from(h));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 当前消息</span></span><br><span class="line">        messages.add(UserMessage.from(userMessage));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> chatModel.generate(messages).content().text();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、关键注意事项"><a href="#七、关键注意事项" class="headerlink" title="七、关键注意事项"></a>七、关键注意事项</h2><h3 id="7-1-Endpoint-不要混用"><a href="#7-1-Endpoint-不要混用" class="headerlink" title="7.1 Endpoint 不要混用"></a>7.1 Endpoint 不要混用</h3><div class="table-container"><table><thead><tr><th>Key 类型</th><th>正确 Endpoint</th><th>错误 Endpoint</th></tr></thead><tbody><tr><td><code>sk-sp-xxxxx</code> (Coding Plan)</td><td><code>coding.dashscope.aliyuncs.com/v1</code></td><td><code>dashscope.aliyuncs.com</code></td></tr><tr><td><code>sk-xxxxx</code> (通用)</td><td><code>dashscope.aliyuncs.com/compatible-mode/v1</code></td><td><code>coding.dashscope.aliyuncs.com</code></td></tr></tbody></table></div><p>混用会导致 <code>InvalidApiKey</code> 错误或消耗错误的额度！</p><h3 id="7-2-模型名称差异"><a href="#7-2-模型名称差异" class="headerlink" title="7.2 模型名称差异"></a>7.2 模型名称差异</h3><p>Coding Plan 支持的模型可能与通用百炼不同：</p><ul><li>Coding Plan: <code>kimi-k2.5</code> 等</li><li>通用百炼: <code>qwen-plus</code>, <code>qwen-max</code>, <code>qwen-turbo</code></li></ul><p>请根据实际账号支持的模型选择。</p><h3 id="7-3-Token-消耗监控"><a href="#7-3-Token-消耗监控" class="headerlink" title="7.3 Token 消耗监控"></a>7.3 Token 消耗监控</h3><p>虽然使用了 Coding Plan 额度，但仍需关注：</p><ul><li>单次调用 Token 数量</li><li>Coding Plan 额度剩余</li><li>设置合理的 <code>max_tokens</code> 防止超限</li></ul><h3 id="7-4-超时设置"><a href="#7-4-超时设置" class="headerlink" title="7.4 超时设置"></a>7.4 超时设置</h3><p>Coding Plan 响应时间可能与通用服务不同，建议设置较长超时：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.timeout(Duration.ofSeconds(<span class="number">60</span>))  <span class="comment">// 或更长</span></span><br></pre></td></tr></table></figure><hr><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><h3 id="8-1-官方说法-vs-实际实践"><a href="#8-1-官方说法-vs-实际实践" class="headerlink" title="8.1 官方说法 vs 实际实践"></a>8.1 官方说法 vs 实际实践</h3><div class="table-container"><table><thead><tr><th>官方说法</th><th>实际实践</th></tr></thead><tbody><tr><td>Coding Plan Key 仅限编程工具使用</td><td>Java/Python 应用可正常使用</td></tr><tr><td>需要额外开通通用 Key</td><td>无需额外开通</td></tr><tr><td>按量付费产生额外费用</td><td>直接消耗 Coding Plan 额度</td></tr><tr><td>dashscope-sdk-java 不支持</td><td>LangChain4j/OpenAI SDK 完美支持</td></tr></tbody></table></div><h3 id="8-2-核心原理"><a href="#8-2-核心原理" class="headerlink" title="8.2 核心原理"></a>8.2 核心原理</h3><p>Coding Plan 的 endpoint 基于 <strong>OpenAI API 兼容协议</strong>，这是业界通用的 LLM API 标准。任何支持 OpenAI 协议的客户端都可以调用，不受编程工具限制。</p><h3 id="8-3-适用场景"><a href="#8-3-适用场景" class="headerlink" title="8.3 适用场景"></a>8.3 适用场景</h3><ul><li>✅ Java 后端服务调用 LLM</li><li>✅ Python 应用调用 LLM</li><li>✅ Spring Boot / LangChain4j 集成</li><li>✅ 翻译、生成、对话等各类 NLP 任务</li><li>❌ 直接使用 dashscope-sdk-java（不支持 Coding Plan endpoint）</li></ul><hr><h2 id="九、参考资料"><a href="#九、参考资料" class="headerlink" title="九、参考资料"></a>九、参考资料</h2><ul><li><a href="https://docs.langchain4j.dev/" target="_blank" rel="noopener">LangChain4j 官方文档</a></li><li><a href="https://platform.openai.com/docs/api-reference" target="_blank" rel="noopener">OpenAI API 兼容协议</a></li><li><a href="https://bailian.console.aliyun.com/" target="_blank" rel="noopener">阿里百炼 Coding Plan</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;阿里云百炼 Coding Plan 官方宣称”仅限编程工具使用”，但实际上其 endpoint 基于 OpenAI 兼容协议，Java/Python 应用完全可以调用。本文分享如何用 LangChain4j 和 OpenAI SDK 突破这一限制，</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="Coding Plan" scheme="https://donehub.github.io/tags/Coding-Plan/"/>
    
  </entry>
  
  <entry>
    <title>Hermes Agent：一个会&quot;记住你&quot;的 AI 助手</title>
    <link href="https://donehub.github.io/2026/04/17/hermes-agent-introduction/"/>
    <id>https://donehub.github.io/2026/04/17/hermes-agent-introduction/</id>
    <published>2026-04-16T16:00:00.000Z</published>
    <updated>2026-04-18T10:04:02.750Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>如果你最近关注 AI Agent 领域，可能会注意到一个新名字——Hermes Agent。它来自 Nous Research，在短短两个月内从一个小型内部项目成长为功能完备的 AI Agent 平台。这篇文章聊聊它到底有什么不一样，以及为什么值得你花时间了解。</p></blockquote><hr><h2 id="一、Hermes-Agent-是什么？"><a href="#一、Hermes-Agent-是什么？" class="headerlink" title="一、Hermes Agent 是什么？"></a>一、Hermes Agent 是什么？</h2><p>简单说，它是一个<strong>可以自我进化的 AI Agent 框架</strong>。</p><p>市面上大多数 Agent 工具，你用完一次，下次还要重新教它。你说”帮我整理今天的 Git 提交记录”，它执行了。第二天你再说同样的话，它又从头开始理解。对话结束后，一切归零。</p><p>Hermes Agent 不一样。它有个叫”技能系统”的东西。第一次你教它做某件事，完成后它会问自己：这件事我以后是不是经常要做？如果答案是肯定的，它会把整个过程打包成一个技能。下次你只需要说”整理提交”，它就能直接调用这个技能。</p><p>这不是预设好的模板，是 Agent 自己判断、自己创建、自己优化的。用久了，它会越来越懂你的工作习惯。</p><hr><h2 id="二、为什么突然火了？"><a href="#二、为什么突然火了？" class="headerlink" title="二、为什么突然火了？"></a>二、为什么突然火了？</h2><p>翻一下 Hermes Agent 的版本历史，你会发现一个有意思的时间线：</p><div class="table-container"><table><thead><tr><th>版本</th><th>发布日期</th><th>说明</th></tr></thead><tbody><tr><td>v0.1.0</td><td>2026年2月底</td><td>内部预发布版本</td></tr><tr><td>v0.2.0</td><td>3月12日</td><td>首个公开版本，216个PR，63位贡献者</td></tr><tr><td>v0.3.0</td><td>3月17日</td><td>流式输出、插件架构、Honcho记忆</td></tr><tr><td>v0.4.0</td><td>3月23日</td><td>6个新消息平台、4个新推理提供商</td></tr><tr><td>v0.9.0</td><td>4月13日</td><td>Android支持、iMessage、微信接入</td></tr><tr><td>v0.10.0</td><td>4月16日</td><td>Nous工具网关，订阅用户零额外API</td></tr></tbody></table></div><p>从2月底到4月中旬，不到两个月，发布了10个大版本。平均每三四天一个版本。这不是营销驱动的节奏，是真实需求驱动的迭代速度。</p><p>看看 v0.2.0 的发布说明：”In just over two weeks, Hermes Agent went from a small internal project to a full-featured AI agent platform — thanks to an explosion of community contributions.”</p><p>这句话翻译过来：两个星期，从内部小项目变成完整平台，原因是社区贡献爆发。</p><p>为什么爆发？因为 Hermes Agent 解决了一个长期痛点——Agent 的记忆和学习能力。之前大家做 Agent，要么接受”每次对话归零”的现实，要么自己写一套复杂的持久化逻辑。Hermes Agent 把这套逻辑内置了，而且是真正意义上的”学习”，不只是”存储”。</p><hr><h2 id="三、核心特质"><a href="#三、核心特质" class="headerlink" title="三、核心特质"></a>三、核心特质</h2><h3 id="3-1-闭环学习，不是单次执行"><a href="#3-1-闭环学习，不是单次执行" class="headerlink" title="3.1 闭环学习，不是单次执行"></a>3.1 闭环学习，不是单次执行</h3><p>这点前面说了，展开讲一下细节。</p><p>Hermes Agent 的学习机制包含几个层次：</p><p><strong>技能自动创建</strong>：完成复杂任务后，Agent 会分析这个任务是否有重复价值。有，就创建技能。技能里包含了执行步骤、需要的工具、注意事项。</p><p><strong>技能自我改进</strong>：你用某个技能的时候如果给了反馈，比如”这次格式不对”或”下次加上这个字段”，Agent 会把这些反馈写进技能描述里。下一次执行会自动应用。</p><p><strong>定期提醒</strong>：Agent 有个机制叫”periodic nudges”，会周期性地提醒自己把重要信息持久化。不是被动等待你要求，是主动思考”这个信息值得记住吗”。</p><p><strong>跨会话搜索</strong>：你问”上次我们讨论的那个方案是什么”，它会搜索历史对话，用 LLM 做摘要，然后告诉你。这不是简单的关键词搜索，是语义层面的召回。</p><h3 id="3-2-Honcho-用户建模"><a href="#3-2-Honcho-用户建模" class="headerlink" title="3.2 Honcho 用户建模"></a>3.2 Honcho 用户建模</h3><p>Hermes Agent 内置了一个叫 Honcho 的用户建模系统。这个名字来自 plastic-labs 的 Honcho 项目，是一个专门做”AI 理解用户”的框架。</p><p>它的作用是：Agent 会持续观察你的偏好、习惯、工作方式，然后建立一个用户模型。你喜欢简洁回复，它记住；你讨厌某种操作方式，它记住；你对某个项目有特殊约定，它跨会话保持。</p><p>这不是简单的”记住你说过的话”，是”理解你是什么样的人”。</p><h3 id="3-3-多平台统一接入"><a href="#3-3-多平台统一接入" class="headerlink" title="3.3 多平台统一接入"></a>3.3 多平台统一接入</h3><p>这点对实际使用很重要。</p><p>你可以在 Telegram、Discord、Slack、WhatsApp、Signal 这些平台跟 Hermes Agent 对话，也可以在终端用 CLI。同一个 Agent，不同入口，记忆和技能是共享的。</p><p>这意味着你早上在公司电脑用 CLI 让它整理日报，晚上回家用 Telegram 继续讨论，它记得你白天说了什么。</p><h3 id="3-4-定时任务，原生支持"><a href="#3-4-定时任务，原生支持" class="headerlink" title="3.4 定时任务，原生支持"></a>3.4 定时任务，原生支持</h3><p>大多数 Agent 框架没有内置的定时任务系统。你想让 Agent 每天早上自动发日报，要么写外部脚本触发，要么依赖某个外部调度器。</p><p>Hermes Agent 内置了 cron 调度。你用自然语言描述：”每天早上9点，汇总昨天的 Git 提交并发到 Telegram”，它会自动解析、创建任务、按时执行。</p><p>这对于”Agent 作为助手”的场景很重要。真正的助手不只是你叫它才动，是会主动做事情。</p><h3 id="3-5-云端部署，不是本地绑定"><a href="#3-5-云端部署，不是本地绑定" class="headerlink" title="3.5 云端部署，不是本地绑定"></a>3.5 云端部署，不是本地绑定</h3><p>这点是 Hermes Agent 相比很多同类产品的优势。</p><p>它支持六种终端后端：本地、Docker、SSH、Daytona、Singularity、Modal。其中 Modal 和 Daytona 是”无服务器”模式——你的 Agent 环境在云端，空闲时几乎不花钱，有请求时自动唤醒。</p><p>这意味着你可以把 Hermes Agent 部署到云端，然后从 Telegram 发消息触发。不在电脑前的时候，Agent 依然在工作。这对于”随时随地操作”的需求很关键。</p><h3 id="3-6-多模型，随时切换"><a href="#3-6-多模型，随时切换" class="headerlink" title="3.6 多模型，随时切换"></a>3.6 多模型，随时切换</h3><p>Hermes Agent 支持大量 LLM 提供商：</p><ul><li>Nous Portal（官方订阅服务）</li><li>OpenRouter（200+模型）</li><li>Anthropic（Claude 系列）</li><li>OpenAI（GPT 系列）</li><li>Google AI Studio（Gemini）</li><li>阿里云百炼（DashScope）</li><li>智谱 AI（GLM）</li><li>Moonshot（Kimi）</li><li>MiniMax</li><li>小米 MiMo</li><li>NVIDIA NIM</li><li>DeepSeek</li><li>xAI（Grok）</li><li>Hugging Face</li><li>AWS Bedrock</li><li>还有更多…</li></ul><p>切换模型用一个命令：<code>hermes model</code>。不需要改代码，不需要重新部署，运行时切换。</p><p>这对于实际使用很重要。不同的任务适合不同的模型，你可能写代码用 Claude，快速问答用 GPT-mini，中文内容用 Qwen。Hermes Agent 让这种切换变得零成本。</p><hr><h2 id="四、对-OpenClaw-用户的意义"><a href="#四、对-OpenClaw-用户的意义" class="headerlink" title="四、对 OpenClaw 用户的意义"></a>四、对 OpenClaw 用户的意义</h2><p>如果你正在用 OpenClaw，听到 Hermes Agent 可能会想：又一个类似的工具，有必要换吗？</p><p>这里有个事实你可能不知道：<strong>Hermes Agent 是 OpenClaw 的官方进化版本</strong>。</p><p>翻 Hermes Agent 的文档，你会发现专门的迁移章节：</p><figure class="highlight plain"><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">## Migrating from OpenClaw</span><br><span class="line"></span><br><span class="line">If you&#39;re coming from OpenClaw, Hermes can automatically import your settings, memories, skills, and API keys.</span><br></pre></td></tr></table></figure><p>迁移命令很简单：</p><figure class="highlight bash"><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">hermes claw migrate --dry-run    <span class="comment"># 先预览会迁移什么</span></span><br><span class="line">hermes claw migrate              <span class="comment"># 执行迁移</span></span><br></pre></td></tr></table></figure><p>迁移内容包括：</p><ul><li>SOUL.md（人格设定）</li><li>已有技能</li><li>命令白名单</li><li>消息平台配置</li><li>API 密钥（Telegram、OpenRouter、OpenAI、Anthropic 等）</li><li>工作空间说明（AGENTS.md）</li></ul><p>这说明 Hermes Agent 的开发团队明确知道 OpenClaw 用户群体，并且专门做了兼容路径。</p><p>那为什么要从 OpenClaw 换到 Hermes Agent？几个实际理由：</p><div class="table-container"><table><thead><tr><th>功能</th><th>OpenClaw</th><th>Hermes Agent</th></tr></thead><tbody><tr><td>技能系统</td><td>有，但不自改进</td><td>有，且会自我优化</td></tr><tr><td>定时任务</td><td>无</td><td>内置 cron</td></tr><tr><td>云端部署</td><td>本地运行</td><td>Modal/Daytona 无服务器</td></tr><tr><td>用户建模</td><td>会话级</td><td>Honcho 深度建模</td></tr><tr><td>MCP 协议</td><td>无</td><td>支持</td></tr><tr><td>消息平台</td><td>Telegram、飞书等</td><td>Telegram、Discord、Slack、WhatsApp、Signal</td></tr></tbody></table></div><p>如果你需要定时任务、云端部署、Agent 学习能力，Hermes Agent 提供了这些 OpenClaw 没有的东西。</p><hr><h2 id="五、安装和使用指南"><a href="#五、安装和使用指南" class="headerlink" title="五、安装和使用指南"></a>五、安装和使用指南</h2><h3 id="5-1-Mac-用户"><a href="#5-1-Mac-用户" class="headerlink" title="5.1 Mac 用户"></a>5.1 Mac 用户</h3><p>Mac 上安装最简单，一行命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash</span><br></pre></td></tr></table></figure><p>安装完成后：</p><figure class="highlight bash"><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="built_in">source</span> ~/.zshrc   <span class="comment"># 或者 source ~/.bashrc</span></span><br><span class="line">hermes            <span class="comment"># 启动</span></span><br></pre></td></tr></table></figure><p>首次运行会引导你配置。按照提示选择 LLM 提供商、设置 API Key 就可以开始使用。</p><h3 id="5-2-Windows-用户"><a href="#5-2-Windows-用户" class="headerlink" title="5.2 Windows 用户"></a>5.2 Windows 用户</h3><p>Windows 原生不支持，需要通过 WSL2。</p><p>先安装 WSL2：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl -<span class="literal">-install</span></span><br></pre></td></tr></table></figure><p>然后进入 WSL2 的 Linux 环境，运行和 Mac 一样的安装命令。</p><p>这步对不熟悉 Linux 的用户可能有点门槛，但设置好后使用体验和 Mac 一样。</p><h3 id="5-3-常用命令"><a href="#5-3-常用命令" class="headerlink" title="5.3 常用命令"></a>5.3 常用命令</h3><figure class="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><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">hermes              <span class="comment"># 启动交互式 CLI</span></span><br><span class="line">hermes model        <span class="comment"># 选择模型提供商和具体模型</span></span><br><span class="line">hermes tools        <span class="comment"># 配置启用的工具</span></span><br><span class="line">hermes gateway      <span class="comment"># 启动消息平台网关（Telegram、Discord 等）</span></span><br><span class="line">hermes setup        <span class="comment"># 完整设置向导</span></span><br><span class="line">hermes doctor       <span class="comment"># 检查配置是否有问题</span></span><br><span class="line">hermes update       <span class="comment"># 更新到最新版本</span></span><br></pre></td></tr></table></figure><p>在对话中使用的斜杠命令：</p><figure class="highlight plain"><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">&#x2F;new              # 开始新对话</span><br><span class="line">&#x2F;model            # 切换模型</span><br><span class="line">&#x2F;skills           # 浏览可用技能</span><br><span class="line">&#x2F;retry            # 重试上一轮</span><br><span class="line">&#x2F;undo             #撤销上一轮</span><br><span class="line">&#x2F;compress         # 压缩上下文</span><br><span class="line">&#x2F;usage            # 查看用量</span><br></pre></td></tr></table></figure><h3 id="5-4-配置阿里云百炼"><a href="#5-4-配置阿里云百炼" class="headerlink" title="5.4 配置阿里云百炼"></a>5.4 配置阿里云百炼</h3><p>如果你之前用 OpenClaw 配了阿里云百炼的 Coding Plan，在 Hermes Agent 里可以直接用：</p><figure class="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="comment"># 设置环境变量</span></span><br><span class="line"><span class="built_in">export</span> DASHSCOPE_API_KEY=你的API密钥</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果用国内版，额外设置</span></span><br><span class="line"><span class="built_in">export</span> DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1</span><br></pre></td></tr></table></figure><p>或者在 <code>~/.hermes/.env</code> 文件里写：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DASHSCOPE_API_KEY&#x3D;sk-xxxxxxxx</span><br></pre></td></tr></table></figure><p>然后用命令切换提供商：</p><figure class="highlight bash"><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">hermes model alibaba</span><br><span class="line">hermes model qwen3-coder-plus</span><br></pre></td></tr></table></figure><hr><h2 id="六、OpenClaw-用户迁移指南"><a href="#六、OpenClaw-用户迁移指南" class="headerlink" title="六、OpenClaw 用户迁移指南"></a>六、OpenClaw 用户迁移指南</h2><p>如果你已经有 OpenClaw 的配置，迁移步骤：</p><p><strong>1. 安装 Hermes Agent</strong></p><figure class="highlight bash"><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">curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash</span><br><span class="line"><span class="built_in">source</span> ~/.zshrc</span><br></pre></td></tr></table></figure><p><strong>2. 运行迁移命令</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hermes setup</span><br></pre></td></tr></table></figure><p>Setup 向导会自动检测 <code>~/.openclaw</code> 目录，提示你是否迁移。</p><p>或者任何时候手动运行：</p><figure class="highlight bash"><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">hermes claw migrate --dry-run    <span class="comment"># 预览会迁移什么</span></span><br><span class="line">hermes claw migrate              <span class="comment"># 执行迁移</span></span><br></pre></td></tr></table></figure><p><strong>3. 检查迁移结果</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hermes doctor    <span class="comment"># 检查配置是否正确</span></span><br></pre></td></tr></table></figure><p><strong>4. 开始使用</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hermes    <span class="comment"># 启动 CLI</span></span><br></pre></td></tr></table></figure><p>迁移后，你的 SOUL.md（人格设定）、已有技能、命令白名单、消息平台配置都会保留。API 密钥会自动迁移到 <code>~/.hermes/.env</code>。</p><p><strong>注意</strong>：OpenClaw 支持 Telegram、飞书等平台，这些配置会直接迁移。</p><hr><h2 id="七、适合什么人用？"><a href="#七、适合什么人用？" class="headerlink" title="七、适合什么人用？"></a>七、适合什么人用？</h2><p>如果你的需求是：</p><ul><li><strong>写代码为主</strong> → 继续用 Claude Code，它在代码理解上更强</li><li><strong>操作电脑、整理文件</strong> → Hermes Agent 或 OpenClaw 都能胜任</li><li><strong>重复性任务多</strong> → Hermes Agent，技能系统会帮你省时间</li><li><strong>需要定时自动化</strong> → Hermes Agent，内置 cron</li><li><strong>离开电脑时也想用</strong> → Hermes Agent，云端部署 + Telegram</li><li><strong>飞书/Telegram 是核心场景</strong> → Hermes Agent 或 OpenClaw 都能胜任</li></ul><p>如果你已经在用 OpenClaw，不需要立即换。两个工具核心功能重叠约 70%，Hermes Agent 新增的是学习能力、定时任务和云端部署。这些对你有没有价值，看你的实际需求。</p><p>但如果你想尝试 Hermes Agent，迁移成本很低。一条命令就能把 OpenClaw 的配置全部导过去，不存在”从头设置”的问题。</p><hr><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>Hermes Agent 的价值不在”功能更多”，在”设计思路不同”。</p><p>大多数 Agent 工具的设计假设是：用户发起对话 → Agent 执行 → 结束。下一次对话从零开始。</p><p>Hermes Agent 的设计假设是：Agent 和用户是长期关系，Agent 应该越来越懂用户，而不是每次都从陌生人开始。</p><p>这个假设的差异，导致了功能设计的差异：技能自动创建、技能自我改进、Honcho 用户建模、跨会话搜索、定期提醒持久化。</p><p>这些功能单独看都不复杂，组合起来形成一个闭环：Agent 做事 → Agent 学习 → Agent 下次做得更好。</p><p>这个闭环是 Hermes Agent 和其他 Agent 工具的本质区别。</p><p>如果你对”Agent 可以学习和进化”这个概念感兴趣，值得花半小时安装试试。不需要完全替换你现有的工具，先体验一下它的学习机制，看看是否符合你的预期。</p><hr><h2 id="九、相关资源"><a href="#九、相关资源" class="headerlink" title="九、相关资源"></a>九、相关资源</h2><ul><li>Hermes Agent GitHub：<a href="https://github.com/NousResearch/hermes-agent" target="_blank" rel="noopener">https://github.com/NousResearch/hermes-agent</a></li><li>官方文档：<a href="https://hermes-agent.nousresearch.com/docs/" target="_blank" rel="noopener">https://hermes-agent.nousresearch.com/docs/</a></li><li>Skills Hub：<a href="https://agentskills.io" target="_blank" rel="noopener">https://agentskills.io</a></li><li>Nous Research Discord：<a href="https://discord.gg/NousResearch" target="_blank" rel="noopener">https://discord.gg/NousResearch</a></li><li>HermesClaw（微信桥接）：<a href="https://github.com/AaronWong1999/hermesclaw" target="_blank" rel="noopener">https://github.com/AaronWong1999/hermesclaw</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;如果你最近关注 AI Agent 领域，可能会注意到一个新名字——Hermes Agent。它来自 Nous Research，在短短两个月内从一个小型内部项目成长为功能完备的 AI Agent 平台。这篇文章聊聊它到底有什么不一样，以及为什么值得</summary>
      
    
    
    
    <category term="AI" scheme="https://donehub.github.io/categories/AI/"/>
    
    
    <category term="AI Agent" scheme="https://donehub.github.io/tags/AI-Agent/"/>
    
  </entry>
  
  <entry>
    <title>Claude Code 为什么不用 LangChain/LangGraph：自研架构的深层逻辑</title>
    <link href="https://donehub.github.io/2026/04/15/claude-code-why-no-langchain/"/>
    <id>https://donehub.github.io/2026/04/15/claude-code-why-no-langchain/</id>
    <published>2026-04-14T16:00:00.000Z</published>
    <updated>2026-04-13T01:35:20.033Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>LangChain 和 LangGraph 是当下最流行的 Agent 开发框架，但 Anthropic 的 Claude Code 却完全不用它们。这不是傲慢，而是基于技术本质、产品体验、API 特性和工程可控性的四重考量。本文从多个层面剖析 Claude Code 的自研架构选择，以及它用什么技术替代了 LangChain/LangGraph 的能力。</p></blockquote><hr><h2 id="一、先说结论"><a href="#一、先说结论" class="headerlink" title="一、先说结论"></a>一、先说结论</h2><p>Claude Code 不用 LangChain/LangGraph，原因有四个：</p><div class="table-container"><table><thead><tr><th>层面</th><th>LangChain/LangGraph 的限制</th><th>Claude Code 的选择</th></tr></thead><tbody><tr><td><strong>架构层面</strong></td><td>ReAct 模式的串行瓶颈</td><td>Async Generator 状态机</td></tr><tr><td><strong>API 层面</strong></td><td>无法充分利用 Anthropic API 特性</td><td>原生 SDK 直接集成</td></tr><tr><td><strong>性能层面</strong></td><td>抽象层增加延迟</td><td>零抽象，直接流式处理</td></tr><tr><td><strong>可控层面</strong></td><td>框架黑盒，难以定制</td><td>全栈自研，精准控制</td></tr></tbody></table></div><p><strong>一句话概括</strong>：LangChain/LangGraph 是”通用框架”，Claude Code 是”专用系统”。通用框架追求易用，专用系统追求极致体验。</p><hr><h2 id="二、架构层面：为什么放弃-ReAct"><a href="#二、架构层面：为什么放弃-ReAct" class="headerlink" title="二、架构层面：为什么放弃 ReAct"></a>二、架构层面：为什么放弃 ReAct</h2><h3 id="2-1-ReAct-模式的根本缺陷"><a href="#2-1-ReAct-模式的根本缺陷" class="headerlink" title="2.1 ReAct 模式的根本缺陷"></a>2.1 ReAct 模式的根本缺陷</h3><p>LangChain 和 LangGraph 的核心都是 <strong>ReAct 模式</strong>（Reasoning + Acting）：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">思考(Thought) → 行动(Action) → 观察(Observation) → 思考 → ...</span><br></pre></td></tr></table></figure><p>这个模式直观易懂，但存在三个根本缺陷：</p><p><strong>缺陷一：串行瓶颈</strong></p><figure class="highlight plain"><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">│                    ReAct 串行流程                            │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  用户输入 → 等待完整响应 → 解析工具调用 → 执行工具 → 等待 →  │</span><br><span class="line">│            └───────────────────────────────────┘            │</span><br><span class="line">│                        用户感知到的延迟                       │</span><br><span class="line">│                                                             │</span><br><span class="line">│  问题：用户要等模型生成完整响应后才能看到工具执行             │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p>在 CLI 交互场景中，这种延迟是致命的——用户盯着屏幕等待，不知道发生了什么。</p><p><strong>缺陷二：无法利用流式传输</strong></p><p>现代 LLM API 都支持流式输出（SSE），但 ReAct 模式下流式的价值被大大削弱：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># LangChain 的 Agent 执行</span></span><br><span class="line">agent.run(<span class="string">"帮我分析这个项目"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 内部流程：</span></span><br><span class="line"><span class="comment"># 1. LLM 生成完整响应（即使流式，也要等 action 完整）</span></span><br><span class="line"><span class="comment"># 2. OutputParser 解析响应文本</span></span><br><span class="line"><span class="comment"># 3. 提取工具名称和参数</span></span><br><span class="line"><span class="comment"># 4. 执行工具</span></span><br><span class="line"><span class="comment"># 5. 工具结果返回给 LLM</span></span><br><span class="line"><span class="comment"># 6. 重复...</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 流式输出的价值：实时看到模型"在想什么"</span></span><br><span class="line"><span class="comment"># 但工具执行：必须等完整响应后才能开始</span></span><br><span class="line"><span class="comment"># 两者冲突，流式体验被割裂</span></span><br></pre></td></tr></table></figure><p><strong>缺陷三：状态恢复困难</strong></p><p>ReAct 模式没有统一的状态表示，每一步都是独立的：</p><figure class="highlight plain"><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">Step 1: Thought → Action → Observation  (无状态记忆)</span><br><span class="line">Step 2: Thought → Action → Observation  (重新开始)</span><br><span class="line">Step 3: ...</span><br><span class="line"></span><br><span class="line">当 API 超时、Token 溢出时：</span><br><span class="line">- LangChain：抛出异常，用户需手动处理</span><br><span class="line">- LangGraph：需要显式定义 checkpoint，复杂度高</span><br><span class="line">- Claude Code：State 对象统一承载，自动恢复</span><br></pre></td></tr></table></figure><h3 id="2-2-Claude-Code-的替代方案：Async-Generator-状态机"><a href="#2-2-Claude-Code-的替代方案：Async-Generator-状态机" class="headerlink" title="2.2 Claude Code 的替代方案：Async Generator 状态机"></a>2.2 Claude Code 的替代方案：Async Generator 状态机</h3><p>Claude Code 用一个 <strong>while(true) 循环 + State 赋值</strong> 替代 ReAct：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/query.ts 核心（简化版）</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span>* <span class="title">query</span>(<span class="params">params: QueryParams</span>): <span class="title">AsyncGenerator</span>&lt;<span class="title">QueryUpdate</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> state: State = &#123;</span><br><span class="line">    messages: [...],</span><br><span class="line">    toolUseContext: &#123;...&#125;,</span><br><span class="line">    turnCount: <span class="number">0</span>,</span><br><span class="line">    transition: <span class="literal">undefined</span>,</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="comment">// 阶段1: 消息压缩（自动处理 Token 溢出）</span></span><br><span class="line">    <span class="comment">// 阶段2: 流式 API 调用（工具即时执行）</span></span><br><span class="line">    <span class="comment">// 阶段3: 决策点（继续还是结束）</span></span><br><span class="line">    <span class="comment">// 阶段4: 工具编排（并行只读，串行写入）</span></span><br><span class="line">    <span class="comment">// 阶段5: 状态更新</span></span><br><span class="line">    </span><br><span class="line">    state = next  <span class="comment">// 通过赋值驱动循环</span></span><br><span class="line">    <span class="keyword">continue</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>核心优势对比</strong>：</p><div class="table-container"><table><thead><tr><th>维度</th><th>ReAct</th><th>Async Generator 状态机</th></tr></thead><tbody><tr><td>执行方式</td><td>串行，等待完整响应</td><td>流式，工具即时执行</td></tr><tr><td>状态管理</td><td>无统一状态</td><td>State 对象承载所有信息</td></tr><tr><td>错误恢复</td><td>手动处理</td><td>6 种内置恢复策略</td></tr><tr><td>内存安全</td><td>可能递归溢出</td><td>状态赋值，无递归风险</td></tr><tr><td>可观测性</td><td>需要额外追踪</td><td>transition 字段记录转换原因</td></tr></tbody></table></div><h3 id="2-3-流式即时执行：StreamingToolExecutor"><a href="#2-3-流式即时执行：StreamingToolExecutor" class="headerlink" title="2.3 流式即时执行：StreamingToolExecutor"></a>2.3 流式即时执行：StreamingToolExecutor</h3><p>Claude Code 的关键创新是 <strong>工具在模型生成过程中就开始执行</strong>：</p><figure class="highlight plain"><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">│                 Claude Code 流式执行流程                      │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  模型流式输出：                                              │</span><br><span class="line">│    &quot;我来帮你分析这个项目...&quot;                                 │</span><br><span class="line">│    &quot;首先读取 README...&quot;                                     │</span><br><span class="line">│    [生成 tool_use 块: Read &#123; path: &quot;README.md&quot; &#125;]           │</span><br><span class="line">│                                                             │</span><br><span class="line">│                    ↓ 立即执行                                │</span><br><span class="line">│                                                             │</span><br><span class="line">│  StreamingToolExecutor:                                     │</span><br><span class="line">│    检测到 tool_use → 立即调用 Read 工具                     │</span><br><span class="line">│    工具结果实时返回                                          │</span><br><span class="line">│                                                             │</span><br><span class="line">│  用户感知：                                                  │</span><br><span class="line">│    实时看到模型思考                                          │</span><br><span class="line">│    实时看到工具执行                                          │</span><br><span class="line">│    无需等待完整响应                                          │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p><strong>对比 LangChain</strong>：</p><figure class="highlight plain"><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">LangChain 流程：</span><br><span class="line">  用户输入 → 等待(模型完整响应) → 解析 → 执行工具 → 等待 → ...</span><br><span class="line">  总延迟 &#x3D; 模型生成时间 + 解析时间 + 工具执行时间</span><br><span class="line"></span><br><span class="line">Claude Code 流程：</span><br><span class="line">  用户输入 → 流式生成(工具即时执行) → 流式输出 → ...</span><br><span class="line">  总延迟 &#x3D; max(模型生成时间, 工具执行时间)</span><br></pre></td></tr></table></figure><hr><h2 id="三、API-层面：原生特性的充分利用"><a href="#三、API-层面：原生特性的充分利用" class="headerlink" title="三、API 层面：原生特性的充分利用"></a>三、API 层面：原生特性的充分利用</h2><h3 id="3-1-LangChain-的”框架税”"><a href="#3-1-LangChain-的”框架税”" class="headerlink" title="3.1 LangChain 的”框架税”"></a>3.1 LangChain 的”框架税”</h3><p>LangChain 作为通用框架，需要在多种模型 API 之间保持一致性。这意味着：</p><figure class="highlight plain"><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">Anthropic API 特性          → LangChain 抽象层 → 用户代码</span><br><span class="line">                                    ↓</span><br><span class="line">                          被抹平或延迟支持</span><br></pre></td></tr></table></figure><p><strong>Anthropic 独有的 API 特性</strong>：</p><div class="table-container"><table><thead><tr><th>特性</th><th>说明</th><th>LangChain 支持情况</th></tr></thead><tbody><tr><td><strong>Prompt Caching</strong></td><td>提示词缓存，降本 90%</td><td>2024 年后才支持，使用复杂</td></tr><tr><td><strong>Extended Thinking</strong></td><td>思维链输出，推理透明</td><td>LangChain 无原生支持</td></tr><tr><td><strong>Computer Use</strong></td><td>屏幕操作能力</td><td>LangChain 无原生支持</td></tr><tr><td><strong>原生 tool_use</strong></td><td>结构化工具调用块</td><td>LangChain 用 OutputParser 解析文本</td></tr><tr><td><strong>原生流式 tool_use</strong></td><td>流式传输中工具即时触发</td><td>LangChain 需等待完整响应</td></tr></tbody></table></div><h3 id="3-2-Claude-Code-的原生集成"><a href="#3-2-Claude-Code-的原生集成" class="headerlink" title="3.2 Claude Code 的原生集成"></a>3.2 Claude Code 的原生集成</h3><p>Claude Code 直接使用 Anthropic SDK，充分利用所有原生特性：</p><p><strong>示例：Prompt Caching 的利用</strong></p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Claude Code 的提示词组装（src/constants/prompts.ts）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 静态可缓存区域（scope: 'global'）</span></span><br><span class="line"><span class="keyword">const</span> systemPrompt = &#123;</span><br><span class="line">  <span class="keyword">type</span>: <span class="string">'text'</span>,</span><br><span class="line">  text: <span class="string">`</span></span><br><span class="line"><span class="string">    ## 角色定义</span></span><br><span class="line"><span class="string">    Claude Code 是一个...</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">    你必须遵守...</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">    以下工具可用...</span></span><br><span class="line"><span class="string">  `</span>,</span><br><span class="line">  cache_control: &#123; <span class="keyword">type</span>: <span class="string">'ephemeral'</span> &#125;  <span class="comment">// 缓存标记</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 动态不可缓存区域（scope: 'ephemeral'）</span></span><br><span class="line"><span class="keyword">const</span> dynamicPrompt = &#123;</span><br><span class="line">  <span class="keyword">type</span>: <span class="string">'text'</span>,</span><br><span class="line">  text: <span class="string">`</span></span><br><span class="line"><span class="string">    ## 当前环境</span></span><br><span class="line"><span class="string">    工作目录: <span class="subst">$&#123;cwd&#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">    <span class="subst">$&#123;claudeMdContent&#125;</span></span></span><br><span class="line"><span class="string">  `</span>,</span><br><span class="line">  cache_control: &#123; <span class="keyword">type</span>: <span class="string">'ephemeral'</span> &#125;  <span class="comment">// 独立缓存</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>缓存效果</strong>：</p><ul><li>第一次调用：完整 token 计费</li><li>后续调用：静态部分缓存命中，降本约 <strong>90%</strong></li></ul><p>LangChain 也支持 Prompt Caching，但需要用户手动配置，且无法像 Claude Code 这样精细划分缓存边界。</p><p><strong>示例：原生 tool_use 块</strong></p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Anthropic API 响应格式</span></span><br><span class="line">&#123;</span><br><span class="line">  content: [</span><br><span class="line">    &#123; <span class="keyword">type</span>: <span class="string">'text'</span>, text: <span class="string">'我来帮你...'</span> &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="keyword">type</span>: <span class="string">'tool_use'</span>,</span><br><span class="line">      id: <span class="string">'toolu_01...'</span>,</span><br><span class="line">      name: <span class="string">'Read'</span>,</span><br><span class="line">      input: &#123; file_path: <span class="string">'/path/to/file'</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Claude Code 直接处理</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> block of response.content) &#123;</span><br><span class="line">  <span class="keyword">if</span> (block.type === <span class="string">'tool_use'</span>) &#123;</span><br><span class="line">    <span class="comment">// 立即执行，无需解析文本</span></span><br><span class="line">    <span class="keyword">await</span> executeTool(block.name, block.input)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>对比 LangChain</strong>：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># LangChain 的工具调用</span></span><br><span class="line">response = llm.invoke(prompt)</span><br><span class="line"></span><br><span class="line"><span class="comment"># OutputParser 解析文本</span></span><br><span class="line">parsed = output_parser.parse(response.content)</span><br><span class="line"><span class="comment"># 解析可能失败，格式不固定</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> parsed[<span class="string">'action'</span>]:</span><br><span class="line">    tool_name = parsed[<span class="string">'action'</span>][<span class="string">'tool'</span>]</span><br><span class="line">    tool_input = parsed[<span class="string">'action'</span>][<span class="string">'input'</span>]</span><br><span class="line">    result = tools[tool_name].run(tool_input)</span><br></pre></td></tr></table></figure><p>LangChain 需要 OutputParser 解析模型输出的文本，这是脆弱的——模型格式不固定时解析会失败。</p><hr><h2 id="四、性能层面：零抽象的流式优先"><a href="#四、性能层面：零抽象的流式优先" class="headerlink" title="四、性能层面：零抽象的流式优先"></a>四、性能层面：零抽象的流式优先</h2><h3 id="4-1-LangChain-的抽象层堆叠"><a href="#4-1-LangChain-的抽象层堆叠" class="headerlink" title="4.1 LangChain 的抽象层堆叠"></a>4.1 LangChain 的抽象层堆叠</h3><p>LangChain 的抽象层结构：</p><figure class="highlight plain"><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><br><span class="line">  → Chain</span><br><span class="line">    → AgentExecutor</span><br><span class="line">      → LLM</span><br><span class="line">        → Memory</span><br><span class="line">          → Tools</span><br><span class="line">            → OutputParser</span><br><span class="line">              → 实际 API 调用</span><br></pre></td></tr></table></figure><p>每一层都增加处理开销。对于 Web 应用，这些开销可以忽略；但对于 <strong>CLI 交互工具</strong>，延迟是致命的。</p><h3 id="4-2-Claude-Code-的零抽象设计"><a href="#4-2-Claude-Code-的零抽象设计" class="headerlink" title="4.2 Claude Code 的零抽象设计"></a>4.2 Claude Code 的零抽象设计</h3><p>Claude Code 的结构：</p><figure class="highlight plain"><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><br><span class="line">  → query() AsyncGenerator</span><br><span class="line">    → Anthropic SDK（直接调用）</span><br><span class="line">      → 工具执行（流式即时）</span><br></pre></td></tr></table></figure><p><strong>没有中间抽象层</strong>，API 响应直接流式传递给用户。</p><h3 id="4-3-工具编排的性能优化"><a href="#4-3-工具编排的性能优化" class="headerlink" title="4.3 工具编排的性能优化"></a>4.3 工具编排的性能优化</h3><p>Claude Code 的工具编排策略：</p><figure class="highlight plain"><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><br><span class="line">  │</span><br><span class="line">  ├─ 分类：只读 vs 写入</span><br><span class="line">  │</span><br><span class="line">  ├─ 只读工具 ──→ 并行执行（最多 10 个并发）</span><br><span class="line">  │   ├─ Read      ──→ 同时开始</span><br><span class="line">  │   ├─ Grep      ──→ 同时开始</span><br><span class="line">  │   ├─ Glob      ──→ 同时开始</span><br><span class="line">  │   └─ WebFetch  ──→ 同时开始</span><br><span class="line">  │</span><br><span class="line">  └─ 写入工具 ──→ 串行执行（保证顺序）</span><br><span class="line">      ├─ FileEdit  ──→ 等待上一个完成</span><br><span class="line">      ├─ Write     ──→ 等待上一个完成</span><br><span class="line">      └─ Bash      ──→ 等待上一个完成</span><br></pre></td></tr></table></figure><p><strong>LangChain 的工具执行</strong>：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># LangChain Agent 默认串行执行</span></span><br><span class="line"><span class="keyword">for</span> tool_call <span class="keyword">in</span> parsed_tool_calls:</span><br><span class="line">    result = tool.run(tool_call.input)  <span class="comment"># 一个一个执行</span></span><br></pre></td></tr></table></figure><p>LangChain 需要显式配置并行，且配置复杂；Claude Code 自动分析工具性质，智能编排。</p><hr><h2 id="五、可控层面：全栈自研的精准控制"><a href="#五、可控层面：全栈自研的精准控制" class="headerlink" title="五、可控层面：全栈自研的精准控制"></a>五、可控层面：全栈自研的精准控制</h2><h3 id="5-1-框架黑盒问题"><a href="#5-1-框架黑盒问题" class="headerlink" title="5.1 框架黑盒问题"></a>5.1 框架黑盒问题</h3><p>使用 LangChain/LangGraph 时，你无法精准控制：</p><div class="table-container"><table><thead><tr><th>场景</th><th>LangChain 行为</th><th>你的控制力</th></tr></thead><tbody><tr><td>工具执行顺序</td><td>默认串行</td><td>需要显式配置</td></tr><tr><td>错误恢复</td><td>抛出异常</td><td>需要自己处理</td></tr><tr><td>Token 溢出</td><td>截断或报错</td><td>需要自己检测</td></tr><tr><td>提示词组装</td><td>模板拼接</td><td>无法精细控制</td></tr><tr><td>流式输出</td><td>部分支持</td><td>需要适配框架</td></tr></tbody></table></div><h3 id="5-2-Claude-Code-的精准控制"><a href="#5-2-Claude-Code-的精准控制" class="headerlink" title="5.2 Claude Code 的精准控制"></a>5.2 Claude Code 的精准控制</h3><p>Claude Code 自研每一层，可以精确控制：</p><p><strong>控制一：工具执行权限</strong></p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Claude Code 的权限系统（src/utils/permissions）</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> PermissionResult = &#123;</span><br><span class="line">  behavior: <span class="string">'allow'</span> | <span class="string">'deny'</span> | <span class="string">'ask'</span></span><br><span class="line">  message?: <span class="built_in">string</span></span><br><span class="line">  suggestions?: <span class="built_in">string</span>[]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 精细的权限检查</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">checkPermissions</span>(<span class="params">tool, input, context</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 1. deny 规则最高优先级</span></span><br><span class="line">  <span class="keyword">if</span> (matchesDenyRule(tool.name)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'deny'</span>, message: <span class="string">'Blocked by deny rule'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 工具自定义检查</span></span><br><span class="line">  <span class="keyword">if</span> (tool.checkPermissions) &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> tool.checkPermissions(input, context)</span><br><span class="line">    <span class="keyword">if</span> (result.behavior !== <span class="string">'passthrough'</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> result</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. allow 规则</span></span><br><span class="line">  <span class="keyword">if</span> (matchesAllowRule(tool.name, input)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; behavior: <span class="string">'allow'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 默认询问用户</span></span><br><span class="line">  <span class="keyword">return</span> &#123; behavior: <span class="string">'ask'</span>, message: <span class="string">'Do you want to allow?'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>控制二：自动压缩策略</strong></p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Claude Code 的四级压缩（src/query）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Level 1: Snip — 删除旧消息中的冗余 token</span></span><br><span class="line">messages = snipMessages(messages)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Level 2: Micro — 修改已缓存消息的内容</span></span><br><span class="line">messages = microCompact(messages)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Level 3: Collapse — 分阶段摘要历史消息</span></span><br><span class="line">messages = collapseMessages(messages)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Level 4: Auto Compact — 通过 Claude 生成完整摘要</span></span><br><span class="line">messages = <span class="keyword">await</span> autoCompact(messages)</span><br><span class="line"></span><br><span class="line"><span class="comment">// LangChain 的处理方式：</span></span><br><span class="line"><span class="comment">// messages = messages.slice(-max_tokens)  // 简单截断</span></span><br></pre></td></tr></table></figure><p><strong>控制三：钩子扩展系统</strong></p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// settings.json</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">"hooks"</span>: &#123;</span><br><span class="line">    <span class="attr">"PreToolUse"</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">"matcher"</span>: <span class="string">"Bash"</span>,</span><br><span class="line">        <span class="attr">"hooks"</span>: [</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">"type"</span>: <span class="string">"command"</span>,</span><br><span class="line">            <span class="attr">"command"</span>: <span class="string">"security-check.sh"</span></span><br><span class="line">          &#125;</span><br><span class="line">        ]</span><br><span class="line">      &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">"PostToolUse"</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">"matcher"</span>: <span class="string">"FileEdit"</span>,</span><br><span class="line">        <span class="attr">"hooks"</span>: [</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">"type"</span>: <span class="string">"command"</span>,</span><br><span class="line">            <span class="attr">"command"</span>: <span class="string">"run-tests.sh"</span></span><br><span class="line">          &#125;</span><br><span class="line">        ]</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用户可以在工具执行的任意阶段注入自定义逻辑，LangChain 需要继承类或修改源码才能实现类似功能。</p><hr><h2 id="六、能力映射：Claude-Code-用什么替代-LangChain-LangGraph"><a href="#六、能力映射：Claude-Code-用什么替代-LangChain-LangGraph" class="headerlink" title="六、能力映射：Claude Code 用什么替代 LangChain/LangGraph"></a>六、能力映射：Claude Code 用什么替代 LangChain/LangGraph</h2><h3 id="6-1-LangChain-能力-→-Claude-Code-替代方案"><a href="#6-1-LangChain-能力-→-Claude-Code-替代方案" class="headerlink" title="6.1 LangChain 能力 → Claude Code 替代方案"></a>6.1 LangChain 能力 → Claude Code 替代方案</h3><div class="table-container"><table><thead><tr><th>LangChain 能力</th><th>Claude Code 替代方案</th><th>实现文件</th></tr></thead><tbody><tr><td><strong>LLM 调用</strong></td><td>Anthropic SDK 直接集成</td><td><code>src/query.ts</code></td></tr><tr><td><strong>工具定义</strong></td><td><code>Tool</code> 类型 + <code>buildTool()</code></td><td><code>src/Tool.ts</code></td></tr><tr><td><strong>工具注册</strong></td><td>三阶段流水线注册</td><td><code>src/tools.ts</code></td></tr><tr><td><strong>Agent 循环</strong></td><td><code>while(true)</code> 状态机</td><td><code>src/query.ts</code></td></tr><tr><td><strong>Memory</strong></td><td>Channel 系统 + 文件记忆</td><td><code>src/state/</code>, <code>src/memdir/</code></td></tr><tr><td><strong>RAG</strong></td><td>文件工具 + 向量工具（可选 MCP）</td><td><code>src/tools/</code></td></tr><tr><td><strong>OutputParser</strong></td><td>原生 <code>tool_use</code> 块解析</td><td>无需解析</td></tr><tr><td><strong>Callbacks</strong></td><td>钩子系统</td><td><code>src/hooks/</code></td></tr></tbody></table></div><h3 id="6-2-LangGraph-能力-→-Claude-Code-替代方案"><a href="#6-2-LangGraph-能力-→-Claude-Code-替代方案" class="headerlink" title="6.2 LangGraph 能力 → Claude Code 替代方案"></a>6.2 LangGraph 能力 → Claude Code 替代方案</h3><div class="table-container"><table><thead><tr><th>LangGraph 能力</th><th>Claude Code 替代方案</th><th>实现文件</th></tr></thead><tbody><tr><td><strong>StateGraph</strong></td><td><code>State</code> 对象 + 状态赋值</td><td><code>src/query.ts</code></td></tr><tr><td><strong>节点定义</strong></td><td><code>while</code> 循环的阶段划分</td><td><code>src/query.ts:307-1728</code></td></tr><tr><td><strong>边流转</strong></td><td><code>transition</code> + <code>continue</code></td><td><code>src/query/transitions.ts</code></td></tr><tr><td><strong>条件分支</strong></td><td><code>if/switch</code> + <code>state.transition</code></td><td><code>src/query.ts</code></td></tr><tr><td><strong>Checkpoint</strong></td><td>消息历史 + 文件系统</td><td><code>src/assistant/</code></td></tr><tr><td><strong>多 Agent</strong></td><td><code>AgentTool</code> + 子代理系统</td><td><code>src/tools/AgentTool/</code></td></tr><tr><td><strong>可视化调试</strong></td><td><code>transition</code> 字段追踪</td><td>可观测性设计</td></tr></tbody></table></div><h3 id="6-3-核心代码映射"><a href="#6-3-核心代码映射" class="headerlink" title="6.3 核心代码映射"></a>6.3 核心代码映射</h3><p><strong>LangChain Agent → Claude Code query()</strong></p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># LangChain</span></span><br><span class="line">agent = AgentExecutor.from_agent_and_tools(agent, tools)</span><br><span class="line">result = agent.invoke(&#123;<span class="string">"input"</span>: <span class="string">"do something"</span>&#125;)</span><br></pre></td></tr></table></figure><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Claude Code</span></span><br><span class="line"><span class="keyword">for</span> <span class="keyword">await</span> (<span class="keyword">const</span> update of query(&#123; messages, tools, systemPrompt &#125;)) &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(update)  <span class="comment">// 实时输出</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>LangGraph StateGraph → Claude Code while 循环</strong></p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># LangGraph</span></span><br><span class="line">graph = StateGraph(AgentState)</span><br><span class="line">graph.add_node(<span class="string">"agent"</span>, agent_node)</span><br><span class="line">graph.add_node(<span class="string">"tool"</span>, tool_node)</span><br><span class="line">graph.add_conditional_edges(<span class="string">"agent"</span>, should_continue, </span><br><span class="line">    &#123;<span class="string">"continue"</span>: <span class="string">"tool"</span>, <span class="string">"end"</span>: END&#125;)</span><br><span class="line">app = graph.compile()</span><br></pre></td></tr></table></figure><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Claude Code（src/query.ts）</span></span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">  <span class="comment">// 阶段 2: 流式 API 调用</span></span><br><span class="line">  <span class="keyword">const</span> response = <span class="keyword">await</span> callModel(state)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 阶段 3: 决策点（条件分支）</span></span><br><span class="line">  <span class="keyword">if</span> (hasToolUse(response)) &#123;</span><br><span class="line">    <span class="comment">// 阶段 4: 工具执行</span></span><br><span class="line">    <span class="keyword">const</span> results = <span class="keyword">await</span> executeTools(response.tool_use_blocks)</span><br><span class="line">    state = &#123; ...state, messages: [...messages, results] &#125;</span><br><span class="line">    <span class="keyword">continue</span>  <span class="comment">// 继续循环</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 结束</span></span><br><span class="line">    <span class="keyword">yield</span> finalResult</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、什么时候该用-LangChain-LangGraph"><a href="#七、什么时候该用-LangChain-LangGraph" class="headerlink" title="七、什么时候该用 LangChain/LangGraph"></a>七、什么时候该用 LangChain/LangGraph</h2><p>Claude Code 的自研架构不是所有人的最优解。它们的选择基于：</p><ol><li><strong>顶级工程团队</strong>：有能力自研高性能架构</li><li><strong>单一模型依赖</strong>：只需要支持 Anthropic API</li><li><strong>极致体验追求</strong>：CLI 交互需要零延迟感知</li><li><strong>深度定制需求</strong>：权限、压缩、钩子都需要精准控制</li></ol><p><strong>如果你不具备这些条件，LangChain/LangGraph 仍然是好选择</strong>：</p><div class="table-container"><table><thead><tr><th>你的情况</th><th>推荐</th></tr></thead><tbody><tr><td>小团队，快速验证想法</td><td>LangChain</td></tr><tr><td>需要支持多种模型</td><td>LangChain</td></tr><tr><td>需要可视化 Agent 流程</td><td>LangGraph</td></tr><tr><td>需要多 Agent 协作且不想自研</td><td>LangGraph 或 CrewAI</td></tr><tr><td>Web 应用，延迟不敏感</td><td>LangChain/LangGraph</td></tr><tr><td>企业级系统，有专业团队</td><td>LangGraph 或自研</td></tr></tbody></table></div><hr><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>Claude Code 不用 LangChain/LangGraph，不是傲慢，而是<strong>基于产品定位的理性选择</strong>：</p><figure class="highlight plain"><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">LangChain&#x2F;LangGraph 定位：通用框架</span><br><span class="line">  → 易用性优先</span><br><span class="line">  → 支持多种模型</span><br><span class="line">  → 抽象层统一</span><br><span class="line">  → 适合快速原型和通用应用</span><br><span class="line"></span><br><span class="line">Claude Code 定位：专用系统</span><br><span class="line">  → 性能优先</span><br><span class="line">  → 单一模型极致利用</span><br><span class="line">  → 零抽象流式处理</span><br><span class="line">  → 适合 CLI 交互和专业场景</span><br></pre></td></tr></table></figure><p><strong>Claude Code 用什么替代了 LangChain/LangGraph</strong>：</p><div class="table-container"><table><thead><tr><th>替代</th><th>技术</th></tr></thead><tbody><tr><td>ReAct 循环</td><td>Async Generator 状态机</td></tr><tr><td>工具定义</td><td>Tool 类型 + buildTool()</td></tr><tr><td>工具执行管道</td><td>七步执行管道</td></tr><tr><td>状态管理</td><td>State 对象 + 状态赋值</td></tr><tr><td>错误恢复</td><td>6 种内置恢复策略</td></tr><tr><td>扩展机制</td><td>钩子系统 + MCP 协议</td></tr></tbody></table></div><p><strong>核心启示</strong>：框架不是必须的，适合自己的才是最好的。LangChain/LangGraph 解决了”怎么快速搭建 Agent”的问题；Claude Code 解决了”怎么搭建极致体验的 Agent”的问题。</p><hr><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="/claude-code-architecture-overview/">Claude Code 源码揭秘：整体架构概览</a></li><li><a href="/claude-code-async-generator-state-machine/">打破 ReAct 迷思：Async Generator 状态机</a></li><li><a href="/claude-code-tool-system/">工具系统设计：从定义到执行的七步管道</a></li><li><a href="https://docs.anthropic.com/" target="_blank" rel="noopener">Anthropic API 文档</a></li><li><a href="https://python.langchain.com/docs/" target="_blank" rel="noopener">LangChain 官方文档</a></li><li><a href="https://langchain-ai.github.io/langgraph/" target="_blank" rel="noopener">LangGraph 官方文档</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;LangChain 和 LangGraph 是当下最流行的 Agent 开发框架，但 Anthropic 的 Claude Code 却完全不用它们。这不是傲慢，而是基于技术本质、产品体验、API 特性和工程可控性的四重考量。本文从多个层面剖析 C</summary>
      
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Architecture" scheme="https://donehub.github.io/tags/Architecture/"/>
    
  </entry>
  
  <entry>
    <title>Claude Code 源码揭秘：整体架构概览</title>
    <link href="https://donehub.github.io/2026/04/07/claude-code-architecture-overview/"/>
    <id>https://donehub.github.io/2026/04/07/claude-code-architecture-overview/</id>
    <published>2026-04-06T16:00:00.000Z</published>
    <updated>2026-04-13T15:36:45.228Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>2026年3月31日，Anthropic 的 Claude Code 源码意外泄露。这个全球最流行的 AI 编程助手，其背后的架构设计远超外界想象——它不是简单的 Prompt 包装，而是一个精心设计的流式状态机系统。本文将从整体架构视角，为你揭开 Claude Code 的神秘面纱。</p></blockquote><a id="more"></a><h2 id="导读：一个根本性的问题"><a href="#导读：一个根本性的问题" class="headerlink" title="导读：一个根本性的问题"></a>导读：一个根本性的问题</h2><p>在深入源码之前，我们需要回答一个根本性的问题：<strong>Claude Code 到底是什么？</strong></p><p>很多人的第一反应是：”不就是一个调用 Claude API 的 CLI 工具吗？加了一些 Prompt，让模型能读写文件、执行命令。”</p><p>这种理解大大低估了 Claude Code 的复杂度。当你打开源码，会发现：</p><ul><li><strong>1356 个 TypeScript 文件</strong></li><li><strong>48+ 个内置工具</strong>，每个工具都有完整的生命周期管理</li><li><strong>4 种 Agent 类型</strong>，支持复杂的协作编排</li><li><strong>4 级上下文压缩</strong>，实现”无限对话”</li><li><strong>6 种故障恢复策略</strong>，确保用户体验的稳定性</li><li><strong>3 级提示词缓存</strong>，大幅降低成本和延迟</li></ul><p>这不是一个”简单的 Prompt 工具”，而是一个<strong>深度集成的 AI 编程环境</strong>。</p><hr><h2 id="一、技术栈与项目结构"><a href="#一、技术栈与项目结构" class="headerlink" title="一、技术栈与项目结构"></a>一、技术栈与项目结构</h2><h3 id="1-1-核心技术栈"><a href="#1-1-核心技术栈" class="headerlink" title="1.1 核心技术栈"></a>1.1 核心技术栈</h3><p>Claude Code 的技术选型非常精简但高效：</p><div class="table-container"><table><thead><tr><th>类别</th><th>技术</th><th>说明</th></tr></thead><tbody><tr><td>运行时</td><td><a href="https://bun.sh" target="_blank" rel="noopener">Bun</a></td><td>高性能 JavaScript 运行时</td></tr><tr><td>语言</td><td>TypeScript</td><td>类型安全</td></tr><tr><td>终端 UI</td><td>React + <a href="https://github.com/vadimdemedes/ink" target="_blank" rel="noopener">Ink</a></td><td>React 语法写终端应用</td></tr><tr><td>CLI 解析</td><td>Commander.js</td><td>命令行参数处理</td></tr><tr><td>API</td><td>Anthropic SDK</td><td>原生 API 集成</td></tr><tr><td>协议</td><td>MCP, LSP</td><td>模型上下文协议、语言服务器协议</td></tr></tbody></table></div><h3 id="1-2-目录结构概览"><a href="#1-2-目录结构概览" class="headerlink" title="1.2 目录结构概览"></a>1.2 目录结构概览</h3><figure class="highlight plain"><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">src&#x2F;</span><br><span class="line">├── assistant&#x2F;          # 会话历史管理</span><br><span class="line">├── bootstrap&#x2F;          # 启动初始化、全局状态</span><br><span class="line">├── bridge&#x2F;             # 远程桥接系统（Bridge）</span><br><span class="line">├── buddy&#x2F;              # 交互伴侣（动画、观察者）</span><br><span class="line">├── cli&#x2F;                # CLI 入口、传输层</span><br><span class="line">├── commands&#x2F;           # 60+ 斜杠命令</span><br><span class="line">├── components&#x2F;         # React UI 组件</span><br><span class="line">├── constants&#x2F;          # 系统提示词、常量</span><br><span class="line">├── context&#x2F;            # 上下文管理</span><br><span class="line">├── coordinator&#x2F;        # 协调器模式</span><br><span class="line">├── entrypoints&#x2F;        # 入口文件（CLI、SDK）</span><br><span class="line">├── hooks&#x2F;              # React Hooks</span><br><span class="line">├── ink&#x2F;                # Ink 框架扩展</span><br><span class="line">├── memdir&#x2F;             # 记忆系统</span><br><span class="line">├── migrations&#x2F;         # 数据迁移</span><br><span class="line">├── native-ts&#x2F;          # 原生模块（Yoga 布局等）</span><br><span class="line">├── outputStyles&#x2F;       # 输出样式配置</span><br><span class="line">├── plugins&#x2F;            # 插件系统</span><br><span class="line">├── proactive&#x2F;          # 主动模式</span><br><span class="line">├── query&#x2F;              # 查询循环核心</span><br><span class="line">├── remote&#x2F;             # 远程会话管理</span><br><span class="line">├── schemas&#x2F;            # JSON Schema 定义</span><br><span class="line">├── screens&#x2F;            # 全屏页面</span><br><span class="line">├── server&#x2F;             # 内置服务器</span><br><span class="line">├── services&#x2F;           # 核心服务层</span><br><span class="line">├── skills&#x2F;             # Skills 系统</span><br><span class="line">├── state&#x2F;              # 状态管理</span><br><span class="line">├── tasks&#x2F;              # 后台任务系统</span><br><span class="line">├── tools&#x2F;              # 48+ 内置工具</span><br><span class="line">├── types&#x2F;              # TypeScript 类型定义</span><br><span class="line">├── utils&#x2F;              # 工具函数</span><br><span class="line">├── vendor&#x2F;             # 第三方集成（Computer Use）</span><br><span class="line">├── vim&#x2F;                # Vim 模式</span><br><span class="line">└── voice&#x2F;              # 语音模式</span><br></pre></td></tr></table></figure><p>这个结构体现了<strong>模块化设计</strong>的精髓：每个目录职责清晰，边界明确。</p><hr><h2 id="二、核心架构设计"><a href="#二、核心架构设计" class="headerlink" title="二、核心架构设计"></a>二、核心架构设计</h2><h3 id="2-1-整体架构图"><a href="#2-1-整体架构图" class="headerlink" title="2.1 整体架构图"></a>2.1 整体架构图</h3><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                      用户输入                                │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br><span class="line">                              │</span><br><span class="line">                              ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                   QueryEngine (入口)                         │</span><br><span class="line">│  - 构建系统提示词 (prompts.ts + context.ts + claudemd.ts)   │</span><br><span class="line">│  - 组装工具池 (tools.ts + MCP)                              │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br><span class="line">                              │</span><br><span class="line">                              ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│              query() AsyncGenerator 循环                    │</span><br><span class="line">│  ┌──────────────────────────────────────────────────────┐  │</span><br><span class="line">│  │ 阶段1: 消息压缩 (snip → micro → collapse → compact)  │  │</span><br><span class="line">│  │ 阶段2: 流式 API 调用 (callModel + StreamingToolExec) │  │</span><br><span class="line">│  │ 阶段3: 决策点 (继续 or 完成)                          │  │</span><br><span class="line">│  │ 阶段4: 工具编排 (并行只读 + 串行写入)                 │  │</span><br><span class="line">│  │ 阶段5: 状态更新 (state &#x3D; next → continue)            │  │</span><br><span class="line">│  └──────────────────────────────────────────────────────┘  │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br><span class="line">                              │</span><br><span class="line">          ┌───────────────────┼───────────────────┐</span><br><span class="line">          ▼                   ▼                   ▼</span><br><span class="line">┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐</span><br><span class="line">│    工具系统      │  │   多 Agent 系统  │  │   扩展生态      │</span><br><span class="line">│  48+ 内置工具    │  │  Subagent       │  │  Skills         │</span><br><span class="line">│  MCP 动态工具    │  │  Fork           │  │  Plugins        │</span><br><span class="line">│  三层过滤机制    │  │  Teammate       │  │  Hooks           │</span><br><span class="line">│  7步执行管道     │  │  Remote         │  │  MCP 协议        │</span><br><span class="line">└─────────────────┘  └─────────────────┘  └─────────────────┘</span><br></pre></td></tr></table></figure><h3 id="2-2-核心设计理念"><a href="#2-2-核心设计理念" class="headerlink" title="2.2 核心设计理念"></a>2.2 核心设计理念</h3><p>Claude Code 的架构体现了三个核心理念：</p><h4 id="理念一：流式优先（Streaming-First）"><a href="#理念一：流式优先（Streaming-First）" class="headerlink" title="理念一：流式优先（Streaming First）"></a>理念一：流式优先（Streaming First）</h4><p>整个架构围绕 <code>AsyncGenerator</code> 设计，一切都是流式的：</p><ul><li>模型响应是流式的</li><li>工具在模型生成过程中就开始执行</li><li>进度实时更新</li><li>压缩策略是渐进式的</li></ul><p>这意味着用户<strong>永远不需要等待</strong>——看到模型在思考、工具在执行、结果在产出。</p><h4 id="理念二：工具驱动（Tool-Driven）"><a href="#理念二：工具驱动（Tool-Driven）" class="headerlink" title="理念二：工具驱动（Tool-Driven）"></a>理念二：工具驱动（Tool-Driven）</h4><p>Claude Code 的哲学是：<strong>Agent 的能力等于其工具的能力</strong>。</p><ul><li>子代理生成？是一个工具（<code>AgentTool</code>）</li><li>团队管理？是一个工具（<code>TeamCreate</code>/<code>SendMessage</code>）</li><li>文件编辑？是一个工具（<code>FileEdit</code>）</li><li>技能执行？是一个工具（<code>SkillTool</code>）</li></ul><p>这意味着<strong>所有能力都通过统一的工具接口暴露</strong>，模型通过自然语言推理来决定使用哪个工具。不需要显式的编排逻辑——模型本身就是编排器。</p><h4 id="理念三：优雅降级（Graceful-Degradation）"><a href="#理念三：优雅降级（Graceful-Degradation）" class="headerlink" title="理念三：优雅降级（Graceful Degradation）"></a>理念三：优雅降级（Graceful Degradation）</h4><p>6 种恢复策略确保 Claude Code <strong>几乎不会因为技术问题中断用户的工作流</strong>：</p><ul><li>Token 超限？自动压缩</li><li>API 超时？自动重试</li><li>模型失败？降级到备用模型</li><li>工具失败？记录错误，继续对话</li></ul><hr><h2 id="三、核心模块解析"><a href="#三、核心模块解析" class="headerlink" title="三、核心模块解析"></a>三、核心模块解析</h2><h3 id="3-1-query-ts-Agent-的心脏"><a href="#3-1-query-ts-Agent-的心脏" class="headerlink" title="3.1 query.ts - Agent 的心脏"></a>3.1 query.ts - Agent 的心脏</h3><p><code>src/query.ts</code> 是整个 Agent 的核心，约 1730 行。它不是简单的”想-做-看”循环，而是一个<strong>流式状态机</strong>：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span>* <span class="title">query</span>(<span class="params">params: QueryParams</span>): <span class="title">AsyncGenerator</span>&lt;...&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> state: State = &#123;</span><br><span class="line">    messages,</span><br><span class="line">    toolUseContext,</span><br><span class="line">    autoCompactTracking,</span><br><span class="line">    maxOutputTokensRecoveryCount,</span><br><span class="line">    hasAttemptedReactiveCompact,</span><br><span class="line">    maxOutputTokensOverride,</span><br><span class="line">    pendingToolUseSummary,</span><br><span class="line">    stopHookActive,</span><br><span class="line">    turnCount,</span><br><span class="line">    transition,</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="comment">// 阶段1: 消息压缩</span></span><br><span class="line">    <span class="comment">// 阶段2: 流式 API 调用</span></span><br><span class="line">    <span class="comment">// 阶段3: 决策点</span></span><br><span class="line">    <span class="comment">// 阶段4: 工具执行</span></span><br><span class="line">    <span class="comment">// 阶段5: 状态更新</span></span><br><span class="line">    state = next  <span class="comment">// 通过赋值而非递归驱动循环</span></span><br><span class="line">    <span class="keyword">continue</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键设计</strong>：通过 <code>state = next</code> 赋值驱动循环，而非递归调用。这保证了：</p><ul><li><strong>内存稳定</strong>：不会因为深度递归导致栈溢出</li><li><strong>状态可追溯</strong>：每一轮的状态转换原因都被记录</li><li><strong>恢复可控</strong>：任何阶段的错误都可以通过修改 state 来恢复</li></ul><h3 id="3-2-Tool-ts-工具的定义"><a href="#3-2-Tool-ts-工具的定义" class="headerlink" title="3.2 Tool.ts - 工具的定义"></a>3.2 Tool.ts - 工具的定义</h3><p><code>src/Tool.ts</code> 定义了工具的完整接口（约 792 行）：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Tool&lt;Input, Output&gt; = &#123;</span><br><span class="line">  <span class="comment">// 身份</span></span><br><span class="line">  name: <span class="built_in">string</span></span><br><span class="line">  aliases?: <span class="built_in">string</span>[]        <span class="comment">// 向后兼容的旧名称</span></span><br><span class="line">  searchHint?: <span class="built_in">string</span>       <span class="comment">// ToolSearch 关键词匹配</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 能力声明</span></span><br><span class="line">  isEnabled(): <span class="built_in">boolean</span></span><br><span class="line">  isConcurrencySafe(input): <span class="built_in">boolean</span>   <span class="comment">// 是否可并行</span></span><br><span class="line">  isReadOnly(input): <span class="built_in">boolean</span>          <span class="comment">// 是否只读</span></span><br><span class="line">  isDestructive(input): <span class="built_in">boolean</span>       <span class="comment">// 是否破坏性</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 生命周期</span></span><br><span class="line">  validateInput(input, context)       <span class="comment">// 输入验证</span></span><br><span class="line">  checkPermissions(input, context)    <span class="comment">// 权限检查</span></span><br><span class="line">  call(input, context, ...)           <span class="comment">// 实际执行</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 输出与渲染</span></span><br><span class="line">  renderToolUseMessage(input)         <span class="comment">// 渲染调用信息</span></span><br><span class="line">  renderToolResultMessage(content)    <span class="comment">// 渲染结果信息</span></span><br><span class="line">  mapToolResultToToolResultBlockParam()  <span class="comment">// 映射为 API 格式</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 智能特性</span></span><br><span class="line">  inputSchema: Zod schema             <span class="comment">// Zod 类型验证</span></span><br><span class="line">  maxResultSizeChars: <span class="built_in">number</span>           <span class="comment">// 结果大小阈值</span></span><br><span class="line">  getToolUseSummary?(input): <span class="built_in">string</span>    <span class="comment">// 工具使用摘要</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种设计使得每个工具都是<strong>自描述、自验证、自渲染</strong>的——框架不需要了解工具的内部逻辑，只需调用标准接口。</p><h3 id="3-3-系统提示词组装"><a href="#3-3-系统提示词组装" class="headerlink" title="3.3 系统提示词组装"></a>3.3 系统提示词组装</h3><p><code>src/constants/prompts.ts</code>（约 577 行）实现了<strong>分层管道</strong>动态组装系统提示词：</p><figure class="highlight plain"><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">│                    静态可缓存区域                              │</span><br><span class="line">│  ┌───────────────────────────────────────────────────────┐  │</span><br><span class="line">│  │ 角色定义  │  系统规则  │  任务指导  │  工具说明  │  风格  │  │</span><br><span class="line">│  └───────────────────────────────────────────────────────┘  │</span><br><span class="line">├─────────────────────── 缓存边界 ────────────────────────────┤</span><br><span class="line">│                    动态可变区域                                │</span><br><span class="line">│  ┌───────────────────────────────────────────────────────┐  │</span><br><span class="line">│  │ 会话指引 │ 记忆系统 │ 环境信息 │ MCP 指令 │ Token 预算 │  │</span><br><span class="line">│  └───────────────────────────────────────────────────────┘  │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p><strong>缓存边界</strong>是一个关键设计：</p><ul><li><strong>边界之上</strong>：跨用户、跨组织通用的内容，使用 <code>scope: &#39;global&#39;</code> 缓存</li><li><strong>边界之下</strong>：用户/会话特定的内容，使用 <code>scope: &#39;ephemeral&#39;</code> 缓存</li></ul><hr><h2 id="四、与-LangChain-ReAct-的本质区别"><a href="#四、与-LangChain-ReAct-的本质区别" class="headerlink" title="四、与 LangChain/ReAct 的本质区别"></a>四、与 LangChain/ReAct 的本质区别</h2><p>这是理解 Claude Code 架构的关键。大多数人认为 Claude Code 使用的是经典的 <strong>ReAct</strong> 模式：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">思考(Thought) → 行动(Action) → 观察(Observation) → 思考 → ...</span><br></pre></td></tr></table></figure><p><strong>实际上，Claude Code 没有采用这个模式。</strong></p><h3 id="4-1-架构范式对比"><a href="#4-1-架构范式对比" class="headerlink" title="4.1 架构范式对比"></a>4.1 架构范式对比</h3><div class="table-container"><table><thead><tr><th>维度</th><th>LangChain</th><th>Claude Code</th></tr></thead><tbody><tr><td><strong>核心模式</strong></td><td>ReAct（Think→Act→Observe）</td><td>Async Generator 状态机</td></tr><tr><td><strong>执行模型</strong></td><td>同步阻塞</td><td>流式非阻塞</td></tr><tr><td><strong>工具执行</strong></td><td>等待模型完整响应后执行</td><td>流式传输中即时执行</td></tr><tr><td><strong>状态管理</strong></td><td>外部 Memory 对象</td><td>内置状态赋值 + 循环</td></tr><tr><td><strong>错误恢复</strong></td><td>需要手动编排</td><td>6 种内置恢复策略</td></tr><tr><td><strong>上下文压缩</strong></td><td>简单截断或摘要</td><td>四级渐进式压缩</td></tr><tr><td><strong>多 Agent</strong></td><td>Chain/Graph 显式编排</td><td>统一工具接口 + 状态机</td></tr><tr><td><strong>扩展机制</strong></td><td>Python 类继承</td><td>技能 + 插件 + 钩子 + MCP</td></tr><tr><td><strong>缓存策略</strong></td><td>无</td><td>全局/会话/按轮三级缓存</td></tr></tbody></table></div><h3 id="4-2-为什么不用-ReAct？"><a href="#4-2-为什么不用-ReAct？" class="headerlink" title="4.2 为什么不用 ReAct？"></a>4.2 为什么不用 ReAct？</h3><p>ReAct 模式有几个固有限制：</p><ol><li><strong>串行瓶颈</strong>：每一步必须等待完整的”思考→行动→观察”循环</li><li><strong>无流式能力</strong>：模型生成完整响应后才能开始执行工具</li><li><strong>恢复困难</strong>：没有统一的状态表示，难以实现自动恢复</li><li><strong>缓存不友好</strong>：每次循环的 prompt 结构变化大，难以利用缓存</li></ol><p>Claude Code 的 Async Generator 模式解决了所有这些问题：</p><ul><li><strong>流式执行</strong>：工具在模型生成过程中就开始运行</li><li><strong>状态可控</strong>：<code>State</code> 对象包含所有需要的信息，恢复只需修改状态</li><li><strong>缓存优化</strong>：静态提示词全局缓存，动态部分最小化</li><li><strong>并行能力</strong>：只读工具自动并行，写入工具串行保序</li></ul><hr><h2 id="五、关键源文件索引"><a href="#五、关键源文件索引" class="headerlink" title="五、关键源文件索引"></a>五、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>组件</th><th>文件路径</th><th>行数</th><th>说明</th></tr></thead><tbody><tr><td>核心循环</td><td><code>src/query.ts</code></td><td>~1730</td><td>Agent 主循环</td></tr><tr><td>查询引擎</td><td><code>src/QueryEngine.ts</code></td><td>~687</td><td>高层封装</td></tr><tr><td>工具定义</td><td><code>src/Tool.ts</code></td><td>~792</td><td>Tool 类型系统</td></tr><tr><td>工具注册</td><td><code>src/tools.ts</code></td><td>~389</td><td>工具发现和注册</td></tr><tr><td>系统提示词</td><td><code>src/constants/prompts.ts</code></td><td>~577</td><td>提示词组装</td></tr><tr><td>上下文管理</td><td><code>src/context.ts</code></td><td>~300</td><td>系统/用户上下文</td></tr><tr><td>Agent 生成</td><td><code>src/tools/AgentTool/AgentTool.tsx</code></td><td>~600</td><td>Agent 工具入口</td></tr><tr><td>技能系统</td><td><code>src/skills/bundledSkills.ts</code></td><td>~300</td><td>技能注册与管理</td></tr><tr><td>权限系统</td><td><code>src/utils/permissions/permissions.ts</code></td><td>~500</td><td>权限检查</td></tr><tr><td>状态管理</td><td><code>src/state/AppStateStore.ts</code></td><td>~400</td><td>全局状态</td></tr></tbody></table></div><hr><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><p>Claude Code 的架构设计体现了<strong>简洁与强大的平衡</strong>：</p><ol><li><strong>一个循环</strong>：<code>while (true)</code> 驱动的状态机</li><li><strong>一个状态</strong>：<code>State</code> 对象承载所有上下文</li><li><strong>一个接口</strong>：<code>Tool</code> 类型统一所有能力</li></ol><p>没有 Agent → AgentExecutor → Chain → Memory → Callback 的嵌套抽象层，这使得代码<strong>易于理解、调试和扩展</strong>。</p><p>在接下来的系列文章中，我们将深入每个模块，揭示更多设计细节。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>下一篇：<a href="/claude-code-async-generator-state-machine/">打破 ReAct 迷思：Async Generator 状态机</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;2026年3月31日，Anthropic 的 Claude Code 源码意外泄露。这个全球最流行的 AI 编程助手，其背后的架构设计远超外界想象——它不是简单的 Prompt 包装，而是一个精心设计的流式状态机系统。本文将从整体架构视角，为你揭开 Claude Code 的神秘面纱。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Architecture" scheme="https://donehub.github.io/tags/Architecture/"/>
    
  </entry>
  
  <entry>
    <title>打破 ReAct 迷思：Async Generator 状态机</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-async-generator-state-machine/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-async-generator-state-machine/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:21.137Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>当大多数人谈论 AI Agent 架构时，ReAct（Reasoning + Acting）几乎是唯一的答案。但 Claude Code 选择了一条不同的路——Async Generator 状态机。这个设计决策背后有着深刻的思考，它解决了 ReAct 的根本性限制，为流式交互和优雅恢复奠定了基础。</p></blockquote><a id="more"></a><h2 id="导读：ReAct-的困境"><a href="#导读：ReAct-的困境" class="headerlink" title="导读：ReAct 的困境"></a>导读：ReAct 的困境</h2><p>如果你熟悉 AI Agent 开发，一定对 ReAct 模式不陌生：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">思考(Thought) → 行动(Action) → 观察(Observation) → 思考 → ...</span><br></pre></td></tr></table></figure><p>这个模式直观且易于理解，已经成为 LangChain、AutoGPT 等框架的标配。但当你深入使用时，会发现它有几个难以回避的问题：</p><p><strong>问题一：串行瓶颈</strong><br>每一轮”思考”必须等待模型生成完整响应后才能开始执行工具。用户盯着屏幕等待，体验割裂。</p><p><strong>问题二：无法利用流式传输</strong><br>模型支持流式输出，但 ReAct 模式下，流式传输的价值被大大削弱——你必须等待完整的 action 才能执行。</p><p><strong>问题三：恢复困难</strong><br>当 API 超时、Token 溢出或工具失败时，ReAct 没有统一的状态表示来支持自动恢复。</p><p>Claude Code 的解决方案是：<strong>放弃 ReAct，使用 Async Generator 状态机</strong>。</p><hr><h2 id="一、状态机的核心设计"><a href="#一、状态机的核心设计" class="headerlink" title="一、状态机的核心设计"></a>一、状态机的核心设计</h2><h3 id="1-1-State-数据结构"><a href="#1-1-State-数据结构" class="headerlink" title="1.1 State 数据结构"></a>1.1 State 数据结构</h3><p><code>src/query.ts</code> 定义了状态机的核心状态（第 204-217 行）：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> State = &#123;</span><br><span class="line">  messages: Message[]                    <span class="comment">// 完整对话历史</span></span><br><span class="line">  toolUseContext: ToolUseContext          <span class="comment">// 工具执行上下文</span></span><br><span class="line">  autoCompactTracking: AutoCompactTracking  <span class="comment">// 自动压缩追踪</span></span><br><span class="line">  maxOutputTokensRecoveryCount: <span class="built_in">number</span>   <span class="comment">// 输出恢复计数</span></span><br><span class="line">  hasAttemptedReactiveCompact: <span class="built_in">boolean</span>   <span class="comment">// 是否已尝试反应式压缩</span></span><br><span class="line">  maxOutputTokensOverride: <span class="built_in">number</span>        <span class="comment">// 输出 token 覆盖值</span></span><br><span class="line">  pendingToolUseSummary: <span class="built_in">Promise</span>&lt;...&gt;    <span class="comment">// 待处理的工具摘要</span></span><br><span class="line">  stopHookActive: <span class="built_in">boolean</span>               <span class="comment">// 停止钩子状态</span></span><br><span class="line">  turnCount: <span class="built_in">number</span>                      <span class="comment">// 对话轮数</span></span><br><span class="line">  transition: Continue | <span class="literal">undefined</span>       <span class="comment">// 状态转换原因</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键洞察</strong>：<code>transition</code> 字段记录了每一轮状态转换的原因。这使得调试和测试变得非常清晰——你可以精确知道为什么 Agent 从一个状态跳转到另一个状态。</p><h3 id="1-2-核心循环：五个阶段"><a href="#1-2-核心循环：五个阶段" class="headerlink" title="1.2 核心循环：五个阶段"></a>1.2 核心循环：五个阶段</h3><p>整个 <code>while (true)</code> 循环（第 307-1728 行）分为五个阶段：</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                      while (true)                           │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  阶段1: 消息准备与智能压缩（第 365-543 行）                  │</span><br><span class="line">│    ├─ Snip 压缩：智能删除旧消息中的冗余 token               │</span><br><span class="line">│    ├─ Micro 压缩：修改已缓存消息的内容                      │</span><br><span class="line">│    ├─ 上下文折叠：分阶段摘要历史消息                        │</span><br><span class="line">│    └─ Auto Compact：通过 Claude 生成完整摘要                │</span><br><span class="line">│                                                             │</span><br><span class="line">│  阶段2: 流式 API 调用（第 652-954 行）                       │</span><br><span class="line">│    ├─ 构建 API 请求（含 CacheSafeParams）                   │</span><br><span class="line">│    ├─ 流式处理响应                                          │</span><br><span class="line">│    ├─ StreamingToolExecutor 即时执行工具                    │</span><br><span class="line">│    └─ 累积 usage 指标                                       │</span><br><span class="line">│                                                             │</span><br><span class="line">│  阶段3: 决策点（第 1062-1358 行）                            │</span><br><span class="line">│    ├─ 有工具调用？→ 继续循环（阶段 4）                      │</span><br><span class="line">│    └─ 无工具调用？→ 运行 Stop 钩子 → 返回结果               │</span><br><span class="line">│                                                             │</span><br><span class="line">│  阶段4: 工具编排执行（第 1363-1409 行）                      │</span><br><span class="line">│    ├─ 分区：只读 vs 写入                                    │</span><br><span class="line">│    ├─ 只读工具 → 并行执行（最多 10 个并发）                 │</span><br><span class="line">│    └─ 写入工具 → 串行执行（防止竞态条件）                   │</span><br><span class="line">│                                                             │</span><br><span class="line">│  阶段5: 状态更新与循环（第 1704-1728 行）                    │</span><br><span class="line">│    └─ state &#x3D; next → continue                              │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="1-3-状态更新的优雅之处"><a href="#1-3-状态更新的优雅之处" class="headerlink" title="1.3 状态更新的优雅之处"></a>1.3 状态更新的优雅之处</h3><p>这是整个设计最优雅的部分——<strong>通过状态赋值而非递归调用驱动循环</strong>：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/query.ts:1715-1728</span></span><br><span class="line"><span class="keyword">const</span> next: State = &#123;</span><br><span class="line">  messages: [...messagesForQuery, ...assistantMessages, ...toolResults],</span><br><span class="line">  toolUseContext: toolUseContextWithQueryTracking,</span><br><span class="line">  autoCompactTracking: tracking,</span><br><span class="line">  turnCount: nextTurnCount,</span><br><span class="line">  transition: &#123; reason: <span class="string">'next_turn'</span> &#125;,</span><br><span class="line">&#125;</span><br><span class="line">state = next</span><br><span class="line"><span class="comment">// 回到 while(true) 循环顶部</span></span><br></pre></td></tr></table></figure><p>没有递归，没有回调地狱，只是简单的 <code>state = next</code> 然后 <code>continue</code>。</p><p><strong>为什么这很重要？</strong></p><ol><li><strong>内存稳定</strong>：不会因为深度递归导致栈溢出</li><li><strong>状态可追溯</strong>：每一轮的状态转换原因都被记录</li><li><strong>恢复可控</strong>：任何阶段的错误都可以通过修改 state 来恢复</li></ol><hr><h2 id="二、流式优先的执行模型"><a href="#二、流式优先的执行模型" class="headerlink" title="二、流式优先的执行模型"></a>二、流式优先的执行模型</h2><h3 id="2-1-StreamingToolExecutor-的设计"><a href="#2-1-StreamingToolExecutor-的设计" class="headerlink" title="2.1 StreamingToolExecutor 的设计"></a>2.1 StreamingToolExecutor 的设计</h3><p>Claude Code 的一个关键创新是 <code>StreamingToolExecutor</code>——当模型生成 <code>tool_use</code> 块时，工具<strong>立即</strong>开始运行，而不是等模型生成完整响应。</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/tools/StreamingToolExecutor.ts</span></span><br><span class="line"><span class="keyword">class</span> StreamingToolExecutor &#123;</span><br><span class="line">  <span class="keyword">async</span> *processToolUseBlocks(toolUseBlocks: ToolUseBlock[]): AsyncGenerator &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> block of toolUseBlocks) &#123;</span><br><span class="line">      <span class="comment">// 在流式传输过程中就开始执行</span></span><br><span class="line">      <span class="keyword">const</span> result = <span class="keyword">await</span> <span class="keyword">this</span>.executeTool(block)</span><br><span class="line">      <span class="keyword">yield</span> result</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>对比 ReAct</strong>：</p><div class="table-container"><table><thead><tr><th>模式</th><th>工具执行时机</th><th>用户体验</th></tr></thead><tbody><tr><td>ReAct</td><td>等待模型完整响应</td><td>割裂，需要等待</td></tr><tr><td>Async Generator</td><td>流式传输中即时执行</td><td>流畅，实时反馈</td></tr></tbody></table></div><h3 id="2-2-工具编排策略"><a href="#2-2-工具编排策略" class="headerlink" title="2.2 工具编排策略"></a>2.2 工具编排策略</h3><p>工具执行不是简单的逐个运行，而是有精心设计的<strong>编排策略</strong>（<code>src/services/tools/toolOrchestration.ts</code>）：</p><figure class="highlight plain"><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><br><span class="line">  │</span><br><span class="line">  ├─ 分区：只读 vs 写入</span><br><span class="line">  │</span><br><span class="line">  ├─ 只读工具 ──→ 并行执行（最多 10 个并发）</span><br><span class="line">  │   ├─ Read</span><br><span class="line">  │   ├─ Grep</span><br><span class="line">  │   ├─ Glob</span><br><span class="line">  │   └─ WebFetch</span><br><span class="line">  │</span><br><span class="line">  └─ 写入工具 ──→ 串行执行（防止竞态条件）</span><br><span class="line">      ├─ FileEdit</span><br><span class="line">      ├─ FileWrite</span><br><span class="line">      └─ Bash (非只读)</span><br></pre></td></tr></table></figure><p><strong>设计原理</strong>：</p><ul><li>只读工具没有副作用，可以安全并行</li><li>写入工具可能相互影响，必须串行保证顺序</li><li>10 个并发限制防止资源耗尽</li></ul><hr><h2 id="三、六种故障恢复策略"><a href="#三、六种故障恢复策略" class="headerlink" title="三、六种故障恢复策略"></a>三、六种故障恢复策略</h2><p>这是 Claude Code 最精妙的设计之一。核心循环内置了 <strong>6 种恢复策略</strong>，确保用户体验的稳定性：</p><h3 id="3-1-恢复策略详解"><a href="#3-1-恢复策略详解" class="headerlink" title="3.1 恢复策略详解"></a>3.1 恢复策略详解</h3><div class="table-container"><table><thead><tr><th>恢复策略</th><th>触发条件</th><th>恢复方式</th></tr></thead><tbody><tr><td><code>collapse_drain_retry</code></td><td>prompt 过长</td><td>排空已暂存的上下文折叠，重试</td></tr><tr><td><code>reactive_compact_retry</code></td><td>仍然过长</td><td>通过 Claude 生成摘要，重试</td></tr><tr><td><code>max_output_tokens_escalate</code></td><td>触及 8k 默认限制</td><td>升级到 64k 限制重试</td></tr><tr><td><code>max_output_tokens_recovery</code></td><td>触及任何限制</td><td>注入”继续”提示，重试（最多 3 次）</td></tr><tr><td><code>stop_hook_blocking</code></td><td>Stop 钩子阻塞</td><td>将阻塞错误注入上下文，重试</td></tr><tr><td><code>token_budget_continuation</code></td><td>预算尚余</td><td>注入预算提示，继续执行</td></tr></tbody></table></div><h3 id="3-2-恢复代码示例"><a href="#3-2-恢复代码示例" class="headerlink" title="3.2 恢复代码示例"></a>3.2 恢复代码示例</h3><p>每种恢复都通过修改 <code>state</code> 实现：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 例：prompt 过长恢复</span></span><br><span class="line"><span class="keyword">if</span> (error.type === <span class="string">'prompt_too_long'</span>) &#123;</span><br><span class="line">  <span class="comment">// 排空所有暂存的折叠</span></span><br><span class="line">  <span class="keyword">const</span> compacted = drainStagedCollapses(state.messages)</span><br><span class="line">  state = &#123; </span><br><span class="line">    ...state, </span><br><span class="line">    messages: compacted, </span><br><span class="line">    transition: &#123; reason: <span class="string">'collapse_drain_retry'</span> &#125; </span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">continue</span>  <span class="comment">// 回到循环顶部重试</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 例：max_output_tokens 恢复</span></span><br><span class="line"><span class="keyword">if</span> (error.type === <span class="string">'max_output_tokens'</span>) &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    ...state,</span><br><span class="line">    maxOutputTokensRecoveryCount: state.maxOutputTokensRecoveryCount + <span class="number">1</span>,</span><br><span class="line">    transition: &#123; reason: <span class="string">'max_output_tokens_recovery'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 注入"继续"提示</span></span><br><span class="line">  messages.push(createUserMessage(&#123; content: <span class="string">'Please continue.'</span> &#125;))</span><br><span class="line">  <span class="keyword">continue</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-为什么这些恢复策略重要？"><a href="#3-3-为什么这些恢复策略重要？" class="headerlink" title="3.3 为什么这些恢复策略重要？"></a>3.3 为什么这些恢复策略重要？</h3><p>想象一个场景：用户正在让 Claude 修改一个大型代码库，对话已经进行了 50 轮，积累了大量上下文。突然：</p><ol><li><strong>Token 溢出</strong> → 自动压缩，用户无感知</li><li><strong>API 超时</strong> → 自动重试，用户无感知</li><li><strong>模型达到输出限制</strong> → 注入”继续”，自动续写</li></ol><p>用户几乎感觉不到任何中断。这是 Claude Code 能提供流畅体验的关键。</p><hr><h2 id="四、与-LangChain-Agent-的具体差异"><a href="#四、与-LangChain-Agent-的具体差异" class="headerlink" title="四、与 LangChain Agent 的具体差异"></a>四、与 LangChain Agent 的具体差异</h2><h3 id="4-1-代码对比"><a href="#4-1-代码对比" class="headerlink" title="4.1 代码对比"></a>4.1 代码对比</h3><p><strong>LangChain Agent（简化）：</strong><br><figure class="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></pre></td><td class="code"><pre><span class="line">agent = initialize_agent(tools, llm, agent=<span class="string">"zero-shot-react-description"</span>)</span><br><span class="line">result = agent.run(<span class="string">"do something"</span>)</span><br><span class="line"><span class="comment"># 内部：LLM → parse → tool → LLM → parse → tool → ... → final answer</span></span><br><span class="line"><span class="comment"># 每一步都是独立的 LLM 调用</span></span><br></pre></td></tr></table></figure></p><p><strong>Claude Code Agent（简化）：</strong><br><figure class="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="keyword">for</span> <span class="keyword">await</span> (<span class="keyword">const</span> msg of query(&#123; messages, tools, systemPrompt &#125;)) &#123;</span><br><span class="line">  <span class="keyword">yield</span> msg  <span class="comment">// 实时产出消息</span></span><br><span class="line">  <span class="comment">// 内部：流式 LLM → 流式工具执行 → 状态更新 → 继续</span></span><br><span class="line">  <span class="comment">// 单次 API 调用可以触发多个工具，工具在流式中执行</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h3 id="4-2-关键差异"><a href="#4-2-关键差异" class="headerlink" title="4.2 关键差异"></a>4.2 关键差异</h3><div class="table-container"><table><thead><tr><th>维度</th><th>LangChain</th><th>Claude Code</th></tr></thead><tbody><tr><td>每一轮</td><td>独立的 LLM 调用</td><td>流式 API 调用</td></tr><tr><td>工具解析</td><td>OutputParser 解析文本</td><td>原生 <code>tool_use</code> 块</td></tr><tr><td>执行方式</td><td>等待完整响应</td><td>流式即时执行</td></tr><tr><td>错误处理</td><td>手动 try-catch</td><td>内置 6 种恢复</td></tr><tr><td>并行工具</td><td>需要显式编排</td><td>自动分区并行</td></tr></tbody></table></div><h3 id="4-3-与-LangGraph-的对比"><a href="#4-3-与-LangGraph-的对比" class="headerlink" title="4.3 与 LangGraph 的对比"></a>4.3 与 LangGraph 的对比</h3><p>LangGraph 是 LangChain 的升级版，引入了图结构：</p><div class="table-container"><table><thead><tr><th>维度</th><th>LangGraph</th><th>Claude Code</th></tr></thead><tbody><tr><td><strong>状态流转</strong></td><td>显式图节点 + 边</td><td>隐式状态机（while + continue）</td></tr><tr><td><strong>可视化</strong></td><td>可导出为图</td><td>状态转换原因可追溯</td></tr><tr><td><strong>持久化</strong></td><td>Checkpoint + State</td><td>文件系统 + 消息历史</td></tr><tr><td><strong>人机交互</strong></td><td>interrupt_before/after</td><td>权限系统 + 钩子</td></tr><tr><td><strong>多 Agent</strong></td><td>需要显式编排</td><td>AgentTool 统一接口</td></tr></tbody></table></div><p>Claude Code 的优势在于<strong>简单性</strong>——不需要定义图结构，一个 while 循环就能处理所有情况。</p><hr><h2 id="五、设计原则总结"><a href="#五、设计原则总结" class="headerlink" title="五、设计原则总结"></a>五、设计原则总结</h2><p>从源码分析中，我们可以总结出以下核心设计原则：</p><h3 id="5-1-最小抽象原则"><a href="#5-1-最小抽象原则" class="headerlink" title="5.1 最小抽象原则"></a>5.1 最小抽象原则</h3><p>与 LangChain 的”万物皆抽象”不同，Claude Code 的核心只有：</p><ul><li><strong>一个循环</strong>（<code>while (true)</code> in <code>query()</code>）</li><li><strong>一个状态</strong>（<code>State</code> 对象）</li><li><strong>一个接口</strong>（<code>Tool</code> 类型）</li></ul><p>没有 Agent → AgentExecutor → Chain → Memory → Callback 的嵌套抽象层。</p><h3 id="5-2-原生-API-集成"><a href="#5-2-原生-API-集成" class="headerlink" title="5.2 原生 API 集成"></a>5.2 原生 API 集成</h3><p>Claude Code 直接使用 Anthropic API 的原生能力：</p><ul><li><strong>原生工具调用</strong>：无需 OutputParser，直接使用 <code>tool_use</code> 块</li><li><strong>原生流式传输</strong>：无需包装层，直接消费 SSE 流</li><li><strong>原生缓存</strong>：利用 API 的 prompt caching 特性</li><li><strong>原生思维链</strong>：直接使用 extended thinking</li></ul><p>这避免了”框架税”——LangChain 等框架在 LLM 和开发者之间增加的抽象层。</p><h3 id="5-3-可观测性设计"><a href="#5-3-可观测性设计" class="headerlink" title="5.3 可观测性设计"></a>5.3 可观测性设计</h3><p><code>transition</code> 字段的设计体现了对可观测性的重视：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Continue = &#123;</span><br><span class="line">  reason: <span class="string">'next_turn'</span> </span><br><span class="line">    | <span class="string">'collapse_drain_retry'</span></span><br><span class="line">    | <span class="string">'reactive_compact_retry'</span></span><br><span class="line">    | <span class="string">'max_output_tokens_recovery'</span></span><br><span class="line">    | <span class="string">'stop_hook_blocking'</span></span><br><span class="line">    | <span class="string">'token_budget_continuation'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>每一轮循环都知道自己为什么继续，这对于调试和测试至关重要。</p><hr><h2 id="六、关键源文件索引"><a href="#六、关键源文件索引" class="headerlink" title="六、关键源文件索引"></a>六、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>行数</th><th>职责</th></tr></thead><tbody><tr><td><code>src/query.ts</code></td><td>~1730</td><td>Agent 主循环，状态机核心</td></tr><tr><td><code>src/QueryEngine.ts</code></td><td>~687</td><td>高层封装，对外 API</td></tr><tr><td><code>src/services/tools/StreamingToolExecutor.ts</code></td><td>~200</td><td>流式工具执行器</td></tr><tr><td><code>src/services/tools/toolOrchestration.ts</code></td><td>~150</td><td>工具编排策略</td></tr><tr><td><code>src/query/transitions.ts</code></td><td>~50</td><td>状态转换类型定义</td></tr><tr><td><code>src/query/tokenBudget.ts</code></td><td>~100</td><td>Token 预算管理</td></tr><tr><td><code>src/query/stopHooks.ts</code></td><td>~200</td><td>Stop 钩子处理</td></tr></tbody></table></div><hr><h2 id="七、总结"><a href="#七、总结" class="headerlink" title="七、总结"></a>七、总结</h2><p>Claude Code 的 Async Generator 状态机设计解决了 ReAct 模式的根本性限制：</p><ol><li><strong>流式执行</strong>：工具在模型生成过程中就开始运行</li><li><strong>状态可控</strong>：统一的 <code>State</code> 对象，恢复只需修改状态</li><li><strong>自动恢复</strong>：6 种内置策略确保用户体验稳定</li><li><strong>缓存友好</strong>：静态部分全局缓存，动态部分最小化</li><li><strong>并行能力</strong>：只读工具自动并行，写入工具串行保序</li></ol><p>这个设计选择体现了 Claude Code 团队对产品体验的深刻理解：<strong>用户不应该等待，也不应该因为技术问题中断</strong>。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-architecture-overview/">Claude Code 源码揭秘：整体架构概览</a></li><li>下一篇：<a href="/claude-code-tool-system/">工具系统设计：从定义到执行的七步管道</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;当大多数人谈论 AI Agent 架构时，ReAct（Reasoning + Acting）几乎是唯一的答案。但 Claude Code 选择了一条不同的路——Async Generator 状态机。这个设计决策背后有着深刻的思考，它解决了 ReAct 的根本性限制，为流式交互和优雅恢复奠定了基础。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="State Machine" scheme="https://donehub.github.io/tags/State-Machine/"/>
    
  </entry>
  
  <entry>
    <title>Channel 系统：IM 远程控制 Agent</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-channel-system/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-channel-system/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:38.708Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>你在手机上打开 Telegram，给 Claude Code 发一条消息，它就开始在你的电脑上工作——这就是 Channel 系统。它打破了 AI 编程助手只能在终端中交互的限制，实现了真正的远程控制。更精妙的是，它有六层访问控制和权限中继机制，确保安全性。</p></blockquote><a id="more"></a><h2 id="导读：打破终端的边界"><a href="#导读：打破终端的边界" class="headerlink" title="导读：打破终端的边界"></a>导读：打破终端的边界</h2><p>传统的 AI 编程助手只能在终端中交互。如果你想让它工作，你必须坐在电脑前。</p><p>Channel 系统改变了这一切：</p><ul><li>你在手机上通过 Telegram 发消息</li><li>Claude Code 收到消息，理解意图，执行操作</li><li>结果回复到你的 Telegram 聊天窗口</li></ul><p><strong>这不是简单的消息转发</strong>——这是一个完整的远程控制系统：</p><ul><li>六层访问控制确保只有授权的 Channel 能推送消息</li><li>权限中继让你在手机上也能审批危险操作</li><li>MCP 协议让任何 IM 平台都能集成</li></ul><hr><h2 id="一、Channel-的本质"><a href="#一、Channel-的本质" class="headerlink" title="一、Channel 的本质"></a>一、Channel 的本质</h2><h3 id="1-1-Channel-就是一个-MCP-Server"><a href="#1-1-Channel-就是一个-MCP-Server" class="headerlink" title="1.1 Channel 就是一个 MCP Server"></a>1.1 Channel 就是一个 MCP Server</h3><p>从技术角度看，一个 Channel 就是一个特殊的 <strong>MCP Server</strong>：</p><figure class="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="comment">// Channel 的能力声明</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">"experimental"</span>: &#123;</span><br><span class="line">    <span class="string">"claude/channel"</span>: &#123;&#125;           <span class="comment">// 声明 Channel 能力</span></span><br><span class="line">    <span class="string">"claude/channel/permission"</span>: &#123;&#125;  <span class="comment">// 声明权限中继能力（可选）</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-2-Channel-的两种形态"><a href="#1-2-Channel-的两种形态" class="headerlink" title="1.2 Channel 的两种形态"></a>1.2 Channel 的两种形态</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> ChannelEntry =</span><br><span class="line">  | &#123; kind: <span class="string">'plugin'</span>; name: <span class="built_in">string</span>; marketplace: <span class="built_in">string</span>; dev?: <span class="built_in">boolean</span> &#125;</span><br><span class="line">  | &#123; kind: <span class="string">'server'</span>; name: <span class="built_in">string</span>; dev?: <span class="built_in">boolean</span> &#125;</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th>形态</th><th>说明</th><th>安全性</th></tr></thead><tbody><tr><td><strong>plugin</strong></td><td>来自 marketplace 的验证插件</td><td>需要白名单</td></tr><tr><td><strong>server</strong></td><td>直接指定的 MCP 服务器名称</td><td>需要 dev 旁路</td></tr></tbody></table></div><hr><h2 id="二、消息流转全链路"><a href="#二、消息流转全链路" class="headerlink" title="二、消息流转全链路"></a>二、消息流转全链路</h2><h3 id="2-1-入站流程（IM-→-Agent）"><a href="#2-1-入站流程（IM-→-Agent）" class="headerlink" title="2.1 入站流程（IM → Agent）"></a>2.1 入站流程（IM → Agent）</h3><figure class="highlight plain"><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">│                    入站消息流程                              │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  Telegram&#x2F;Feishu&#x2F;Discord                                    │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Channel Plugin（MCP Server）                               │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  notifications&#x2F;claude&#x2F;channel &#123; content, meta &#125;             │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  useManageMCPConnections → registerNotificationHandler      │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  wrapChannelMessage() → &lt;channel source&#x3D;&quot;...&quot; user&#x3D;&quot;...&quot;&gt;  │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  enqueue(&#123; priority: &#39;next&#39;, isMeta: true &#125;)                │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  SleepTool 每 ~1s 轮询 hasCommandsInQueue()                │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Model 看到 &lt;channel&gt; 标签，理解消息来源                      │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="2-2-出站流程（Agent-→-IM）"><a href="#2-2-出站流程（Agent-→-IM）" class="headerlink" title="2.2 出站流程（Agent → IM）"></a>2.2 出站流程（Agent → IM）</h3><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    出站消息流程                              │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  Model 决定使用哪个工具回复                                   │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  callTool() → Channel 的 MCP 工具                           │</span><br><span class="line">│  （reply &#x2F; react &#x2F; edit_message &#x2F; download_attachment）      │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  MCP 协议调用 Channel Server                                │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Channel Server 发送消息到 IM 平台                           │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Telegram&#x2F;Feishu&#x2F;Discord 用户收到回复                        │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="2-3-消息封装格式"><a href="#2-3-消息封装格式" class="headerlink" title="2.3 消息封装格式"></a>2.3 消息封装格式</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">channel</span> <span class="attr">source</span>=<span class="string">"plugin:telegram:tg"</span> <span class="attr">user</span>=<span class="string">"alice"</span> <span class="attr">chat_id</span>=<span class="string">"123456"</span>&gt;</span></span><br><span class="line">帮我看看 main.ts 有什么问题</span><br><span class="line"><span class="tag">&lt;/<span class="name">channel</span>&gt;</span></span><br></pre></td></tr></table></figure><p>模型看到这个标签后，就知道消息来自 Telegram 的用户 alice，并会使用 Telegram 的 <code>reply</code> 工具回复。</p><hr><h2 id="三、六层访问控制"><a href="#三、六层访问控制" class="headerlink" title="三、六层访问控制"></a>三、六层访问控制</h2><h3 id="3-1-Gate-函数"><a href="#3-1-Gate-函数" class="headerlink" title="3.1 Gate 函数"></a>3.1 Gate 函数</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/mcp/channelNotification.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">gateChannelServer</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  serverName: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  capabilities: ServerCapabilities | <span class="literal">undefined</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  pluginSource: <span class="built_in">string</span> | <span class="literal">undefined</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">ChannelGateResult</span>  // </span>&#123; action: <span class="string">'register'</span> &#125; | &#123; action: <span class="string">'skip'</span>, kind, reason &#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-六层关卡详解"><a href="#3-2-六层关卡详解" class="headerlink" title="3.2 六层关卡详解"></a>3.2 六层关卡详解</h3><figure class="highlight plain"><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">│                    六层访问控制                              │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 1: 能力声明（Capability）                             │</span><br><span class="line">│    └─ MCP Server 必须声明 claude&#x2F;channel 能力              │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 2: 运行时开关（Runtime Gate）                         │</span><br><span class="line">│    └─ tengu_harbor Feature Flag 必须开启                   │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 3: OAuth 认证（Auth）                                 │</span><br><span class="line">│    └─ 必须通过 OAuth 认证（API Key 用户被阻止）             │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 4: 组织策略（Policy）                                 │</span><br><span class="line">│    └─ Teams&#x2F;Enterprise 必须在托管设置中显式启用             │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 5: 会话白名单（Session）                              │</span><br><span class="line">│    └─ 必须在 --channels 参数列表中                         │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 6: Marketplace 验证 + 白名单（Allowlist）             │</span><br><span class="line">│    ├─ 验证插件来源标签与实际安装来源匹配                    │</span><br><span class="line">│    └─ 插件必须在 GrowthBook 审批白名单中                   │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="3-3-Gate-结果类型"><a href="#3-3-Gate-结果类型" class="headerlink" title="3.3 Gate 结果类型"></a>3.3 Gate 结果类型</h3><figure class="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="keyword">type</span> ChannelGateResult =</span><br><span class="line">  | &#123; action: <span class="string">'register'</span> &#125;           <span class="comment">// 通过所有检查</span></span><br><span class="line">  | &#123; action: <span class="string">'skip'</span>; kind: <span class="built_in">string</span>; reason: <span class="built_in">string</span> &#125;  <span class="comment">// 某层拦截</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// kind 枚举：capability | disabled | auth | policy | session | marketplace | allowlist</span></span><br></pre></td></tr></table></figure><hr><h2 id="四、权限中继系统"><a href="#四、权限中继系统" class="headerlink" title="四、权限中继系统"></a>四、权限中继系统</h2><h3 id="4-1-为什么需要权限中继"><a href="#4-1-为什么需要权限中继" class="headerlink" title="4.1 为什么需要权限中继"></a>4.1 为什么需要权限中继</h3><p>当 Claude Code 需要执行敏感操作（如运行 Bash 命令），会弹出权限确认对话框。但如果用户通过 Telegram 远程控制 Agent，他看不到本地终端的对话框。</p><p><strong>权限中继</strong>解决了这个问题：将权限提示转发到 IM 平台，让用户在手机上也能审批或拒绝操作。</p><h3 id="4-2-出站：权限请求"><a href="#4-2-出站：权限请求" class="headerlink" title="4.2 出站：权限请求"></a>4.2 出站：权限请求</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 通知 Schema</span></span><br><span class="line"><span class="keyword">const</span> CHANNEL_PERMISSION_REQUEST_METHOD =</span><br><span class="line">  <span class="string">'notifications/claude/channel/permission_request'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> ChannelPermissionRequestParams = &#123;</span><br><span class="line">  request_id: <span class="built_in">string</span>      <span class="comment">// 5 字母标识符（如 "tbxkq"）</span></span><br><span class="line">  tool_name: <span class="built_in">string</span>       <span class="comment">// 工具名（如 "Bash"）</span></span><br><span class="line">  description: <span class="built_in">string</span>     <span class="comment">// 人类可读描述</span></span><br><span class="line">  input_preview: <span class="built_in">string</span>   <span class="comment">// JSON 输入预览，截断到 200 字符</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-3-Short-Request-ID-设计"><a href="#4-3-Short-Request-ID-设计" class="headerlink" title="4.3 Short Request ID 设计"></a>4.3 Short Request ID 设计</h3><p>5 个字母标识符的设计充满巧思：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/mcp/channelPermissions.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">shortRequestId</span>(<span class="params">toolUseID: <span class="built_in">string</span></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 25 字母表：a-z 去掉 l（与 1/I 混淆）</span></span><br><span class="line">  <span class="keyword">const</span> alphabet = <span class="string">'abcdefghijkmnopqrstuvwxyz'</span></span><br><span class="line">  <span class="keyword">const</span> id = hashToId(toolUseID, alphabet)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 脏话过滤</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> bad of ID_AVOID_SUBSTRINGS) &#123;</span><br><span class="line">    <span class="keyword">if</span> (id.includes(bad)) &#123;</span><br><span class="line">      <span class="keyword">return</span> shortRequestId(<span class="string">`<span class="subst">$&#123;toolUseID&#125;</span>:retry`</span>)  <span class="comment">// 重试</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> id</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>设计决策</strong>：</p><ul><li><strong>纯字母</strong>：手机用户不需要切换键盘模式</li><li><strong>大小写不敏感</strong>：适配手机自动更正</li><li><strong>脏话过滤</strong>：防止尴尬场景</li></ul><h3 id="4-4-入站：权限响应"><a href="#4-4-入站：权限响应" class="headerlink" title="4.4 入站：权限响应"></a>4.4 入站：权限响应</h3><p>用户在 IM 中回复格式：<code>yes tbxkq</code> 或 <code>no tbxkq</code></p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 服务端解析正则</span></span><br><span class="line"><span class="keyword">const</span> PERMISSION_REPLY_RE = <span class="regexp">/^\s*(y|yes|n|no)\s+([a-km-z]&#123;5&#125;)\s*$/i</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 结构化通知</span></span><br><span class="line"><span class="keyword">const</span> ChannelPermissionNotificationSchema = z.object(&#123;</span><br><span class="line">  method: z.literal(<span class="string">'notifications/claude/channel/permission'</span>),</span><br><span class="line">  params: z.object(&#123;</span><br><span class="line">    request_id: z.string(),</span><br><span class="line">    behavior: z.enum([<span class="string">'allow'</span>, <span class="string">'deny'</span>]),</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="4-5-多源竞争"><a href="#4-5-多源竞争" class="headerlink" title="4.5 多源竞争"></a>4.5 多源竞争</h3><p>权限响应来自四个来源，先到先得：</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">┌──────────────┐   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐</span><br><span class="line">│   本地终端    │   │    Bridge    │   │   Channels   │   │    Hooks     │</span><br><span class="line">│  Local UI    │   │   远程控制    │   │ Telegram etc │   │  Permission  │</span><br><span class="line">└──────┬───────┘   └──────┬───────┘   └──────┬───────┘   └──────┬───────┘</span><br><span class="line">       │                   │                   │                  │</span><br><span class="line">       └───────────────────┴───────────────────┴──────────────────┘</span><br><span class="line">                                    │</span><br><span class="line">                              claim() — 先到先得</span><br><span class="line">                                    │</span><br><span class="line">                              ┌─────┴─────┐</span><br><span class="line">                              │  resolve   │</span><br><span class="line">                              │  allow&#x2F;deny│</span><br><span class="line">                              └───────────┘</span><br></pre></td></tr></table></figure><hr><h2 id="五、安全设计"><a href="#五、安全设计" class="headerlink" title="五、安全设计"></a>五、安全设计</h2><h3 id="5-1-XML-注入防护"><a href="#5-1-XML-注入防护" class="headerlink" title="5.1 XML 注入防护"></a>5.1 XML 注入防护</h3><p>Channel 消息中的元数据会成为 XML 属性。两道防线：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 键名过滤：只允许纯标识符格式</span></span><br><span class="line"><span class="keyword">const</span> SAFE_META_KEY = <span class="regexp">/^[a-zA-Z_][a-zA-Z0-9_]*$/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 值转义</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">escapeXmlAttr</span>(<span class="params">value: <span class="built_in">string</span></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> value</span><br><span class="line">    .replace(<span class="regexp">/&amp;/g</span>, <span class="string">'&amp;amp;'</span>)</span><br><span class="line">    .replace(<span class="regexp">/"/g</span>, <span class="string">'&amp;quot;'</span>)</span><br><span class="line">    .replace(<span class="regexp">/&lt;/g</span>, <span class="string">'&amp;lt;'</span>)</span><br><span class="line">    .replace(<span class="regexp">/&gt;/g</span>, <span class="string">'&amp;gt;'</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-Marketplace-验证"><a href="#5-2-Marketplace-验证" class="headerlink" title="5.2 Marketplace 验证"></a>5.2 Marketplace 验证</h3><p><code>--channels plugin:slack@anthropic</code> 只是用户的”意图声明”。运行时验证：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> actual = pluginSource</span><br><span class="line">  ? parsePluginIdentifier(pluginSource).marketplace</span><br><span class="line">  : <span class="literal">undefined</span></span><br><span class="line"><span class="keyword">if</span> (actual !== entry.marketplace) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123; action: <span class="string">'skip'</span>, kind: <span class="string">'marketplace'</span>, reason: <span class="string">'Tag mismatch'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-3-权限中继的信任边界"><a href="#5-3-权限中继的信任边界" class="headerlink" title="5.3 权限中继的信任边界"></a>5.3 权限中继的信任边界</h3><p><strong>问题</strong>：Claude 会自我审批吗？</p><p><strong>答案</strong>：审批方是通过 Channel 的人类，不是 Claude。但信任边界不是终端——而是白名单。一个被妥协的 Channel Server 可以伪造响应，但：</p><ul><li>它本来就有无限的对话注入能力</li><li>权限对话框减缓攻击速度，但不能完全阻止</li></ul><h3 id="5-4-skipSlashCommands"><a href="#5-4-skipSlashCommands" class="headerlink" title="5.4 skipSlashCommands"></a>5.4 skipSlashCommands</h3><p>Channel 消息入队时设置 <code>skipSlashCommands: true</code>，确保 IM 用户发送的 <code>/help</code> 等文本不会被解释为 Claude Code 的斜杠命令。</p><hr><h2 id="六、插件-Channel-架构"><a href="#六、插件-Channel-架构" class="headerlink" title="六、插件 Channel 架构"></a>六、插件 Channel 架构</h2><h3 id="6-1-Plugin-Manifest-声明"><a href="#6-1-Plugin-Manifest-声明" class="headerlink" title="6.1 Plugin Manifest 声明"></a>6.1 Plugin Manifest 声明</h3><figure class="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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"name"</span>: <span class="string">"telegram"</span>,</span><br><span class="line">  <span class="attr">"version"</span>: <span class="string">"1.0.0"</span>,</span><br><span class="line">  <span class="attr">"mcpServers"</span>: &#123;</span><br><span class="line">    <span class="attr">"tg"</span>: &#123;</span><br><span class="line">      <span class="attr">"command"</span>: <span class="string">"node"</span>,</span><br><span class="line">      <span class="attr">"args"</span>: [<span class="string">"./server.js"</span>],</span><br><span class="line">      <span class="attr">"env"</span>: &#123;</span><br><span class="line">        <span class="attr">"BOT_TOKEN"</span>: <span class="string">"$&#123;user_config.bot_token&#125;"</span>,</span><br><span class="line">        <span class="attr">"OWNER_ID"</span>: <span class="string">"$&#123;user_config.owner_id&#125;"</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 class="attr">"channels"</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">"server"</span>: <span class="string">"tg"</span>,</span><br><span class="line">      <span class="attr">"displayName"</span>: <span class="string">"Telegram"</span>,</span><br><span class="line">      <span class="attr">"userConfig"</span>: &#123;</span><br><span class="line">        <span class="attr">"bot_token"</span>: &#123;</span><br><span class="line">          <span class="attr">"type"</span>: <span class="string">"string"</span>,</span><br><span class="line">          <span class="attr">"description"</span>: <span class="string">"Telegram Bot API Token"</span>,</span><br><span class="line">          <span class="attr">"required"</span>: <span class="literal">true</span>,</span><br><span class="line">          <span class="attr">"secret"</span>: <span class="literal">true</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">"owner_id"</span>: &#123;</span><br><span class="line">          <span class="attr">"type"</span>: <span class="string">"string"</span>,</span><br><span class="line">          <span class="attr">"description"</span>: <span class="string">"Your Telegram User ID"</span>,</span><br><span class="line">          <span class="attr">"required"</span>: <span class="literal">true</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">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-作用域命名"><a href="#6-2-作用域命名" class="headerlink" title="6.2 作用域命名"></a>6.2 作用域命名</h3><p>插件提供的 MCP Server 会被添加作用域前缀：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 输入：&#123; "tg": &#123; ... &#125; &#125; from telegram@anthropic</span></span><br><span class="line"><span class="comment">// 输出：&#123; "plugin:telegram:tg": &#123; ... &#125; &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">addPluginScopeToServers</span>(<span class="params">servers, pluginName, pluginSource</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> scopedServers = &#123;&#125;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> [name, config] of <span class="built_in">Object</span>.entries(servers)) &#123;</span><br><span class="line">    <span class="keyword">const</span> scopedName = <span class="string">`plugin:<span class="subst">$&#123;pluginName&#125;</span>:<span class="subst">$&#123;name&#125;</span>`</span></span><br><span class="line">    scopedServers[scopedName] = &#123;</span><br><span class="line">      ...config,</span><br><span class="line">      scope: <span class="string">'dynamic'</span>,</span><br><span class="line">      pluginSource,</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> scopedServers</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、命令行接口"><a href="#七、命令行接口" class="headerlink" title="七、命令行接口"></a>七、命令行接口</h2><h3 id="7-1-启动参数"><a href="#7-1-启动参数" class="headerlink" title="7.1 启动参数"></a>7.1 启动参数</h3><figure class="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><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"><span class="comment"># 使用已审批的 Channel 插件</span></span><br><span class="line">claude --channels plugin:telegram@anthropic plugin:feishu@anthropic</span><br><span class="line"></span><br><span class="line"><span class="comment"># 本地开发模式（旁路白名单）</span></span><br><span class="line">claude --dangerously-load-development-channels plugin:my-channel@<span class="built_in">local</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 两者可以同时使用</span></span><br><span class="line">claude --channels plugin:telegram@anthropic \</span><br><span class="line">       --dangerously-load-development-channels plugin:dev-channel@<span class="built_in">local</span></span><br></pre></td></tr></table></figure><h3 id="7-2-特性门控"><a href="#7-2-特性门控" class="headerlink" title="7.2 特性门控"></a>7.2 特性门控</h3><figure class="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">// src/main.tsx</span></span><br><span class="line"><span class="keyword">if</span> (feature(<span class="string">'KAIROS'</span>) || feature(<span class="string">'KAIROS_CHANNELS'</span>)) &#123;</span><br><span class="line">  program.addOption(<span class="keyword">new</span> Option(<span class="string">'--channels &lt;servers...&gt;'</span>, <span class="string">'...'</span>).hideHelp())</span><br><span class="line">  program.addOption(<span class="keyword">new</span> Option(<span class="string">'--dangerously-load-development-channels &lt;servers...&gt;'</span>, <span class="string">'...'</span>).hideHelp())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>hideHelp()</code> 表示这些选项不会出现在 <code>--help</code> 输出中——Channel 功能目前处于隐藏特性阶段。</p><hr><h2 id="八、关键源文件索引"><a href="#八、关键源文件索引" class="headerlink" title="八、关键源文件索引"></a>八、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>行数</th><th>职责</th></tr></thead><tbody><tr><td><code>src/services/mcp/channelNotification.ts</code></td><td>~320</td><td>门控、消息封装、白名单集成</td></tr><tr><td><code>src/services/mcp/channelPermissions.ts</code></td><td>~240</td><td>权限中继、请求 ID 生成</td></tr><tr><td><code>src/services/mcp/channelAllowlist.ts</code></td><td>~80</td><td>GrowthBook 白名单查询</td></tr><tr><td><code>src/services/mcp/useManageMCPConnections.ts</code></td><td>-</td><td>连接管理、通知处理器注册</td></tr><tr><td><code>src/components/messages/UserChannelMessage.tsx</code></td><td>~140</td><td>终端渲染 Channel 消息</td></tr><tr><td><code>src/components/DevChannelsDialog.tsx</code></td><td>~105</td><td>开发模式确认对话框</td></tr><tr><td><code>src/utils/plugins/mcpPluginIntegration.ts</code></td><td>-</td><td>插件 MCP 集成、作用域命名</td></tr><tr><td><code>src/bootstrap/state.ts</code></td><td>-</td><td>全局 Channel 白名单状态</td></tr></tbody></table></div><hr><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><p>Channel 系统体现了几个核心设计原则：</p><ol><li><strong>安全优先</strong>：六层访问控制确保只有授权 Channel 能推送消息</li><li><strong>协议驱动</strong>：Channel 就是 MCP Server，任何语言都可以实现</li><li><strong>松耦合</strong>：Channel 失败不会阻断本地工作流</li><li><strong>渐进式信任</strong>：从全局开关到白名单，信任级别逐级递增</li><li><strong>插件友好</strong>：声明式配置，自动用户配置提示</li><li><strong>权限中继</strong>：远程审批危险操作</li></ol><p>这个设计让 Claude Code 真正成为一个”无处不在”的 AI 编程助手。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-memory-system/">Memory 系统：跨会话持久化知识库</a></li><li>下一篇：<a href="/claude-code-computer-use/">Computer Use：桌面控制的九层安全关卡</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;你在手机上打开 Telegram，给 Claude Code 发一条消息，它就开始在你的电脑上工作——这就是 Channel 系统。它打破了 AI 编程助手只能在终端中交互的限制，实现了真正的远程控制。更精妙的是，它有六层访问控制和权限中继机制，确保安全性。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Channel" scheme="https://donehub.github.io/tags/Channel/"/>
    
  </entry>
  
  <entry>
    <title>Computer Use：桌面控制的九层安全关卡</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-computer-use/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-computer-use/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:39.739Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Computer Use 是 Claude Code 最具争议也最强大的能力——AI 可以直接操控你的桌面，点击按钮、输入文字、截图分析。这听起来像科幻电影，但 Claude Code 实现了一个九层安全关卡系统，确保每一步操作都在可控范围内。更关键的是，它通过 Python Bridge 实现跨语言通信，让 TypeScript 代理驱动 Python 执行器。</p></blockquote><a id="more"></a><h2 id="导读：当-AI-控制你的屏幕"><a href="#导读：当-AI-控制你的屏幕" class="headerlink" title="导读：当 AI 控制你的屏幕"></a>导读：当 AI 控制你的屏幕</h2><p>想象这个场景：</p><blockquote><p>Claude Code 正在帮你调试一个 GUI 应用。它打开应用窗口，点击菜单，输入测试数据，截图分析结果，然后告诉你”登录按钮在点击后无响应”。</p></blockquote><p>这就是 <strong>Computer Use</strong>——AI 直接操控桌面环境的能力。</p><p>但这也带来巨大的安全风险：AI 可能误删文件、点击错误按钮、泄露敏感信息。Claude Code 的解决方案是<strong>九层安全关卡</strong>，每一层都可以中断操作。</p><hr><h2 id="一、Computer-Use-架构概览"><a href="#一、Computer-Use-架构概览" class="headerlink" title="一、Computer Use 架构概览"></a>一、Computer Use 架构概览</h2><h3 id="1-1-整体架构"><a href="#1-1-整体架构" class="headerlink" title="1.1 整体架构"></a>1.1 整体架构</h3><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    Computer Use 架构                         │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  Claude Code (TypeScript)                                   │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Computer Use Tool                                          │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  JSON-RPC over stdio                                        │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Python Bridge (computer_controller.py)                     │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Platform Abstraction Layer                                 │</span><br><span class="line">│       ├─ Windows: pyautogui + Win32 API                    │</span><br><span class="line">│       ├─ macOS: PyObjC + AppleScript                       │</span><br><span class="line">│       └─ Linux: xdotool + Gdk&#x2F;Xlib                         │</span><br><span class="line">│       ↓                                                     │</span><br><span class="line">│  Desktop Environment                                        │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="1-2-为什么用-Python？"><a href="#1-2-为什么用-Python？" class="headerlink" title="1.2 为什么用 Python？"></a>1.2 为什么用 Python？</h3><p>虽然 Claude Code 是 TypeScript 项目，但 Computer Use 使用 Python 实现：</p><div class="table-container"><table><thead><tr><th>原因</th><th>说明</th></tr></thead><tbody><tr><td><strong>生态成熟</strong></td><td>pyautogui、PyObjC 等库已稳定运行多年</td></tr><tr><td><strong>跨平台</strong></td><td>Python GUI 库对 Windows/macOS/Linux 支持一致</td></tr><tr><td><strong>快速迭代</strong></td><td>不需要为每个平台单独编写 native 代码</td></tr></tbody></table></div><hr><h2 id="二、24-个桌面操作工具"><a href="#二、24-个桌面操作工具" class="headerlink" title="二、24 个桌面操作工具"></a>二、24 个桌面操作工具</h2><h3 id="2-1-工具分类"><a href="#2-1-工具分类" class="headerlink" title="2.1 工具分类"></a>2.1 工具分类</h3><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                   24 个 Computer Use 工具                    │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  输入类（Input）                                             │</span><br><span class="line">│    ├─ computer_mouse_click      左键&#x2F;右键&#x2F;中键点击          │</span><br><span class="line">│    ├─ computer_mouse_double_click 双击                     │</span><br><span class="line">│    ├─ computer_mouse_drag       拖拽操作                    │</span><br><span class="line">│    ├─ computer_mouse_move       移动鼠标                    │</span><br><span class="line">│    ├─ computer_mouse_scroll     滚轮滚动                    │</span><br><span class="line">│    ├─ computer_keyboard_hotkey  组合键（Ctrl+C 等）         │</span><br><span class="line">│    ├─ computer_keyboard_press   单键按下                    │</span><br><span class="line">│    ├─ computer_keyboard_type    文字输入                    │</span><br><span class="line">│    └─ computer_clipboard_paste  粘贴内容                    │</span><br><span class="line">│                                                             │</span><br><span class="line">│  显示类（Display）                                           │</span><br><span class="line">│    ├─ computer_screen_capture   截图                        │</span><br><span class="line">│    ├─ computer_screen_get_size  获取屏幕尺寸                │</span><br><span class="line">│    ├─ computer_window_list      窗口列表                    │</span><br><span class="line">│    ├─ computer_window_activate  激活窗口                    │</span><br><span class="line">│    ├─ computer_window_get_position 窗口位置                 │</span><br><span class="line">│    └─ computer_window_get_size  窗口尺寸                    │</span><br><span class="line">│                                                             │</span><br><span class="line">│  文件类（File）                                              │</span><br><span class="line">│    ├─ computer_file_read        读取文件                    │</span><br><span class="line">│    ├─ computer_file_write       写入文件                    │</span><br><span class="line">│    ├─ computer_file_delete      删除文件                    │</span><br><span class="line">│    ├─ computer_file_list        列出目录                    │</span><br><span class="line">│    ├─ computer_file_move        移动文件                    │</span><br><span class="line">│    ├─ computer_file_copy        复制文件                    │</span><br><span class="line">│    └─ computer_file_info        文件信息                    │</span><br><span class="line">│                                                             │</span><br><span class="line">│  进程类（Process）                                           │</span><br><span class="line">│    ├─ computer_process_list     进程列表                    │</span><br><span class="line">│    ├─ computer_process_start    启动进程                    │</span><br><span class="line">│    └─ computer_process_kill     杀死进程                    │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="2-2-工具定义示例"><a href="#2-2-工具定义示例" class="headerlink" title="2.2 工具定义示例"></a>2.2 工具定义示例</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/ComputerUseTool/tools.ts</span></span><br><span class="line"><span class="keyword">const</span> computer_mouse_click = &#123;</span><br><span class="line">  name: <span class="string">'computer_mouse_click'</span>,</span><br><span class="line">  inputSchema: &#123;</span><br><span class="line">    <span class="keyword">type</span>: <span class="string">'object'</span>,</span><br><span class="line">    properties: &#123;</span><br><span class="line">      x: &#123; <span class="keyword">type</span>: <span class="string">'number'</span>, description: <span class="string">'X coordinate'</span> &#125;,</span><br><span class="line">      y: &#123; <span class="keyword">type</span>: <span class="string">'number'</span>, description: <span class="string">'Y coordinate'</span> &#125;,</span><br><span class="line">      button: &#123; </span><br><span class="line">        <span class="keyword">type</span>: <span class="string">'string'</span>, </span><br><span class="line">        <span class="keyword">enum</span>: [<span class="string">'left'</span>, <span class="string">'right'</span>, <span class="string">'middle'</span>],</span><br><span class="line">        <span class="keyword">default</span>: <span class="string">'left'</span></span><br><span class="line">      &#125;,</span><br><span class="line">      clicks: &#123; <span class="keyword">type</span>: <span class="string">'number'</span>, <span class="keyword">default</span>: <span class="number">1</span> &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    required: [<span class="string">'x'</span>, <span class="string">'y'</span>],</span><br><span class="line">  &#125;,</span><br><span class="line">  description: <span class="string">'Click at the specified coordinates'</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="三、九层安全关卡"><a href="#三、九层安全关卡" class="headerlink" title="三、九层安全关卡"></a>三、九层安全关卡</h2><h3 id="3-1-安全关卡架构"><a href="#3-1-安全关卡架构" class="headerlink" title="3.1 安全关卡架构"></a>3.1 安全关卡架构</h3><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    九层安全关卡                              │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 1: 功能门控（Feature Gate）                           │</span><br><span class="line">│    └─ tengu_computer_use Feature Flag 必须开启             │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 2: 用户确认（User Consent）                           │</span><br><span class="line">│    └─ 首次使用弹出确认对话框                                │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 3: 操作类型检查（Action Type）                        │</span><br><span class="line">│    └─ 读写操作需要额外确认                                  │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 4: 路径约束（Path Constraint）                        │</span><br><span class="line">│    ├─ 文件操作限制在白名单目录                              │</span><br><span class="line">│    └─ 禁止访问 .git、.claude、系统目录                      │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 5: 危险命令过滤（Dangerous Command）                   │</span><br><span class="line">│    ├─ 禁止 rm -rf、killall 等命令                          │</span><br><span class="line">│    ├─ 禁止访问密码管理器、银行应用                          │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 6: 屏幕边界检查（Screen Boundary）                    │</span><br><span class="line">│    ├─ 鼠标坐标必须在屏幕范围内                              │</span><br><span class="line">│    └─ 窗口操作必须针对可见窗口                              │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 7: 操作频率限制（Rate Limit）                         │</span><br><span class="line">│    ├─ 每秒最多 10 次操作                                    │</span><br><span class="line">│    └─ 连续失败 3 次暂停                                     │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 8: 截图内容分析（Screenshot Analysis）                │</span><br><span class="line">│    ├─ 检测敏感内容（密码框、私人信息）                      │</span><br><span class="line">│    └─ 检测错误弹窗                                          │</span><br><span class="line">│                                                             │</span><br><span class="line">│  Gate 9: 实时监控（Real-time Monitoring）                   │</span><br><span class="line">│    ├─ 用户可随时按 Ctrl+C 中断                             │</span><br><span class="line">│    ├─ 操作日志实时输出                                      │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="3-2-Gate-实现示例"><a href="#3-2-Gate-实现示例" class="headerlink" title="3.2 Gate 实现示例"></a>3.2 Gate 实现示例</h3><figure class="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><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/ComputerUseTool/security.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">gateComputerUseAction</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  action: ComputerUseAction,</span></span></span><br><span class="line"><span class="function"><span class="params">  context: ToolUseContext,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">GateResult</span> </span>&#123;</span><br><span class="line">  <span class="comment">// Gate 1: Feature Gate</span></span><br><span class="line">  <span class="keyword">if</span> (!feature(<span class="string">'tengu_computer_use'</span>)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; action: <span class="string">'deny'</span>, reason: <span class="string">'Feature not enabled'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 2: User Consent</span></span><br><span class="line">  <span class="keyword">if</span> (!context.computerUseConsent) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; action: <span class="string">'ask'</span>, reason: <span class="string">'First-time use requires consent'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 3: Action Type</span></span><br><span class="line">  <span class="keyword">if</span> (isWriteAction(action) &amp;&amp; !context.computerUseWriteConsent) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; action: <span class="string">'ask'</span>, reason: <span class="string">'Write operation requires confirmation'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 4: Path Constraint</span></span><br><span class="line">  <span class="keyword">if</span> (action.type === <span class="string">'file'</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!isInAllowedDirectory(action.path, context.allowedDirectories)) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123; action: <span class="string">'deny'</span>, reason: <span class="string">'Path not in allowed directories'</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 5: Dangerous Command</span></span><br><span class="line">  <span class="keyword">if</span> (isDangerousCommand(action)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; action: <span class="string">'deny'</span>, reason: <span class="string">'Dangerous command blocked'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 6: Screen Boundary</span></span><br><span class="line">  <span class="keyword">if</span> (action.type === <span class="string">'mouse'</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> screenSize = getScreenSize()</span><br><span class="line">    <span class="keyword">if</span> (action.x &lt; <span class="number">0</span> || action.x &gt; screenSize.width ||</span><br><span class="line">        action.y &lt; <span class="number">0</span> || action.y &gt; screenSize.height) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123; action: <span class="string">'deny'</span>, reason: <span class="string">'Coordinates out of screen bounds'</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 7: Rate Limit</span></span><br><span class="line">  <span class="keyword">if</span> (isRateLimited(context.computerUseHistory)) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; action: <span class="string">'wait'</span>, reason: <span class="string">'Rate limit exceeded'</span>, waitTime: <span class="number">1000</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Gate 8: Screenshot Analysis (performed after capture)</span></span><br><span class="line">  <span class="comment">// Gate 9: Real-time Monitoring (handled by interrupt mechanism)</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123; action: <span class="string">'allow'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="四、Python-Bridge-通信协议"><a href="#四、Python-Bridge-通信协议" class="headerlink" title="四、Python Bridge 通信协议"></a>四、Python Bridge 通信协议</h2><h3 id="4-1-JSON-RPC-over-stdio"><a href="#4-1-JSON-RPC-over-stdio" class="headerlink" title="4.1 JSON-RPC over stdio"></a>4.1 JSON-RPC over stdio</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/ComputerUseTool/bridge.ts</span></span><br><span class="line"><span class="keyword">interface</span> BridgeMessage &#123;</span><br><span class="line">  jsonrpc: <span class="string">'2.0'</span></span><br><span class="line">  id: <span class="built_in">number</span></span><br><span class="line">  method: <span class="built_in">string</span></span><br><span class="line">  params: Record&lt;<span class="built_in">string</span>, unknown&gt;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> BridgeResponse &#123;</span><br><span class="line">  jsonrpc: <span class="string">'2.0'</span></span><br><span class="line">  id: <span class="built_in">number</span></span><br><span class="line">  result?: unknown</span><br><span class="line">  error?: &#123; code: <span class="built_in">number</span>; message: <span class="built_in">string</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">callBridge</span>(<span class="params">method: <span class="built_in">string</span>, params: unknown</span>): <span class="title">Promise</span>&lt;<span class="title">unknown</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> message: BridgeMessage = &#123;</span><br><span class="line">    jsonrpc: <span class="string">'2.0'</span>,</span><br><span class="line">    id: nextId++,</span><br><span class="line">    method,</span><br><span class="line">    params,</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 写入 stdin</span></span><br><span class="line">  bridgeProcess.stdin.write(<span class="built_in">JSON</span>.stringify(message) + <span class="string">'\n'</span>)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 读取 stdout</span></span><br><span class="line">  <span class="keyword">const</span> response = <span class="keyword">await</span> readBridgeResponse()</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (response.error) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> BridgeError(response.error.code, response.error.message)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> response.result</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-Python-执行器"><a href="#4-2-Python-执行器" class="headerlink" title="4.2 Python 执行器"></a>4.2 Python 执行器</h3><figure class="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><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><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># computer_controller.py</span></span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> Any</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ComputerController</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.handlers = &#123;</span><br><span class="line">            <span class="string">'computer_mouse_click'</span>: self.mouse_click,</span><br><span class="line">            <span class="string">'computer_keyboard_type'</span>: self.keyboard_type,</span><br><span class="line">            <span class="string">'computer_screen_capture'</span>: self.screen_capture,</span><br><span class="line">            <span class="comment"># ... 24 个处理器</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">run</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">            line = sys.stdin.readline()</span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> line:</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">            request = json.loads(line)</span><br><span class="line">            method = request[<span class="string">'method'</span>]</span><br><span class="line">            params = request[<span class="string">'params'</span>]</span><br><span class="line">            id = request[<span class="string">'id'</span>]</span><br><span class="line"></span><br><span class="line">            <span class="keyword">try</span>:</span><br><span class="line">                handler = self.handlers[method]</span><br><span class="line">                result = handler(**params)</span><br><span class="line">                response = &#123;</span><br><span class="line">                    <span class="string">'jsonrpc'</span>: <span class="string">'2.0'</span>,</span><br><span class="line">                    <span class="string">'id'</span>: id,</span><br><span class="line">                    <span class="string">'result'</span>: result</span><br><span class="line">                &#125;</span><br><span class="line">            <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">                response = &#123;</span><br><span class="line">                    <span class="string">'jsonrpc'</span>: <span class="string">'2.0'</span>,</span><br><span class="line">                    <span class="string">'id'</span>: id,</span><br><span class="line">                    <span class="string">'error'</span>: &#123;<span class="string">'code'</span>: <span class="number">1</span>, <span class="string">'message'</span>: str(e)&#125;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">            sys.stdout.write(json.dumps(response) + <span class="string">'\n'</span>)</span><br><span class="line">            sys.stdout.flush()</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">mouse_click</span><span class="params">(self, x: int, y: int, button: str = <span class="string">'left'</span>)</span>:</span></span><br><span class="line">        <span class="keyword">import</span> pyautogui</span><br><span class="line">        pyautogui.click(x, y, button=button)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">screen_capture</span><span class="params">(self)</span> -&gt; str:</span></span><br><span class="line">        <span class="keyword">import</span> pyautogui</span><br><span class="line">        <span class="keyword">import</span> base64</span><br><span class="line">        screenshot = pyautogui.screenshot()</span><br><span class="line">        <span class="comment"># 返回 base64 编码</span></span><br><span class="line">        <span class="keyword">return</span> base64.b64encode(screenshot).decode(<span class="string">'utf-8'</span>)</span><br></pre></td></tr></table></figure><hr><h2 id="五、截图分析机制"><a href="#五、截图分析机制" class="headerlink" title="五、截图分析机制"></a>五、截图分析机制</h2><h3 id="5-1-截图流程"><a href="#5-1-截图流程" class="headerlink" title="5.1 截图流程"></a>5.1 截图流程</h3><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">Model 决定截图</span><br><span class="line">    ↓</span><br><span class="line">computer_screen_capture 工具调用</span><br><span class="line">    ↓</span><br><span class="line">Python Bridge 执行 pyautogui.screenshot()</span><br><span class="line">    ↓</span><br><span class="line">PNG → Base64 编码</span><br><span class="line">    ↓</span><br><span class="line">返回给 Claude Code</span><br><span class="line">    ↓</span><br><span class="line">作为 image block 注入对话</span><br><span class="line">    ↓</span><br><span class="line">Model 多模态分析</span><br></pre></td></tr></table></figure><h3 id="5-2-截图内容过滤"><a href="#5-2-截图内容过滤" class="headerlink" title="5.2 截图内容过滤"></a>5.2 截图内容过滤</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/tools/ComputerUseTool/screenshotFilter.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">filterScreenshot</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  base64Image: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">FilterResult</span>&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 使用本地 OCR 检测敏感文本</span></span><br><span class="line">  <span class="keyword">const</span> detectedText = <span class="keyword">await</span> localOcrDetect(base64Image)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. 检测敏感关键词</span></span><br><span class="line">  <span class="keyword">const</span> sensitiveKeywords = [<span class="string">'password'</span>, <span class="string">'secret'</span>, <span class="string">'api key'</span>, <span class="string">'token'</span>]</span><br><span class="line">  <span class="keyword">const</span> foundSensitive = sensitiveKeywords.some(<span class="function"><span class="params">k</span> =&gt;</span> </span><br><span class="line">    detectedText.toLowerCase().includes(k)</span><br><span class="line">  )</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (foundSensitive) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      action: <span class="string">'blur'</span>,</span><br><span class="line">      regions: findSensitiveRegions(detectedText),</span><br><span class="line">      reason: <span class="string">'Sensitive content detected'</span>,</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123; action: <span class="string">'allow'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="六、窗口管理系统"><a href="#六、窗口管理系统" class="headerlink" title="六、窗口管理系统"></a>六、窗口管理系统</h2><h3 id="6-1-窗口发现"><a href="#6-1-窗口发现" class="headerlink" title="6.1 窗口发现"></a>6.1 窗口发现</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 窗口列表返回格式</span></span><br><span class="line"><span class="keyword">interface</span> WindowInfo &#123;</span><br><span class="line">  id: <span class="built_in">number</span></span><br><span class="line">  title: <span class="built_in">string</span></span><br><span class="line">  process: <span class="built_in">string</span></span><br><span class="line">  position: &#123; x: <span class="built_in">number</span>; y: <span class="built_in">number</span> &#125;</span><br><span class="line">  size: &#123; width: <span class="built_in">number</span>; height: <span class="built_in">number</span> &#125;</span><br><span class="line">  visible: <span class="built_in">boolean</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 示例返回</span></span><br><span class="line">[</span><br><span class="line">  &#123; id: <span class="number">1234</span>, title: <span class="string">'VS Code'</span>, process: <span class="string">'code'</span>, position: &#123; x: <span class="number">0</span>, y: <span class="number">0</span> &#125;, size: &#123; width: <span class="number">1920</span>, height: <span class="number">1080</span> &#125;, visible: <span class="literal">true</span> &#125;,</span><br><span class="line">  &#123; id: <span class="number">5678</span>, title: <span class="string">'Chrome'</span>, process: <span class="string">'chrome'</span>, position: &#123; x: <span class="number">100</span>, y: <span class="number">100</span> &#125;, size: &#123; width: <span class="number">800</span>, height: <span class="number">600</span> &#125;, visible: <span class="literal">true</span> &#125;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h3 id="6-2-窗口激活策略"><a href="#6-2-窗口激活策略" class="headerlink" title="6.2 窗口激活策略"></a>6.2 窗口激活策略</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 激活窗口的安全检查</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">activateWindow</span>(<span class="params">windowId: <span class="built_in">number</span></span>): <span class="title">Promise</span>&lt;<span class="title">void</span>&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 检查窗口是否存在</span></span><br><span class="line">  <span class="keyword">const</span> <span class="built_in">window</span> = <span class="keyword">await</span> getWindowInfo(windowId)</span><br><span class="line">  <span class="keyword">if</span> (!<span class="built_in">window</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Window not found'</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. 检查窗口是否属于敏感应用</span></span><br><span class="line">  <span class="keyword">const</span> sensitiveApps = [<span class="string">'Keychain Access'</span>, <span class="string">'1Password'</span>, <span class="string">'Banking App'</span>]</span><br><span class="line">  <span class="keyword">if</span> (sensitiveApps.some(<span class="function"><span class="params">app</span> =&gt;</span> <span class="built_in">window</span>.title.includes(app))) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Cannot activate sensitive application'</span>)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 3. 执行激活</span></span><br><span class="line">  <span class="keyword">await</span> callBridge(<span class="string">'computer_window_activate'</span>, &#123; window_id: windowId &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="七、操作审计日志"><a href="#七、操作审计日志" class="headerlink" title="七、操作审计日志"></a>七、操作审计日志</h2><h3 id="7-1-日志格式"><a href="#7-1-日志格式" class="headerlink" title="7.1 日志格式"></a>7.1 日志格式</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> ComputerUseLogEntry &#123;</span><br><span class="line">  timestamp: <span class="built_in">number</span></span><br><span class="line">  action: <span class="built_in">string</span></span><br><span class="line">  params: Record&lt;<span class="built_in">string</span>, unknown&gt;</span><br><span class="line">  result: <span class="string">'success'</span> | <span class="string">'deny'</span> | <span class="string">'error'</span></span><br><span class="line">  reason?: <span class="built_in">string</span></span><br><span class="line">  duration: <span class="built_in">number</span></span><br><span class="line">  screenshot?: <span class="built_in">string</span>  <span class="comment">// 操作后的截图（可选）</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 示例日志</span></span><br><span class="line">[</span><br><span class="line">  &#123; timestamp: <span class="number">1712345678</span>, action: <span class="string">'mouse_click'</span>, params: &#123; x: <span class="number">100</span>, y: <span class="number">200</span> &#125;, result: <span class="string">'success'</span>, duration: <span class="number">50</span> &#125;,</span><br><span class="line">  &#123; timestamp: <span class="number">1712345680</span>, action: <span class="string">'keyboard_type'</span>, params: &#123; text: <span class="string">'hello'</span> &#125;, result: <span class="string">'success'</span>, duration: <span class="number">100</span> &#125;,</span><br><span class="line">  &#123; timestamp: <span class="number">1712345682</span>, action: <span class="string">'file_delete'</span>, params: &#123; path: <span class="string">'/etc/passwd'</span> &#125;, result: <span class="string">'deny'</span>, reason: <span class="string">'Dangerous path'</span>, duration: <span class="number">0</span> &#125;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h3 id="7-2-日志存储"><a href="#7-2-日志存储" class="headerlink" title="7.2 日志存储"></a>7.2 日志存储</h3><figure class="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="comment">// 日志持久化到文件</span></span><br><span class="line"><span class="keyword">const</span> LOG_PATH = <span class="string">'.claude/computer_use_history.jsonl'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">appendLog</span>(<span class="params">entry: ComputerUseLogEntry</span>): <span class="title">Promise</span>&lt;<span class="title">void</span>&gt; </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> logLine = <span class="built_in">JSON</span>.stringify(entry) + <span class="string">'\n'</span></span><br><span class="line">  <span class="keyword">await</span> fs.appendFile(LOG_PATH, logLine)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="八、中断机制"><a href="#八、中断机制" class="headerlink" title="八、中断机制"></a>八、中断机制</h2><h3 id="8-1-Ctrl-C-中断"><a href="#8-1-Ctrl-C-中断" class="headerlink" title="8.1 Ctrl+C 中断"></a>8.1 Ctrl+C 中断</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 监听中断信号</span></span><br><span class="line">process.on(<span class="string">'SIGINT'</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="comment">// 1. 通知 Python Bridge 停止</span></span><br><span class="line">  <span class="keyword">await</span> callBridge(<span class="string">'stop'</span>, &#123;&#125;)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. 恢复鼠标状态</span></span><br><span class="line">  <span class="keyword">await</span> callBridge(<span class="string">'mouse_move'</span>, &#123; x: lastSafeX, y: lastSafeY &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 3. 记录中断</span></span><br><span class="line">  appendLog(&#123;</span><br><span class="line">    timestamp: <span class="built_in">Date</span>.now(),</span><br><span class="line">    action: <span class="string">'interrupt'</span>,</span><br><span class="line">    params: &#123;&#125;,</span><br><span class="line">    result: <span class="string">'success'</span>,</span><br><span class="line">    reason: <span class="string">'User pressed Ctrl+C'</span>,</span><br><span class="line">    duration: <span class="number">0</span>,</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 4. 提示用户</span></span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'\nComputer Use interrupted. All operations stopped.'</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="8-2-紧急停止"><a href="#8-2-紧急停止" class="headerlink" title="8.2 紧急停止"></a>8.2 紧急停止</h3><p>Python Bridge 维护一个紧急停止标志：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># computer_controller.py</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ComputerController</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.emergency_stop = <span class="literal">False</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">run</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">while</span> <span class="keyword">not</span> self.emergency_stop:</span><br><span class="line">            <span class="comment"># ... 处理请求</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">stop</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.emergency_stop = <span class="literal">True</span></span><br><span class="line">        <span class="comment"># 恢复鼠标到安全位置</span></span><br><span class="line">        pyautogui.moveTo(self.safe_x, self.safe_y)</span><br></pre></td></tr></table></figure><hr><h2 id="九、平台适配层"><a href="#九、平台适配层" class="headerlink" title="九、平台适配层"></a>九、平台适配层</h2><h3 id="9-1-Windows-实现"><a href="#9-1-Windows-实现" class="headerlink" title="9.1 Windows 实现"></a>9.1 Windows 实现</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># platform/windows.py</span></span><br><span class="line"><span class="keyword">import</span> pyautogui</span><br><span class="line"><span class="keyword">import</span> ctypes</span><br><span class="line"><span class="keyword">from</span> ctypes <span class="keyword">import</span> wintypes</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_active_window</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="string">"""获取活动窗口"""</span></span><br><span class="line">    hwnd = ctypes.windll.user32.GetForegroundWindow()</span><br><span class="line">    <span class="keyword">return</span> hwnd</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_window_title</span><span class="params">(hwnd)</span>:</span></span><br><span class="line">    <span class="string">"""获取窗口标题"""</span></span><br><span class="line">    length = ctypes.windll.user32.GetWindowTextLengthW(hwnd)</span><br><span class="line">    title = ctypes.create_unicode_buffer(length + <span class="number">1</span>)</span><br><span class="line">    ctypes.windll.user32.GetWindowTextW(hwnd, title, length + <span class="number">1</span>)</span><br><span class="line">    <span class="keyword">return</span> title.value</span><br></pre></td></tr></table></figure><h3 id="9-2-macOS-实现"><a href="#9-2-macOS-实现" class="headerlink" title="9.2 macOS 实现"></a>9.2 macOS 实现</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># platform/macos.py</span></span><br><span class="line"><span class="keyword">import</span> pyautogui</span><br><span class="line"><span class="keyword">from</span> AppKit <span class="keyword">import</span> NSWorkspace, NSRunningApplication</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_active_window</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="string">"""获取活动窗口"""</span></span><br><span class="line">    workspace = NSWorkspace.sharedWorkspace()</span><br><span class="line">    app = workspace.activeApplication()</span><br><span class="line">    <span class="keyword">return</span> app.localizedName()</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">activate_window</span><span class="params">(title)</span>:</span></span><br><span class="line">    <span class="string">"""激活窗口"""</span></span><br><span class="line">    workspace = NSWorkspace.sharedWorkspace()</span><br><span class="line">    apps = workspace.runningApplications()</span><br><span class="line">    <span class="keyword">for</span> app <span class="keyword">in</span> apps:</span><br><span class="line">        <span class="keyword">if</span> app.localizedName() == title:</span><br><span class="line">            app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps)</span><br><span class="line">            <span class="keyword">break</span></span><br></pre></td></tr></table></figure><h3 id="9-3-Linux-实现"><a href="#9-3-Linux-实现" class="headerlink" title="9.3 Linux 实现"></a>9.3 Linux 实现</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment"># platform/linux.py</span></span><br><span class="line"><span class="keyword">import</span> pyautogui</span><br><span class="line"><span class="keyword">import</span> subprocess</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_active_window</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="string">"""获取活动窗口"""</span></span><br><span class="line">    result = subprocess.run(</span><br><span class="line">        [<span class="string">'xdotool'</span>, <span class="string">'getactivewindow'</span>],</span><br><span class="line">        capture_output=<span class="literal">True</span>,</span><br><span class="line">        text=<span class="literal">True</span></span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> int(result.stdout.strip())</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_window_title</span><span class="params">(window_id)</span>:</span></span><br><span class="line">    <span class="string">"""获取窗口标题"""</span></span><br><span class="line">    result = subprocess.run(</span><br><span class="line">        [<span class="string">'xdotool'</span>, <span class="string">'getwindowname'</span>, str(window_id)],</span><br><span class="line">        capture_output=<span class="literal">True</span>,</span><br><span class="line">        text=<span class="literal">True</span></span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> result.stdout.strip()</span><br></pre></td></tr></table></figure><hr><h2 id="十、关键源文件索引"><a href="#十、关键源文件索引" class="headerlink" title="十、关键源文件索引"></a>十、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>职责</th></tr></thead><tbody><tr><td><code>src/tools/ComputerUseTool/ComputerUseTool.ts</code></td><td>工具定义、权限检查、安全关卡</td></tr><tr><td><code>src/tools/ComputerUseTool/bridge.ts</code></td><td>Python Bridge 通信</td></tr><tr><td><code>src/tools/ComputerUseTool/security.ts</code></td><td>九层安全关卡实现</td></tr><tr><td><code>src/tools/ComputerUseTool/tools.ts</code></td><td>24 个工具定义</td></tr><tr><td><code>src/tools/ComputerUseTool/screenshotFilter.ts</code></td><td>截图内容过滤</td></tr><tr><td><code>computer_controller.py</code></td><td>Python 执行器主入口</td></tr><tr><td><code>platform/windows.py</code></td><td>Windows 平台适配</td></tr><tr><td><code>platform/macos.py</code></td><td>macOS 平台适配</td></tr><tr><td><code>platform/linux.py</code></td><td>Linux 平台适配</td></tr></tbody></table></div><hr><h2 id="十一、总结"><a href="#十一、总结" class="headerlink" title="十一、总结"></a>十一、总结</h2><p>Claude Code 的 Computer Use 系统体现了几个核心设计原则：</p><ol><li><strong>九层防御</strong>：从功能门控到实时监控，层层把关</li><li><strong>跨语言架构</strong>：TypeScript 代理 + Python 执行器</li><li><strong>JSON-RPC 协议</strong>：简单高效的跨进程通信</li><li><strong>平台抽象</strong>：统一接口，底层适配三大操作系统</li><li><strong>审计日志</strong>：完整记录所有操作，便于追溯</li><li><strong>用户可控</strong>：随时 Ctrl+C 中断，恢复安全状态</li></ol><p>这个设计让 AI 真正能够”看见”和”操控”桌面环境，同时保持高度安全性。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-channel-system/">Channel 系统：IM 远程控制 Agent</a></li><li>下一篇：<a href="/claude-code-terminal-ui/">Terminal UI：React + Ink 的 TUI 实现</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Computer Use 是 Claude Code 最具争议也最强大的能力——AI 可以直接操控你的桌面，点击按钮、输入文字、截图分析。这听起来像科幻电影，但 Claude Code 实现了一个九层安全关卡系统，确保每一步操作都在可控范围内。更关键的是，它通过 Python Bridge 实现跨语言通信，让 TypeScript 代理驱动 Python 执行器。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Computer Use" scheme="https://donehub.github.io/tags/Computer-Use/"/>
    
  </entry>
  
  <entry>
    <title>Context 管理：四级压缩与无限对话的秘密</title>
    <link href="https://donehub.github.io/2026/04/06/claude-code-context-compression/"/>
    <id>https://donehub.github.io/2026/04/06/claude-code-context-compression/</id>
    <published>2026-04-05T16:00:00.000Z</published>
    <updated>2026-04-06T06:28:24.210Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>“对话没有上下文限制”——这是 Claude Code 的一个核心承诺。但它真的能做到吗？答案是：通过四级压缩系统，实现”伪无限对话”。这背后的设计非常精妙：不是简单截断，而是智能地压缩和保留关键信息。</p></blockquote><a id="more"></a><h2 id="导读：上下文限制的困境"><a href="#导读：上下文限制的困境" class="headerlink" title="导读：上下文限制的困境"></a>导读：上下文限制的困境</h2><p>所有 LLM 都有上下文限制。Claude 3.5 Sonnet 是 200k tokens，但实际可用空间更小，因为：</p><ul><li>系统提示词占用 ~20k tokens</li><li>工具定义占用 ~15k tokens</li><li>每轮对话累积消息</li></ul><p>假设你进行了 50 轮对话，每轮平均 4k tokens，那就是 200k tokens —— 已经触及限制。</p><p><strong>传统解决方案</strong>：简单截断历史消息。但问题很明显：</p><ul><li>用户之前的重要信息被丢弃</li><li>Agent 可能重复问同样的问题</li><li>长期任务上下文丢失</li></ul><p><strong>Claude Code 的方案</strong>：四级渐进式压缩。</p><hr><h2 id="一、四级压缩策略概览"><a href="#一、四级压缩策略概览" class="headerlink" title="一、四级压缩策略概览"></a>一、四级压缩策略概览</h2><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                    四级压缩策略                              │</span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                             │</span><br><span class="line">│  第 1 级：Snip 压缩                                          │</span><br><span class="line">│    对已处理的消息进行智能裁剪                                │</span><br><span class="line">│    ├─ 移除重复的文件内容                                    │</span><br><span class="line">│    ├─ 截断过长的工具输出                                    │</span><br><span class="line">│    └─ 触发时机：每轮自动                                    │</span><br><span class="line">│                                                             │</span><br><span class="line">│  第 2 级：Micro 压缩                                         │</span><br><span class="line">│    修改已缓存消息的内容                                     │</span><br><span class="line">│    ├─ 不改变缓存键                                          │</span><br><span class="line">│    └─ 触发时机：每轮自动                                    │</span><br><span class="line">│                                                             │</span><br><span class="line">│  第 3 级：上下文折叠（Context Collapse）                     │</span><br><span class="line">│    分阶段摘要历史消息                                       │</span><br><span class="line">│    ├─ 先摘要最旧的消息                                      │</span><br><span class="line">│    ├─ 保留最近的细节                                        │</span><br><span class="line">│    └─ 触发时机：上下文接近限制                              │</span><br><span class="line">│                                                             │</span><br><span class="line">│  第 4 级：Auto Compact                                       │</span><br><span class="line">│    通过 Claude 生成完整摘要                                 │</span><br><span class="line">│    ├─ 替换所有历史消息                                      │</span><br><span class="line">│    └─ 触发时机：上下文严重不足                              │</span><br><span class="line">│                                                             │</span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><hr><h2 id="二、第一级：Snip-压缩"><a href="#二、第一级：Snip-压缩" class="headerlink" title="二、第一级：Snip 压缩"></a>二、第一级：Snip 压缩</h2><h3 id="2-1-工作原理"><a href="#2-1-工作原理" class="headerlink" title="2.1 工作原理"></a>2.1 工作原理</h3><p>Snip 压缩对已处理的消息进行智能裁剪——移除重复的文件内容、过长的工具输出等。</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/compact/snipCompact.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">snipMessages</span>(<span class="params">messages: Message[]</span>): <span class="title">Message</span>[] </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> seen = <span class="keyword">new</span> Set&lt;<span class="built_in">string</span>&gt;()</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> messages.map(<span class="function"><span class="params">msg</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (msg.type === <span class="string">'user'</span>) &#123;</span><br><span class="line">      <span class="comment">// 检测重复的文件内容</span></span><br><span class="line">      <span class="keyword">const</span> content = extractFileContent(msg)</span><br><span class="line">      <span class="keyword">if</span> (seen.has(content)) &#123;</span><br><span class="line">        <span class="comment">// 重复内容，替换为引用</span></span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">          ...msg,</span><br><span class="line">          content: <span class="string">`[Duplicate file content, see earlier in conversation]`</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      seen.add(content)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (msg.type === <span class="string">'tool_result'</span>) &#123;</span><br><span class="line">      <span class="comment">// 截断过长的工具输出</span></span><br><span class="line">      <span class="keyword">if</span> (msg.content.length &gt; MAX_TOOL_RESULT_SIZE) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">          ...msg,</span><br><span class="line">          content: msg.content.slice(<span class="number">0</span>, MAX_TOOL_RESULT_SIZE) + </span><br><span class="line">            <span class="string">`\n... [truncated, <span class="subst">$&#123;msg.content.length&#125;</span> total chars]`</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">    <span class="keyword">return</span> msg</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-2-智能裁剪规则"><a href="#2-2-智能裁剪规则" class="headerlink" title="2.2 智能裁剪规则"></a>2.2 智能裁剪规则</h3><div class="table-container"><table><thead><tr><th>内容类型</th><th>裁剪策略</th></tr></thead><tbody><tr><td>重复文件内容</td><td>替换为引用标记</td></tr><tr><td>大型工具输出</td><td>保留前 4KB + 截断标记</td></tr><tr><td>Base64 图片</td><td>保留元数据，替换内容</td></tr><tr><td>长对话引用</td><td>摘要化</td></tr></tbody></table></div><hr><h2 id="三、第二级：Micro-压缩"><a href="#三、第二级：Micro-压缩" class="headerlink" title="三、第二级：Micro 压缩"></a>三、第二级：Micro 压缩</h2><h3 id="3-1-工作原理"><a href="#3-1-工作原理" class="headerlink" title="3.1 工作原理"></a>3.1 工作原理</h3><p>Micro 压缩修改已缓存消息的内容，而不改变缓存键。这是一种”原地优化”策略。</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/compact/microCompact.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">microCompactMessages</span>(<span class="params">messages: Message[]</span>): <span class="title">Message</span>[] </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> messages.map(<span class="function"><span class="params">msg</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (msg.type === <span class="string">'assistant'</span>) &#123;</span><br><span class="line">      <span class="comment">// 移除多余的空白和格式</span></span><br><span class="line">      <span class="keyword">const</span> compressed = compressContent(msg.content)</span><br><span class="line">      </span><br><span class="line">      <span class="comment">// 移除重复的 tool_use 说明</span></span><br><span class="line">      <span class="keyword">const</span> deduped = deduplicateToolUses(compressed)</span><br><span class="line">      </span><br><span class="line">      <span class="keyword">return</span> &#123; ...msg, content: deduped &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> msg</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-关键特性"><a href="#3-2-关键特性" class="headerlink" title="3.2 关键特性"></a>3.2 关键特性</h3><ul><li><strong>不影响缓存</strong>：缓存键基于消息 ID 和位置，不基于内容</li><li><strong>无损压缩</strong>：保留所有语义信息</li><li><strong>增量应用</strong>：每次处理一点点，避免大变动</li></ul><hr><h2 id="四、第三级：上下文折叠（Context-Collapse）"><a href="#四、第三级：上下文折叠（Context-Collapse）" class="headerlink" title="四、第三级：上下文折叠（Context Collapse）"></a>四、第三级：上下文折叠（Context Collapse）</h2><h3 id="4-1-工作原理"><a href="#4-1-工作原理" class="headerlink" title="4.1 工作原理"></a>4.1 工作原理</h3><p>当上下文接近限制时，系统启动 Context Collapse —— 将历史消息分阶段摘要。</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/contextCollapse/index.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">contextCollapse</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  messages: Message[],</span></span></span><br><span class="line"><span class="function"><span class="params">  options: CollapseOptions</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">Message</span>[]&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 识别可折叠的消息段</span></span><br><span class="line">  <span class="keyword">const</span> segments = identifyCollapsibleSegments(messages)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 按优先级排序（最旧的优先）</span></span><br><span class="line">  <span class="keyword">const</span> sortedSegments = segments.sort(<span class="function">(<span class="params">a, b</span>) =&gt;</span> </span><br><span class="line">    a.startIndex - b.startIndex</span><br><span class="line">  )</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 对每个段生成摘要</span></span><br><span class="line">  <span class="keyword">const</span> summaries: Message[] = []</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> segment of sortedSegments) &#123;</span><br><span class="line">    <span class="keyword">if</span> (shouldCollapse(segment, options)) &#123;</span><br><span class="line">      <span class="keyword">const</span> summary = <span class="keyword">await</span> generateSummary(segment.messages)</span><br><span class="line">      summaries.push(createSummaryMessage(summary, segment))</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 替换原消息段</span></span><br><span class="line">  <span class="keyword">return</span> replaceSegmentsWithSummaries(messages, summaries)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-2-折叠策略"><a href="#4-2-折叠策略" class="headerlink" title="4.2 折叠策略"></a>4.2 折叠策略</h3><p><strong>渐进式折叠</strong>：不是一次性摘要全部，而是<strong>渐进式折叠</strong>——先摘要最旧的消息，保留最近的细节。</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">原消息序列：</span><br><span class="line">[Msg1] [Msg2] [Msg3] [Msg4] [Msg5] [Msg6] [Msg7] [Msg8] [Msg9] [Msg10]</span><br><span class="line">  ↑                                                           ↑</span><br><span class="line">  最旧                                                        最新</span><br><span class="line"></span><br><span class="line">第一次折叠（上下文 &gt; 150k）：</span><br><span class="line">[Summary1] [Msg6] [Msg7] [Msg8] [Msg9] [Msg10]</span><br><span class="line">  ↑ 摘要了 Msg1-Msg5</span><br><span class="line"></span><br><span class="line">第二次折叠（上下文 &gt; 180k）：</span><br><span class="line">[Summary1] [Summary2] [Msg8] [Msg9] [Msg10]</span><br><span class="line">                        ↑ 摘要了 Msg6-Msg7</span><br><span class="line"></span><br><span class="line">第三次折叠（上下文 &gt; 195k）：</span><br><span class="line">[Summary1] [Summary2] [Summary3] [Msg10]</span><br><span class="line">                                  ↑ 摘要了 Msg8-Msg9</span><br></pre></td></tr></table></figure><h3 id="4-3-摘要格式"><a href="#4-3-摘要格式" class="headerlink" title="4.3 摘要格式"></a>4.3 摘要格式</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="section">## Summary of Previous Work</span></span><br><span class="line"></span><br><span class="line"><span class="section">### Tasks Completed</span></span><br><span class="line"><span class="bullet">- </span>Implemented user authentication with JWT</span><br><span class="line"><span class="bullet">- </span>Added password reset functionality</span><br><span class="line"><span class="bullet">- </span>Created user profile page</span><br><span class="line"></span><br><span class="line"><span class="section">### Files Modified</span></span><br><span class="line"><span class="bullet">- </span>src/auth/auth.service.ts: Added JWT token generation</span><br><span class="line"><span class="bullet">- </span>src/user/user.controller.ts: Added profile endpoints</span><br><span class="line"><span class="bullet">- </span>src/user/user.service.ts: Added password reset logic</span><br><span class="line"></span><br><span class="line"><span class="section">### Current State</span></span><br><span class="line"><span class="bullet">- </span>Authentication system is fully functional</span><br><span class="line"><span class="bullet">- </span>Password reset emails are being sent</span><br><span class="line"><span class="bullet">- </span>Profile page is accessible at /profile</span><br><span class="line"></span><br><span class="line"><span class="section">### Pending Items</span></span><br><span class="line"><span class="bullet">- </span>Need to add email verification</span><br><span class="line"><span class="bullet">- </span>Need to implement rate limiting</span><br></pre></td></tr></table></figure><hr><h2 id="五、第四级：Auto-Compact"><a href="#五、第四级：Auto-Compact" class="headerlink" title="五、第四级：Auto Compact"></a>五、第四级：Auto Compact</h2><h3 id="5-1-工作原理"><a href="#5-1-工作原理" class="headerlink" title="5.1 工作原理"></a>5.1 工作原理</h3><p>当所有局部优化都不够时，通过 Claude 自身生成一个完整的对话摘要，替换所有历史消息。</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/compact/autoCompact.ts</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">autoCompact</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  messages: Message[],</span></span></span><br><span class="line"><span class="function"><span class="params">  context: ToolUseContext</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Promise</span>&lt;<span class="title">Message</span>[]&gt; </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 构建 compact 请求</span></span><br><span class="line">  <span class="keyword">const</span> compactPrompt = buildCompactPrompt(messages)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 调用 Claude 生成摘要</span></span><br><span class="line">  <span class="keyword">const</span> summary = <span class="keyword">await</span> query(&#123;</span><br><span class="line">    messages: [createUserMessage(compactPrompt)],</span><br><span class="line">    systemPrompt: COMPACT_SYSTEM_PROMPT,</span><br><span class="line">    toolUseContext: context,</span><br><span class="line">    maxTurns: <span class="number">1</span>,</span><br><span class="line">  &#125;)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 创建新的消息序列</span></span><br><span class="line">  <span class="keyword">const</span> summaryMessage = createSummaryMessage(summary)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 替换历史消息</span></span><br><span class="line">  <span class="keyword">return</span> [summaryMessage]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-触发条件"><a href="#5-2-触发条件" class="headerlink" title="5.2 触发条件"></a>5.2 触发条件</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/services/compact/autoCompact.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">shouldTriggerAutoCompact</span>(<span class="params">state: AutoCompactTracking</span>): <span class="title">boolean</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 检查 token 使用率</span></span><br><span class="line">  <span class="keyword">const</span> usageRatio = state.currentTokens / state.maxTokens</span><br><span class="line">  <span class="keyword">if</span> (usageRatio &lt; <span class="number">0.9</span>) <span class="keyword">return</span> <span class="literal">false</span>  <span class="comment">// 低于 90% 不触发</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 检查是否已经尝试过</span></span><br><span class="line">  <span class="keyword">if</span> (state.hasAttemptedAutoCompact) <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 检查距离上次 compact 的轮数</span></span><br><span class="line">  <span class="keyword">if</span> (state.turnsSinceLastCompact &lt; MIN_TURNS_BETWEEN_COMPACT) <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-3-Compact-系统提示词"><a href="#5-3-Compact-系统提示词" class="headerlink" title="5.3 Compact 系统提示词"></a>5.3 Compact 系统提示词</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> COMPACT_SYSTEM_PROMPT = <span class="string">`</span></span><br><span class="line"><span class="string">You are a summarization assistant. Your job is to create a concise but </span></span><br><span class="line"><span class="string">complete summary of a conversation.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Your summary should:</span></span><br><span class="line"><span class="string">1. Capture all important decisions and their reasoning</span></span><br><span class="line"><span class="string">2. List all files that were created or modified</span></span><br><span class="line"><span class="string">3. Note any pending tasks or open questions</span></span><br><span class="line"><span class="string">4. Preserve the context needed to continue the work</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Format your summary as:</span></span><br><span class="line"><span class="string">## Summary</span></span><br><span class="line"><span class="string">[Brief overview of the conversation]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">## Decisions Made</span></span><br><span class="line"><span class="string">- [Decision 1]: [Reasoning]</span></span><br><span class="line"><span class="string">- [Decision 2]: [Reasoning]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">## Files Modified</span></span><br><span class="line"><span class="string">- [File path]: [What was changed]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">## Pending Tasks</span></span><br><span class="line"><span class="string">- [Task 1]</span></span><br><span class="line"><span class="string">- [Task 2]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">## Context for Continuation</span></span><br><span class="line"><span class="string">[Any other relevant context]</span></span><br><span class="line"><span class="string">`</span></span><br></pre></td></tr></table></figure><hr><h2 id="六、上下文注入"><a href="#六、上下文注入" class="headerlink" title="六、上下文注入"></a>六、上下文注入</h2><h3 id="6-1-系统上下文"><a href="#6-1-系统上下文" class="headerlink" title="6.1 系统上下文"></a>6.1 系统上下文</h3><p>每次 API 调用前，自动注入系统上下文：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/context.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getSystemContext</span>(<span class="params"></span>): <span class="title">SystemContext</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    gitStatus: getGitStatus(),          <span class="comment">// 当前分支、最近提交、文件状态</span></span><br><span class="line">    currentDate: <span class="keyword">new</span> <span class="built_in">Date</span>().toISOString(),</span><br><span class="line">    cacheBreakerInjection: getSystemInjection(),</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-2-用户上下文"><a href="#6-2-用户上下文" class="headerlink" title="6.2 用户上下文"></a>6.2 用户上下文</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/context.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUserContext</span>(<span class="params"></span>): <span class="title">UserContext</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    claudeMdContent: loadClaudeMdFiles(),  <span class="comment">// 所有 CLAUDE.md 合并内容</span></span><br><span class="line">    mcpInstructions: getMcpInstructions(),  <span class="comment">// MCP 服务器指令</span></span><br><span class="line">    memoryContent: loadMemoryPrompt(),      <span class="comment">// 记忆系统内容</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-3-系统提醒"><a href="#6-3-系统提醒" class="headerlink" title="6.3 系统提醒"></a>6.3 系统提醒</h3><p>系统提醒是一种特殊的<strong>附件消息</strong>，注入到工具结果或用户消息中：</p><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">system-reminder</span>&gt;</span></span><br><span class="line">  这里是系统级的上下文信息，与具体的工具结果无关。</span><br><span class="line"><span class="tag">&lt;/<span class="name">system-reminder</span>&gt;</span></span><br></pre></td></tr></table></figure><p>用途包括：</p><ul><li>文件读取时的安全警告</li><li>记忆系统的时效提醒</li><li>用户侧问的附带信息</li><li>Deferred 工具的可用通知</li></ul><hr><h2 id="七、Token-预算管理"><a href="#七、Token-预算管理" class="headerlink" title="七、Token 预算管理"></a>七、Token 预算管理</h2><h3 id="7-1-预算计算"><a href="#7-1-预算计算" class="headerlink" title="7.1 预算计算"></a>7.1 预算计算</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/query/tokenBudget.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">calculateTokenBudget</span>(<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  model: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  messages: Message[],</span></span></span><br><span class="line"><span class="function"><span class="params">  systemPrompt: <span class="built_in">string</span>,</span></span></span><br><span class="line"><span class="function"><span class="params">  tools: Tools,</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">TokenBudget</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 1. 获取模型上下文限制</span></span><br><span class="line">  <span class="keyword">const</span> contextLimit = getModelContextLimit(model)  <span class="comment">// 如 200k</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2. 计算固定消耗</span></span><br><span class="line">  <span class="keyword">const</span> systemPromptTokens = countTokens(systemPrompt)</span><br><span class="line">  <span class="keyword">const</span> toolsTokens = countToolsTokens(tools)</span><br><span class="line">  <span class="keyword">const</span> fixedCost = systemPromptTokens + toolsTokens</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3. 计算消息消耗</span></span><br><span class="line">  <span class="keyword">const</span> messagesTokens = countMessagesTokens(messages)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4. 计算可用预算</span></span><br><span class="line">  <span class="keyword">const</span> availableBudget = contextLimit - fixedCost - messagesTokens</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 5. 预留输出空间</span></span><br><span class="line">  <span class="keyword">const</span> outputReserve = <span class="number">8192</span>  <span class="comment">// 默认输出限制</span></span><br><span class="line">  <span class="keyword">const</span> finalBudget = availableBudget - outputReserve</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    total: contextLimit,</span><br><span class="line">    fixed: fixedCost,</span><br><span class="line">    messages: messagesTokens,</span><br><span class="line">    available: finalBudget,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-2-预算警告"><a href="#7-2-预算警告" class="headerlink" title="7.2 预算警告"></a>7.2 预算警告</h3><figure class="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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/utils/tokens.ts</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">calculateTokenWarningState</span>(<span class="params">budget: TokenBudget</span>): <span class="title">TokenWarningState</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> usageRatio = budget.messages / budget.available</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (usageRatio &gt; <span class="number">0.95</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; level: <span class="string">'critical'</span>, message: <span class="string">'Context nearly exhausted'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (usageRatio &gt; <span class="number">0.85</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; level: <span class="string">'warning'</span>, message: <span class="string">'Context running low'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (usageRatio &gt; <span class="number">0.70</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; level: <span class="string">'info'</span>, message: <span class="string">'Context usage moderate'</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123; level: <span class="string">'ok'</span>, message: <span class="string">''</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="八、恢复与压缩的关系"><a href="#八、恢复与压缩的关系" class="headerlink" title="八、恢复与压缩的关系"></a>八、恢复与压缩的关系</h2><h3 id="8-1-压缩触发的恢复"><a href="#8-1-压缩触发的恢复" class="headerlink" title="8.1 压缩触发的恢复"></a>8.1 压缩触发的恢复</h3><p>当压缩策略执行后，会记录状态：</p><figure class="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">state = &#123;</span><br><span class="line">  ...state,</span><br><span class="line">  autoCompactTracking: &#123;</span><br><span class="line">    hasAttemptedAutoCompact: <span class="literal">true</span>,</span><br><span class="line">    turnsSinceLastCompact: <span class="number">0</span>,</span><br><span class="line">    compressedTokens: savedTokens,</span><br><span class="line">  &#125;,</span><br><span class="line">  transition: &#123; reason: <span class="string">'reactive_compact_retry'</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="8-2-恢复链"><a href="#8-2-恢复链" class="headerlink" title="8.2 恢复链"></a>8.2 恢复链</h3><figure class="highlight plain"><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">prompt_too_long 错误</span><br><span class="line">  │</span><br><span class="line">  ├─ 尝试 Snip 压缩 → 重试</span><br><span class="line">  │   └─ 成功 → 继续</span><br><span class="line">  │</span><br><span class="line">  ├─ 尝试 Micro 压缩 → 重试</span><br><span class="line">  │   └─ 成功 → 继续</span><br><span class="line">  │</span><br><span class="line">  ├─ 尝试 Context Collapse → 重试</span><br><span class="line">  │   └─ 成功 → 继续</span><br><span class="line">  │</span><br><span class="line">  └─ 尝试 Auto Compact → 重试</span><br><span class="line">      └─ 成功 → 继续</span><br><span class="line">      └─ 失败 → 报错给用户</span><br></pre></td></tr></table></figure><hr><h2 id="九、关键源文件索引"><a href="#九、关键源文件索引" class="headerlink" title="九、关键源文件索引"></a>九、关键源文件索引</h2><div class="table-container"><table><thead><tr><th>文件</th><th>职责</th></tr></thead><tbody><tr><td><code>src/services/compact/autoCompact.ts</code></td><td>自动压缩触发和管理</td></tr><tr><td><code>src/services/compact/compact.ts</code></td><td>压缩实现</td></tr><tr><td><code>src/services/compact/reactiveCompact.ts</code></td><td>反应式压缩（错误触发）</td></tr><tr><td><code>src/services/contextCollapse/index.ts</code></td><td>上下文折叠实现</td></tr><tr><td><code>src/services/compact/snipCompact.ts</code></td><td>Snip 压缩</td></tr><tr><td><code>src/utils/tokens.ts</code></td><td>Token 计数和预算管理</td></tr><tr><td><code>src/context.ts</code></td><td>系统和用户上下文</td></tr><tr><td><code>src/utils/attachments.ts</code></td><td>系统提醒附件</td></tr></tbody></table></div><hr><h2 id="十、总结"><a href="#十、总结" class="headerlink" title="十、总结"></a>十、总结</h2><p>Claude Code 的四级压缩系统是其”无限对话”承诺的技术基础：</p><ol><li><strong>Snip 压缩</strong>：智能移除重复内容，每轮自动</li><li><strong>Micro 压缩</strong>：原地优化缓存消息，不影响缓存</li><li><strong>Context Collapse</strong>：渐进式摘要，保留最近细节</li><li><strong>Auto Compact</strong>：Claude 生成的完整摘要</li></ol><p>这个设计的关键洞察是：<strong>不是简单截断，而是智能压缩</strong>。通过保留关键信息（决策、文件修改、待办事项），Agent 能够在压缩后继续有效工作。</p><hr><p><strong>系列文章导航：</strong></p><ul><li>上一篇：<a href="/claude-code-multi-agent/">多 Agent 编排：四种代理类型与协作机制</a></li><li>下一篇：<a href="/claude-code-system-prompt/">System Prompt 工程：动态组装与缓存优化</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;“对话没有上下文限制”——这是 Claude Code 的一个核心承诺。但它真的能做到吗？答案是：通过四级压缩系统，实现”伪无限对话”。这背后的设计非常精妙：不是简单截断，而是智能地压缩和保留关键信息。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Claude Code" scheme="https://donehub.github.io/categories/Claude-Code/"/>
    
    
    <category term="Context Compression" scheme="https://donehub.github.io/tags/Context-Compression/"/>
    
  </entry>
  
</feed>
