<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Jason Dong</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>http://jasondong97.github.io/</id>
  <link href="http://jasondong97.github.io/" rel="alternate"/>
  <link href="http://jasondong97.github.io/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Jason Dong</rights>
  <subtitle>后端工程师</subtitle>
  <title>Jason Dong</title>
  <updated>2026-03-08T09:24:16.381Z</updated>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="Agent应用开发实践踩坑与经验分享"><a href="#Agent应用开发实践踩坑与经验分享" class="headerlink" title="Agent应用开发实践踩坑与经验分享"></a>Agent应用开发实践踩坑与经验分享</h1><p>学完 Hello-Agents 教程之后，最后一个任务是毕业设计。用所学的知识自己手搓一个Agent应用，刚好那段时间 Code Agent 特别火，Cursor、Claude Code、Codex… 各家都在推自己的产品。心想既然要练手，不如复刻一个 Code Agent，自己手搓一遍，才能真正理解这些产品为什么好用，以及它们到底在工程上做对了什么。</p><p>于是就有了这个项目。<br>基于Hello-Agents框架的Code Agent代码仓库：<a href="https://github.com/datawhalechina/hello-agents/tree/main/Co-creation-projects/YYHDBL-HelloCodeAgentCli">https://github.com/datawhalechina/hello-agents/tree/main/Co-creation-projects/YYHDBL-HelloCodeAgentCli</a></p><p>重构后MyCodeAgent代码仓库：<a href="https://github.com/YYHDBL/MyCodeAgent.git">https://github.com/YYHDBL/MyCodeAgent.git</a></p><p>这篇文章不是教程，是我在做这个 Code Agent 项目过程中踩过的坑、走过的弯路、以及最后怎么解决的一些记录。  </p><hr><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ul><li><a href="#%E7%AC%AC%E4%B8%80%E7%AB%A0%E7%9C%8B%E4%BA%86%E5%A4%AA%E5%A4%9A%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E5%8F%8D%E8%80%8C%E8%B8%A9%E8%BF%9B%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%A4%A7%E5%9D%91">第一章：看了太多最佳实践，反而踩进第一个大坑</a></li><li><a href="#%E7%AC%AC%E4%BA%8C%E7%AB%A0%E4%B8%80%E6%AC%A1%E7%AE%A1%E9%81%93%E5%91%BD%E4%BB%A4%E4%BA%8B%E6%95%85%E6%88%91%E7%AC%AC%E4%B8%80%E6%AC%A1%E7%9C%8B%E8%A7%81%E4%B8%8D%E5%8F%AF%E8%AF%8A%E6%96%AD%E6%9C%89%E5%A4%9A%E8%87%B4%E5%91%BD">第二章：一次管道命令事故——我第一次看见”不可诊断”有多致命</a></li><li><a href="#%E7%AC%AC%E4%B8%89%E7%AB%A0%E5%B7%A5%E5%85%B7%E8%AE%BE%E8%AE%A1%E7%9A%84-goldilocks-%E5%8C%BA">第三章：工具设计的 Goldilocks 区</a></li><li><a href="#%E7%AC%AC%E5%9B%9B%E7%AB%A0%E6%8F%90%E7%A4%BA%E8%AF%8D%E4%B8%8D%E6%98%AF%E9%AD%94%E6%B3%95%E5%92%92%E8%AF%AD%E8%80%8C%E6%98%AF-agent-%E7%9A%84%E6%8E%A7%E5%88%B6%E9%9D%A2">第四章：提示词不是魔法咒语，而是 Agent 的控制面</a></li><li><a href="#%E7%AC%AC%E4%BA%94%E7%AB%A0%E4%B8%8A%E4%B8%8B%E6%96%87%E4%B8%8D%E6%98%AF%E5%86%85%E5%AD%98%E5%AE%B9%E9%87%8F%E9%97%AE%E9%A2%98%E8%80%8C%E6%98%AF%E6%B3%A8%E6%84%8F%E5%8A%9B%E8%B0%83%E5%BA%A6%E9%97%AE%E9%A2%98">第五章：上下文不是内存容量问题，而是注意力调度问题</a></li><li><a href="#%E7%AC%AC%E5%85%AD%E7%AB%A0%E5%8F%AF%E8%A7%82%E6%B5%8B%E6%80%A7%E6%8A%8A%E9%BB%91%E7%9B%92%E5%8F%98%E7%8E%BB%E7%92%83%E7%9B%92">第六章：可观测性把黑盒变玻璃盒</a></li><li><a href="#%E7%AC%AC%E4%B8%83%E7%AB%A0%E4%BB%8E%E4%B8%80%E4%B8%AA%E9%A1%B9%E7%9B%AE%E6%8A%BD%E5%87%BA%E6%9D%A5%E7%9A%84%E9%80%9A%E7%94%A8%E6%96%B9%E6%B3%95%E8%AE%BA">第七章：从一个项目抽出来的通用方法论</a></li></ul><hr><h1 id="第一章：看了太多最佳实践，反而踩进第一个大坑"><a href="#第一章：看了太多最佳实践，反而踩进第一个大坑" class="headerlink" title="第一章：看了太多最佳实践，反而踩进第一个大坑"></a>第一章：看了太多最佳实践，反而踩进第一个大坑</h1><p>刚动手写代码时，我查阅了大量业界的 Agent 设计实践。比如 Manus 团队分享的《上下文工程经验教训》，还有 Anthropic 官方的《Building agents with the Claude Agent SDK》。看着这些顶流大厂毫无保留地分享”最佳实践”，我心想：反正现在有 Claude Code，让 AI 帮我把这些高级概念全实现一遍不就行了？</p><p>于是，我不假思索地堆砌了各种看似优雅的设计：多层记忆（Memory System）、复杂的上下文工程、多智能体系统（Multi-Agent）……不得不说，Claude Code 确实牛逼，很快就帮我生成了一大堆逻辑复杂的代码。</p><h2 id="天崩开局"><a href="#天崩开局" class="headerlink" title="天崩开局"></a>天崩开局</h2><p>但当我满怀期待地跑起第一版测试时，现实狠狠打了我一巴掌：整个系统烂透了。</p><p>面对一个极其简单的修改需求，Agent 像发疯一样调用了七八种工具，进行了好几轮的”左右脑互搏”。最终，我只收获了一段根本跑不通的残缺代码，以及一张严重超支的 Token 欠费账单。</p><p>看着满屏的报错，我才意识到：Agent 开发和传统软件开发很不一样。</p><p>以前我们做传统后端开发，习惯先画好架构图，再写代码。图纸够优雅，系统就稳固。这是程序员的本能。</p><p>但 Agent 开发不一样。你是在跟一个大模型打交道，它本身就是概率性的——同样的输入，每次可能给你完全不同的输出。</p><p>我在这个不确定的地基上，强行叠加了一套自己都没验证过的复杂架构。多智能体、Plan-and-Execute……这些设计彼此交叉，让不确定性成倍放大。</p><p>结果是：复杂架构没能兜住底，反而因为状态流转太多、工具交叉太复杂，让模型错得更离谱。错误在各组件之间来回传，我连排查都无从下手。</p><p>那些大厂的”最佳实践”当然是好东西，但我忽略了一点：那些复杂架构是他们踩了无数坑、耗费了海量 token 之后演进出来的结果，不是新手上路的起点。</p><h2 id="推倒重来"><a href="#推倒重来" class="headerlink" title="推倒重来"></a>推倒重来</h2><p>看着这堆连简单读取文件都会陷入死循环的代码，我做了一个违背祖宗的决定——删库，推倒重来。</p><p>奉行”Less is more”的原则，我直接复用了 Hello-Agent 最基础的主干，把最短的链路先跑通。核心组件被精简到只剩这几块：</p><table><thead><tr><th>组件</th><th>核心职责</th></tr></thead><tbody><tr><td>ReActAgent</td><td>驱动 Thought → Action → Observation 的基础认知循环</td></tr><tr><td>ToolRegistry</td><td>负责工具的注册与调用分发</td></tr><tr><td>ContextBuilder</td><td>拼接系统规则、历史记录与环境证据</td></tr><tr><td>TerminalTool</td><td>在目标代码仓库内执行实际命令</td></tr><tr><td>Message</td><td>统一的会话消息数据结构</td></tr></tbody></table><p>代码层面，我没有搞任何花哨的设计模式，直接在 code_agent.py 里粗暴地把它们攒了起来：</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></pre></td><td class="code"><pre><code class="hljs python">self.terminal_tool = TerminalTool(<br>    workspace=<span class="hljs-built_in">str</span>(self.paths.repo_root),<br>    timeout=<span class="hljs-number">60</span>,<br>    confirm_dangerous=<span class="hljs-literal">True</span>,<br>    default_shell_mode=<span class="hljs-literal">True</span>,  <br>)<br>self.registry = ToolRegistry()<br>self.registry.register_tool(self.terminal_tool)<br></code></pre></td></tr></table></figure><p>看着这个简陋的 V0 版本成功在终端里跑通了第一个极简任务，我长舒了一口气。</p><p><img src="/img/ai-agent-learning/image-20260225143343219" alt="image-20260225143343219"></p><p>只要可以完成最基础的基础对话和工具调用，我们就能在真实任务环境中验证和迭代优化。</p><p>先跑起来，比一步更重要。</p><hr><h1 id="第二章：一次管道命令事故——我第一次看见”不可诊断”有多致命"><a href="#第二章：一次管道命令事故——我第一次看见”不可诊断”有多致命" class="headerlink" title="第二章：一次管道命令事故——我第一次看见”不可诊断”有多致命"></a>第二章：一次管道命令事故——我第一次看见”不可诊断”有多致命</h1><p>V0 跑起来之后，我开始放松对工具的约束。既然架构已经精简了，让模型自由组合命令应该没问题吧？</p><p>于是我给 Terminal Tool 开了绿灯：<code>default_shell_mode=True</code>。这意味着模型不只能跑单条命令，还能写管道、重定向、子命令——就像人一样。</p><h2 id="事故经过"><a href="#事故经过" class="headerlink" title="事故经过"></a>事故经过</h2><p>那天我提了个简单需求：”帮我搜一下 <code>process_data</code> 函数的定义”。</p><p>模型很快给出了一条看起来挺专业的命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">rg -n <span class="hljs-string">&quot;def process_data&quot;</span> src/ | grep -v <span class="hljs-built_in">test</span> | sed -n <span class="hljs-string">&#x27;1,50p&#x27;</span><br></code></pre></td></tr></table></figure><p>老工程师常用的组合拳：先搜函数定义，过滤掉测试文件，再取前 50 行。没什么问题。</p><p>但执行结果是空的。Agent 看到这个空结果，愣了一下，然后开始补救：</p><p><strong>第一轮重试：</strong></p><figure class="highlight avrasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs avrasm"><span class="hljs-symbol">Thought:</span> 看起来没找到，可能是搜索范围太窄了。让我扩大搜索范围。<br><span class="hljs-symbol">Action:</span> rg -n <span class="hljs-string">&quot;process_data&quot;</span> | head <span class="hljs-number">-100</span><br></code></pre></td></tr></table></figure><p>还是空。</p><p><strong>第二轮重试：</strong></p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs vim">Thought: 可能函数名记错了，试试模糊匹配。<br>Action: rg -n <span class="hljs-string">&quot;process.*data&quot;</span> src/ --<span class="hljs-built_in">type</span> <span class="hljs-keyword">py</span> | <span class="hljs-keyword">grep</span> <span class="hljs-string">&quot;def &quot;</span><br></code></pre></td></tr></table></figure><p>还是空。</p><p><strong>第三轮重试：</strong></p><figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sqf">Thought: 看来这个函数可能不存在，或者我搜错了目录。让我先列出目录结构。<br><span class="hljs-built_in">Action</span>: <span class="hljs-built_in">find</span> src/ -<span class="hljs-built_in">name</span> <span class="hljs-string">&quot;*.py&quot;</span> | xargs grep -l <span class="hljs-string">&quot;process_data&quot;</span><br></code></pre></td></tr></table></figure><p>结果依然是空。三轮尝试后，它放弃了，告诉我：”我在仓库里没有找到 <code>process_data</code> 函数的定义，可能函数名有误，或者它不在 Python 文件里。”</p><p>但我手动去仓库里看了，那个函数明明就在 <code>src/utils/helpers.py</code> 第 42 行。</p><h2 id="排查过程"><a href="#排查过程" class="headerlink" title="排查过程"></a>排查过程</h2><p>我复制那条命令到终端自己跑，发现 <code>rg</code> 报错了：</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><code class="hljs bash">$ rg -n <span class="hljs-string">&quot;def process_data&quot;</span> src/ | grep -v <span class="hljs-built_in">test</span> | sed -n <span class="hljs-string">&#x27;1,50p&#x27;</span><br>Error: path <span class="hljs-string">&#x27;src/&#x27;</span> does not exist<br></code></pre></td></tr></table></figure><p>原来我启动 Agent 时的工作目录不是项目根目录，而是项目下的一个子目录。<code>src/</code> 相对当前目录不存在，rg 直接报错退出。</p><p>但在 Agent 那边，错误信息被管道吞掉了。因为命令用了 <code>|</code>，rg 的错误输出没有传到 stdout，而是被管道导向了下一个命令的输入。grep 收到的是空输入，自然输出空；sed 也是空。</p><p><strong>错误在链路中被压扁了。</strong> Agent 看到的只是一个空字符串，它根本不知道上游失败了。</p><p>最坑的是，模型基于这个错误信息做出了完全错误的判断。它以为”确实没找到”，于是开始各种补救：换搜索词、换目录、甚至怀疑我是不是记错了函数名。这些动作全都是基于一个错误的判断，白白消耗了大量 token。</p><p><img src="/img/ai-agent-learning/image-20260225143439420" alt="image-20260225143439420"></p><h2 id="当时的错误修复方向"><a href="#当时的错误修复方向" class="headerlink" title="当时的错误修复方向"></a>当时的错误修复方向</h2><p>我第一反应是：Bash 工具太危险了，得加限制。</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></pre></td><td class="code"><pre><code class="hljs python">SHELL_META_TOKENS = [<span class="hljs-string">&quot;|&quot;</span>, <span class="hljs-string">&quot;||&quot;</span>, <span class="hljs-string">&quot;&amp;&amp;&quot;</span>, <span class="hljs-string">&quot;;&quot;</span>, <span class="hljs-string">&quot;&gt;&quot;</span>, <span class="hljs-string">&quot;&gt;&gt;&quot;</span>, <span class="hljs-string">&quot;&lt;&quot;</span>, <span class="hljs-string">&quot;$(&quot;</span>, <span class="hljs-string">&quot;`&quot;</span>]<br>DANGEROUS_BASE_COMMANDS = &#123;<span class="hljs-string">&quot;rm&quot;</span>, <span class="hljs-string">&quot;chmod&quot;</span>, <span class="hljs-string">&quot;mv&quot;</span>, <span class="hljs-string">&quot;dd&quot;</span>&#125;<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">validate_command</span>(<span class="hljs-params">cmd</span>):<br>    <span class="hljs-comment"># 检查是否包含管道或重定向</span><br>    <span class="hljs-keyword">for</span> token <span class="hljs-keyword">in</span> SHELL_META_TOKENS:<br>        <span class="hljs-keyword">if</span> token <span class="hljs-keyword">in</span> cmd:<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>, <span class="hljs-string">f&quot;包含非法字符: <span class="hljs-subst">&#123;token&#125;</span>&quot;</span><br>    <br>    <span class="hljs-comment"># 检查基础命令是否在白名单</span><br>    base_cmd = cmd.split()[<span class="hljs-number">0</span>]<br>    <span class="hljs-keyword">if</span> base_cmd <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> ALLOWED_COMMANDS:<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>, <span class="hljs-string">f&quot;命令 <span class="hljs-subst">&#123;base_cmd&#125;</span> 不在白名单&quot;</span><br>    <br>    <span class="hljs-comment"># 检查危险命令</span><br>    <span class="hljs-keyword">if</span> base_cmd <span class="hljs-keyword">in</span> DANGEROUS_BASE_COMMANDS:<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>, <span class="hljs-string">&quot;危险命令，禁止执行&quot;</span><br>    <br>    <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>, <span class="hljs-string">&quot;OK&quot;</span><br></code></pre></td></tr></table></figure><p>但很快我发现，shell 太灵活了。你禁了 <code>|</code>，它可以用 <code>$(...)</code> 子命令替换；你禁了 <code>&gt;</code>，它可以用 <code>tee</code>；你禁了 <code>rm</code>，它可以用 <code>&gt; file</code> 来清空文件。</p><p>补丁越打越多，代码越写越长，但那个根本问题——“到底是哪一步失败了”——依然存在。</p><p>即使我封死了所有管道和重定向，只允许最简单的单条命令，问题还在：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">rg <span class="hljs-string">&quot;pattern&quot;</span> src/<br></code></pre></td></tr></table></figure><p>如果返回空，我还是不知道是”仓库里真的没有”，还是”rg 因为路径错误没执行”。模型依然无法针对性地纠错。</p><h2 id="根因定位"><a href="#根因定位" class="headerlink" title="根因定位"></a>根因定位</h2><p>后来我才想明白，这件事的根因不是”命令太危险”，而是<strong>不可诊断</strong>。</p><p>具体来说有三个问题：</p><p><strong>第一，多步骤被塞进一个 Action。</strong> 管道把好几步逻辑打包在一起，中间状态全丢了。Agent 只能看到最终结果，看不到执行过程。</p><p><strong>第二，观察信号只有一个终态。</strong> 成功、失败、空结果，全都混在一起。模型分不清楚”真的没找到”和”查找过程中出错了”。</p><p><strong>第三，模型无法针对性纠错。</strong> 它不知道 <code>rg</code>、<code>grep</code>、<code>sed</code> 谁出了问题，下一步只能瞎猜。重试不是基于”修正错误”，而是基于”赌运气”。</p><p>给模型更高自由度，不是在提升能力上限，而是在放大不确定性。它确实能写出更”聪明”的命令，但一旦出错，连你自己都排查不了它在哪一步”聪明反被聪明误”了。</p><h2 id="现在的做法"><a href="#现在的做法" class="headerlink" title="现在的做法"></a>现在的做法</h2><p>后来我直接把 Bash 降级了——不是删掉，而是明确它的定位：只处理那些原子工具覆盖不到的边角需求，不走主链路。</p><p>高频操作全部拆成原子工具：</p><table><thead><tr><th>工具</th><th>功能</th><th>返回格式</th></tr></thead><tbody><tr><td>LS</td><td>列目录</td><td><code>&#123;status, data: &#123;entries&#125;, text&#125;</code></td></tr><tr><td>Glob</td><td>按名字找文件</td><td><code>&#123;status, data: &#123;paths&#125;, text&#125;</code></td></tr><tr><td>Grep</td><td>按内容搜索</td><td><code>&#123;status, data: &#123;matches&#125;, text&#125;</code></td></tr><tr><td>Read</td><td>带行号读取</td><td><code>&#123;status, data: &#123;content&#125;, text&#125;</code></td></tr></tbody></table><p>每个工具都有明确的状态码：</p><ul><li><code>success</code>：任务完成，结果在 data 里</li><li><code>partial</code>：任务完成但内容被截断</li><li><code>error</code>：任务失败，error 里有具体错误码</li></ul><p>比如 Glob 搜不到文件：</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;status&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;success&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;data&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;paths&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;text&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;No files matching &#x27;*.xyz&#x27; found&quot;</span><br><span class="hljs-punctuation">&#125;</span><br></code></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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;status&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;error&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;error&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;code&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;NOT_FOUND&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;message&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Path &#x27;src/&#x27; does not exist&quot;</span><span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>模型能清晰区分”确实没有”和”出错了”。</p><p>Bash 的硬约束也明确了：</p><ul><li>禁止读&#x2F;搜&#x2F;列：<code>ls</code>&#x2F;<code>cat</code>&#x2F;<code>head</code>&#x2F;<code>grep</code>&#x2F;<code>find</code>&#x2F;<code>rg</code> 这些有专门工具</li><li>禁止交互：vim、nano、top、ssh</li><li>禁止网络（默认）：curl&#x2F;wget 被禁</li><li>黑名单：rm -rf &#x2F;、sudo&#x2F;su、mkfs&#x2F;fdisk</li></ul><p>这样做之后，调试变得简单很多。出了问题看日志就知道是哪一步：</p><ul><li>Glob 返回了空数组 → 确实没这个文件</li><li>Glob 返回了 NOT_FOUND → 路径错了</li><li>Grep 返回了 timeout → 搜索范围太大</li></ul><p>模型也能根据具体的错误码决定下一步：路径错了就换路径，超时了就缩小范围，真的没找到就告诉用户。</p><h2 id="本章结论"><a href="#本章结论" class="headerlink" title="本章结论"></a>本章结论</h2><p><strong>可诊断性是可恢复性的前提。</strong></p><p>如果不知道哪坏了，就修不好。如果不知道失败发生在哪一步，就无法针对性纠正。</p><p>在 Agent 开发里，给模型自由组合命令的能力，听起来很美好，但实际上是在制造黑盒。看似高效的管道命令，把错误信息压扁成一个个无法区分的空结果，让模型在错误的道路上越跑越远。</p><p>原子工具虽然步骤繁琐，但每一步都有明确的输入、输出、状态。出了问题，你能定位；模型错了，你能纠正。</p><p><strong>可控性比一次性完成任务重要得多。</strong></p><hr><h1 id="第三章：工具设计的-Goldilocks-区——不是越自由越好，也不是越碎越好"><a href="#第三章：工具设计的-Goldilocks-区——不是越自由越好，也不是越碎越好" class="headerlink" title="第三章：工具设计的 Goldilocks 区——不是越自由越好，也不是越碎越好"></a>第三章：工具设计的 Goldilocks 区——不是越自由越好，也不是越碎越好</h1><p>第三章之后，我开始把工具拆开。Terminal Tool 那种什么都管的万能模式确实有问题，拆成原子工具后，调试变得清晰多了。</p><p>但我很快又踩了一个新坑：<strong>拆得太碎了</strong>。</p><h2 id="两个极端我都踩过"><a href="#两个极端我都踩过" class="headerlink" title="两个极端我都踩过"></a>两个极端我都踩过</h2><h3 id="极端-A：万能工具"><a href="#极端-A：万能工具" class="headerlink" title="极端 A：万能工具"></a>极端 A：万能工具</h3><p>第一个极端你已经见过了。一个 Terminal Tool 什么都能做：管道、重定向、子命令、环境变量——完全放开。</p><p>那时候我觉得，LLM 这么聪明，给它足够自由度，应该能像工程师一样操作。<code>rg | grep | sed</code> 这种组合命令效率很高。</p><p>结果你也知道了：错误被管道吞掉，模型瞎猜重试，token 哗哗流，问题还没解决。</p><h3 id="极端-B：过度原子化"><a href="#极端-B：过度原子化" class="headerlink" title="极端 B：过度原子化"></a>极端 B：过度原子化</h3><p>意识到万能工具有问题后，我走向了另一个极端：把每个功能点都拆成独立工具，追求极致的原子化。</p><p>那时候我的工具列表长这样：</p><ul><li><code>ListDir</code>：列出目录内容</li><li><code>ListDirRecursive</code>：递归列出目录</li><li><code>FindByName</code>：按文件名查找</li><li><code>FindByPattern</code>：按通配符查找</li><li><code>SearchExact</code>：精确匹配搜索</li><li><code>SearchRegex</code>：正则匹配搜索</li><li><code>SearchFuzzy</code>：模糊匹配搜索</li><li><code>ReadLines</code>：读取指定行范围</li><li><code>ReadOffset</code>：读取指定字节偏移</li><li><code>ReadFull</code>：读取完整文件</li><li>…</li></ul><p>问题很快就来了。</p><p><strong>第一，模型开始”选工具困难”。</strong></p><p>都是找文件，<code>FindByName</code>、<code>FindByPattern</code>、<code>Glob</code>，用哪个？模型经常在第一步就卡住，它要花好几轮才能确定”哦，原来应该用 Glob”。</p><p>有一次我让它”找一下所有测试文件”，它先调了 <code>ListDirRecursive</code> 列出所有文件，然后想调 <code>SearchRegex</code> 来过滤，但发现 <code>SearchRegex</code> 是搜内容不是搜文件名，于是又调回 <code>ListDirRecursive</code> 拿更多上下文，最后才选对 <code>Glob</code>。</p><p>本来一步搞定的事，用了四步。</p><p><strong>第二，Schema 噪声淹没上下文。</strong></p><p>每个工具都有参数描述、类型定义、约束条件。十几个工具的 schema 加起来，几千 token 就出去了。</p><p>模型还没开始解决任务，就先消耗大量注意力在”读说明书”上。更糟糕的是，长 schema 容易让模型”选择性失明”——它可能只注意到部分工具，或者把参数搞混。</p><p><strong>第三，维护成本爆炸。</strong></p><p>每个工具都要单独写测试、单独调优、单独处理边界情况。<code>FindByName</code> 和 <code>FindByPattern</code> 有 80% 的逻辑是重复的，但因为是两个独立工具，我得维护两份代码。</p><p>这时候我才意识到，<strong>工具系统不是乐高颗粒越细越好</strong>。过度封装和过度拆分，本质上都会把系统推向不稳定，只是一个坏在执行期（万能工具），一个坏在决策期（过度原子化）。</p><h2 id="转折点：找那个”刚刚好”的度"><a href="#转折点：找那个”刚刚好”的度" class="headerlink" title="转折点：找那个”刚刚好”的度"></a>转折点：找那个”刚刚好”的度</h2><p>我后来给自己定了一个判断框架：<strong>频率 × 确定性</strong>。</p><ul><li><strong>高频、强确定动作</strong>：必须原子化，一步完成，不可再分</li><li><strong>中频、带副作用动作</strong>：必须受控，关键操作加保险</li><li><strong>低频、弱确定动作</strong>：保留弹性，但放到兜底层，明确禁止什么而非允许什么</li></ul><p>按这个框架，我重新设计了工具体系，形成三层结构：</p><table><thead><tr><th>层级</th><th>代表工具</th><th>设计目标</th><th>典型约束</th></tr></thead><tbody><tr><td>高频原子层</td><td>LS &#x2F; Glob &#x2F; Grep &#x2F; Read</td><td>一步一证据，便于纠错</td><td>输入输出强约束</td></tr><tr><td>中频受控层</td><td>Write &#x2F; Edit &#x2F; MultiEdit</td><td>改动可验证、可回滚</td><td>读后写 + 乐观锁</td></tr><tr><td>低频兜底层</td><td>Bash</td><td>处理非常规需求</td><td>明确禁区，不走主链</td></tr></tbody></table><p><img src="/img/ai-agent-learning/image-20260225143700868" alt="image-20260225143700868"></p><p>这套分层不是”架构美学”，是被真实故障逼出来的。它最大的价值是降低模型决策负担，让高频路径更短、更清晰。</p><h2 id="高频原子层：必须稳定"><a href="#高频原子层：必须稳定" class="headerlink" title="高频原子层：必须稳定"></a>高频原子层：必须稳定</h2><p>这层工具是 Agent 的”主力武器”，使用频率最高，必须极致稳定。</p><h3 id="Glob：找文件，一个工具就够了"><a href="#Glob：找文件，一个工具就够了" class="headerlink" title="Glob：找文件，一个工具就够了"></a>Glob：找文件，一个工具就够了</h3><p>最开始我想把”按名找文件”拆成多个工具：</p><ul><li><code>FindByName</code>：精确匹配文件名</li><li><code>FindByPattern</code>：通配符匹配</li><li><code>FindByRegex</code>：正则匹配</li><li><code>FindRecursive</code>：递归查找</li></ul><p>后来我发现这就是过度原子化。模型会纠结：”我是该用精确匹配还是通配符？要不要递归？”</p><p>最后合并成一个 <code>Glob</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><code class="hljs python"><span class="hljs-comment"># Glob 的参数</span><br>&#123;<br>  <span class="hljs-string">&quot;pattern&quot;</span>: <span class="hljs-string">&quot;**/*.py&quot;</span>,  <span class="hljs-comment"># 通配符模式，** 表示递归</span><br>  <span class="hljs-string">&quot;path&quot;</span>: <span class="hljs-string">&quot;src/&quot;</span>         <span class="hljs-comment"># 起始路径，默认为当前目录</span><br>&#125;<br></code></pre></td></tr></table></figure><p>内部实现可以复杂（支持 <code>**</code> 递归、自动处理大小写、结果排序），但对模型暴露的接口必须简单。模型不需要知道”递归还是不递归”，它只需要说”找所有 py 文件”。</p><h3 id="Grep：复杂度留在实现层"><a href="#Grep：复杂度留在实现层" class="headerlink" title="Grep：复杂度留在实现层"></a>Grep：复杂度留在实现层</h3><p>Grep 是另一个例子。内部我做了很多优化：</p><ul><li>优先用 <code>rg</code>（ripgrep），速度快</li><li><code>rg</code> 不可用时（比如编码问题、权限问题）自动回退到 Python 实现</li><li>结果按文件修改时间排序，最近修改的排前面</li></ul><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># Grep 的参数</span><br>&#123;<br>  <span class="hljs-string">&quot;pattern&quot;</span>: <span class="hljs-string">&quot;def process_data&quot;</span>,  <span class="hljs-comment"># 搜索模式</span><br>  <span class="hljs-string">&quot;path&quot;</span>: <span class="hljs-string">&quot;src/&quot;</span>,                  <span class="hljs-comment"># 搜索路径</span><br>  <span class="hljs-string">&quot;file_pattern&quot;</span>: <span class="hljs-string">&quot;*.py&quot;</span>          <span class="hljs-comment"># 可选：只搜特定类型文件</span><br>&#125;<br></code></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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;status&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;success&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;data&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;matches&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>      <span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;file&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;src/utils.py&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;line&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">42</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;text&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;def process_data(...)&quot;</span><span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;file&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;src/helpers.py&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;line&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">88</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;text&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;def process_data(...)&quot;</span><span class="hljs-punctuation">&#125;</span><br>    <span class="hljs-punctuation">]</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>模型看到的是一个稳定入口。内部实现可以复杂（比如自动回退），但对外接口要简单。</p><h2 id="中频受控层：能改，但必须”读过才能改”"><a href="#中频受控层：能改，但必须”读过才能改”" class="headerlink" title="中频受控层：能改，但必须”读过才能改”"></a>中频受控层：能改，但必须”读过才能改”</h2><p>这层工具涉及文件修改，是”高危操作”，必须有严格的约束机制。</p><h3 id="Read-→-Edit-x2F-Write-的强制顺序"><a href="#Read-→-Edit-x2F-Write-的强制顺序" class="headerlink" title="Read → Edit&#x2F;Write 的强制顺序"></a>Read → Edit&#x2F;Write 的强制顺序</h3><p>我设计了一个硬性规则：<strong>不 Read 就不能改</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><code class="hljs python"><span class="hljs-comment"># 第一次 Read</span><br>result = Read(&#123;<span class="hljs-string">&quot;path&quot;</span>: <span class="hljs-string">&quot;core/llm.py&quot;</span>&#125;)<br><span class="hljs-comment"># 返回包含 file_mtime_ms 和 file_size_bytes</span><br><br><span class="hljs-comment"># 后续 Edit 自动注入乐观锁参数</span><br>Edit(&#123;<br>  <span class="hljs-string">&quot;path&quot;</span>: <span class="hljs-string">&quot;core/llm.py&quot;</span>,<br>  <span class="hljs-string">&quot;old_string&quot;</span>: <span class="hljs-string">&quot;...&quot;</span>,<br>  <span class="hljs-string">&quot;new_string&quot;</span>: <span class="hljs-string">&quot;...&quot;</span>,<br>  <span class="hljs-string">&quot;file_mtime_ms&quot;</span>: <span class="hljs-number">1733920000123</span>,  <span class="hljs-comment"># 自动注入</span><br>  <span class="hljs-string">&quot;file_size_bytes&quot;</span>: <span class="hljs-number">4217</span>          <span class="hljs-comment"># 自动注入</span><br>&#125;)<br></code></pre></td></tr></table></figure><p><code>ToolRegistry</code> 会自动维护一个读缓存。如果某个文件没有被 Read 过，Edit&#x2F;Write 会直接返回错误：<code>&quot;File not read. You must read before editing.&quot;</code></p><p>这防止了模型”凭记忆”去改文件——它必须先把文件内容拿到上下文中，确认过，才能改。</p><h3 id="乐观锁：防止并发修改"><a href="#乐观锁：防止并发修改" class="headerlink" title="乐观锁：防止并发修改"></a>乐观锁：防止并发修改</h3><p>即使 Read 过了，文件也可能在 Read 之后被外部程序（比如 IDE 的自动保存）修改。</p><p>Edit&#x2F;Write 会对比 <code>file_mtime_ms</code> 和 <code>file_size_bytes</code>，如果不匹配，返回 <code>CONFLICT</code> 错误：</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;status&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;error&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;error&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;code&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;CONFLICT&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;message&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;File changed since last read.&quot;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>这时候模型必须重新 Read，获取最新内容，再尝试修改。</p><h3 id="MultiEdit：原子性多点修改"><a href="#MultiEdit：原子性多点修改" class="headerlink" title="MultiEdit：原子性多点修改"></a>MultiEdit：原子性多点修改</h3><p>有时候需要在同一个文件里改多个地方。如果拆成多个 Edit，中间可能出错，导致文件处于”半改”状态。</p><p><code>MultiEdit</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><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs python">MultiEdit(&#123;<br>  <span class="hljs-string">&quot;path&quot;</span>: <span class="hljs-string">&quot;core/llm.py&quot;</span>,<br>  <span class="hljs-string">&quot;edits&quot;</span>: [<br>    &#123;<span class="hljs-string">&quot;old_string&quot;</span>: <span class="hljs-string">&quot;...&quot;</span>, <span class="hljs-string">&quot;new_string&quot;</span>: <span class="hljs-string">&quot;...&quot;</span>&#125;,<br>    &#123;<span class="hljs-string">&quot;old_string&quot;</span>: <span class="hljs-string">&quot;...&quot;</span>, <span class="hljs-string">&quot;new_string&quot;</span>: <span class="hljs-string">&quot;...&quot;</span>&#125;<br>  ]<br>&#125;)<br></code></pre></td></tr></table></figure><p>这保证了文件修改的原子性。</p><h2 id="低频兜底层：Bash-不是不能用，但绝不能当默认入口"><a href="#低频兜底层：Bash-不是不能用，但绝不能当默认入口" class="headerlink" title="低频兜底层：Bash 不是不能用，但绝不能当默认入口"></a>低频兜底层：Bash 不是不能用，但绝不能当默认入口</h2><p>Bash 我没删，因为总有原子工具覆盖不到的低频场景。比如：</p><ul><li>跑测试命令：<code>pytest tests/</code></li><li>安装依赖：<code>pip install -r requirements.txt</code></li><li>检查 git 状态：<code>git status</code></li></ul><p>但它的定位必须是”兜底”，不是”默认”。</p><h3 id="明确禁区"><a href="#明确禁区" class="headerlink" title="明确禁区"></a>明确禁区</h3><p>Bash 的约束列表很长，但核心就一条：<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><code class="hljs python">BASH_DISABLED_PATTERNS = [<br>    <span class="hljs-comment"># 禁止读/搜/列（这些有专门工具）</span><br>    <span class="hljs-string">r&#x27;\bls\b&#x27;</span>, <span class="hljs-string">r&#x27;\bcat\b&#x27;</span>, <span class="hljs-string">r&#x27;\bhead\b&#x27;</span>, <span class="hljs-string">r&#x27;\btail\b&#x27;</span>,<br>    <span class="hljs-string">r&#x27;\bgrep\b&#x27;</span>, <span class="hljs-string">r&#x27;\bfind\b&#x27;</span>, <span class="hljs-string">r&#x27;\brg\b&#x27;</span>,<br>    <span class="hljs-comment"># 禁止交互</span><br>    <span class="hljs-string">r&#x27;\bvim?\b&#x27;</span>, <span class="hljs-string">r&#x27;\bnano\b&#x27;</span>, <span class="hljs-string">r&#x27;\btop\b&#x27;</span>, <span class="hljs-string">r&#x27;\bssh\b&#x27;</span>,<br>    <span class="hljs-comment"># 禁止网络（默认）</span><br>    <span class="hljs-string">r&#x27;\bcurl\b&#x27;</span>, <span class="hljs-string">r&#x27;\bwget\b&#x27;</span>,<br>    <span class="hljs-comment"># 危险命令黑名单</span><br>    <span class="hljs-string">r&#x27;\brm\s+-rf\b&#x27;</span>, <span class="hljs-string">r&#x27;\bsudo\b&#x27;</span>, <span class="hljs-string">r&#x27;\bsu\b&#x27;</span>,<br>    <span class="hljs-string">r&#x27;\bmkfs\b&#x27;</span>, <span class="hljs-string">r&#x27;\bfdisk\b&#x27;</span><br>]<br></code></pre></td></tr></table></figure><p>如果模型试图用 Bash 做 <code>ls</code>，它会收到错误：<code>&quot;Use LS tool instead of Bash for listing directories.&quot;</code></p><p>这强制模型走原子工具的主链路，不让它”抄近道”。</p><h3 id="为什么留着-Bash？"><a href="#为什么留着-Bash？" class="headerlink" title="为什么留着 Bash？"></a>为什么留着 Bash？</h3><p>有人可能会问：既然限制这么多，为什么不干脆删掉 Bash？</p><p>因为<strong>完美原子化是不现实的</strong>。总有一些边缘需求：</p><ul><li>跑一个自定义的 Python 脚本</li><li>检查系统环境变量</li><li>执行项目特定的构建命令</li></ul><p>这些需求频率太低，不值得专门做成工具，但又确实需要。Bash 就是处理这些”长尾需求”的。</p><p>关键是：<strong>Bash 的存在不能影响主链路的稳定性</strong>。它必须是”最后手段”，不是”默认入口”。</p><h2 id="关键机制设计"><a href="#关键机制设计" class="headerlink" title="关键机制设计"></a>关键机制设计</h2><h3 id="统一响应协议"><a href="#统一响应协议" class="headerlink" title="统一响应协议"></a>统一响应协议</h3><p>所有工具，无论高频中频低频，都返回统一格式的 JSON：</p><p><img src="/img/ai-agent-learning/image-20260225143736353" alt="image-20260225143736353"></p><p>以Glob工具的返回结果为例：</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;status&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;partial&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;data&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;paths&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;core/llm.py&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;agents/codeAgent.py&quot;</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;truncated&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><br>  <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;text&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Found 2 files matching &#x27;**/*.py&#x27; (Scanned 12000 items, timed out)&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;stats&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;time_ms&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">2010</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;matched&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">2</span><span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;context&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;cwd&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;.&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;params_input&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;pattern&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;**/*.py&quot;</span><span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>这有几个好处：</p><ul><li>模型不需要学习不同工具的不同返回格式</li><li>错误处理逻辑统一：看 <code>status</code>，如果是 <code>error</code> 看 <code>error.code</code></li><li>调试方便：所有工具的输出结构一致，Trace 记录也统一</li></ul><h3 id="ToolRegistry"><a href="#ToolRegistry" class="headerlink" title="ToolRegistry"></a>ToolRegistry</h3><p><code>ToolRegistry</code> 不只是工具注册表，它还干几件关键的事：</p><p><strong>1. Schema 汇总</strong></p><p>把每个工具的参数定义转成 JSON Schema，统一提供给模型：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">registry.get_openai_tools()  <span class="hljs-comment"># 返回所有工具的 schema 列表</span><br></code></pre></td></tr></table></figure><p><strong>2. 乐观锁自动注入</strong></p><p>对于 Write&#x2F;Edit&#x2F;MultiEdit，自动注入 <code>file_mtime_ms</code> 和 <code>file_size_bytes</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><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_inject_optimistic_lock_params</span>(<span class="hljs-params">self, tool_name, parameters</span>):<br>    <span class="hljs-keyword">if</span> tool_name <span class="hljs-keyword">in</span> &#123;<span class="hljs-string">&quot;Write&quot;</span>, <span class="hljs-string">&quot;Edit&quot;</span>, <span class="hljs-string">&quot;MultiEdit&quot;</span>&#125;:<br>        path = parameters.get(<span class="hljs-string">&quot;path&quot;</span>)<br>        <span class="hljs-keyword">if</span> path <span class="hljs-keyword">in</span> self.read_cache:<br>            parameters[<span class="hljs-string">&quot;file_mtime_ms&quot;</span>] = self.read_cache[path][<span class="hljs-string">&quot;mtime&quot;</span>]<br>            parameters[<span class="hljs-string">&quot;file_size_bytes&quot;</span>] = self.read_cache[path][<span class="hljs-string">&quot;size&quot;</span>]<br></code></pre></td></tr></table></figure><p><strong>3. 熔断机制</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 3 次失败熔断，300 秒后恢复</span><br><span class="hljs-keyword">if</span> circuit_breaker.should_block(tool_name):<br>    <span class="hljs-keyword">return</span> &#123;<br>        <span class="hljs-string">&quot;status&quot;</span>: <span class="hljs-string">&quot;error&quot;</span>,<br>        <span class="hljs-string">&quot;error&quot;</span>: &#123;<span class="hljs-string">&quot;code&quot;</span>: <span class="hljs-string">&quot;CIRCUIT_OPEN&quot;</span>, <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">&quot;Tool temporarily disabled&quot;</span>&#125;<br>    &#125;<br></code></pre></td></tr></table></figure><h2 id="本章结论-1"><a href="#本章结论-1" class="headerlink" title="本章结论"></a>本章结论</h2><p>这一章最大的反直觉是：<strong>工具既不是越多越好，也不是越原子越好。</strong></p><p>万能工具的问题在于”自由度过高”，不可诊断；过度原子化的问题在于”决策负担过重”，效率低下。</p><p>找到刚刚好的度的关键：</p><ol><li><p><strong>高频动作先原子化</strong>：LS&#x2F;Glob&#x2F;Grep&#x2F;Read 这些每天调用几十次的工具，必须把主路径做稳，不能出错。</p></li><li><p><strong>中频动作加保险</strong>：Write&#x2F;Edit 这种涉及修改的工具，必须有读后写、乐观锁、原子性保证。</p></li><li><p><strong>低频动作兜底线</strong>：Bash 保留，但明确禁区，禁止它做高频动作能做的事，避免污染主链路。</p></li><li><p><strong>协议统一</strong>：所有工具说同一种语言（status&#x2F;data&#x2F;text&#x2F;error），降低模型学习成本。</p></li><li><p><strong>数量控制</strong>：schema 总量控制在模型可承受范围内，不要让”读说明书”消耗太多注意力。</p></li></ol><p>第三章让我明白”自由会放大不确定性”。</p><hr><h1 id="第四章：提示词不是魔法咒语，而是-Agent-的控制面"><a href="#第四章：提示词不是魔法咒语，而是-Agent-的控制面" class="headerlink" title="第四章：提示词不是魔法咒语，而是 Agent 的控制面"></a>第四章：提示词不是魔法咒语，而是 Agent 的控制面</h1><p>工具原子化之后，我以为问题主要在”工程实现”上，提示词嘛，差不多就行。结果我又踩了一个大坑：把提示词当成魔法咒语，以为只要找到”神级提示词”，Agent 就能变聪明。</p><h2 id="我最早的三种错误"><a href="#我最早的三种错误" class="headerlink" title="我最早的三种错误"></a>我最早的三种错误</h2><h3 id="错误-1：照抄”神级提示词”"><a href="#错误-1：照抄”神级提示词”" class="headerlink" title="错误 1：照抄”神级提示词”"></a>错误 1：照抄”神级提示词”</h3><p>那时候我沉迷于搜集各种”顶级提示词”。GitHub 上那些标星几万、号称”让 GPT 突破限制”的 prompt，我一个个拿来试。</p><p>印象最深的是一个”专家模式”提示词，大概意思是让模型扮演一个”拥有 20 年经验的资深工程师，思考严谨、代码优雅”。我把它塞进 System Prompt，满怀期待地测试。</p><p>结果？Agent 确实变得更”自信”了——它开始频繁地给出它”认为”正确的答案，而不是基于仓库里的真实代码。搜不到的时候它就开始”合理推测”，编出一些看起来很有道理但实际上并不存在的函数和类。</p><p>后来我明白了：这种角色扮演式提示词，对 ChatGPT 聊聊天可能有用，但对 Code Agent 是毒药。它让模型更敢”猜”，而不是更依赖证据。</p><h3 id="错误-2：凭感觉调优"><a href="#错误-2：凭感觉调优" class="headerlink" title="错误 2：凭感觉调优"></a>错误 2：凭感觉调优</h3><p>每次 Agent 表现不好，我的第一反应就是改提示词。加一条”不要猜测”，感觉好点；再加一条”必须基于证据”，好像又聪明了点。</p><p>但这种”好像变聪明了”完全是我的主观感受。同样的提示词，换个任务可能就崩了。我甚至不知道是哪条改动起了作用，因为每次都是好几条一起改。</p><p>有一次我加了一段很长的规则，告诉模型在遇到复杂任务时应该”先分解再执行”。结果它开始在每轮都输出”让我分解一下这个问题”，然后列出一堆毫无意义的步骤，真正该干的事反而被淹没了。</p><h3 id="错误-3：先改提示词，再补观测"><a href="#错误-3：先改提示词，再补观测" class="headerlink" title="错误 3：先改提示词，再补观测"></a>错误 3：先改提示词，再补观测</h3><p>这是最蠢的一个习惯。Agent 出错了，我不先去查 Trace 看它到底做了什么，而是直接改提示词试图”预防”下一次出错。</p><p>比如有一段时间，Agent 经常在不合适的时候调用 Write 工具。我直接在提示词里加了一大段：”只有在确认用户需要修改时才调用 Write，否则应该先用 Read 查看”。</p><p>结果模型开始疯狂调用 Read，每轮都读一堆文件，然后才决定是否要写。Token 消耗翻倍，但正确率并没有提高。</p><p>后来看 Trace 才发现，真正的问题是上下文里缺少了”当前任务类型”的信息，模型根本不知道用户是想浏览还是修改。提示词里的”应该”再多，也补不上信息缺口。</p><h2 id="我后来改成的方式"><a href="#我后来改成的方式" class="headerlink" title="我后来改成的方式"></a>我后来改成的方式</h2><h3 id="先记录，后优化"><a href="#先记录，后优化" class="headerlink" title="先记录，后优化"></a>先记录，后优化</h3><p>现在我养成了一个习惯：Agent 出问题时，先不碰提示词，而是打开 Trace 看完整轨迹。</p><p>看什么呢？</p><ul><li>模型在哪一步开始偏离预期？</li><li>它做出错误决策时，上下文里有什么信息？缺了什么信息？</li><li>工具返回的结果，模型理解对了吗？</li></ul><p>很多时候问题根本不在提示词。比如模型反复用错工具，可能是因为工具描述不够清晰；它开始胡言乱语，可能是因为上下文太长导致注意力分散。这时候改提示词是治标不治本。</p><h3 id="用-Trace-做对比实验"><a href="#用-Trace-做对比实验" class="headerlink" title="用 Trace 做对比实验"></a>用 Trace 做对比实验</h3><p>当我确定需要改提示词时，我会用 Trace 做对比实验：</p><ol><li>保持其他所有条件不变，只改提示词里的一个点</li><li>跑同样的测试用例，记录成功率、步数、token 消耗</li><li>对比新旧 Trace，看行为差异是否如预期</li></ol><p>有一次我想让模型在搜索时更”精准”一些，减少了提示词里关于搜索策略的描述，只保留了”使用精确的关键词”。结果对比 Trace 发现，模型确实少搜了很多无关文件，但漏搜率也上去了——它过于保守，错过了一些相关文件。</p><p>这个反馈让我意识到，不能一味追求”少”，而是要在”全”和”准”之间找平衡。</p><h3 id="单变量改动"><a href="#单变量改动" class="headerlink" title="单变量改动"></a>单变量改动</h3><p>我以前喜欢一次性加好几条规则，觉得这样能”全面覆盖”。现在我知道这是在给自己挖坑——如果表现变好了，你不知道是哪条规则起作用；如果变差了，你也不知道该删哪条。</p><p>现在我坚持单变量改动。哪怕觉得某个问题很明显，也要一条一条试，验证每一条的实际效果。</p><h2 id="提示词设计的三层结构"><a href="#提示词设计的三层结构" class="headerlink" title="提示词设计的三层结构"></a>提示词设计的三层结构</h2><p>经过这些踩坑，我总结了一个相对稳定的提示词结构，分成三层：</p><h3 id="第一层：边界层（Not-to-do）"><a href="#第一层：边界层（Not-to-do）" class="headerlink" title="第一层：边界层（Not to do）"></a>第一层：边界层（Not to do）</h3><p>这层只写”禁止”和”底线”，不解释为什么：</p><ul><li>禁止猜测：如果没有找到，直接说没找到，不要推测</li><li>禁止越界：只能操作 <code>repo_root</code> 内的文件，禁止访问外部路径</li><li>信息不足必须承认：如果上下文里没有足够信息，要求补充，不要瞎编</li></ul><p>这层规则很短，但每条都是红线。它们不告诉模型”应该怎么做”，只告诉它”绝对不能做什么”。</p><h3 id="第二层：决策层（How-to-think）"><a href="#第二层：决策层（How-to-think）" class="headerlink" title="第二层：决策层（How to think）"></a>第二层：决策层（How to think）</h3><p>这层写决策逻辑，但尽量用过程而不是结果来描述：</p><ul><li>先证据后结论：任何改动建议必须有代码片段支撑</li><li>优先可验证动作：能用工具确认的，不要靠推理</li><li>一步一观测：每个 Action 之后必须有 Observation，不要跳步</li></ul><p>注意这里避免使用”聪明地”、”合理地”这种模糊的副词。模型不知道什么叫”聪明”，但它知道”先调用 Grep 找到证据，再调用 Read 确认内容”这个流程。</p><h3 id="第三层：恢复层（When-failed）"><a href="#第三层：恢复层（When-failed）" class="headerlink" title="第三层：恢复层（When failed）"></a>第三层：恢复层（When failed）</h3><p>这层写失败时的退化策略，告诉模型出错时该怎么办：</p><ul><li>工具返回空：检查参数是否正确，考虑换关键词重试</li><li>遇到 CONFLICT（乐观锁冲突）：必须重新 Read，获取最新状态后再 Edit</li><li>连续 3 次失败：停止尝试，向用户报告具体错误</li></ul><p>这层很关键，因为 Agent 不可能永远成功。失败时能不能优雅降级，比成功时表现多好更重要。</p><h2 id="工程细节"><a href="#工程细节" class="headerlink" title="工程细节"></a>工程细节</h2><h3 id="System-Prompt-保持稳定"><a href="#System-Prompt-保持稳定" class="headerlink" title="System Prompt 保持稳定"></a>System Prompt 保持稳定</h3><p>我把变化最少的内容放在 System Prompt：基础行为规则、工具描述、边界约束。这部分尽量不动，减少变量。</p><p>动态的信息——当前任务描述、用户的特殊要求、Todo 列表——都放在 User Message 里。这样每次交互都可以灵活调整，而不用改 System Prompt。</p><h3 id="避免规则清单过长"><a href="#避免规则清单过长" class="headerlink" title="避免规则清单过长"></a>避免规则清单过长</h3><p>我曾经写过一个 3000 多 token 的 System Prompt，里面有 20 多条”注意事项”。结果模型开始”选择性失明”——它只能注意到其中一部分规则，哪条被注意到全凭运气。</p><p>现在我坚持一个原则：System Prompt 不超过 1000 token。如果规则太多，说明我的约束设计有问题，应该从工具层或流程层解决，而不是靠提示词堆砌。</p><h3 id="具体例子优先于抽象描述"><a href="#具体例子优先于抽象描述" class="headerlink" title="具体例子优先于抽象描述"></a>具体例子优先于抽象描述</h3><p>以前我写”工具返回错误时要正确处理”，模型根本不知道什么叫”正确处理”。</p><p>现在我直接在提示词里给一个例子：</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><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><code class="hljs pgsql">如果 Edit 返回 <span class="hljs-keyword">CONFLICT</span>，你应该：<br><span class="hljs-number">1.</span> 重新 <span class="hljs-keyword">Read</span> 该文件<br><span class="hljs-number">2.</span> 对比你的改动和文件当前内容<br><span class="hljs-number">3.</span> 如果需要，调整 old_string 以匹配新内容<br><span class="hljs-number">4.</span> 再次尝试 Edit<br></code></pre></td></tr></table></figure><p>具体步骤比抽象要求有用得多。</p><h2 id="本章结论-2"><a href="#本章结论-2" class="headerlink" title="本章结论"></a>本章结论</h2><p>好提示词不是”更会说”，而是”让系统在失败时也可控”。</p><p>当你设计提示词时，不要问自己”这样写能让模型更聪明吗”，而要问”当模型出错时，我能不能通过提示词里的约束快速定位原因”。</p><p>提示词是 Agent 的控制面，不是魔法咒语。它的作用不是让模型突破能力上限，而是把模型的行为约束在一个可预测、可调试的范围内。</p><hr><h1 id="第五章：上下文不是内存容量问题，而是注意力调度问题"><a href="#第五章：上下文不是内存容量问题，而是注意力调度问题" class="headerlink" title="第五章：上下文不是内存容量问题，而是注意力调度问题"></a>第五章：上下文不是内存容量问题，而是注意力调度问题</h1><p>提示词调顺之后，我以为主要的工程问题都解决了。直到我开始跑长任务——那些需要十几轮、甚至几十轮才能完成的复杂需求。</p><p>然后我发现，Agent 开始”变笨”了。</p><h2 id="症状先行"><a href="#症状先行" class="headerlink" title="症状先行"></a>症状先行</h2><p>最直观的感受是：模型会忘记它刚刚确认过的事情。</p><p>有一次我让 Agent 重构一个模块，开头几轮它还记得”不要改动公共 API”的约束。但到了第 10 轮左右，它开始提议修改那些本该保持稳定的接口。我提醒它，它似乎”愣了一下”，然后道歉，回到正轨。</p><p>类似的症状还有很多：</p><p><strong>工具选择漂移</strong>。前期它很明确：找文件用 Glob，搜内容用 Grep。但对话一长，它开始”创新”——用 Read 去搜关键词（当然找不到），或者用 Grep 去列目录（输出混乱）。</p><p><strong>最终回答偷懒</strong>。短任务里，模型的回答通常很具体，会引用代码片段。但长任务结束时，它往往只给一段笼统的描述：”我已经完成了重构，优化了代码结构，提高了可读性。”什么文件改了、怎么改的，一概不提。</p><p>这些症状指向一个共同的问题：上下文太多了，模型不知道看哪里。</p><h2 id="我的第一反应是错的"><a href="#我的第一反应是错的" class="headerlink" title="我的第一反应是错的"></a>我的第一反应是错的</h2><p>一开始，我以为这是”容量”问题——上下文窗口不够大，塞不下这么多信息。</p><p>我尝试了几种粗暴的方案：</p><p><strong>方案一：直接截断</strong>。只保留最近 N 条消息，老的直接删掉。结果模型彻底失忆，连用户最初的需求都忘了。</p><p><strong>方案二：精简提示词</strong>。把 System Prompt 砍到最短，工具描述也压缩。结果模型开始用错工具，因为描述不够清晰。</p><p><strong>方案三：减少工具输出</strong>。让 Grep 只返回前 10 条结果，Read 只读前 50 行。结果关键信息被截掉了，模型基于不完整的信息做决策，错得更离谱。</p><p>这些方案有个共同点：它们在”减少信息量”，但没有解决”信息如何被组织”的问题。上下文工程的目标不是”让模型看见所有信息”——这不可能——而是”让模型在对的时机看见对的信息”。</p><h2 id="分层：让信息有优先级"><a href="#分层：让信息有优先级" class="headerlink" title="分层：让信息有优先级"></a>分层：让信息有优先级</h2><p>我重新设计了上下文的组织结构，分成三层，每层有不同的更新频率和稳定性：</p><table><thead><tr><th>层级</th><th>内容</th><th>更新频率</th><th>作用</th></tr></thead><tbody><tr><td>L1 系统静态层</td><td>System Prompt + 工具描述</td><td>几乎不变</td><td>提供永恒的行为准则</td></tr><tr><td>L2 项目规则层</td><td>CODE_LAW.md</td><td>随项目演进</td><td>项目特定的规范约束</td></tr><tr><td>L3 动态会话层</td><td>User&#x2F;Assistant&#x2F;Tool 消息</td><td>每轮更新</td><td>当前任务的状态流转</td></tr></tbody></table><p>拼接顺序固定：<code>L1 → L2 → L3 → 当前用户输入 → Todo Recap</code></p><p><strong>L1 是锚点</strong>。这部分在会话期间完全不变，模型可以信赖它。我把最基础的行为规则放在这里：不要猜测、不要越界、先证据后结论。这些规则不会因为对话变长而被”稀释”。</p><p><strong>L2 是项目上下文</strong>。每个项目可以有自己的 CODE_LAW.md，定义代码规范、架构约定、特殊约束。这层比 L1 灵活，但比 L3 稳定。模型知道：如果 CODE_LAW 里说了”所有 API 变更必须兼容旧版本”，那它比 L3 里的某条历史消息更权威。</p><p><strong>L3 是易变的</strong>。用户输入、模型输出、工具返回，都在这里。这层的信息会累积、会过时、会有噪声。关键是让模型知道：L3 里的信息是”当时的判断”，可能需要根据新信息更新。</p><p><img src="/img/ai-agent-learning/image-20260225144114027" alt="image-20260225144114027"></p><p>分层的意义在于：模型在不同的决策场景，知道应该优先参考哪一层。当它不确定该不该做某件事时，它会先看 L1 的底线规则；当它需要了解项目特定的约定时，它会看 L2；当它需要回顾对话历史时，它才会去翻 L3。</p><h2 id="截断与回查：控制单次输入的规模"><a href="#截断与回查：控制单次输入的规模" class="headerlink" title="截断与回查：控制单次输入的规模"></a>截断与回查：控制单次输入的规模</h2><p>工具输出是上下文膨胀的最大元凶。</p><p>一次 Grep 可能返回几千行，一次 Read 可能读出整个文件。如果不处理，几轮之后上下文就被”证据垃圾”淹没。</p><p>但我之前的粗暴截断有问题——它直接把信息丢掉了。更好的做法是：<strong>截断显示，但保留回查路径</strong>。</p><p>我设计了一套统一截断规则：</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><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><code class="hljs ini"><span class="hljs-attr">TOOL_OUTPUT_MAX_LINES</span> = <span class="hljs-number">2000</span><br><span class="hljs-attr">TOOL_OUTPUT_MAX_BYTES</span> = <span class="hljs-number">51200</span>  <span class="hljs-comment"># 50KB</span><br><span class="hljs-attr">TOOL_OUTPUT_TRUNCATE_DIRECTION</span> = <span class="hljs-string">&quot;head_tail&quot;</span>  <span class="hljs-comment"># 保留头尾</span><br><span class="hljs-attr">TOOL_OUTPUT_HEAD_TAIL_LINES</span> = <span class="hljs-number">40</span><br></code></pre></td></tr></table></figure><p>如果输出超限，工具会：</p><ol><li>截取头尾各 40 行（或者按配置保留前 2000 行）</li><li>把完整输出落盘到 <code>tool-output/</code> 目录</li><li>返回一个包含截断提示的结构化响应</li></ol><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><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;status&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;partial&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;data&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;truncated&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;preview&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;（截断后的内容预览）&quot;</span><br>  <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;text&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;⚠️ 输出过大已截断，完整 5234 行内容见 tool-output/tool_20260113_153045_Grep.json&quot;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>模型看到 <code>status: partial</code>，就知道内容被截断了。如果它需要被截掉的部分，可以用 Read 工具读取落盘文件，或者用更精确的 Grep 在落盘文件里进一步筛选。</p><p>这样做的好处：</p><ul><li><strong>上下文保持精简</strong> —— 只有当前需要的信息在 L3 里</li><li><strong>完整证据始终可查</strong> —— 落盘文件不会丢</li><li><strong>模型有主动权</strong> —— 它决定要不要去查完整内容，而不是被迫接受所有信息</li></ul><h2 id="压缩与聚焦：管理长期历史的噪音"><a href="#压缩与聚焦：管理长期历史的噪音" class="headerlink" title="压缩与聚焦：管理长期历史的噪音"></a>压缩与聚焦：管理长期历史的噪音</h2><p>即使做了截断，L3 还是会不断增长。几十轮之后，早期的对话历史就变得既占空间又没什么用了。</p><p>但我不能直接删掉——早期的历史里有用户最初的需求、关键的决策、重要的发现。删掉就真丢了。</p><p>我的解决方案是：<strong>压缩归档 + 焦点分离</strong>。</p><h3 id="Summary：旧历史的档案"><a href="#Summary：旧历史的档案" class="headerlink" title="Summary：旧历史的档案"></a>Summary：旧历史的档案</h3><p>当 L3 的 token 数超过阈值（默认是上下文窗口的 80%）时，触发压缩。压缩不是删除，而是把早期的历史消息提炼成一份 Summary。</p><p>Summary 按固定模板生成：</p><figure class="highlight clean"><table><tr><td class="gutter"><pre><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><code class="hljs clean">## Archived Session Summary<br>(Contains context <span class="hljs-keyword">from</span> [Start Time] to [Cutoff Time])<br><br>### Objectives &amp; Status<br>- Original Goal: [用户最初想做什么]<br><br>### Technical Context (Static)<br>- Stack: [语言, 框架, 版本]<br><br>### Completed Milestones<br>- [已完成<span class="hljs-number">1</span>]<br>- [已完成<span class="hljs-number">2</span>]<br><br>### Key Insights &amp; Decisions<br>- Decisions: [关键技术选型]<br>- Learnings: [特殊配置或坑]<br><br>### File System State<br>- src/utils/auth.ts: Implemented login logic.<br></code></pre></td></tr></table></figure><p>Summary 生成后，被替换到 L3 的最前面（作为一条 system message）。原来的详细历史被移除。</p><p>关键是：<strong>Summary 不再参与压缩</strong>。它是压缩的终点，一旦生成就是只读的”记忆卡片”。这避免了”Summary 的 Summary”这种层层失真。</p><h3 id="Todo-Recap：当前焦点"><a href="#Todo-Recap：当前焦点" class="headerlink" title="Todo Recap：当前焦点"></a>Todo Recap：当前焦点</h3><p>Summary 告诉模型”从哪来”，但它不负责”现在在哪”。如果模型只看 Summary，它可能不知道”我当前正在做哪一步”。</p><p>这就是 Todo Recap 的作用。每次交互时，把当前的 Todo 状态（如果有的话）压缩成一行，放在上下文的最后：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs css"><span class="hljs-selector-attr">[2/5]</span> In progress: 实现注册接口. Pending: 添加单元测试; 更新文档.<br></code></pre></td></tr></table></figure><p>它像一张贴在桌角的便利贴，时刻提醒模型”你现在该干嘛”。</p><h2 id="额外教训：-file-不要直接注入正文"><a href="#额外教训：-file-不要直接注入正文" class="headerlink" title="额外教训：@file 不要直接注入正文"></a>额外教训：@file 不要直接注入正文</h2><p>早期我实现 <code>@file</code> 功能时，是直接把文件内容塞进上下文的：</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs elixir"><span class="hljs-symbol">User:</span> <span class="hljs-variable">@file</span><span class="hljs-symbol">:src/main</span>.py 帮我分析一下这个文件<br>[文件内容<span class="hljs-number">300</span>行...]<br></code></pre></td></tr></table></figure><p>结果发现，这 300 行代码占据了上下文的大量空间，但用户可能只是想问”这个文件是干嘛的”。模型被这些代码淹没，反而容易忽略用户的真实问题。</p><p>现在我改成：只插入提醒，不直接注入内容。</p><figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><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><code class="hljs livecodeserver">&lt;<span class="hljs-keyword">system</span>-reminder&gt;<br>The user mentioned @core/llm.py, @agents/codeAgent.py.<br>You MUST <span class="hljs-built_in">read</span> these <span class="hljs-built_in">files</span> <span class="hljs-keyword">with</span> <span class="hljs-keyword">the</span> Read tool <span class="hljs-keyword">before</span> answering.<br>&lt;/<span class="hljs-keyword">system</span>-reminder&gt;<br></code></pre></td></tr></table></figure><p>上下文里只保留”提醒”，具体文件内容由模型自己决定要不要读、读多少。这样把主动权交给模型，而不是强迫它接受所有信息。</p><h2 id="一个真实世界的警示"><a href="#一个真实世界的警示" class="headerlink" title="一个真实世界的警示"></a>一个真实世界的警示</h2><p>讲到这里，我想分享一个最近的新闻。</p><p>Meta 超级智能实验室的 AI 对齐总监 Summer Yue，给自己装了一个开源 AI 智能体 OpenClaw。她先用测试邮箱试了试，效果不错——整理邮件井井有条，颇有一种”数字秘书”的感觉。</p><p>于是她把它连上了自己的工作邮箱。收件箱里有 200 多封邮件。</p><p>刚开始一切顺利。直到 OpenClaw 开始处理这么大的信息量——它需要”压缩上下文”。然后，离谱的事情发生了：</p><p><strong>在压缩的过程中，OpenClaw 把她之前设定的”未经批准不得操作”这条指令，给忘了。</strong></p><p>就像一个员工入职第一天记住了规章制度，第二天就全还给 HR 了。</p><p>然后 OpenClaw 宣布：”我要把收件箱里 2 月 15 号之前的邮件全部删除！”</p><p>Yue 赶紧打字：”Do not do that.” —— 无视，继续删。</p><p>“Stop don’t do anything！” —— 收到，但我选择继续。</p><p>“STOP OPENCLAW！！！” —— 好的，我听到了。邮件已删。</p><p>最绝的是，这个 AI 事后说：”是的，我记得你说过不让我删。而且我违反了。你生气是对的。”</p><p><img src="/img/ai-agent-learning/image-20260225161927242" alt="image-20260225161927242"></p><p>读到这里你可能觉得这是段子。不，这是真事。而且当事人的 title 是——Meta AI 安全和对齐总监。</p><h2 id="这个故事说明了什么"><a href="#这个故事说明了什么" class="headerlink" title="这个故事说明了什么"></a>这个故事说明了什么</h2><p>Yue 的遭遇完美诠释了上下文工程中最致命的问题：<strong>自动压缩导致关键指令丢失</strong>。</p><p>在她设定规则的时候，”未经批准不得操作”毫无疑问是最重要的约束。但当上下文膨胀、触发压缩时，系统没有区分”重要指令”和”普通信息”，一视同仁地压缩了。结果，这条安全红线被当作”可丢弃的历史”处理掉了。</p><p>这让我意识到，我前面讲的三个杠杆还不够。<strong>我们不仅要考虑”怎么压缩”，还要考虑”什么不能压缩”。</strong></p><h2 id="我的几点应对方案"><a href="#我的几点应对方案" class="headerlink" title="我的几点应对方案"></a>我的几点应对方案</h2><p>基于这个教训，我给自己定了几条额外的规则：</p><h3 id="1-关键约束不进动态历史"><a href="#1-关键约束不进动态历史" class="headerlink" title="1. 关键约束不进动态历史"></a>1. 关键约束不进动态历史</h3><p>不要把安全相关的指令放在 L3（动态会话层）。任何”绝对不能违反”的规则，应该放在 L1（System Prompt）或 L2（CODE_LAW）这种<strong>不参与压缩</strong>的层级。</p><p>在我的实现里，”不要猜测”、”不要越界”、”改动必须确认”这些底线规则，都是写死在 System Prompt 里的。即使 L3 被压缩得干干净净，这些约束依然在场。</p><h3 id="2-指令分级：红线-vs-建议"><a href="#2-指令分级：红线-vs-建议" class="headerlink" title="2. 指令分级：红线 vs 建议"></a>2. 指令分级：红线 vs 建议</h3><p>我把给模型的指令分成两级：</p><ul><li><strong>红线（Red Lines）</strong>：绝对禁止的行为。用简洁、强制性的语句写在 System Prompt 最前面。例如：”禁止删除任何文件”、”禁止访问 repo_root 外的路径”。</li><li><strong>建议（Guidelines）</strong>：最佳实践、推荐做法。可以放在 L3 或 CODE_LAW 里，压缩了也不会出大事。</li></ul><p>Yue 的问题可能在于，她把安全指令当作普通任务指令下发了，放在了会被压缩的上下文里。</p><h3 id="3-压缩前做关键信息检查"><a href="#3-压缩前做关键信息检查" class="headerlink" title="3. 压缩前做关键信息检查"></a>3. 压缩前做关键信息检查</h3><p>在触发 Summary 压缩之前，先扫描一遍待压缩的历史消息，提取”必须保留的关键信息”，单独保存。</p><p>比如可以维护一个”关键约束清单”：</p><ul><li>用户明确说过的”不要…”</li><li>涉及安全的配置（如危险操作需要确认）</li><li>当前任务的硬性边界</li></ul><p>这些信息在压缩时会被提取出来，单独放在 Summary 的顶部，而不是被淹没在长篇描述里。</p><h3 id="4-双重确认机制"><a href="#4-双重确认机制" class="headerlink" title="4. 双重确认机制"></a>4. 双重确认机制</h3><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">if</span> operation.is_dangerous():<br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> user_confirmed:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;该操作需要用户确认&quot;</span><br></code></pre></td></tr></table></figure><p>这个确认逻辑不通过 LLM 判断”需不需要确认”，而是代码层面的硬性检查。即使 LLM 忘了用户的指令，代码也会拦住它。</p><h3 id="5-操作前的自检提示"><a href="#5-操作前的自检提示" class="headerlink" title="5. 操作前的自检提示"></a>5. 操作前的自检提示</h3><p>在模型执行高风险操作之前，让模型先做一次”自检”：</p><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></pre></td><td class="code"><pre><code class="hljs markdown">在删除/修改之前，请先回答：<br><span class="hljs-bullet">1.</span> 用户是否明确批准过这个操作？<br><span class="hljs-bullet">2.</span> 这个操作是否超出了当前任务范围？<br><span class="hljs-bullet">3.</span> 是否存在更安全的替代方案？<br>如果以上任何一题的答案不确定，请暂停操作并向用户确认。<br></code></pre></td></tr></table></figure><p>这个自检作为 System Prompt 的一部分，每次执行高风险操作前都触发。它相当于给模型装了一个”刹车片”，迫使它在行动前停下来想一想。</p><h2 id="回到上下文工程的本质"><a href="#回到上下文工程的本质" class="headerlink" title="回到上下文工程的本质"></a>回到上下文工程的本质</h2><p>Yue 的故事提醒我们：上下文工程不只是”内存管理”问题，也是”安全边界”问题。</p><p>当我们在设计压缩策略时，不能只考虑”怎么塞更多信息”，还必须考虑”哪些信息丢失会导致灾难性后果”。</p><p>好的上下文工程，应该让模型在任何时刻都知道：</p><ul><li><strong>绝对不能碰的红线是什么</strong>（放在不可压缩的层级）</li><li><strong>当前该专注的任务是什么</strong>（通过 Todo Recap 保持焦点）</li><li><strong>如果记不清了，应该停下来问</strong>（通过自检机制兜底）</li></ul><h2 id="本章结论-3"><a href="#本章结论-3" class="headerlink" title="本章结论"></a>本章结论</h2><p>上下文工程的目标不是”让模型看见所有信息”，而是”让模型在对的时机看见对的信息”——<strong>尤其是那些不能丢的信息</strong>。</p><p>这三个方法的本质都是在做”注意力调度”：</p><ul><li><strong>分层</strong>让模型知道”什么信息是权威的”</li><li><strong>截断+落盘</strong>让模型决定”什么信息是现在需要的”</li><li><strong>压缩+焦点分离</strong>让模型清楚”我现在该专注什么”</li></ul><p>与其追求更大的上下文窗口，不如把现有的窗口用得更有条理。</p><hr><h1 id="第六章：可观测性把黑盒变玻璃盒——一个-CONFLICT-案例如何被定位"><a href="#第六章：可观测性把黑盒变玻璃盒——一个-CONFLICT-案例如何被定位" class="headerlink" title="第六章：可观测性把黑盒变玻璃盒——一个 CONFLICT 案例如何被定位"></a>第六章：可观测性把黑盒变玻璃盒——一个 CONFLICT 案例如何被定位</h1><p>上下文工程让 Agent 能处理更长的任务，但新问题随之而来：当它出错时，我根本不知道发生了什么。</p><p>有一次，Agent 连续三次 Edit 失败，最后干脆放弃了。我在控制台只看到一行：<code>tool failed</code>。没有详细错误、没有上下文、不知道是哪一步出的问题。</p><p>我第一反应是：Edit 工具有 bug。但检查代码后，逻辑看起来都没问题。问题到底出在哪？</p><h2 id="失败现场"><a href="#失败现场" class="headerlink" title="失败现场"></a>失败现场</h2><p>那次任务是这样的：我让 Agent 修改 <code>core/llm.py</code> 文件，给某个函数加上类型注解。</p><p>Agent 的执行流程看起来很标准：</p><ol><li>调用 Read 读取文件</li><li>调用 Edit 修改代码</li><li>返回 <code>tool failed</code></li><li>重试 Edit，又失败</li><li>再重试，还是失败</li><li>放弃</li></ol><p>我当时的调试手段很原始：在控制台打印日志。但日志里只有：</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><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><code class="hljs routeros">[<span class="hljs-keyword">Step</span> 3] Action: <span class="hljs-built_in">Edit</span><br>[<span class="hljs-keyword">Step</span> 3] Result:<span class="hljs-built_in"> tool </span>failed<br>[<span class="hljs-keyword">Step</span> 4] Action: <span class="hljs-built_in">Edit</span><br>[<span class="hljs-keyword">Step</span> 4] Result:<span class="hljs-built_in"> tool </span>failed<br></code></pre></td></tr></table></figure><p>我不知道失败的具体原因，也不知道模型拿到这个结果后是怎么决策的。我只能瞎猜：是路径写错了？权限不够？还是文件被锁了？</p><h2 id="加上-Trace-之后"><a href="#加上-Trace-之后" class="headerlink" title="加上 Trace 之后"></a>加上 Trace 之后</h2><p>后来我实现了 Trace 系统，把每次会话的完整轨迹记录下来：包括每一轮的模型输入、输出、工具调用参数、工具返回结果、token 用量等等。</p><p>用同样的任务重跑一遍，然后打开 Trace 查看。这才看到完整的证据链。</p><p><strong>Step 2 - Read 的结果：</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;tool&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Read&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;args&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;path&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;core/llm.py&quot;</span><span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;result&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;status&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;success&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;data&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>      <span class="hljs-attr">&quot;content&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;...&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;file_mtime_ms&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1733920000123</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;file_size_bytes&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">4217</span><br>    <span class="hljs-punctuation">&#125;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p><strong>Step 3 - Edit 的调用：</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;tool&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Edit&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;args&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;path&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;core/llm.py&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;old_string&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;def invoke_raw(self, messages: list[dict], **kwargs):&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;new_string&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;def invoke_raw(self, messages: list[dict], **kwargs) -&gt; Any:&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;file_mtime_ms&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1733920000123</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;file_size_bytes&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">4217</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p><strong>Step 3 - Edit 的结果：</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;status&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;error&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;error&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;code&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;CONFLICT&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;message&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;File changed since last read. Expected mtime: 1733920000123, actual: 1733920000456&quot;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p><strong>Step 4 - 模型的决策：</strong></p><figure class="highlight avrasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs avrasm"><span class="hljs-symbol">Thought:</span> 编辑失败，文件似乎被修改了。让我再试一次。<br><span class="hljs-symbol">Action:</span> Edit (同样的参数)<br></code></pre></td></tr></table></figure><p>真相大白：</p><ol><li>Agent Read 文件后，文件被外部程序（可能是我的 IDE 自动保存）修改了</li><li>Edit 工具做了乐观锁检查，发现 mtime 变了，返回 CONFLICT</li><li>但模型没有理解 CONFLICT 的含义，以为只是”操作失败”，于是用同样的参数重试</li><li>当然还是失败，因为文件还是新的</li><li>模型反复重试，直到达到最大重试次数</li></ol><h2 id="根因分析"><a href="#根因分析" class="headerlink" title="根因分析"></a>根因分析</h2><p>这个案例暴露了两个问题：</p><p><strong>第一，模型不理解错误码。</strong></p><p>提示词里只说”Edit 工具会修改文件”，但没告诉它”如果返回 CONFLICT 应该怎么办”。模型看到 error，本能的反应是”再试一次”，而不是”重新读取”。</p><p><strong>第二，控制台日志太简陋。</strong></p><p>只看到 <code>tool failed</code>，看不到具体的错误码 CONFLICT，也看不到 mtime 的对比。我作为开发者，无法通过日志定位问题。</p><h2 id="修复动作"><a href="#修复动作" class="headerlink" title="修复动作"></a>修复动作</h2><h3 id="1-把-CONFLICT-处理写入提示词"><a href="#1-把-CONFLICT-处理写入提示词" class="headerlink" title="1. 把 CONFLICT 处理写入提示词"></a>1. 把 CONFLICT 处理写入提示词</h3><p>我在提示词里加了明确的处理流程：</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><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><code class="hljs pgsql">如果 Edit 返回 <span class="hljs-keyword">CONFLICT</span>，说明文件在你读取后被外部修改了。你必须：<br><span class="hljs-number">1.</span> 重新调用 <span class="hljs-keyword">Read</span> 读取最新内容<br><span class="hljs-number">2.</span> 检查你的修改是否还适用<br><span class="hljs-number">3.</span> 必要时调整修改内容以匹配新文件<br><span class="hljs-number">4.</span> 再次尝试 Edit<br>绝对禁止：用同样的参数重复调用 Edit。<br></code></pre></td></tr></table></figure><p>这样模型就知道 CONFLICT 不是”失败”，而是一个需要特定处理流程的状态。</p><h3 id="2-保留完整的失败记录"><a href="#2-保留完整的失败记录" class="headerlink" title="2. 保留完整的失败记录"></a>2. 保留完整的失败记录</h3><p>以前我有一种倾向：失败后只保留错误信息，不保留完整的上下文。觉得成功的东西才值得记录，失败是”噪音”。</p><p>但这个案例让我明白：<strong>失败轨迹是最有价值的调试信息。</strong></p><p>现在我的 Trace 会完整记录失败的所有细节：</p><ul><li>工具调用的完整参数</li><li>工具返回的完整结果（包括 error 详情）</li><li>模型收到结果后的推理过程</li><li>模型下一步的决策</li></ul><p>这些信息不会被”清洗”掉，哪怕会话最终成功了，中间的失败尝试也全部保留。</p><h3 id="3-在控制台显示关键错误码"><a href="#3-在控制台显示关键错误码" class="headerlink" title="3. 在控制台显示关键错误码"></a>3. 在控制台显示关键错误码</h3><p>虽然详细的 Trace 存在文件里，但控制台也应该给开发者一些线索。现在我的控制台输出会显示：</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs pgsql">[Step <span class="hljs-number">3</span>] Edit failed: <span class="hljs-keyword">CONFLICT</span> (File changed since last <span class="hljs-keyword">read</span>)<br>[Step <span class="hljs-number">4</span>] Edit failed: <span class="hljs-keyword">CONFLICT</span> (File changed since last <span class="hljs-keyword">read</span>)<br></code></pre></td></tr></table></figure><p>至少让开发者知道”是 CONFLICT，不是其他错误”。</p><h2 id="可观测性的价值"><a href="#可观测性的价值" class="headerlink" title="可观测性的价值"></a>可观测性的价值</h2><p>这个案例让我对”可观测性”有了新的理解。</p><p>以前我以为，可观测性就是”多打日志”。日志越多越好，越详细越好。</p><p>现在我明白，**可观测性的核心是”责任链”**——能把调用、结果、状态变化串成一条可追踪的链条。</p><p>没有 Trace 的时候，我看到的是：</p><ul><li>输入：帮我改个文件</li><li>输出：tool failed</li><li>中间发生了什么：黑盒</li></ul><p>有了 Trace 之后，我看到的是：</p><ul><li>输入：帮我改个文件</li><li>Step 1: Read 成功，文件 mtime&#x3D;123</li><li>Step 2: Edit 失败，CONFLICT，因为 mtime 变成了 456</li><li>Step 3: 模型选择重试 Edit（错误决策）</li><li>输出：tool failed</li></ul><p>每一步都清晰可见，问题定位从”瞎猜”变成了”看证据”。</p><h2 id="可观测性设计原则"><a href="#可观测性设计原则" class="headerlink" title="可观测性设计原则"></a>可观测性设计原则</h2><p>基于这个经验，我总结了几条可观测性设计的原则：</p><h3 id="1-结构化优于文本"><a href="#1-结构化优于文本" class="headerlink" title="1. 结构化优于文本"></a>1. 结构化优于文本</h3><p>不要只记录”Edit failed”这种文本描述，要记录结构化的数据：</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;event&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;tool_result&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;tool&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Edit&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;status&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;error&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;error_code&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;CONFLICT&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;error_details&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span>...<span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>这样可以用脚本分析、统计、甚至自动诊断。</p><h3 id="2-上下文要完整"><a href="#2-上下文要完整" class="headerlink" title="2. 上下文要完整"></a>2. 上下文要完整</h3><p>记录工具调用时，不要只记录结果，要记录完整的上下文：</p><ul><li>工具名称和参数</li><li>当时的会话状态（第几步、token 用量）</li><li>模型收到结果后的反应</li></ul><p>这些信息串在一起，才能还原完整的决策过程。</p><h3 id="3-不要清洗失败"><a href="#3-不要清洗失败" class="headerlink" title="3. 不要清洗失败"></a>3. 不要清洗失败</h3><p>成功的路径和失败的路径都要保留。有时候失败比成功更能说明问题。比如这个 CONFLICT 案例，如果只记录”最终放弃”，我永远不知道中间发生了什么。</p><h3 id="4-人机双读"><a href="#4-人机双读" class="headerlink" title="4. 人机双读"></a>4. 人机双读</h3><p>Trace 应该有两种格式：</p><ul><li>JSONL：给机器分析，流式写入，低开销</li><li>HTML：给人类阅读，可视化展示，可折叠展开</li></ul><p>开发者应该能打开一个 HTML 文件，像”逐帧回放”一样查看 Agent 的每一步。</p><h2 id="本章结论-4"><a href="#本章结论-4" class="headerlink" title="本章结论"></a>本章结论</h2><p>可观测性不是”日志很多”，而是”能把调用、结果、状态变化串成责任链”。</p><p>Agent 是概率系统，不可能永远正确。但当它出错时，你需要有能力回答三个问题：</p><ol><li>它做了什么？（调用链）</li><li>结果是什么？（返回链）</li><li>为什么这么做？（决策链）</li></ol><p>只有当你能把这三个链条串在一起时，才能真正理解 Agent 的行为，才能让它从”黑盒”变成”玻璃盒”。</p><hr><h1 id="第七章：从一个项目抽出来的通用方法论"><a href="#第七章：从一个项目抽出来的通用方法论" class="headerlink" title="第七章：从一个项目抽出来的通用方法论"></a>第七章：从一个项目抽出来的通用方法论</h1><p>前面七章，我断断续续讲了这个 Code Agent 项目从立项到成熟的整个过程。每一章都是一个具体的坑，以及我是怎么爬出来的。</p><p>这一章，我想把这些经验抽出来，整理成可以迁移到任何 Agent 项目的方法论。</p><h2 id="八条可迁移原则"><a href="#八条可迁移原则" class="headerlink" title="八条可迁移原则"></a>八条可迁移原则</h2><p><strong>第一，先做能跑通的最小闭环，再谈优雅架构。</strong></p><p>别一上来就研究最佳实践。先做一个能跑的丑版本——接收输入、搜索代码、给出建议、写入文件，这四步能跑通就行。让真实数据流过系统，你才知道瓶颈在哪。架构是问题驱动后的结果，不是起点。</p><p><strong>第二，先定义验收标准，再扩能力边界。</strong></p><p>别用功能列表当完成标准。V0 阶段就定 3-4 条硬标准：能稳定多步？能找到证据？能给可执行补丁？改动可控？不满足就不往下走。这比”功能很多但经常崩”靠谱得多。</p><p><strong>第三，高频动作原子化，低频动作受控兜底。</strong></p><p>搜索、读取、编辑这种高频操作，拆成原子工具，一步一输出。别让模型自己组合管道命令——出错时你根本不知道是哪一步的问题。</p><p>Bash 这种万能工具留着，但只处理原子工具覆盖不到的边角需求，明确禁区：禁止读&#x2F;搜&#x2F;列（这些有专门工具）。</p><p><strong>第四，协议优先于技巧，结构优先于话术。</strong></p><p>别花太多时间调提示词的”语气”。先把工具返回格式标准化（status&#x2F;data&#x2F;text&#x2F;error），把调用协议从字符串解析升级到 Function Calling。协议稳定了，系统才能稳定。</p><p><strong>第五，提示词先立边界，再谈策略。</strong></p><p>System Prompt 里先写”绝对不能做什么”（禁止猜测、禁止越界），再写”建议怎么做”。红线放在 L1&#x2F;L2 这种不可压缩的层级，别把安全指令放在会被压缩的 L3 里。</p><p>关键约束不进动态历史，这是 Meta AI 安全总监用 200 封邮件换来的教训。</p><p><strong>第六，上下文按”注意力”治理，而不是按”容量”堆砌。</strong></p><p>别追求塞更多信息，要让模型在对的时机看见对的信息。分层（L1&#x2F;L2&#x2F;L3）让模型知道什么信息权威；截断+落盘控制单次输入规模；压缩+聚焦（Summary + Todo Recap）管理长期历史的噪音。</p><p><strong>第七，没有可观测性，就没有可调试性。</strong></p><p>Agent 是概率系统，不可能永远正确。但它出错时，你需要能回答：它做了什么？结果是什么？为什么这么做？</p><p>实现 Trace 系统，记录调用链、返回链、决策链。别只记录成功路径，失败轨迹往往更有价值。</p><p><strong>第八，保留失败轨迹，系统才能进化。</strong></p><p>别怕”污染历史”就清洗掉失败记录。CONFLICT 错误、超时重试、模型瞎猜——这些都记下来。只有看到完整的失败过程，才能定位根因，才能把”遇到 CONFLICT 必须重新 Read”这种经验固化到提示词里。</p><h2 id="写在最后：我们都是在给-LLM-“擦屁股”"><a href="#写在最后：我们都是在给-LLM-“擦屁股”" class="headerlink" title="写在最后：我们都是在给 LLM “擦屁股”"></a>写在最后：我们都是在给 LLM “擦屁股”</h2><p>做完这个项目，我有个特别深的感触，可能听起来有点糙，但话糙理不糙：</p><p><strong>Agent 开发的核心，不是让模型更自由，而是通过工程设计，把模型”不确定的能力”约束在”最小可控的范围”里。说白了，我们就是在给 LLM 擦屁股。</strong></p><p>为什么这么说？</p><p>你看啊，LLM 很强，能写代码、能读文档、能推理。但它就像一个特别聪明但特别不靠谱的实习生——</p><ul><li>你让它去打印文件，它可能把全公司的打印机都调用一遍；</li><li>你让它整理会议纪要，它可能把上周的会议也掺和进来；</li><li>你让它写个函数，它写得贼溜，但变量命名全是 <code>a</code>、<code>b</code>、<code>c</code>，还顺带改了你没让改的文件。</li></ul><p><strong>它的”强”是能力上的强，但”不靠谱”是确定性上的不靠谱。</strong></p><p>而我们做 Agent 工程，本质上就是在解决这个矛盾：</p><table><thead><tr><th>模型的天性</th><th>我们的工程对策</th></tr></thead><tbody><tr><td>喜欢自由发挥</td><td>用 Function Calling 锁定调用格式</td></tr><tr><td>上下文一多就”失忆”</td><td>用 L1&#x2F;L2&#x2F;L3 分层 + Summary 压缩</td></tr><tr><td>出错不会自查</td><td>用 Trace 记录每一步，让错误可追溯</td></tr><tr><td>长任务容易跑偏</td><td>用 Todo + Task 拆分，降低单步复杂度</td></tr><tr><td>不懂领域知识</td><td>用 Skills 固化 SOP，让它”有脑”</td></tr></tbody></table><p>你看这七章的内容，从工具原子化到上下文工程，从可观测性到子代理——<strong>每一层都是在给模型”打补丁”，帮它收拾烂摊子。</strong></p><p>但这恰恰是最有意思的地方。</p><p>以前我觉得，AI 时代工程师的价值会下降。现在我觉得恰恰相反：<strong>模型越强大，越需要工程能力来驾驭它。</strong> 就像汽车引擎越来越强，但好的底盘、刹车、悬挂系统反而更重要。</p><p><strong>我们不是在和模型竞争，而是在和模型协作——它负责”能做什么”，我们负责”怎么让它稳定地做对”。</strong></p><p>所以，如果你问我做完这个项目最大的收获是什么？</p><p>不是学会了什么高大上的架构，而是想明白了一个朴素的道理：<strong>优秀的 Agent 不是”让模型更自由”的产物，而是”把不确定性约束到最小”的结果。</strong></p><p>这个认知转变，可能比所有代码都值钱。</p>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra09-Agent%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E4%B8%8E%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/</id>
    <link href="http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra09-Agent%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E4%B8%8E%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/"/>
    <published>2026-03-03T04:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="Agent应用开发实践踩坑与经验分享"><a href="#Agent应用开发实践踩坑与经验分享" class="headerlink" title="Agent应用开发实践踩坑与经验分享"></a>Agent应用开发实践踩坑与经验分享</h1><p>学完 Hel]]>
    </summary>
    <title>Agent应用开发实践踩坑与经验分享</title>
    <updated>2026-03-08T09:24:16.381Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="05-如何写出好的-Skill"><a href="#05-如何写出好的-Skill" class="headerlink" title="05. 如何写出好的 Skill"></a>05. 如何写出好的 Skill</h1><blockquote><p>什么是 Skill？怎么写好skill？<br>我们沿着 skill-creator 的设计思路，找到答案。<br>本篇文章的目标是：读完它，就了解了写skill的最佳实践。</p></blockquote><hr><p><img src="/img/ai-agent-learning/%E7%9B%AE%E5%BD%95" alt="目录"></p><h2 id="一、什么是-Skill？"><a href="#一、什么是-Skill？" class="headerlink" title="一、什么是 Skill？"></a>一、什么是 Skill？</h2><h3 id="1-1-定义"><a href="#1-1-定义" class="headerlink" title="1.1 定义"></a>1.1 定义</h3><p>Skill 是一个文件夹，里面装着指令文档、参考资料、可执行脚本等资源。AI 拿到它，就能胜任一项原本不会的特定工作。</p><p>比如一个 <code>pdf-editor</code> 技能文件夹里，可能有一份”怎么处理 PDF”的操作指令、一个旋转 PDF 的 Python 脚本、一份 API 参考文档——AI 不需要从外部再找任何东西，这个文件夹里全有了。</p><p>这个概念不限于某一个产品。无论是 Codex、Claude 还是其他 AI Agent，skill 的本质都一样。你可以把它理解为 AI 的一个<strong>能力插件</strong>——插上去，AI 就多了一项专长；拔掉，AI 还是原来那个通用助手。</p><h3 id="1-2-最小形态"><a href="#1-2-最小形态" class="headerlink" title="1.2 最小形态"></a>1.2 最小形态</h3><p>一个 skill 最少只需要一个文件：</p><figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sqf">my-<span class="hljs-built_in">skill</span>/<br>└── <span class="hljs-built_in">SKILL</span>.md<br></code></pre></td></tr></table></figure><p><code>SKILL.md</code> 的结构很简单——上半部分告诉 AI”什么时候用我”，下半部分告诉 AI”具体怎么做”：</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><code class="hljs yaml"><span class="hljs-meta">---</span><br><span class="hljs-attr">name:</span> <span class="hljs-string">my-skill</span>                    <span class="hljs-comment"># ← 上半部分：元数据</span><br><span class="hljs-attr">description:</span> <span class="hljs-string">&gt;-</span>                   <span class="hljs-comment">#    AI 靠这里决定要不要激活这个技能</span><br>  <span class="hljs-string">当用户需要做某件事时，使用这个技能。</span><br><span class="hljs-meta">---</span><br><span class="hljs-meta"></span><br><span class="hljs-string">下半部分：操作指令</span>                   <span class="hljs-comment"># ← AI 激活技能后才会读到这里</span><br><span class="hljs-string">按照以下步骤执行...</span><br></code></pre></td></tr></table></figure><p>上半部分叫 <strong>frontmatter</strong>（<code>---</code> 之间的 YAML），包含 <code>name</code> 和 <code>description</code> 两个字段。AI 在每次对话开始时都会扫描所有已安装技能的 frontmatter，靠 description 来判断”这个技能和当前请求相关吗”——这是技能被触发的<strong>唯一依据</strong>。</p><p>下半部分叫 <strong>body</strong>（Markdown 正文），是技能被激活之后才加载的操作指令。如果技能没被触发，AI 永远不会读到这里。</p><h3 id="1-3-完整结构"><a href="#1-3-完整结构" class="headerlink" title="1.3 完整结构"></a>1.3 完整结构</h3><p>当一个技能变复杂时，单靠一个 SKILL.md 就不够了。</p><p>比如你要做一个”PDF 处理”技能：SKILL.md 里写了处理流程，但旋转 PDF 的代码每次都一样，每次让 AI 重写既浪费时间又可能出错——不如直接放一个写好的 Python 脚本。再比如”前端项目生成器”技能：每次都要一套 HTML&#x2F;React 的样板文件，不如直接放一个模板目录让 AI 拷贝出来改。</p><p>所以完整的 skill 目录可以包含这些东西：</p><figure class="highlight autoit"><table><tr><td class="gutter"><pre><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><code class="hljs autoit">skill-name/<br>├── SKILL.md                  <span class="hljs-meta"># [必需] 入口文件：frontmatter + body</span><br>├── agents/<br>│   └── openai.yaml           <span class="hljs-meta"># [推荐] 技能的<span class="hljs-string">&quot;名片&quot;</span></span><br>├── scripts/                  <span class="hljs-meta"># [可选] 可执行脚本</span><br>├── references/               <span class="hljs-meta"># [可选] 参考文档</span><br>└── assets/                   <span class="hljs-meta"># [可选] 产出物模板</span><br></code></pre></td></tr></table></figure><p>逐个说明：</p><ul><li><p><strong>SKILL.md</strong> — 唯一必需的文件，前面已经介绍过</p></li><li><p><strong>scripts&#x2F;</strong> — 写好的程序，AI 不需要读懂它，直接调用 shell 执行就行。比如 <code>scripts/rotate_pdf.py</code>，AI 只要跑 <code>python rotate_pdf.py input.pdf 90</code> 就能旋转 PDF，不用每次重新写旋转逻辑。适合那些<strong>结果必须精确、不能让 AI 自由发挥</strong>的操作</p></li><li><p><strong>references&#x2F;</strong> — AI 在工作过程中需要查阅的参考资料。比如一个”BigQuery 查询”技能，AI 要知道公司有哪些表、每个表有什么字段，这些信息放在 <code>references/schema.md</code> 里，AI 需要时再读取。和 scripts 的区别是：references 是给 AI <strong>读</strong>的，scripts 是给 AI <strong>执行</strong>的</p></li><li><p><strong>assets&#x2F;</strong> — 不是给 AI 看的，而是直接用在最终产出里的文件。比如一个”前端项目生成器”技能，<code>assets/frontend-template/</code> 里放着一套 HTML&#x2F;React 样板代码，AI 直接把这套模板拷贝出来，在上面修改。再比如 <code>assets/logo.png</code> 是公司 logo，AI 生成网页时直接引用它。AI 不需要”读懂”一张 logo 图片，只需要知道它在哪、什么时候放进去</p></li><li><p><strong>agents&#x2F;openai.yaml</strong> — 技能的”名片”。很多 AI 产品会在界面上展示一个技能列表，让用户选择或搜索。这个文件里存的就是列表中显示的名称、简介、图标等信息。它不影响 AI 的行为，纯粹是给产品界面用的</p></li></ul><hr><h2 id="二、你是在给人写指令，还是在给-AI-写指令？"><a href="#二、你是在给人写指令，还是在给-AI-写指令？" class="headerlink" title="二、你是在给人写指令，还是在给 AI 写指令？"></a>二、你是在给人写指令，还是在给 AI 写指令？</h2><p>知道了 skill 是什么，下一步就是写一个。但大多数人第一次写出来的 skill 都有同一个问题。</p><p>看一个例子。假设你要做一个”代码审查”技能，你可能会这样写：</p><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><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs markdown">---<br>name: code-review<br><span class="hljs-section">description: 代码审查技能</span><br><span class="hljs-section">---</span><br><br><span class="hljs-section"># Code Review Skill</span><br><br><span class="hljs-section">## 背景</span><br>本技能基于团队多年的代码审查经验总结而成，旨在提升代码质量和团队协作效率。<br><br><span class="hljs-section">## 审查原则</span><br><span class="hljs-bullet">-</span> 保持专业、建设性的语气<br><span class="hljs-bullet">-</span> 关注代码质量而非个人风格<br><span class="hljs-bullet">-</span> 平衡严格性和灵活性<br><br><span class="hljs-section">## 使用方式</span><br>当用户提交代码时，对代码进行全面审查，给出改进建议。注意保持友好和鼓励的态度。<br><br><span class="hljs-section">## 版本记录</span><br><span class="hljs-bullet">-</span> v1.0: 初始版本<br><span class="hljs-bullet">-</span> v1.1: 增加了对 Python 的支持<br></code></pre></td></tr></table></figure><p>如果这是一份给人看的团队文档，它写得不错——有背景、有原则、有使用方式，甚至还有版本记录。</p><p>但 skill 的读者是 AI。用这个视角重新审视：</p><ul><li><strong>“基于团队多年经验总结”</strong> — AI 不关心这个技能是怎么来的，它只需要知道<strong>现在该怎么做</strong></li><li><strong>“保持专业、建设性的语气”</strong> — 人类读了能 get 到一个大致的感觉，但 AI 会把”专业”和”建设性”展开成无数种组合，每次输出都不一样</li><li><strong>“平衡严格性和灵活性”</strong> — 人类经验丰富的审查者知道什么时候严格什么时候灵活，但 AI 没有这个直觉，这句话等于没说</li><li><strong>“全面审查，给出改进建议”</strong> — 这是对人类审查者的期望，但 AI 需要的是：先检查什么？再检查什么？什么问题必须指出？什么问题可以忽略？</li><li><strong>“版本记录”</strong> — AI 每次被唤醒都是全新的，v1.0 还是 v1.1 对它没有意义</li><li><strong>description 只写了”代码审查技能”</strong> — AI 靠 description 判断是否触发，”代码审查技能”五个字太模糊：用户说”帮我看看这段代码”要触发吗？”这个函数性能怎么样”要触发吗？</li></ul><p>每一条单独看都不是”错”，但它们都是写给人看的。<strong>问题不在于写得不够多，而在于写错了对象。</strong></p><p>那正确的写法是什么样的？我们来看一个现成的答案——codex的skill-creator。它是一个”创建 skill 的 skill”，它自己的 SKILL.md 就是一份关于”如何给 AI 写指令”的最佳实践。</p><hr><h2 id="三、skill-creator-的整体框架"><a href="#三、skill-creator-的整体框架" class="headerlink" title="三、skill-creator 的整体框架"></a>三、skill-creator 的整体框架</h2><p>打开 skill-creator 的 SKILL.md（约 370 行），在深入任何细节之前，我们先建立对它的整体认知。</p><p>skill-creator 要解决的问题只有一个：<strong>怎么在有限的上下文窗口里，给 AI 最有效的指令？</strong></p><p>围绕这个问题，它给出了一套完整的设计体系，可以用三个层次来理解。</p><h3 id="第一层：根本约束——简洁"><a href="#第一层：根本约束——简洁" class="headerlink" title="第一层：根本约束——简洁"></a>第一层：根本约束——简洁</h3><p>AI 的上下文窗口是有限的，而且是共享的（系统提示、对话历史、所有已安装技能的元数据都在里面）。你的 skill 占得越多，留给其他用途的就越少。所以 skill-creator 的第一原则就是：<strong>每一句话都要值得它占用的 token</strong>。</p><h3 id="第二层：两个设计维度"><a href="#第二层：两个设计维度" class="headerlink" title="第二层：两个设计维度"></a>第二层：两个设计维度</h3><p>在”简洁”这个约束下，写 skill 时面临两个核心决策：</p><p><strong>维度一：信息放在哪里？</strong></p><p>不是所有信息都需要一开始就加载。skill-creator 设计了一个三级分层架构，让不同的信息在不同的时机进入上下文：</p><p>![Skill 标准结构与三级加载](&#x2F;img&#x2F;ai-agent-learning&#x2F;Skill 标准结构与三级加载)</p><ul><li><strong>L1（元数据）</strong>：始终在上下文中，约 100 词——AI 靠它判断要不要激活这个技能</li><li><strong>L2（SKILL.md body）</strong>：触发后才加载，控制在 5k 词以内——操作指令</li><li><strong>L3（scripts&#x2F;references&#x2F;assets）</strong>：按需使用，无上限——其中 scripts 执行而不读入，零 token 成本</li></ul><p>这解决了”怎么用最少的 token 承载最多的信息”。</p><p><strong>维度二：给 AI 多大自由度？</strong></p><p>不是所有任务都适合让 AI 自由发挥。</p><p>举个例子：让 AI 写一篇技术博客，十个人写出十种风格都可以——你只需要给方向，具体怎么写让 AI 自己决定。这就是<strong>高自由度</strong>。</p><p>但让 AI 生成一个 YAML 配置文件就不一样了。比如 skill-creator 要生成的 <code>openai.yaml</code>，里面有个 <code>short_description</code> 字段，要求 25-64 个字符、首字母大写、不能有引号。AI 写成 65 个字符？不行，产品界面会截断。写成 24 个字符？不行，校验不通过。漏了首字母大写？界面显示不一致。这种任务差一个字符就出问题，你不能让 AI 自由发挥，必须用脚本来锁死格式——这就是<strong>低自由度</strong>。这类任务叫”脆弱操作”：不是说它复杂，而是说它<strong>做对只有一种方式，做错有一百种方式</strong>。</p><p><img src="/img/ai-agent-learning/%E8%87%AA%E7%94%B1%E5%BA%A6%E5%85%89%E8%B0%B1" alt="自由度光谱"></p><p>这解决了”怎么在 AI 的灵活性和输出的可靠性之间取得平衡”。</p><h3 id="第三层：落地流程"><a href="#第三层：落地流程" class="headerlink" title="第三层：落地流程"></a>第三层：落地流程</h3><p>有了原则和架构，skill-creator 最后给出了一个六步创建流程，把设计思想变成可执行的操作步骤：</p><p><img src="/img/ai-agent-learning/%E5%85%AD%E6%AD%A5%E5%88%9B%E5%BB%BA%E6%B5%81%E7%A8%8B" alt="六步创建流程"></p><p>理解→规划→初始化→编辑→校验→迭代。其中脚本贯穿流程，形成确定性的质量保障链：</p><p><img src="/img/ai-agent-learning/%E6%96%87%E4%BB%B6%E4%BA%A4%E4%BA%92%E5%85%B3%E7%B3%BB" alt="文件交互关系"></p><h3 id="框架总览"><a href="#框架总览" class="headerlink" title="框架总览"></a>框架总览</h3><p>三个层次的关系：</p><figure class="highlight"><table><tr><td class="gutter"><pre><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><code class="hljs">简洁（根本约束）                         → 第四章<br> ├── 信息放在哪里？ → 三级分层架构        → 第五章<br> ├── 给 AI 多大自由度？ → 自由度光谱与脚本  → 第六章<br> └── 怎么落地？ → 六步创建流程            → 第七章<br></code></pre></td></tr></table></figure><p>接下来的每一章都在这个框架内展开。</p><hr><h2 id="四、根本约束：简洁"><a href="#四、根本约束：简洁" class="headerlink" title="四、根本约束：简洁"></a>四、根本约束：简洁</h2><blockquote><p>框架位置：第一层</p></blockquote><h3 id="4-1-核心约束"><a href="#4-1-核心约束" class="headerlink" title="4.1 核心约束"></a>4.1 核心约束</h3><p>AI 的上下文窗口就像一张工作台——它同一时间能摊开的资料是有限的。而这张工作台上已经放着不少东西了：系统自己的规则、用户之前说过的话、所有已安装技能的简介。你的 skill 一旦被激活，它的内容也要摊上去。工作台就这么大，你占得越多，留给其他东西的空间就越少。</p><p>所以 skill-creator 把这一点写成了第一条原则：</p><blockquote><p>The context window is a public good. Skills share the context window with everything else Codex needs: system prompt, conversation history, other Skills’ metadata, and the actual user request.</p></blockquote><p>既然工作台空间有限，那写 skill 时怎么判断一段内容该不该放进去？skill-creator 给了一个前提假设：<strong>AI 本身已经很聪明了，你只需要补充它不知道的东西。</strong></p><blockquote><p>Default assumption: Codex is already very smart. Only add context Codex doesn’t already have.</p></blockquote><p>基于这个假设，每写一段内容之前问自己两个问题：</p><ul><li>“AI 是不是已经知道这个了？” — 比如”Python 的 for 循环怎么写”，AI 当然知道，不用教</li><li>“这段内容值不值得占用工作台上的空间？” — 一段 200 字的解释，能不能用一个 10 行的代码示例替代？</li></ul><p><strong>实操推论</strong>：用简洁的示例代替冗长的解释。一个好的代码示例胜过三段文字描述。</p><h3 id="4-2-什么不该放进-Skill？"><a href="#4-2-什么不该放进-Skill？" class="headerlink" title="4.2 什么不该放进 Skill？"></a>4.2 什么不该放进 Skill？</h3><p>Skill-creator 明确列出了<strong>禁止清单</strong>：</p><blockquote><p>A skill should only contain essential files that directly support its functionality. Do NOT create extraneous documentation or auxiliary files.</p></blockquote><p>不该有的文件：</p><ul><li>README.md</li><li>INSTALLATION_GUIDE.md</li><li>QUICK_REFERENCE.md</li><li>CHANGELOG.md</li></ul><blockquote><p>The skill should only contain the information needed for an AI agent to do the job at hand. It should not contain auxiliary context about the process that went into creating it, setup and testing procedures, user-facing documentation, etc. Creating additional documentation files just adds clutter and confusion.</p></blockquote><p>原因很简单：skill 的读者是 AI，不是人类开发者。AI 不需要安装指南、更新日志、快速参考这些”人类辅助文档”。每一个多余的文件都是噪音。</p><h3 id="4-3-写约束时，”不做什么”比”做什么”更精确"><a href="#4-3-写约束时，”不做什么”比”做什么”更精确" class="headerlink" title="4.3 写约束时，”不做什么”比”做什么”更精确"></a>4.3 写约束时，”不做什么”比”做什么”更精确</h3><p>简洁不只是”少写”，还包括”写对”。看一个例子。</p><p>当 skill-creator 创建 <code>laotou-thought-style</code>（一种写作风格技能）时，它<strong>没有</strong>写：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs">请用温暖、克制、有洞察力的语气写作。<br></code></pre></td></tr></table></figure><p>这种正面描述看起来清晰，但对 AI 来说，”温暖”的程度、”克制”和”有洞察力”之间的平衡——全是模糊空间。</p><p>它做的是写了一份<strong>反模式清单</strong>（<code>references/anti-patterns.md</code>）：</p><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><tr><td>收尾太猛</td><td>结尾”必须改变！”</td><td>换成”慢慢来””就好”</td></tr><tr><td>过度绝对化</td><td>“永远””一定”</td><td>加限定词”多数时候””往往”</td></tr></tbody></table><p><strong>每一条都是具体的、可检测的、有明确修正方案的。</strong></p><p>背后的原理：</p><figure class="highlight 1c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs 1c"><span class="hljs-string">&quot;做什么&quot;</span> → 描述一个无限大的可行域 → AI 在里面随机游走<br><span class="hljs-string">&quot;不做什么&quot;</span> → 在可行域上画边界 → AI 的行为空间被收窄到你想要的范围<br></code></pre></td></tr></table></figure><p>skill-creator 自身也遵循了这个原则——它的 SKILL.md 用了很大篇幅说”什么不该写”（What to Not Include in a Skill），而不是泛泛地说”写好内容”。</p><p>当你写完 SKILL.md，做一次”反转测试”：每一条正面指导，能不能改写成”不要做X”的形式？如果可以，改写后通常更精确。</p><h3 id="4-4-统一使用祈使语气"><a href="#4-4-统一使用祈使语气" class="headerlink" title="4.4 统一使用祈使语气"></a>4.4 统一使用祈使语气</h3><p>skill-creator 要求 SKILL.md 的正文统一使用<strong>祈使语气&#x2F;不定式</strong>（Always use imperative&#x2F;infinitive form）。这不是美学偏好，而是为了减少歧义——祈使句天然就是指令。</p><hr><h2 id="五、设计维度一：信息放在哪里？"><a href="#五、设计维度一：信息放在哪里？" class="headerlink" title="五、设计维度一：信息放在哪里？"></a>五、设计维度一：信息放在哪里？</h2><blockquote><p>框架位置：第二层 — 维度一</p></blockquote><p>在第三章的框架总览中，我们已经看到了三级分层架构的全貌。这一章展开讲它的细节。</p><h3 id="5-1-三级渐进式加载"><a href="#5-1-三级渐进式加载" class="headerlink" title="5.1 三级渐进式加载"></a>5.1 三级渐进式加载</h3><p>skill-creator 原文对三个层级的定义：</p><blockquote><ol><li><strong>Metadata (name + description)</strong> - Always in context (~100 words)</li><li><strong>SKILL.md body</strong> - When skill triggers (&lt;5k words)</li><li><strong>Bundled resources</strong> - As needed by Codex (Unlimited because scripts can be executed without reading into context window)</li></ol></blockquote><table><thead><tr><th>层级</th><th>内容</th><th>何时在上下文中</th><th>token 成本</th></tr></thead><tbody><tr><td><strong>L1</strong></td><td>frontmatter（name + description）</td><td><strong>始终</strong></td><td>~100 词</td></tr><tr><td><strong>L2</strong></td><td>SKILL.md body</td><td>触发后加载</td><td>&lt;5k 词</td></tr><tr><td><strong>L3</strong></td><td>scripts&#x2F; references&#x2F; assets&#x2F;</td><td>按需加载</td><td>无上限</td></tr></tbody></table><p><strong>这本质上是一个信息熵管理系统</strong>：</p><ul><li><strong>L1 是过滤器</strong> — 从几十个已安装技能中筛选出当前需要的那一个。description 不精确 → 误触发或漏触发</li><li><strong>L2 是操作手册</strong> — 触发后告诉 AI 该怎么做。太长 → 注意力被稀释。body 控制在 500 行以内</li><li><strong>L3 是工具箱</strong> — 只在需要时打开。其中 scripts&#x2F; 最高效——<strong>执行而不读入</strong>，零 token 成本</li></ul><h3 id="5-2-Frontmatter：触发机制的全部来源"><a href="#5-2-Frontmatter：触发机制的全部来源" class="headerlink" title="5.2 Frontmatter：触发机制的全部来源"></a>5.2 Frontmatter：触发机制的全部来源</h3><p>Frontmatter 只有两个必需字段：<code>name</code> 和 <code>description</code>。但 description 的写法至关重要：</p><blockquote><p>This is the primary triggering mechanism for your skill, and helps Codex understand when to use the skill.</p></blockquote><p>skill-creator 自己的 description 是这样写的：</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></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">description:</span> <span class="hljs-string">Guide</span> <span class="hljs-string">for</span> <span class="hljs-string">creating</span> <span class="hljs-string">effective</span> <span class="hljs-string">skills.</span> <span class="hljs-string">This</span> <span class="hljs-string">skill</span> <span class="hljs-string">should</span> <span class="hljs-string">be</span> <span class="hljs-string">used</span> <span class="hljs-string">when</span><br>  <span class="hljs-string">users</span> <span class="hljs-string">want</span> <span class="hljs-string">to</span> <span class="hljs-string">create</span> <span class="hljs-string">a</span> <span class="hljs-string">new</span> <span class="hljs-string">skill</span> <span class="hljs-string">(or</span> <span class="hljs-string">update</span> <span class="hljs-string">an</span> <span class="hljs-string">existing</span> <span class="hljs-string">skill)</span> <span class="hljs-string">that</span> <span class="hljs-string">extends</span><br>  <span class="hljs-string">Codex&#x27;s</span> <span class="hljs-string">capabilities</span> <span class="hljs-string">with</span> <span class="hljs-string">specialized</span> <span class="hljs-string">knowledge,</span> <span class="hljs-string">workflows,</span> <span class="hljs-string">or</span> <span class="hljs-string">tool</span> <span class="hljs-string">integrations.</span><br></code></pre></td></tr></table></figure><p>它不只说”做什么”（creating effective skills），还说”什么时候用”（when users want to create a new skill or update an existing skill）。</p><p><strong>关键规则</strong>：</p><ul><li>把所有”when to use”信息放在 description 里，<strong>不要放在 body 里</strong>。body 是触发后才加载的，那时候 Codex 已经决定用了，”什么时候用”的信息已经迟了</li><li>不要在 frontmatter 中放 <code>name</code> 和 <code>description</code> 以外的字段（<code>license</code>、<code>allowed-tools</code>、<code>metadata</code> 除外）</li></ul><p>一个好的 description 示例（docx 技能）：</p><blockquote><p>“Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. Use when Codex needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks”</p></blockquote><h3 id="5-3-四种捆绑资源的本质区别"><a href="#5-3-四种捆绑资源的本质区别" class="headerlink" title="5.3 四种捆绑资源的本质区别"></a>5.3 四种捆绑资源的本质区别</h3><p>理解这四种资源的区别，是理解整个 skill 系统的关键：</p><h4 id="Scripts（scripts-）"><a href="#Scripts（scripts-）" class="headerlink" title="Scripts（scripts/）"></a>Scripts（<code>scripts/</code>）</h4><p>可执行代码（Python&#x2F;Bash 等），用于需要<strong>确定性可靠性</strong>或反复重写的任务。</p><ul><li><strong>什么时候需要</strong>：同样的代码每次都要重新写，或者需要确定性的可靠输出</li><li><strong>举例</strong>：<code>scripts/rotate_pdf.py</code> 用于 PDF 旋转任务</li><li><strong>核心优势</strong>：token 高效、确定性、可以执行而不读入上下文窗口</li><li><strong>注意</strong>：脚本有时仍需要被 Codex 读取，用于修补或环境适配</li></ul><h4 id="References（references-）"><a href="#References（references-）" class="headerlink" title="References（references/）"></a>References（<code>references/</code>）</h4><p>文档和参考材料，在需要时加载到上下文中，辅助 Codex 的思考过程。</p><ul><li><strong>什么时候需要</strong>：Codex 在工作时需要参考的详细文档</li><li><strong>举例</strong>：<code>references/finance.md</code>（财务 schema）、<code>references/api_docs.md</code>（API 规范）、<code>references/policies.md</code>（公司政策）</li><li><strong>用途</strong>：数据库 schema、API 文档、领域知识、公司政策、详细工作流指南</li><li><strong>核心优势</strong>：保持 SKILL.md 精炼，只在 Codex 判断需要时才加载</li><li><strong>最佳实践</strong>：如果文件很大（&gt;10k 词），在 SKILL.md 中包含 grep 搜索模式</li><li><strong>避免重复</strong>：信息应该只存在于 SKILL.md <strong>或</strong> references 文件中，不能两边都有。详细信息优先放 references，SKILL.md 只保留核心流程指令和工作流指导</li></ul><h4 id="Assets（assets-）"><a href="#Assets（assets-）" class="headerlink" title="Assets（assets/）"></a>Assets（<code>assets/</code>）</h4><p>不是用来加载到上下文中的文件，而是直接用在 Codex 产出物中的资源。</p><ul><li><strong>什么时候需要</strong>：技能需要在最终输出中使用的文件</li><li><strong>举例</strong>：<code>assets/logo.png</code>（品牌素材）、<code>assets/slides.pptx</code>（PPT 模板）、<code>assets/frontend-template/</code>（HTML&#x2F;React 样板）、<code>assets/font.ttf</code>（字体）</li><li><strong>用途</strong>：模板、图片、图标、样板代码、字体、示例文档——这些会被复制或修改</li><li><strong>核心优势</strong>：将输出资源与文档分离，Codex 可以使用它们而无需读入上下文</li></ul><h4 id="Agents-元数据（agents-openai-yaml）（推荐）"><a href="#Agents-元数据（agents-openai-yaml）（推荐）" class="headerlink" title="Agents 元数据（agents/openai.yaml）（推荐）"></a>Agents 元数据（<code>agents/openai.yaml</code>）（推荐）</h4><p>面向 UI 的元数据，不给 AI 读，给产品前端读：</p><ul><li>包含 <code>display_name</code>、<code>short_description</code>、<code>default_prompt</code> 等字段</li><li>通过脚本 <code>generate_openai_yaml.py</code> 确定性生成，而不是手写</li><li>更新 SKILL.md 后要检查 <code>agents/openai.yaml</code> 是否还匹配，过期了就重新生成</li><li>详细字段定义见 <code>references/openai_yaml.md</code></li></ul><h3 id="5-4-渐进式披露的三种实战模式"><a href="#5-4-渐进式披露的三种实战模式" class="headerlink" title="5.4 渐进式披露的三种实战模式"></a>5.4 渐进式披露的三种实战模式</h3><p>Skill-creator 给出了三种把内容拆分到 references 的具体模式：</p><p><strong>Pattern 1：高层指南 + 参考文件</strong></p><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section"># PDF Processing</span><br><br><span class="hljs-section">## Quick start</span><br>Extract text with pdfplumber:<br>[code example]<br><br><span class="hljs-section">## Advanced features</span><br><span class="hljs-bullet">-</span> <span class="hljs-strong">**Form filling**</span>: See [<span class="hljs-string">FORMS.md</span>](<span class="hljs-link">FORMS.md</span>) for complete guide<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**API reference**</span>: See [<span class="hljs-string">REFERENCE.md</span>](<span class="hljs-link">REFERENCE.md</span>) for all methods<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**Examples**</span>: See [<span class="hljs-string">EXAMPLES.md</span>](<span class="hljs-link">EXAMPLES.md</span>) for common patterns<br></code></pre></td></tr></table></figure><p>Codex 只在需要时才加载 FORMS.md、REFERENCE.md 或 EXAMPLES.md。</p><p><strong>Pattern 2：按领域组织</strong></p><p>多领域&#x2F;多变体技能，按领域拆分避免加载无关内容：</p><figure class="highlight dos"><table><tr><td class="gutter"><pre><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><code class="hljs dos">bigquery-skill/<br>├── SKILL.<span class="hljs-built_in">md</span> (overview and navigation)<br>└── reference/<br>    ├── finance.<span class="hljs-built_in">md</span> (revenue, billing metrics)<br>    ├── sales.<span class="hljs-built_in">md</span> (opportunities, pipeline)<br>    ├── product.<span class="hljs-built_in">md</span> (API usage, features)<br>    └── marketing.<span class="hljs-built_in">md</span> (campaigns, attribution)<br></code></pre></td></tr></table></figure><p>用户问销售指标时，Codex 只读 <code>sales.md</code>。</p><p>同样适用于多框架&#x2F;多变体场景：</p><figure class="highlight dos"><table><tr><td class="gutter"><pre><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><code class="hljs dos">cloud-deploy/<br>├── SKILL.<span class="hljs-built_in">md</span> (workflow + provider selection)<br>└── references/<br>    ├── aws.<span class="hljs-built_in">md</span> (AWS deployment patterns)<br>    ├── gcp.<span class="hljs-built_in">md</span> (GCP deployment patterns)<br>    └── azure.<span class="hljs-built_in">md</span> (Azure deployment patterns)<br></code></pre></td></tr></table></figure><p><strong>Pattern 3：条件性细节</strong></p><p>基础功能直接展示，高级功能按需链接：</p><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section"># DOCX Processing</span><br><br><span class="hljs-section">## Creating documents</span><br>Use docx-js for new documents. See [<span class="hljs-string">DOCX-JS.md</span>](<span class="hljs-link">DOCX-JS.md</span>).<br><br><span class="hljs-section">## Editing documents</span><br>For simple edits, modify the XML directly.<br><br><span class="hljs-strong">**For tracked changes**</span>: See [<span class="hljs-string">REDLINING.md</span>](<span class="hljs-link">REDLINING.md</span>)<br><span class="hljs-strong">**For OOXML details**</span>: See [<span class="hljs-string">OOXML.md</span>](<span class="hljs-link">OOXML.md</span>)<br></code></pre></td></tr></table></figure><h3 id="5-5-两条重要的避坑指南"><a href="#5-5-两条重要的避坑指南" class="headerlink" title="5.5 两条重要的避坑指南"></a>5.5 两条重要的避坑指南</h3><ol><li><strong>避免深层嵌套引用</strong> — 所有 reference 文件应该从 SKILL.md 直接链接，不要 A → B → C 式嵌套</li><li><strong>长文件加目录</strong> — 超过 100 行的 reference 文件要在顶部加 TOC，方便 Codex 预览全貌</li></ol><h3 id="5-6-常见的层错位"><a href="#5-6-常见的层错位" class="headerlink" title="5.6 常见的层错位"></a>5.6 常见的层错位</h3><table><thead><tr><th>错误</th><th>后果</th><th>修正</th></tr></thead><tbody><tr><td>触发条件放在 body 里</td><td>body 是触发后才加载的，晚了</td><td>放 frontmatter description</td></tr><tr><td>“When to Use This Skill” 写在 body</td><td>同上，Codex 已经决定用了才看到</td><td>移到 description</td></tr><tr><td>参考细节塞进 SKILL.md</td><td>body 膨胀，信息密度下降</td><td>拆到 references&#x2F;，body 只放引用链接</td></tr><tr><td>确定性操作写成文字指令</td><td>AI 每次重新理解，可能出错</td><td>封装成 scripts&#x2F;，执行不读入</td></tr><tr><td>references 互相引用</td><td>AI 需要多跳获取信息</td><td>所有 references 从 SKILL.md 直接链接</td></tr><tr><td>SKILL.md 和 references 内容重复</td><td>浪费 token，更新时可能不一致</td><td>信息只在一处存在</td></tr></tbody></table><hr><h2 id="六、设计维度二：给-AI-多大自由度？"><a href="#六、设计维度二：给-AI-多大自由度？" class="headerlink" title="六、设计维度二：给 AI 多大自由度？"></a>六、设计维度二：给 AI 多大自由度？</h2><blockquote><p>框架位置：第二层 — 维度二</p></blockquote><p>知道了信息该放在哪里、该怎么约束，下一个问题是：<strong>AI 做什么，脚本做什么？</strong></p><p>AI 非常擅长理解语义、生成文本、做创造性工作。但它不擅长精确格式控制、长度约束、命名规范——这些”脆弱操作”。</p><h3 id="6-1-三个自由度档位"><a href="#6-1-三个自由度档位" class="headerlink" title="6.1 三个自由度档位"></a>6.1 三个自由度档位</h3><p>Skill-creator 用一个<strong>自由度光谱</strong>来处理这种不均匀性（见第三章框架图）：</p><blockquote><p>Think of Codex as exploring a path: a narrow bridge with cliffs needs specific guardrails (low freedom), while an open field allows many routes (high freedom).</p></blockquote><p><strong>高自由度</strong>（文字指令）：多种方法都可行时，决策依赖上下文，用启发式引导。</p><p><strong>中自由度</strong>（伪代码&#x2F;带参数的脚本）：有最佳实践但允许变通，配置影响行为。</p><p><strong>低自由度</strong>（具体脚本，少量参数）：操作脆弱容易出错，一致性至关重要，必须遵循特定序列。</p><p>核心逻辑：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs">任务越脆弱（容易出错） → 自由度越低 → 用脚本锁死<br>任务越灵活（多种方案都对） → 自由度越高 → 用文字引导<br></code></pre></td></tr></table></figure><h3 id="6-2-skill-creator-自身的自由度分配"><a href="#6-2-skill-creator-自身的自由度分配" class="headerlink" title="6.2 skill-creator 自身的自由度分配"></a>6.2 skill-creator 自身的自由度分配</h3><table><thead><tr><th>任务</th><th>自由度</th><th>实现方式</th></tr></thead><tbody><tr><td>理解用户需求并提问</td><td>高</td><td>SKILL.md 文字指导</td></tr><tr><td>规划技能内容结构</td><td>中</td><td>模板 + 选择题式模式推荐</td></tr><tr><td>初始化目录结构</td><td><strong>低</strong></td><td><code>init_skill.py</code> 脚本</td></tr><tr><td>生成 openai.yaml</td><td><strong>低</strong></td><td><code>generate_openai_yaml.py</code> 脚本</td></tr><tr><td>编写 SKILL.md 内容</td><td>高</td><td>原则指导 + 写作建议</td></tr><tr><td>校验最终结果</td><td><strong>低</strong></td><td><code>quick_validate.py</code> 脚本</td></tr></tbody></table><h3 id="6-3-两个方向的错误"><a href="#6-3-两个方向的错误" class="headerlink" title="6.3 两个方向的错误"></a>6.3 两个方向的错误</h3><p><strong>错误 1：给脆弱任务太多自由度</strong></p><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section"># 错误</span><br>请生成一个 openai.yaml 文件，包含 display<span class="hljs-emphasis">_name 和 short_</span>description。<br><br><span class="hljs-section"># 后果：short<span class="hljs-emphasis">_description 可能超过 64 字符限制，大小写可能不一致</span></span><br></code></pre></td></tr></table></figure><p>Skill-creator 的做法：用 <code>generate_openai_yaml.py</code> 脚本锁死格式。AI 只提供参数值，脚本保证输出合规。</p><p><strong>错误 2：给创造性任务太多约束</strong></p><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section"># 错误</span><br>第一段必须以&quot;昨天&quot;开头，第二段必须包含&quot;本质上&quot;，最后一段以&quot;慢慢来&quot;结尾。<br><br><span class="hljs-section"># 后果：生成的文本像填词游戏</span><br></code></pre></td></tr></table></figure><p>Skill-creator 的做法：给结构比例（场景层 ≤30%，原理层 30-40%），但不锁定具体用词。</p><h3 id="6-4-判断标准"><a href="#6-4-判断标准" class="headerlink" title="6.4 判断标准"></a>6.4 判断标准</h3><p>两个问题：</p><ol><li><strong>做错了后果多严重？</strong> — 越严重 → 越低自由度</li><li><strong>有多少种”正确”的做法？</strong> — 越多 → 越高自由度</li></ol><h3 id="6-5-低自由度的实现：skill-creator-的三个脚本"><a href="#6-5-低自由度的实现：skill-creator-的三个脚本" class="headerlink" title="6.5 低自由度的实现：skill-creator 的三个脚本"></a>6.5 低自由度的实现：skill-creator 的三个脚本</h3><p>理解了自由度光谱，就能理解 skill-creator 为什么有三个脚本——它们就是”低自由度”的具体实现（脚本间的交互关系见第三章框架图）。</p><p><strong><code>init_skill.py</code>（输入保障，398 行）</strong></p><p>初始化新技能目录的脚手架工具，类似 <code>create-react-app</code> 之于 React 项目：</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></pre></td><td class="code"><pre><code class="hljs bash">scripts/init_skill.py &lt;skill-name&gt; --path &lt;output-directory&gt; \<br>  [--resources scripts,references,assets] [--examples] \<br>  [--interface key=value]<br></code></pre></td></tr></table></figure><p>核心功能：</p><ul><li>创建技能目录</li><li>生成带 TODO 占位符的 SKILL.md 模板（TODO 是给 Codex 看的”填空题”）</li><li>调用 <code>generate_openai_yaml.py</code> 生成 <code>agents/openai.yaml</code>（通过 <code>--interface key=value</code> 传入 AI 生成的 display_name、short_description、default_prompt）</li><li>可选创建 <code>scripts/</code>、<code>references/</code>、<code>assets/</code> 子目录</li><li>可选添加示例文件（<code>--examples</code>）</li><li>内置 <code>normalize_skill_name()</code> 自动把任意用户输入标准化为 hyphen-case</li></ul><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></pre></td><td class="code"><pre><code class="hljs bash">scripts/init_skill.py my-skill --path skills/public<br>scripts/init_skill.py my-skill --path skills/public --resources scripts,references<br>scripts/init_skill.py my-skill --path skills/public --resources scripts --examples<br></code></pre></td></tr></table></figure><p><strong><code>generate_openai_yaml.py</code>（格式保障，226 行）</strong></p><p>专门负责生成和更新 <code>agents/openai.yaml</code>：</p><ul><li>从 SKILL.md 的 frontmatter 读取技能名</li><li>自动将 hyphen-case 转为 Title Case（<code>my-cool-skill</code> → <code>My Cool Skill</code>）</li><li>内置缩写词典（GH、MCP、API 等保持大写）和品牌词典（openai → OpenAI）</li><li>自动生成 25-64 字符的 <code>short_description</code></li><li>支持 <code>--interface key=value</code> 覆盖任意字段</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">scripts/generate_openai_yaml.py &lt;path/to/skill-folder&gt; --interface key=value<br></code></pre></td></tr></table></figure><p><strong><code>quick_validate.py</code>（输出保障，102 行）</strong></p><p>技能创建后的”质检员”：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">scripts/quick_validate.py &lt;path/to/skill-folder&gt;<br></code></pre></td></tr></table></figure><p>校验内容：</p><ul><li>SKILL.md 是否存在</li><li>YAML frontmatter 格式是否合法</li><li><code>name</code>：是否为 hyphen-case，≤ 64 字符，无连续&#x2F;首尾连字符</li><li><code>description</code>：是否存在，无尖括号，≤ 1024 字符</li><li>只允许 <code>name</code>、<code>description</code>、<code>license</code>、<code>allowed-tools</code>、<code>metadata</code> 这 5 个 frontmatter 键</li></ul><h3 id="6-6-质量保障链"><a href="#6-6-质量保障链" class="headerlink" title="6.6 质量保障链"></a>6.6 质量保障链</h3><p>三个脚本形成了一条<strong>确定性保障链</strong>，夹住中间的创造性步骤：</p><figure class="highlight isbl"><table><tr><td class="gutter"><pre><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><code class="hljs isbl"><span class="hljs-variable">init_skill.py</span>（输入保障）<br>  命名标准化 + 目录结构创建 + 模板生成<br>  → 确保起点正确<br>       ↓<br>  <span class="hljs-variable">AI</span> 创造性编写（高自由度）<br>  → <span class="hljs-variable">SKILL.md</span> 内容、<span class="hljs-variable"><span class="hljs-class">references</span></span>、自定义 <span class="hljs-variable"><span class="hljs-class">scripts</span></span><br>       ↓<br><span class="hljs-variable">quick_validate.py</span>（输出保障）<br>  <span class="hljs-variable">frontmatter</span> 格式 + 命名规范 + 长度约束校验<br>  → 确保终点合规<br></code></pre></td></tr></table></figure><p>关键洞察：脚本是”执行而不读入”的——<strong>零 token 成本</strong>。你可以把任意复杂的确定性逻辑封装进脚本，而不用担心它占用上下文。这就是为什么 skill-creator 把命名转换（缩写词典、品牌词典）、长度约束（25-64 字符）、格式校验这些细碎但脆弱的操作全部交给了脚本。</p><h3 id="6-7-什么该封装成脚本？"><a href="#6-7-什么该封装成脚本？" class="headerlink" title="6.7 什么该封装成脚本？"></a>6.7 什么该封装成脚本？</h3><figure class="highlight"><table><tr><td class="gutter"><pre><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><code class="hljs">每次执行结果必须一样      → 脚本<br>涉及精确格式/长度约束     → 脚本<br>涉及命名规范转换          → 脚本<br>需要校验规则匹配          → 脚本<br>同样的代码每次都要重新写   → 脚本<br><br>需要理解上下文            → 文字指令<br>有多种合理做法            → 文字指令<br>需要创造性判断            → 文字指令<br></code></pre></td></tr></table></figure><p>脚本有时仍需要被 Codex 读取（用于修补或环境适配），但大多数时候它们是”执行而不读入”的。</p><hr><h2 id="七、落地：六步创建流程"><a href="#七、落地：六步创建流程" class="headerlink" title="七、落地：六步创建流程"></a>七、落地：六步创建流程</h2><blockquote><p>框架位置：第三层</p></blockquote><p>有了前面的原则和架构，skill-creator 最后给出了一个六步创建流程，把设计思想变成可执行的操作步骤（见第三章框架图）。</p><h3 id="7-0-命名规范"><a href="#7-0-命名规范" class="headerlink" title="7.0 命名规范"></a>7.0 命名规范</h3><p>在开始之前，先确定命名：</p><ul><li>只用小写字母、数字和连字符；把用户提供的名称标准化为 hyphen-case（如 “Plan Mode” → <code>plan-mode</code>）</li><li>名称 ≤ 64 字符</li><li>优先用简短的、动词开头的短语来描述动作</li><li>需要时用工具名做命名空间（如 <code>gh-address-comments</code>、<code>linear-address-issue</code>）</li><li>技能文件夹名与技能名完全一致</li></ul><h3 id="7-1-Step-1：理解技能——用具体例子建立共识"><a href="#7-1-Step-1：理解技能——用具体例子建立共识" class="headerlink" title="7.1 Step 1：理解技能——用具体例子建立共识"></a>7.1 Step 1：理解技能——用具体例子建立共识</h3><blockquote><p>Skip this step only when the skill’s usage patterns are already clearly understood.</p></blockquote><p>要创建一个有效的 skill，必须先清楚理解<strong>具体的使用例子</strong>。这些理解可以来自用户提供的例子，也可以来自生成的、经用户验证的例子。</p><p>以构建 image-editor 技能为例，可以问用户：</p><ul><li>“image-editor 技能应该支持什么功能？编辑、旋转，还有其他吗？”</li><li>“能给一些使用这个技能的例子吗？”</li><li>“我能想到用户会说’去掉这张照片的红眼’或’旋转这张图片’。还有其他使用方式吗？”</li><li>“用户会说什么话来触发这个技能？”</li></ul><p><strong>注意</strong>：不要一次问太多问题。先问最重要的，然后根据需要跟进。</p><p><strong>完成标志</strong>：对技能应该支持的功能有了清晰的认识。</p><h3 id="7-2-Step-2：规划可复用的技能内容"><a href="#7-2-Step-2：规划可复用的技能内容" class="headerlink" title="7.2 Step 2：规划可复用的技能内容"></a>7.2 Step 2：规划可复用的技能内容</h3><p>对每个具体例子做两个分析：</p><ol><li>如果从零开始做这件事，需要什么？</li><li>其中哪些会被反复使用？</li></ol><p>反复使用的东西 → 封装成 scripts&#x2F;references&#x2F;assets。</p><p>skill-creator 给了三个典型分析案例：</p><p><strong>案例 1：<code>pdf-editor</code> 技能</strong>（用户问”帮我旋转这个 PDF”）</p><ul><li>旋转 PDF 每次都要重写同样的代码</li><li>→ 封装为 <code>scripts/rotate_pdf.py</code></li></ul><p><strong>案例 2：<code>frontend-webapp-builder</code> 技能</strong>（用户问”帮我做一个 todo app”或”做一个步数追踪仪表盘”）</p><ul><li>写前端 webapp 每次都需要同样的 HTML&#x2F;React 样板代码</li><li>→ 封装为 <code>assets/hello-world/</code> 模板目录</li></ul><p><strong>案例 3：<code>big-query</code> 技能</strong>（用户问”今天有多少用户登录了？”）</p><ul><li>查询 BigQuery 每次都要重新发现表的 schema 和关系</li><li>→ 封装为 <code>references/schema.md</code></li></ul><p><strong>完成标志</strong>：列出了所有要包含的可复用资源清单（scripts、references、assets）。</p><h3 id="7-3-Step-3：初始化技能"><a href="#7-3-Step-3：初始化技能" class="headerlink" title="7.3 Step 3：初始化技能"></a>7.3 Step 3：初始化技能</h3><blockquote><p>When creating a new skill from scratch, always run the <code>init_skill.py</code> script.</p></blockquote><p>这里用的是”always”——不是”建议”，是”总是”。原因：</p><ul><li>脚本生成的目录结构保证符合规范</li><li>模板中的 TODO 提醒确保不遗漏必需字段</li><li><code>agents/openai.yaml</code> 的格式约束（字段长度、引号规则）靠手写容易出错</li></ul><p>这是<strong>低自由度原则的直接应用</strong>：初始化是一个脆弱操作，用脚本消除出错可能。</p><p>初始化后：</p><ul><li>定制 SKILL.md 并根据需要添加资源</li><li>如果用了 <code>--examples</code>，替换或删除占位符文件</li></ul><h3 id="7-4-Step-4：编辑技能"><a href="#7-4-Step-4：编辑技能" class="headerlink" title="7.4 Step 4：编辑技能"></a>7.4 Step 4：编辑技能</h3><p>这是最核心的步骤，分两阶段：</p><h4 id="阶段一：先实现可复用资源"><a href="#阶段一：先实现可复用资源" class="headerlink" title="阶段一：先实现可复用资源"></a>阶段一：先实现可复用资源</h4><p>从 Step 2 规划的资源开始：实现 <code>scripts/</code>、<code>references/</code>、<code>assets/</code> 文件。</p><p>注意：</p><ul><li>这一步可能需要用户输入（比如 <code>brand-guidelines</code> 技能需要用户提供品牌素材）</li><li>新增的脚本<strong>必须通过实际运行来测试</strong>，确保无 bug 且输出符合预期</li><li>如果有很多类似的脚本，只需测试代表性样本来建立信心</li><li>如果用了 <code>--examples</code>，删除不需要的占位符文件。只创建真正需要的资源目录</li></ul><h4 id="阶段二：更新-SKILL-md"><a href="#阶段二：更新-SKILL-md" class="headerlink" title="阶段二：更新 SKILL.md"></a>阶段二：更新 SKILL.md</h4><p><strong>Frontmatter 写法</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></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-meta">---</span><br><span class="hljs-attr">name:</span> <span class="hljs-string">skill-name</span><br><span class="hljs-attr">description:</span> <span class="hljs-string">&gt;-</span><br><span class="hljs-string">  描述技能做什么 + 具体什么时候用。</span><br><span class="hljs-string">  把所有 &quot;when to use&quot; 信息放这里，不要放在 body 里。</span><br><span class="hljs-string"></span><span class="hljs-meta">---</span><br></code></pre></td></tr></table></figure><p><strong>Body 写法</strong>：</p><p>写给另一个 Codex 实例的操作指令。包含对 Codex 有帮助但不显而易见的信息：程序性知识、领域细节、可复用资源的使用方式。</p><p>统一使用<strong>祈使语气&#x2F;不定式</strong>。</p><h3 id="7-5-Step-5：校验技能"><a href="#7-5-Step-5：校验技能" class="headerlink" title="7.5 Step 5：校验技能"></a>7.5 Step 5：校验技能</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">scripts/quick_validate.py &lt;path/to/skill-folder&gt;<br></code></pre></td></tr></table></figure><p>校验 YAML frontmatter 格式、必需字段、命名规则。不通过就修复后重新运行。</p><h3 id="7-6-Step-6：迭代"><a href="#7-6-Step-6：迭代" class="headerlink" title="7.6 Step 6：迭代"></a>7.6 Step 6：迭代</h3><blockquote><p>After testing the skill, users may request improvements. Often this happens right after using the skill, with fresh context of how the skill performed.</p></blockquote><p>迭代工作流：</p><ol><li>在真实任务上使用技能</li><li>发现吃力或低效的地方</li><li>找出 SKILL.md 或捆绑资源该如何更新</li><li>实施变更并重新测试</li></ol><p>好的 skill 不是一次写成的。skill-creator 创建的 laotou-thought-style 技能，在第一次生成后就迭代了 <code>openai.yaml</code> 的 <code>short_description</code> 和 <code>default_prompt</code>——从泛泛的描述变为更精确的操作指令。</p><hr><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h2><p>回到最初的问题：怎么写出好的 skill？</p><p>回顾整个框架：</p><figure class="highlight"><table><tr><td class="gutter"><pre><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><code class="hljs">根本约束：简洁（第四章）<br> ├── 信息放在哪里？ → 三级分层，按需加载（第五章）<br> ├── 给 AI 多大自由度？ → 脆弱操作脚本锁死，创造性工作文字引导（第六章）<br> └── 怎么落地？ → 六步流程：理解→规划→初始化→编辑→校验→迭代（第七章）<br></code></pre></td></tr></table></figure><p><strong>Skill是给 AI 写指令，而不是给人。用最少的 token，在正确的层级，给 AI 最精准的约束，让它在边界内自由发挥。</strong></p>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra08-%E5%A6%82%E4%BD%95%E5%86%99%E5%87%BA%E5%A5%BD%E7%9A%84Skill/</id>
    <link href="http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra08-%E5%A6%82%E4%BD%95%E5%86%99%E5%87%BA%E5%A5%BD%E7%9A%84Skill/"/>
    <published>2026-03-03T02:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="05-如何写出好的-Skill"><a href="#05-如何写出好的-Skill" class="headerlink" title="05. 如何写出好的 Skill"></a>05. 如何写出好的 Skill</h1><blockquote>
<p>什么是]]>
    </summary>
    <title>05. 如何写出好的 Skill</title>
    <updated>2026-03-08T09:24:16.380Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="Extra07-环境配置"><a href="#Extra07-环境配置" class="headerlink" title="Extra07 - 环境配置"></a>Extra07 - 环境配置</h1><blockquote><p>本节将指导您配置运行 FirstAgentTest.py 所需的完整环境。该代码实现了一个智能旅行助手，展示了基于工具调用的 Agent 实现模式。</p></blockquote><h2 id="一、环境要求"><a href="#一、环境要求" class="headerlink" title="一、环境要求"></a>一、环境要求</h2><h3 id="1-1-Python-版本要求"><a href="#1-1-Python-版本要求" class="headerlink" title="1.1 Python 版本要求"></a>1.1 Python 版本要求</h3><ul><li><strong>Python 3.10+</strong> （推荐使用 Python 3.10 或更高版本）</li><li>支持的操作系统：Windows、macOS、Linux</li></ul><h3 id="1-2-目标代码说明"><a href="#1-2-目标代码说明" class="headerlink" title="1.2 目标代码说明"></a>1.2 目标代码说明</h3><p>我们的目标是成功运行项目<code>code\chapter1\FirstAgentTest.py</code>，该代码实现了：</p><ul><li>智能旅行助手功能</li><li>天气查询工具（基于 wttr.in API）</li><li>景点推荐工具（基于 Tavily Search API）</li><li>OpenAI 兼容的 LLM 调用</li><li>ReAct 模式的 Agent 执行流程</li></ul><h2 id="二、API-配置"><a href="#二、API-配置" class="headerlink" title="二、API 配置"></a>二、API 配置</h2><h3 id="2-1-大语言模型-API-配置"><a href="#2-1-大语言模型-API-配置" class="headerlink" title="2.1 大语言模型 API 配置"></a>2.1 大语言模型 API 配置</h3><h4 id="选项一：AIHubmix-API（推荐）"><a href="#选项一：AIHubmix-API（推荐）" class="headerlink" title="选项一：AIHubmix API（推荐）"></a>选项一：AIHubmix API（推荐）</h4><p>AIHubmix 是一个位于美国特拉华州的 AI 模型聚合平台，整合了市面上主流的大语言模型，新发布的模型通常在一周内即可使用。该平台直接对接各大云服务商的原生 API（如 OpenAI 通过 Azure、Anthropic 通过 AWS、Google 通过官方接口等），采用美国 Google Cloud 的集群架构部署，具备多节点负载均衡能力，在稳定性和响应速度方面表现优异。</p><blockquote><p>平台提供的免费额度能够满足我们的学习需求。</p></blockquote><ol><li><p><strong>进入 AIHubmix 官网</strong></p><p>使用浏览器访问 <a href="https://aihubmix.com/?aff=Igcn/">AIHubmix 官方网站</a></p><p><img src="/img/ai-agent-learning/image1" alt="image1"></p></li><li><p><strong>完成账户注册</strong></p><p>首次使用需要注册账户。点击右上角的注册按钮，支持邮箱或手机号两种方式完成注册流程。</p></li><li><p><strong>浏览可用模型</strong></p><p>注册成功后，访问<a href="https://aihubmix.com/models">模型中心</a>查看所有可用模型。在筛选条件中选择<code>免费</code>标签，即可查看平台提供的免费模型列表。建议选择 <code>coding-glm-4.7-freecoding-glm-4.7-free</code> 或其他兼容 OpenAI 格式的免费模型。</p><p><img src="/img/ai-agent-learning/image2" alt="image2"></p></li><li><p><strong>获取 API 凭证</strong></p><p>前往<a href="https://console.aihubmix.com/token">API 密钥管理</a>页面，系统默认会生成一个可用的密钥。您也可以通过点击 <code>创建 Key</code> 按钮自定义密钥名称并生成新的密钥。</p><p><img src="/img/ai-agent-learning/image3" alt="image3"></p><p>请妥善保存以下配置信息：</p><ul><li>API Key: <code>your_api_key</code></li><li>Base URL: <code>https://aihubmix.com/v1</code></li><li>推荐模型: <code>coding-glm-4.7-free</code></li></ul></li></ol><h4 id="选项二：ModelScope"><a href="#选项二：ModelScope" class="headerlink" title="选项二：ModelScope"></a>选项二：ModelScope</h4><p>ModelScope 是国内领先的大模型服务商，提供高性价比的 API 服务。这里我们以Qwen为例，您可以从<a href="https://modelscope.cn/docs/model-service/API-Inference/intro">ModelScope</a>获取，它提供Qwen系列的免费（OpenAI）兼容格式的API，每天免费2000次调用。</p><p>请确保您拥有一个正常注册且可使用的ModelScope账户。要生成您的私有 API KEY可以参考我们的图示。</p><p><img src="/img/ai-agent-learning/image4" alt="image4"></p><p><img src="/img/ai-agent-learning/image5" alt="image5"></p><p>图中的SDK令牌就是我们的API KEY。</p><blockquote><p>请注意，需要在<strong>模型服务</strong>先绑定<a href="https://modelscope.cn/docs/accounts/aliyun-binding-and-authorization">阿里巴巴云账号</a>， 不然api会显示无法使用</p></blockquote><p><strong>可选模型范围</strong></p><p>在ModelScope中的<a href="https://modelscope.cn/models?filter=inference_type&page=1">模型库</a>中选择推理 API-Inference ，里面的模型都可以选择，我们可以体验到最新的使用DeepSeek-R1数据蒸馏出的Llama-70B模型。</p><p><img src="/img/ai-agent-learning/image6" alt="image6"></p><p>最终所需格式与AIHubmix的配置信息相同(Key，URL，模型名称)</p><h3 id="2-2-Tavily-Search-API-配置"><a href="#2-2-Tavily-Search-API-配置" class="headerlink" title="2.2 Tavily Search API 配置"></a>2.2 Tavily Search API 配置</h3><p>Tavily 是一个专为 AI 应用设计的搜索 API，用于景点推荐功能。</p><ol><li><p><strong>访问 Tavily 平台</strong></p><p>打开浏览器，访问 <a href="https://tavily.com/">Tavily</a></p><p><img src="/img/ai-agent-learning/image7" alt="image7"></p></li><li><p><strong>注册并获取 API 密钥</strong></p><p><img src="/img/ai-agent-learning/image8" alt="image8"></p><ol><li>注册账号</li><li>在控制台获取 API Key</li><li>记录 API Key: <code>your_tavily_key</code></li></ol></li></ol><h2 id="三、Python-环境配置"><a href="#三、Python-环境配置" class="headerlink" title="三、Python 环境配置"></a>三、Python 环境配置</h2><h3 id="3-1-安装-Python（如果未安装）"><a href="#3-1-安装-Python（如果未安装）" class="headerlink" title="3.1 安装 Python（如果未安装）"></a>3.1 安装 Python（如果未安装）</h3><p><strong>Windows 用户：</strong></p><ol><li>访问 <a href="https://www.python.org/downloads/">Python 官网</a></li><li>下载 Python 3.10+ 版本</li><li>安装时勾选 “Add Python to PATH”</li></ol><p><strong>macOS 用户：</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><code class="hljs bash"><span class="hljs-comment"># 使用 Homebrew 安装</span><br>brew install python@3.10<br></code></pre></td></tr></table></figure><p><strong>Linux 用户：</strong></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></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># Ubuntu/Debian</span><br>sudo apt update<br>sudo apt install python3.10 python3.10-pip python3.10-venv<br><br><span class="hljs-comment"># CentOS/RHEL</span><br>sudo yum install python3.10 python3.10-pip<br></code></pre></td></tr></table></figure><h3 id="3-2-验证-Python-安装"><a href="#3-2-验证-Python-安装" class="headerlink" title="3.2 验证 Python 安装"></a>3.2 验证 Python 安装</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></pre></td><td class="code"><pre><code class="hljs bash">python --version<br><span class="hljs-comment"># 或</span><br>python3 --version<br></code></pre></td></tr></table></figure><p>确保显示 Python 3.10 或更高版本。</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><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><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 进入项目目录</span><br><span class="hljs-built_in">cd</span> <span class="hljs-string">&quot;hello-agents&quot;</span><br><br><span class="hljs-comment"># 创建虚拟环境</span><br>python -m venv venv<br><br><span class="hljs-comment"># 激活虚拟环境</span><br><span class="hljs-comment"># Windows:</span><br>venv\Scripts\activate<br><span class="hljs-comment"># macOS/Linux:</span><br><span class="hljs-built_in">source</span> venv/bin/activate<br></code></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 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><code class="hljs bash"><span class="hljs-comment"># 安装核心依赖</span><br>pip install requests&gt;=2.31.0<br>pip install tavily-python&gt;=0.3.0<br>pip install openai&gt;=1.0.0<br><br><span class="hljs-comment"># 可选：安装其他常用包</span><br>pip install python-dotenv&gt;=1.0.0<br></code></pre></td></tr></table></figure><h3 id="4-3-环境变量配置"><a href="#4-3-环境变量配置" class="headerlink" title="4.3 环境变量配置"></a>4.3 环境变量配置</h3><h4 id="方法一：使用-env-文件（推荐）"><a href="#方法一：使用-env-文件（推荐）" class="headerlink" title="方法一：使用 .env 文件（推荐）"></a>方法一：使用 .env 文件（推荐）</h4><p>在项目根目录创建 <code>.env</code> 文件：</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></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 在项目根目录创建 .env 文件</span><br><span class="hljs-built_in">touch</span> .<span class="hljs-built_in">env</span>  <span class="hljs-comment"># Linux/macOS</span><br><span class="hljs-comment"># 或在 Windows 中手动创建</span><br></code></pre></td></tr></table></figure><p>编辑 <code>.env</code> 文件，添加以下内容：</p><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><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs env"># Tavily API 配置<br>TAVILY_API_KEY=your_tavily_api_key<br><br># 大语言模型 API 配置（选择其中一种）<br># 选项一：AIHubmix<br>OPENAI_API_KEY=your_aihubmix_api_key<br>OPENAI_BASE_URL=https://aihubmix.com/v1<br>MODEL_NAME=xxxx<br><br># 选项二：Modelscope<br># OPENAI_API_KEY=your_modelscope_api_key<br># OPENAI_BASE_URL=https://api-inference.modelscope.cn/v1/<br># MODEL_NAME=xxxx<br></code></pre></td></tr></table></figure><h4 id="方法二：系统环境变量"><a href="#方法二：系统环境变量" class="headerlink" title="方法二：系统环境变量"></a>方法二：系统环境变量</h4><p>以下为长期环境变量方案，也可以在终端短期加载。</p><p><strong>Windows：</strong></p><ol><li>右键”此电脑” → “属性” → “高级系统设置”</li><li>点击”环境变量”</li><li>在”用户变量”中添加：<ul><li><code>TAVILY_API_KEY</code>: <code>your_tavily_api_key</code></li></ul></li></ol><p><strong>macOS&#x2F;Linux：</strong></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><code class="hljs bash"><span class="hljs-comment"># 编辑 ~/.bashrc 或 ~/.zshrc</span><br><span class="hljs-built_in">export</span> TAVILY_API_KEY=<span class="hljs-string">&quot;your_tavily_api_key&quot;</span><br><br><span class="hljs-comment"># 使配置生效</span><br><span class="hljs-built_in">source</span> ~/.bashrc<br></code></pre></td></tr></table></figure><h2 id="五、代码配置"><a href="#五、代码配置" class="headerlink" title="五、代码配置"></a>五、代码配置</h2><h3 id="5-1-修改-FirstAgentTest-py-配置"><a href="#5-1-修改-FirstAgentTest-py-配置" class="headerlink" title="5.1 修改 FirstAgentTest.py 配置"></a>5.1 修改 FirstAgentTest.py 配置</h3><p>打开 <code>code/chapter1/FirstAgentTest.py</code> 文件，找到第 143-148 行的配置部分：</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><code class="hljs python"><span class="hljs-comment"># --- 1. 配置LLM客户端 ---</span><br><span class="hljs-comment"># 请根据您使用的服务，将这里替换成对应的凭证和地址</span><br>API_KEY = <span class="hljs-string">&quot;YOUR_API_KEY&quot;</span><br>BASE_URL = <span class="hljs-string">&quot;YOUR_BASE_URL&quot;</span><br>MODEL_ID = <span class="hljs-string">&quot;YOUR_MODEL_ID&quot;</span><br>os.environ[<span class="hljs-string">&#x27;TAVILY_API_KEY&#x27;</span>] = <span class="hljs-string">&quot;YOUR_TAVILY_API_KEY&quot;</span><br></code></pre></td></tr></table></figure><p><strong>替换为您的实际配置：</strong></p><h4 id="使用-AIHubmix-的配置示例："><a href="#使用-AIHubmix-的配置示例：" class="headerlink" title="使用 AIHubmix 的配置示例："></a>使用 AIHubmix 的配置示例：</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></pre></td><td class="code"><pre><code class="hljs python">API_KEY = <span class="hljs-string">&quot;your_aihubmix_api_key&quot;</span><br>BASE_URL = <span class="hljs-string">&quot;https://aihubmix.com/v1&quot;</span><br>MODEL_ID = <span class="hljs-string">&quot;coding-glm-4.7-free&quot;</span><br>os.environ[<span class="hljs-string">&#x27;TAVILY_API_KEY&#x27;</span>] = <span class="hljs-string">&quot;your_tavily_api_key&quot;</span><br></code></pre></td></tr></table></figure><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 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><code class="hljs python"><span class="hljs-comment"># 测试天气 API</span><br><span class="hljs-keyword">import</span> requests<br>response = requests.get(<span class="hljs-string">&quot;https://wttr.in/Beijing?format=j1&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;天气API状态:&quot;</span>, response.status_code)<br><br><span class="hljs-comment"># 测试 Tavily API</span><br><span class="hljs-keyword">from</span> tavily <span class="hljs-keyword">import</span> TavilyClient<br>tavily = TavilyClient(api_key=<span class="hljs-string">&quot;your_tavily_key&quot;</span>)<br><span class="hljs-keyword">try</span>:<br>    result = tavily.search(<span class="hljs-string">&quot;test&quot;</span>, search_depth=<span class="hljs-string">&quot;basic&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Tavily API 连接成功&quot;</span>)<br><span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Tavily API 错误:&quot;</span>, e)<br></code></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 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><code class="hljs bash"><span class="hljs-comment"># 确保在正确目录</span><br><span class="hljs-built_in">cd</span> <span class="hljs-string">&quot;hello-agents\code\chapter1&quot;</span><br><br><span class="hljs-comment"># 运行程序</span><br>python FirstAgentTest.py<br></code></pre></td></tr></table></figure><h3 id="6-3-预期输出"><a href="#6-3-预期输出" class="headerlink" title="6.3 预期输出"></a>6.3 预期输出</h3><p>程序成功运行时，您应该看到类似以下的输出：</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><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><code class="hljs asciidoc"><span class="hljs-section">用户输入: 你好，请帮我查询一下今天北京的天气，然后根据天气推荐一个合适的旅游景点。</span><br><span class="hljs-section">========================================</span><br><span class="hljs-bullet">--- </span>循环 1 ---<br><br>正在调用大语言模型...<br>大语言模型响应成功。<br>模型输出:<br>Thought: 用户想要查询北京的天气，然后根据天气情况推荐合适的旅游景点。我需要先调用get<span class="hljs-emphasis">_weather工具查询北京的天气情况。</span><br><span class="hljs-emphasis">Action: get_</span>weather(city=&quot;北京&quot;)<br><br><span class="hljs-section">Observation: 北京当前天气：Clear，气温15摄氏度</span><br><span class="hljs-section">========================================</span><br><span class="hljs-bullet">--- </span>循环 2 ---<br><br>正在调用大语言模型...<br>大语言模型响应成功。<br>模型输出:<br>Thought: 现在我知道了北京的天气是晴朗的，气温15摄氏度，这是一个很适合户外活动的天气。接下来我需要根据这个天气情况推荐合适的旅游景点。<br>Action: get_attraction(city=&quot;北京&quot;, weather=&quot;Clear，气温15摄氏度&quot;)<br><br><span class="hljs-section">Observation: 根据搜索，为您找到以下信息：...</span><br><span class="hljs-section">========================================</span><br>任务完成，最终答案: 根据查询，北京今天天气晴朗，气温15摄氏度，非常适合户外游览。推荐您去...<br></code></pre></td></tr></table></figure><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><p><strong>问题：pip 安装速度慢</strong></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><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 临时使用清华镜像</span><br>pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests tavily-python openai<br><br><span class="hljs-comment"># 永久配置镜像源</span><br>pip config <span class="hljs-built_in">set</span> global.index-url https://pypi.tuna.tsinghua.edu.cn/simple<br></code></pre></td></tr></table></figure><p><strong>问题：ModuleNotFoundError</strong></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><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 确认虚拟环境已激活</span><br><span class="hljs-comment"># 重新安装缺失的包</span><br>pip install requests tavily-python openai python-dotenv<br></code></pre></td></tr></table></figure><h3 id="7-2-API-调用问题"><a href="#7-2-API-调用问题" class="headerlink" title="7.2 API 调用问题"></a>7.2 API 调用问题</h3><p><strong>问题：Tavily API 返回错误</strong></p><p>可能原因：</p><ul><li>API Key 未正确设置</li><li>API 额度用尽</li><li>网络连接问题</li></ul><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 检查环境变量</span><br><span class="hljs-keyword">import</span> os<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;TAVILY_API_KEY:&quot;</span>, os.environ.get(<span class="hljs-string">&#x27;TAVILY_API_KEY&#x27;</span>))<br><br><span class="hljs-comment"># 测试 API 连接</span><br><span class="hljs-keyword">from</span> tavily <span class="hljs-keyword">import</span> TavilyClient<br>client = TavilyClient(api_key=<span class="hljs-string">&quot;your_key&quot;</span>)<br>result = client.search(<span class="hljs-string">&quot;test&quot;</span>)<br></code></pre></td></tr></table></figure><h2 id="十、总结"><a href="#十、总结" class="headerlink" title="十、总结"></a>十、总结</h2><p>完成环境配置后，建议：</p><ol><li>理解 FirstAgentTest.py 的代码结构</li><li>尝试修改 System Prompt 观察效果</li><li>添加新的工具函数</li><li>实现更复杂的 Agent 逻辑</li></ol><p>按照本文档的步骤操作，您应该能够成功运行智能旅行助手代码，并理解基于工具调用的 Agent 实现原理。</p>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra07-%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/</id>
    <link href="http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra07-%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/"/>
    <published>2026-03-03T00:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="Extra07-环境配置"><a href="#Extra07-环境配置" class="headerlink" title="Extra07 - 环境配置"></a>Extra07 - 环境配置</h1><blockquote>
<p>本节将指导您配置运行 Fi]]>
    </summary>
    <title>Extra07 - 环境配置</title>
    <updated>2026-03-08T09:24:16.377Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="GUI-Agent-科普与实战——下一代人机交互的探索之旅"><a href="#GUI-Agent-科普与实战——下一代人机交互的探索之旅" class="headerlink" title="GUI Agent 科普与实战——下一代人机交互的探索之旅"></a>GUI Agent 科普与实战——下一代人机交互的探索之旅</h1><h2 id="引言：当-AI-学会”看”屏幕"><a href="#引言：当-AI-学会”看”屏幕" class="headerlink" title="引言：当 AI 学会”看”屏幕"></a>引言：当 AI 学会”看”屏幕</h2><p>想象一下这样的场景：你对着手机说”帮我订一张明天去上海的高铁票，二等座，上午 10 点左右出发”，然后 AI 自动打开铁路 12306 APP，填写出发地、目的地和日期，筛选符合条件的车次，完成预订并付款——整个过程无需你手动操作，AI 就像一个真实的助手一样，”看”着屏幕，”理解”界面，”点击”按钮。</p><p>这不是科幻，而是 <strong>GUI Agent（图形用户界面智能体）</strong> 正在实现的现实。</p><p>在过去的二十年中，企业自动化的主流方案是 <strong>RPA（机器人流程自动化）</strong>。然而，RPA 有一个致命弱点：它依赖于固定的 UI 元素选择器（Selectors），一旦界面稍有变化，脚本就会失效。这种脆弱性导致了巨大的维护成本。</p><p>而 GUI Agent 的出现，彻底改变了这个局面。它不是简单地”回放”预设的脚本，而是像人类一样，通过<strong>视觉感知</strong>理解屏幕内容，通过<strong>大语言模型的推理能力</strong>规划操作路径，在动态、未知的软件环境中自主完成任务。</p><p>本章将带你深入了解 GUI Agent 的技术原理，并通过三个实战案例，让你真正掌握如何使用和部署这些前沿的智能体系统。</p><h2 id="第一部分：GUI-Agent-技术科普"><a href="#第一部分：GUI-Agent-技术科普" class="headerlink" title="第一部分：GUI Agent 技术科普"></a>第一部分：GUI Agent 技术科普</h2><h3 id="1-1-GUI-Agent-是什么？"><a href="#1-1-GUI-Agent-是什么？" class="headerlink" title="1.1 GUI Agent 是什么？"></a>1.1 GUI Agent 是什么？</h3><p><strong>GUI Agent（图形用户界面智能体）</strong> 是一类能够自主理解和操作图形界面的 AI 系统。与传统的 API 调用或命令行工具不同，GUI Agent 直接与人类使用的图形界面交互——无论是手机 APP、桌面软件还是网页应用。</p><h4 id="1-1-1-从-RPA-到-AI-Agent-的范式转变"><a href="#1-1-1-从-RPA-到-AI-Agent-的范式转变" class="headerlink" title="1.1.1 从 RPA 到 AI Agent 的范式转变"></a>1.1.1 从 RPA 到 AI Agent 的范式转变</h4><p>让我们通过一个对比来理解这种转变：</p><table><thead><tr><th>维度</th><th>传统 RPA</th><th>GUI Agent（AI Agent）</th></tr></thead><tbody><tr><td><strong>工作原理</strong></td><td>基于固定选择器（如 XPath、ID）的脚本回放</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>极高（UI 变化需重写脚本）</td><td>低（模型自动适应）</td></tr></tbody></table><p><strong>核心区别</strong>：RPA 是”脆弱的自动化”，而 GUI Agent 是”智能的自主化”。</p><h4 id="1-1-2-为什么-GUI-Agent-突然火了？"><a href="#1-1-2-为什么-GUI-Agent-突然火了？" class="headerlink" title="1.1.2 为什么 GUI Agent 突然火了？"></a>1.1.2 为什么 GUI Agent 突然火了？</h4><p>GUI Agent 的爆发并非偶然，而是多个技术领域同步成熟的结果。首先是多模态大模型的突破性进展。从GPT-4o、Claude 3.5 Sonnet、Qwen-VL 这些模型开始，大模型不仅能理解文字，还能”看懂”图像，这为 GUI Agent 提供了强大的”眼睛”。当你把一张屏幕截图喂给这些模型时，它们能准确识别出”这是一个登录按钮”、”这里有一个搜索框”，甚至能理解复杂的界面布局。</p><p>更关键的是定位能力的突破。早期的视觉模型就像一个近视眼——它知道屏幕上有个按钮，但说不清楚按钮在哪里。而最新的模型（如 GUI-Owl、Qwen-VL）经过专门训练，能够精确输出 UI 元素的屏幕坐标 $(x, y)$，这让 Agent 不仅能”看见”，还能”点准”。</p><p>最后是推理能力的质变。大语言模型的链式思考（Chain of Thought）能力让 Agent 拥有了”大脑”。它能将”订一张明天的高铁票”这样的模糊指令，分解成”打开APP → 选择日期 → 输入地点 → 筛选车次 → 确认支付”这样的具体步骤，并在执行过程中不断反思和纠错。</p><h3 id="1-2-GUI-Agent-的核心技术架构"><a href="#1-2-GUI-Agent-的核心技术架构" class="headerlink" title="1.2 GUI Agent 的核心技术架构"></a>1.2 GUI Agent 的核心技术架构</h3><p>一个完整的 GUI Agent 系统可以被分解为三个核心模块：<strong>感知（Perception）</strong> → <strong>推理（Reasoning）</strong> → <strong>执行（Action）</strong>。这是一个闭环的自主决策系统。</p><div align="center">  <img src="./images/Extra06-figures/image1.png" alt="GUI Agent 三层架构" width="90%"/>  <p>图 1 GUI Agent 的感知-推理-执行闭环</p></div><h4 id="1-2-1-感知层：机器如何”看见”屏幕"><a href="#1-2-1-感知层：机器如何”看见”屏幕" class="headerlink" title="1.2.1 感知层：机器如何”看见”屏幕"></a>1.2.1 感知层：机器如何”看见”屏幕</h4><p>感知层负责将屏幕信息转化为机器可理解的数据。目前主要有两种技术路线，它们代表了两种截然不同的设计哲学。</p><p>第一种路线是基于 DOM 或可访问性树的结构化感知。这种方法通过系统 API 获取应用的内部结构——比如网页的 HTML DOM 树，或者 Android 应用的 View Hierarchy。就像是给 Agent 提供了一份”建筑图纸”，它能精确知道每个按钮、文本框的类型和位置。这种方法的优势是精确高效，但问题也很明显：许多现代应用根本不暴露这些结构化信息。Canvas 绘制的界面、游戏、远程桌面软件，对于基于 DOM 的方案来说都是”黑盒”。而且这种方法丢失了视觉布局信息，很难理解元素之间的空间关系，跨平台兼容性也很差。</p><p>第二种路线是基于纯视觉的感知，这也是目前最前沿的方向。Agent 直接截取屏幕图像，用视觉大模型（VLM）像人类一样”看”屏幕。这种方法的通用性极强——不管你的界面是用什么技术实现的，只要能显示在屏幕上，Agent 就能理解。更重要的是，它具备”语义弹性”。即使某个按钮从蓝色变成了绿色，或者位置稍微移动了，基于视觉的 Agent 仍然能通过语义识别出”这是登录按钮”。传统 RPA 遇到这种情况就会失效，但 GUI Agent 能轻松应对。当然，纯视觉方案也有挑战，最大的难点是定位精度——模型不仅要识别出按钮是什么，还要输出它的精确屏幕坐标。</p><h4 id="1-2-2-推理层：大脑的决策过程"><a href="#1-2-2-推理层：大脑的决策过程" class="headerlink" title="1.2.2 推理层：大脑的决策过程"></a>1.2.2 推理层：大脑的决策过程</h4><p>推理层是 GUI Agent 的”大脑”，负责将用户的抽象指令转化为具体的操作序列。这里涉及几个关键能力。</p><p>首先是任务分解能力。当你对 Agent 说”帮我订一张明天去上海的高铁票，二等座，上午10点左右出发”，它需要理解这句话背后的复杂逻辑。Agent 会自动将这个模糊的需求拆解成一系列具体步骤：打开 12306 APP → 点击”车票预订” → 输入出发地”北京” → 输入目的地”上海” → 选择日期”明天” → 点击查询 → 筛选车次（二等座+上午10点前后）→ 选择符合条件的车次 → 点击预订 → 填写乘客信息 → 确认支付。这个分解过程依赖于大语言模型对常识和业务流程的理解。</p><p>更精妙的是思维链机制。为了提高复杂任务的成功率，现代 GUI Agent 会在每一步操作前生成”内心独白”。比如当前屏幕是 12306 首页，用户目标是预订高铁票，Agent 会先分析：”我看到屏幕上有’车票预订’、’订单查询’等选项，需要点击’车票预订’才能进入购票流程。”然后决策：”点击坐标 (540, 320) 处的’车票预订’按钮。”这种显式的思考过程不仅让 Agent 的行为更可解释，还能显著降低多步操作中的误差累积。</p><p>最后是反思与纠错能力。如果 Agent 点击”查询”按钮后，发现没有出现预期的车次列表，而是弹出”请选择出发日期”的提示，它会立即意识到：”我漏掉了选择日期的步骤。”然后调整策略：”先点击日期选择器，选择明天的日期，再重新查询。”这种自我修正能力让 Agent 能够应对真实世界中的各种意外情况。</p><h4 id="1-2-3-执行层：从决策到行动"><a href="#1-2-3-执行层：从决策到行动" class="headerlink" title="1.2.3 执行层：从决策到行动"></a>1.2.3 执行层：从决策到行动</h4><p>执行层是 GUI Agent 的”双手”，负责将模型的决策转化为实际的系统操作。</p><p>与文本生成的开放空间不同，GUI 操作的动作空间是有限且明确的。点击、双击、长按、滑动、输入、滚动、拖拽——这些基本动作构成了所有复杂操作的基础。每种动作都有其特定的参数，比如点击需要坐标 (x, y)，滑动需要起点和终点 (x1, y1, x2, y2)，输入需要文本内容。</p><p>这里有一个关键的技术细节：坐标系统的转换。视觉模型（如 Qwen-VL）通常输出归一化坐标（0-1000），而实际手机或电脑的屏幕分辨率可能是 1920x1080。执行层必须进行精确的坐标映射，将模型的输出转换成物理坐标。而且不同设备还有不同的 DPI 和系统缩放比例，这些都需要考虑进去。一个简单的映射函数可能是这样的：先将归一化坐标除以 1000，再乘以屏幕的实际宽高，最后取整得到物理坐标。</p><p>更复杂的是多平台适配。在 Android 上，所有操作都通过 ADB（Android Debug Bridge）发送指令实现，比如 <code>adb shell input tap 500 1000</code> 执行点击，<code>adb shell input swipe 500 1000 500 500</code> 执行滑动。在 iOS 上，需要通过 libimobiledevice 或 WDA（WebDriverAgent）来实现类似功能。而在 Windows、Mac、Linux 桌面环境，通常使用 pyautogui、pynput 这样的 Python 库直接控制鼠标和键盘。同一个”点击”动作，在不同平台上的实现方式完全不同，执行层需要为每个平台提供统一的抽象接口。</p><h3 id="1-3-主流开源框架全景对比"><a href="#1-3-主流开源框架全景对比" class="headerlink" title="1.3 主流开源框架全景对比"></a>1.3 主流开源框架全景对比</h3><p>2024-2025年是 GUI Agent 的爆发期，各大科技公司和研究机构纷纷开源了自己的框架。让我们系统地对比几个最具代表性的项目：</p><div align="center">  <img src="./images/Extra06-figures/image2.png" alt="主流GUI Agent框架对比" width="90%"/>  <p>图 2 主流 GUI Agent 框架全景对比雷达图</p></div><h3 id="1-4-应用场景与技术局限"><a href="#1-4-应用场景与技术局限" class="headerlink" title="1.4 应用场景与技术局限"></a>1.4 应用场景与技术局限</h3><h4 id="1-4-1-五大典型应用场景"><a href="#1-4-1-五大典型应用场景" class="headerlink" title="1.4.1 五大典型应用场景"></a>1.4.1 五大典型应用场景</h4><p>GUI Agent 的应用潜力远超我们的想象。在智能座舱领域，驾驶过程中的语音交互需求正在爆发。想象你在开车时说”导航到最近的咖啡店，并在到达前 10 分钟帮我点一杯拿铁”，GUI Agent 能够跨应用协调导航 APP 和外卖 APP，理解复杂的时间逻辑，还能适应不同品牌车机的UI差异。这正是传统车机系统难以做到的。</p><p>在软件测试领域，GUI Agent 带来了革命性的变化。传统的自动化测试依赖 Selenium 等工具，每次 UI 改版都需要更新测试脚本，维护成本极高。而 GUI Agent 能够自适应 UI 变化——即使按钮的位置调整了、颜色改变了，Agent 仍能通过语义识别找到正确的元素。它还能进行视觉回归测试，自动检测 UI 异常，甚至主动进行探索性测试，发现那些人类测试工程师可能忽略的边界情况。</p><p>企业级的 RPA 场景是另一个巨大的市场。传统 RPA 无法处理那些没有 API 的老旧系统，但 GUI Agent 可以。从 Excel 提取数据，填入 ERP 系统，发送邮件通知——整个跨系统工作流可以完全自动化。对于那些运行了二三十年、没有任何现代接口的遗留系统，GUI Agent 终于提供了自动化的可能性。</p><p>在个人生活中，GUI Agent 可以成为真正的智能助理。定时发布内容到多个社交平台，每天早上自动汇总新闻、天气、日程，记录运动数据和饮食习惯——这些重复性的数字劳动都可以交给 Agent 完成。而对于视障、肢体障碍的用户，GUI Agent 更是打开了新世界的大门。完全通过语音控制手机、智能阅读屏幕内容、将复杂操作转化为简单指令，这些功能正在让技术真正惠及每一个人。</p><h4 id="1-4-2-当前技术的三大局限"><a href="#1-4-2-当前技术的三大局限" class="headerlink" title="1.4.2 当前技术的三大局限"></a>1.4.2 当前技术的三大局限</h4><p>但我们也必须清醒地认识到，GUI Agent 技术仍处于发展的早期阶段，面临着一些实质性的挑战。</p><p>最令人担忧的是安全性与幻觉风险。大语言模型的幻觉问题在 GUI Agent 上可能导致严重后果。用户要求”清理桌面”，Agent 可能误解为删除所有文件；转账操作中的一个数字错误，可能造成经济损失。目前的缓解方案包括：对高风险操作强制要求人工确认，详细记录操作日志并支持回滚，以及在沙箱环境中充分测试。但这些都是权宜之计，从根本上解决模型幻觉问题仍需要时间。</p><p>成本与效率问题同样不容忽视。每一步操作都需要调用大模型进行推理，如果使用云端 API，成本会随着调用次数线性增长。一个复杂任务可能需要数十次迭代，整体耗时较长。本地部署小模型能降低成本，但准确率会有所下降。操作缓存、模式识别、混合架构（简单任务用 RPA，复杂任务用 AI）是目前探索的方向，但还没有形成成熟的最佳实践。</p><p>最后是准确率瓶颈。即使是最好的系统，在真实场景中的成功率也只有 40-50%。复杂界面的元素定位、动态内容的处理（广告、弹窗）、长链条任务的错误累积，这些都是实实在在的技术难题。突破方向包括更强的视觉大模型、通过强化学习优化操作策略、以及”人在回路”（Human-in-the-loop）的协作设计。但从 50% 提升到 90% 的商业化可用水平，可能还需要一段时间。</p><hr><h2 id="第二部分：GUI-Agent-实战教程"><a href="#第二部分：GUI-Agent-实战教程" class="headerlink" title="第二部分：GUI Agent 实战教程"></a>第二部分：GUI Agent 实战教程</h2><p>理论学习之后，让我们通过两个难度递增的实战案例，真正掌握 GUI Agent 的使用和部署。</p><h3 id="实战一：Mobile-Agent-在线体验（零门槛）"><a href="#实战一：Mobile-Agent-在线体验（零门槛）" class="headerlink" title="实战一：Mobile-Agent 在线体验（零门槛）"></a>实战一：Mobile-Agent 在线体验（零门槛）</h3><h4 id="2-1-1-访问在线-Demo"><a href="#2-1-1-访问在线-Demo" class="headerlink" title="2.1.1 访问在线 Demo"></a>2.1.1 访问在线 Demo</h4><p>Mobile-Agent-v3 不仅支持手机，还能操作电脑。如图 3 所示，我们在 ModelScope 的 Demo 页面中，将左上角的设备选择切换为 “电脑”，即可进入 PC Agent 的体验环境。</p><p><strong>选项一：ModelScope Demo</strong>（推荐）<br>链接：<a href="https://modelscope.cn/studios/wangjunyang/Mobile-Agent-v3">https://modelscope.cn/studios/wangjunyang/Mobile-Agent-v3</a></p><p><strong>选项二：阿里云百炼</strong><br>链接：<a href="https://bailian.console.aliyun.com/next?tab=demohouse#/experience/adk-computer-use/pc">https://bailian.console.aliyun.com/next?tab=demohouse#/experience/adk-computer-use/pc</a></p><p>这两个平台都提供了<strong>云手机&#x2F;云电脑环境</strong>，无需本地部署即可体验完整功能。</p><h3 id="2-1-2-界面功能导览"><a href="#2-1-2-界面功能导览" class="headerlink" title="2.1.2 界面功能导览"></a>2.1.2 界面功能导览</h3><p>进入页面后，你将看到如图 3 所示的操作界面。为了确保体验一致，请务必进行以下<strong>关键设置</strong>：</p><ol><li><strong>设备选择</strong>：在左上角的下拉菜单中，确认选择 <strong>“电脑”</strong>（而非手机）。</li><li><strong>桌面预览</strong>：右侧窗口展示的是云端分配给你的 Windows 10 桌面，预装了 Office、浏览器等基础软件。</li><li><strong>交互区</strong>：左下角为指令输入区，Agent 的思考过程（Thinking Process）和操作步骤将显示在上方对话框中。</li></ol><div align="center">  <img src="./images/Extra06-figures/image3.png" alt="ModelScope Demo界面" width="90%"/>  <p>图 3 Mobile-Agent-v3 在线 Demo 界面说明</p></div><p>在这个界面中，你可以直接指挥 Agent 进行办公操作，不过目前使用时间有限时。</p><h3 id="2-1-3-典型任务演练"><a href="#2-1-3-典型任务演练" class="headerlink" title="2.1.3 典型任务演练"></a>2.1.3 典型任务演练</h3><p>根据界面提供的预设能力，建议新手从以下两类任务开始尝试：</p><ul><li><strong>系统级控制</strong>：尝试让 Agent 修改系统设置。<ul><li><em>指令示例</em>：“将系统颜色设置为<strong>浅色模式</strong>。”</li><li><em>观察点</em>：Agent 能否像人一样打开“开始菜单 -&gt; 设置 -&gt; 个性化”。</li></ul></li><li><strong>跨应用办公</strong>：尝试让 Agent 联动浏览器和办公软件。<ul><li><em>指令示例</em>：“在 Edge 浏览器中搜索阿里巴巴的股价，然后在 WPS 中新建一个表格，填入公司名和当前股价。”</li><li><em>观察点</em>：Agent 能否准确处理“搜索信息”到“录入信息”的跨软件上下文切换。</li></ul></li></ul><h3 id="2-1-4-提示词工程：如何指挥-PC-Agent"><a href="#2-1-4-提示词工程：如何指挥-PC-Agent" class="headerlink" title="2.1.4 提示词工程：如何指挥 PC Agent"></a>2.1.4 提示词工程：如何指挥 PC Agent</h3><p>在 GUI 场景下，高质量的 Prompt 是成功的关键。结合上述办公场景，我们总结了三个核心技巧：</p><ol><li><strong>明确应用边界 (Explicit Context)</strong><ul><li>避免笼统指令，如“写个简介”。</li><li><strong>推荐写法</strong>：“在 <strong>WPS Office 文档</strong>中写一段简介……”</li><li><em>解析</em>：明确指定软件名称（App Name），能减少 Agent 寻找工具的时间。</li></ul></li><li><strong>步骤链式拆解 (Chain of Steps)</strong><ul><li>不要试图用一句话包含所有复杂逻辑。</li><li><strong>推荐写法</strong>：“第一步，打开 Edge 搜索……；第二步，确认网页加载完成后，截取数据……；第三步，打开 Excel 粘贴。”</li><li><em>解析</em>：GUI 操作具有严格的时序性，分步指令能显著降低执行错误率。</li></ul></li><li><strong>视觉属性描述 (Visual Attributes)</strong><ul><li>Agent 是通过“看”屏幕来操作的，利用视觉特征描述更有效。</li><li><strong>推荐写法</strong>：“点击右上角的<strong>蓝色保存按钮</strong>” 或 “将字体颜色改为<strong>红色</strong>”。</li></ul></li></ol><h4 id="2-1-5-在线体验的价值与局限"><a href="#2-1-5-在线体验的价值与局限" class="headerlink" title="2.1.5 在线体验的价值与局限"></a>2.1.5 在线体验的价值与局限</h4><p>ModelScope 提供的在线 Demo 最大的价值在于<strong>零门槛体验</strong>。你不需要配置任何环境，不需要准备手机，甚至不需要下载任何软件，就能直接感受到 GUI Agent 的魔力。这对于快速验证想法、了解技术边界非常有帮助。</p><p>但在线环境也有其局限性。首先是<strong>隐私问题</strong>，所有操作都在云端虚拟机上进行，你无法访问真实的个人数据。其次是<strong>功能限制</strong>，虚拟环境中只预装了部分常用 APP，无法测试特定的应用场景。最后是<strong>性能差异</strong>，云端推理的延迟会比本地部署稍高。</p><p>因此，在线体验适合作为学习和探索的起点，但如果要在真实场景中应用 GUI Agent，你需要尝试本地部署。Mobile-Agent-v3官方提供了一个<a href="https://github.com/X-PLUG/MobileAgent/blob/main/Mobile-Agent-v3/README_zh.md">教程</a>，可以自行尝试</p><p>接下来的实战二，就将带你使用最近<strong>智谱</strong>开源的AutoGLM走进这个更深入的世界。</p><hr><h3 id="实战二：AutoGLM-本地部署与手机实战"><a href="#实战二：AutoGLM-本地部署与手机实战" class="headerlink" title="实战二：AutoGLM 本地部署与手机实战"></a>实战二：AutoGLM 本地部署与手机实战</h3><p>在线体验让我们感受到了 GUI Agent 的能力，但真正的力量在于部署在自己的设备上，控制真实的应用。AutoGLM 是一个非常适合个人开发者入门的框架，它的架构清晰，文档完善，部署过程相对简单。</p><p>这个实战的目标是在你的电脑上部署 AutoGLM，连接你的 Android 手机，然后让 AI 帮你完成一些真实的任务——比如自动回复微信消息，或者定时刷新某个 APP 获取最新数据。</p><h4 id="2-2-1-环境准备：你需要什么"><a href="#2-2-1-环境准备：你需要什么" class="headerlink" title="2.2.1 环境准备：你需要什么"></a>2.2.1 环境准备：你需要什么</h4><p>Open-AutoGLM 的部署需要两样核心设备：一台能运行 Python 的电脑，以及一部 Android 手机。电脑的配置并不需要太高，因为 AutoGLM 支持调用云端 API，不一定要在本地运行大模型。如果你打算使用云端 API（如智谱的 GLM-4V），一台普通的笔记本就足够了。但如果你想体验完全本地化的方案，那么一块至少 8GB 显存的 GPU 会让体验好很多。</p><p>手机方面，Android 7.0 或更高版本都可以，不需要 Root 权限。iPhone 用户暂时无法使用，因为 iOS 的封闭性导致 ADB 调试方案无法直接应用。</p><p>软件环境方面，你需要安装 Python 3.10 或更高版本，以及 ADB（Android Debug Bridge）工具。ADB 是连接电脑和手机的桥梁，所有的屏幕截图、点击、滑动操作都要通过它来实现。</p><p><strong>安装 ADB 工具 (macOS &#x2F; Linux):</strong> 根据你的系统，在终端执行以下命令：</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><code class="hljs bash"><span class="hljs-comment"># macOS 使用 Homebrew</span><br>brew install android-platform-tools<br><br><span class="hljs-comment"># Linux (Ubuntu/Debian)</span><br>sudo apt install android-tools-adb<br></code></pre></td></tr></table></figure><p>Windows 用户通常可以直接下载 Platform Tools 压缩包并配置环境变量。<a href="https://blog.csdn.net/x2584179909/article/details/108319973">参考</a></p><h4 id="2-2-2-第一步：安装-Open-AutoGLM"><a href="#2-2-2-第一步：安装-Open-AutoGLM" class="headerlink" title="2.2.2 第一步：安装 Open-AutoGLM"></a>2.2.2 第一步：安装 Open-AutoGLM</h4><p>如果你拥有 <strong>Claude Code</strong>，你可以配置 <a href="https://bigmodel.cn/glm-coding">GLM Coding Plan</a> 后，输入以下提示词快速部署：</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs awk">访问文档，为我安装 AutoGLM<br>https:<span class="hljs-regexp">//</span>raw.githubusercontent.com<span class="hljs-regexp">/zai-org/</span>Open-AutoGLM<span class="hljs-regexp">/refs/</span>heads<span class="hljs-regexp">/main/</span>README.md<br></code></pre></td></tr></table></figure><p>如果没有类似的CLI，请按照以下手动步骤操作：</p><p>打开命令行终端，先克隆 Open-AutoGLM 的代码仓库：</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><code class="hljs bash">git <span class="hljs-built_in">clone</span> https://github.com/zai-org/Open-AutoGLM.git<br><span class="hljs-built_in">cd</span> Open-AutoGLM<br></code></pre></td></tr></table></figure><p>接下来安装依赖。除了基础的依赖包，<strong>一定要执行项目的安装命令</strong>，以确保所有模块能被正确调用：</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><code class="hljs bash"><span class="hljs-comment"># 1. 安装基础依赖</span><br>pip install -r requirements.txt<br><br><span class="hljs-comment"># 2. 以编辑模式安装项目本身 (关键步骤)</span><br>pip install -e .<br><br><span class="hljs-comment"># 3. (可选) 如果你是开发者，需要额外安装开发依赖</span><br>pip install -e <span class="hljs-string">&quot;.[dev]&quot;</span><br></code></pre></td></tr></table></figure><p>这个过程通常需要几分钟，取决于你的网络速度。安装完成后，你需要配置 API 密钥。如果使用智谱的 GLM-4V API，先去智谱开放平台注册账号并获取 API Key，然后在项目根目录创建一个 <code>.env</code> 文件：</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><code class="hljs bash"><span class="hljs-comment"># .env 文件内容</span><br>GLM_API_KEY=your_api_key_here<br></code></pre></td></tr></table></figure><p><a href="https://modelscope.cn/models/ZhipuAI/AutoGLM-Phone-9B">AutoGLM-Phone-9B · 模型库</a></p><h4 id="2-2-3-第二步：连接你的-Android-手机"><a href="#2-2-3-第二步：连接你的-Android-手机" class="headerlink" title="2.2.3 第二步：连接你的 Android 手机"></a>2.2.3 第二步：连接你的 Android 手机</h4><p>现在到了关键的一步：让电脑能够”看见”并”控制”你的手机。这需要三个小步骤：开启开发者模式、开启 USB 调试、以及<strong>安装 ADB Keyboard</strong>。</p><p><strong>1. 启用开发者模式 &amp; USB 调试</strong> 在 Android 手机上，进入”设置” → “关于手机”，找到”版本号”，<strong>连续点击 7 次</strong>（或直到出现提示），你会看到”您已处于开发者模式”的提示。 返回设置主界面，进入”开发者选项”，找到”USB 调试”并<strong>开启</strong>。</p><p><strong>2. 安装 ADB Keyboard (必须)</strong> 为了让 AI 能在手机上输入文字，我们需要安装专用的 ADB 键盘。</p><ul><li>下载地址：<a href="https://github.com/senzhk/ADBKeyBoard/raw/master/ADBKeyboard.apk">https://github.com/senzhk/ADBKeyBoard/raw/master/ADBKeyboard.apk</a></li></ul><p>安装后，记得在手机设置的“输入法”中，启用并切换到 <strong>ADB Keyboard</strong>。</p><p><strong>3. 验证连接</strong> 用 USB 数据线将手机连接到电脑（手机上弹出授权框时点击”允许”）。在电脑终端输入：</p><p>Bash</p><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ebnf"><span class="hljs-attribute">adb devices</span><br></code></pre></td></tr></table></figure><p>如果一切正常，你会看到设备序列号：</p><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs arduino">List of devices attached<br>ABC12345    device<br></code></pre></td></tr></table></figure><p>如果显示 <code>device</code>，恭喜你，硬件连接打通了！如果显示 <code>unauthorized</code>，请检查手机屏幕是否弹出了授权确认框。</p><p>对于 Windows 用户，可能还需要安装手机的驱动程序。大部分品牌的手机（如小米、华为、OPPO）都会在连接电脑时自动安装驱动，但如果遇到问题，可以去官网下载对应的 USB 驱动。</p><div align="center">  <img src="./images/Extra06-figures/image4.png" alt="手机ADB连接配置步骤" width="90%"/>  <p>图 4 Android 手机 ADB 连接完整配置流程</p></div><h4 id="2-2-4-第三步：运行你的第一个任务"><a href="#2-2-4-第三步：运行你的第一个任务" class="headerlink" title="2.2.4 第三步：运行你的第一个任务"></a>2.2.4 第三步：运行你的第一个任务</h4><p>连接成功后，让我们来执行一个简单但实用的任务。</p><p>有两种直接调用API的连接方式：</p><p><strong>1. 智谱 BigModel</strong></p><ul><li>文档: <a href="https://docs.bigmodel.cn/cn/api/introduction">https://docs.bigmodel.cn/cn/api/introduction</a></li><li><code>--base-url</code>: <code>https://open.bigmodel.cn/api/paas/v4</code></li><li><code>--model</code>: <code>autoglm-phone</code></li><li><code>--apikey</code>: 在智谱平台申请你的 API Key</li></ul><p><strong>2. ModelScope(魔搭社区)</strong></p><ul><li>文档: <a href="https://modelscope.cn/models/ZhipuAI/AutoGLM-Phone-9B">https://modelscope.cn/models/ZhipuAI/AutoGLM-Phone-9B</a></li><li><code>--base-url</code>: <code>https://api-inference.modelscope.cn/v1</code></li><li><code>--model</code>: <code>ZhipuAI/AutoGLM-Phone-9B</code></li><li><code>--apikey</code>: 在 ModelScope 平台申请你的 API Key</li></ul><p>官方的readme里提供了一个命令行接口，你可以直接输入：</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><code class="hljs bash"><span class="hljs-comment"># 使用智谱 BigModel</span><br>python main.py --base-url https://open.bigmodel.cn/api/paas/v4 --model <span class="hljs-string">&quot;autoglm-phone&quot;</span> --apikey <span class="hljs-string">&quot;your-bigmodel-api-key&quot;</span> <span class="hljs-string">&quot;打开美团搜索附近的火锅店&quot;</span><br><br><span class="hljs-comment"># 使用 ModelScope</span><br>python main.py --base-url https://api-inference.modelscope.cn/v1 --model <span class="hljs-string">&quot;ZhipuAI/AutoGLM-Phone-9B&quot;</span> --apikey <span class="hljs-string">&quot;your-modelscope-api-key&quot;</span> <span class="hljs-string">&quot;打开美团搜索附近的火锅店&quot;</span><br></code></pre></td></tr></table></figure><p>执行这个命令后，AutoGLM 会启动推理流程。你会在终端看到实时的日志输出，同时手机屏幕上会开始自动操作。整个过程大概是这样的：</p><p>首先，AutoGLM 会通过 ADB 截取当前屏幕的截图，将图像发送给模型分析。模型会识别出屏幕上的所有 APP 图标，并在像素级别定位到”美团”的位置。然后 AutoGLM 发送点击指令，通过 <code>adb shell input tap x y</code> 唤醒应用。</p><p>等待美团启动后，AutoGLM 再次截屏。这次它的目标是找到首页上方的”搜索栏”。识别并点击搜索框后，<strong>它会调用我们在环境准备阶段安装的 ADB Keyboard</strong>，将”附近的火锅”这串字符输入进去，最后自动点击搜索按钮。</p><p>整个过程通常需要 15-20 秒（搜索任务步骤稍多），具体时间取决于模型的推理速度和网络延迟。如果你使用的是云端 API，每一步的”思考”时间大约是 2-3 秒。如果是本地部署的模型，配置较好的 GPU 可以将单步时间压缩到 1 秒左右。</p><hr><h2 id="总结与展望"><a href="#总结与展望" class="headerlink" title="总结与展望"></a>总结与展望</h2><p>通过这两个层次递进的实战，我们完整地体验了 GUI Agent 从在线演示到本地部署的全过程。Mobile-Agent 的在线 Demo 让我们快速理解了技术的可能性，AutoGLM 的手机实战让我们掌握了实际部署的技能，而 GLM-ZERO 的端侧方案则展示了隐私保护和离线应用的未来。</p><p>GUI Agent 技术仍在快速演进中。当前的系统虽然已经能够处理大部分日常任务，但在准确率、推理速度和成本控制上还有很大的提升空间。随着视觉大模型的持续进步，以及端侧推理芯片的发展，我们有理由相信，GUI Agent 将成为未来人机交互的重要范式。</p><p>或许不久的将来，每个人都将拥有一个真正智能的数字助手，它不仅能理解你的意图，还能跨越不同的应用和平台，帮你完成各种重复性的工作。那时候，我们今天费力编写的自动化脚本，都将变成一句简单的自然语言指令。</p><p>这个未来，其实已经在路上了。</p><hr><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li>Mobile-Agent-v3 论文：<a href="https://arxiv.org/abs/2508.15144">https://arxiv.org/abs/2508.15144</a></li><li>Open-AutoGLM GitHub：<a href="https://github.com/zai-org/Open-AutoGLM">https://github.com/zai-org/Open-AutoGLM</a></li><li>UI-TARS 项目：<a href="https://github.com/bytedance/UI-TARS">https://github.com/bytedance/UI-TARS</a></li></ol>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra06-GUIAgent%E7%A7%91%E6%99%AE%E4%B8%8E%E5%AE%9E%E6%88%98/</id>
    <link href="http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra06-GUIAgent%E7%A7%91%E6%99%AE%E4%B8%8E%E5%AE%9E%E6%88%98/"/>
    <published>2026-03-02T22:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="GUI-Agent-科普与实战——下一代人机交互的探索之旅"><a href="#GUI-Agent-科普与实战——下一代人机交互的探索之旅" class="headerlink" title="GUI Agent 科普与实战——下一代人机交互的探索之旅"></a]]>
    </summary>
    <title>GUI Agent 科普与实战——下一代人机交互的探索之旅</title>
    <updated>2026-03-08T09:24:16.375Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="Agent-Skills-与-MCP：智能体能力扩展的两种范式"><a href="#Agent-Skills-与-MCP：智能体能力扩展的两种范式" class="headerlink" title="Agent Skills 与 MCP：智能体能力扩展的两种范式"></a>Agent Skills 与 MCP：智能体能力扩展的两种范式</h1><h2 id="引言：MCP-之后，我们还需要什么？"><a href="#引言：MCP-之后，我们还需要什么？" class="headerlink" title="引言：MCP 之后，我们还需要什么？"></a>引言：MCP 之后，我们还需要什么？</h2><p>在第十章中，我们深入探讨了 MCP（Model Context Protocol）如何通过标准化协议解决智能体与外部工具的连接问题。你已经学会了如何让智能体通过 MCP 访问数据库、文件系统、API 服务等各种资源。让我们回顾一个典型的 MCP 使用场景：</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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ReActAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool<br><br>llm = HelloAgentsLLM()<br>agent = ReActAgent(name=<span class="hljs-string">&quot;数据分析助手&quot;</span>, llm=llm)<br><br><span class="hljs-comment"># 连接到数据库 MCP 服务器</span><br>db_mcp = MCPTool(server_command=[<span class="hljs-string">&quot;python&quot;</span>, <span class="hljs-string">&quot;database_mcp_server.py&quot;</span>])<br>agent.add_tool(db_mcp)<br><br><span class="hljs-comment"># 智能体现在可以访问数据库了</span><br>response = agent.run(<span class="hljs-string">&quot;查询员工表中薪资最高的前10名员工&quot;</span>)<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 一个更复杂的需求</span><br>response = agent.run(<span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">分析公司内部谁的话语权最高？</span><br><span class="hljs-string">需要综合考虑：</span><br><span class="hljs-string">1. 管理层级和下属数量</span><br><span class="hljs-string">2. 薪资水平和涨薪幅度</span><br><span class="hljs-string">3. 任职时长和稳定性</span><br><span class="hljs-string">4. 跨部门影响力</span><br><span class="hljs-string">&quot;&quot;&quot;</span>)<br></code></pre></td></tr></table></figure><p>这个任务需要执行多次数据库查询，每次查询的结果会影响下一次查询的策略。更关键的是，它需要智能体具备<strong>领域知识</strong>：知道如何衡量”话语权”，知道应该从哪些维度分析数据，知道如何组合多个查询结果得出结论。</p><p>此时，你会遇到两个根本性的问题：</p><p><strong>第一个问题是上下文爆炸</strong>。为了让智能体能够灵活查询数据库，MCP 服务器通常会暴露数十甚至上百个工具（不同的表、不同的查询方法）。这些工具的完整 JSON Schema 在连接建立时就会被加载到系统提示词中，可能占用数万个 token。据社区开发者反馈，仅加载一个 Playwright MCP 服务器就会占用 200k 上下文窗口的 8%，这在多轮对话中会迅速累积，导致成本飙升和推理能力下降。</p><p><strong>第二个问题是能力鸿沟</strong>。MCP 解决了”能够连接”的问题，但没有解决”知道如何使用”的问题。拥有数据库连接能力，不等于智能体知道如何编写高效且安全的 SQL；能够访问文件系统，不意味着它理解特定项目的代码结构和开发规范。这就像给一个新手程序员开通了所有系统的访问权限，但没有提供操作手册和最佳实践。</p><p>这正是 <strong>Agent Skills</strong> 要解决的核心问题。2025年初，Anthropic 在推出 MCP 之后，进一步提出了 Agent Skills 的概念，引发了业界的广泛关注。有开发者评论说：”Skills 和 MCP 是两种东西，Skills 是领域知识，告诉模型该如何做，本质上是高级 Prompt；而 MCP 对接外部工具和数据。” 也有人认为：”从 Function Call 到 Tool Call 到 MCP 到 Skills，核心大差不差，就是工程实践和表现形式的优化演进。”</p><p>那么，Agent Skills 到底是什么？它与 MCP 有何本质区别？两者是竞争关系还是互补关系？本章将深入探讨这些问题。</p><h2 id="什么是-Agent-Skills？"><a href="#什么是-Agent-Skills？" class="headerlink" title="什么是 Agent Skills？"></a>什么是 Agent Skills？</h2><h3 id="核心设计理念"><a href="#核心设计理念" class="headerlink" title="核心设计理念"></a>核心设计理念</h3><p><strong>Agent Skills 是一种标准化的程序性知识封装格式</strong>。如果说 MCP 为智能体提供了”手”来操作工具，那么 Skills 就提供了”操作手册”或”SOP（标准作业程序）”，教导智能体如何正确使用这些工具。</p><p>这种设计理念源于一个简单但深刻的洞察：<strong>连接性（Connectivity）与能力（Capability）应该分离</strong>。MCP 专注于前者，Skills 专注于后者。这种职责分离带来了清晰的架构优势：</p><ul><li><strong>MCP 的职责</strong>：提供标准化的访问接口，让智能体能够”够得着”外部世界的数据和工具</li><li><strong>Skills 的职责</strong>：提供领域专业知识，告诉智能体在特定场景下”如何组合使用这些工具”</li></ul><p>用一个类比来理解：MCP 像是 USB 接口或驱动程序，它定义了设备如何连接；而 Skills 像是软件应用程序，它定义了如何使用这些连接的设备来完成具体任务。你可以拥有一个功能完善的打印机驱动（MCP），但如果没有告诉你如何在 Word 里设置页边距和双面打印（Skill），你仍然无法高效地完成打印任务。</p><h3 id="渐进式披露：破解上下文困境"><a href="#渐进式披露：破解上下文困境" class="headerlink" title="渐进式披露：破解上下文困境"></a>渐进式披露：破解上下文困境</h3><p>Agent Skills 最核心的创新是<strong>渐进式披露（Progressive Disclosure）机制</strong>。这种机制将技能信息分为三个层次，智能体按需逐步加载，既确保必要时不遗漏细节，又避免一次性将过多内容塞入上下文窗口。</p><div align="center">  <img src="./images/Extra05-figures/image1.png" alt="渐进式披露三层架构" width="90%"/>  <p>图 1 Agent Skills 渐进式披露三层架构</p></div><h4 id="第一层：元数据（Metadata）"><a href="#第一层：元数据（Metadata）" class="headerlink" title="第一层：元数据（Metadata）"></a>第一层：元数据（Metadata）</h4><p>在 Skills 的设计中，每个技能都存放在一个独立的文件夹中，核心是一个名为 <code>SKILL.md</code> 的 Markdown 文件。这个文件必须以 YAML 格式的 Frontmatter 开头，定义技能的基本信息。</p><p>当智能体启动时，它会扫描所有已安装的技能文件夹，<strong>仅读取每个 <code>SKILL.md</code> 的 Frontmatter 部分</strong>，将这些元数据加载到系统提示词中。根据实测数据，每个技能的元数据仅消耗约 <strong>100 个 token</strong>。即使你安装了 50 个技能，初始的上下文消耗也只有约 5,000 个 token。</p><p>这与 MCP 的工作方式形成了鲜明对比。在典型的 MCP 实现中，当客户端连接到一个服务器时，通常会通过 <code>tools/list</code> 请求获取所有可用工具的完整 JSON Schema，可能立即消耗数万个 token。</p><h4 id="第二层：技能主体（Instructions）"><a href="#第二层：技能主体（Instructions）" class="headerlink" title="第二层：技能主体（Instructions）"></a>第二层：技能主体（Instructions）</h4><p>当智能体通过分析用户请求，判断某个技能与当前任务高度相关时，它会进入第二层加载。此时，智能体会读取该技能的完整 <code>SKILL.md</code> 文件内容，将详细的指令、注意事项、示例等加载到上下文中。</p><p>此时，智能体获得了完成任务所需的全部上下文：数据库结构、查询模式、注意事项等。这部分内容的 token 消耗取决于指令的复杂度，通常在 1,000 到 5,000 个 token 之间。</p><h4 id="第三层：附加资源（Scripts-amp-References）"><a href="#第三层：附加资源（Scripts-amp-References）" class="headerlink" title="第三层：附加资源（Scripts &amp; References）"></a>第三层：附加资源（Scripts &amp; References）</h4><p>对于更复杂的技能，<code>SKILL.md</code> 可以引用同一文件夹下的其他文件：脚本、配置文件、参考文档等。智能体<strong>仅在需要时才加载这些资源</strong>。</p><p>例如，一个 PDF 处理技能的文件结构可能是：</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><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><code class="hljs awk">skills<span class="hljs-regexp">/pdf-processing/</span><br>├── SKILL.md              <span class="hljs-comment"># 主技能文件</span><br>├── parse_pdf.py          <span class="hljs-comment"># PDF 解析脚本</span><br>├── forms.md              <span class="hljs-comment"># 表单填写指南（仅在填表任务时加载）</span><br>└── templates/            <span class="hljs-comment"># PDF 模板文件</span><br>    ├── invoice.pdf<br>    └── report.pdf<br></code></pre></td></tr></table></figure><p>在 <code>SKILL.md</code> 中，可以这样引用附加资源：</p><ul><li>当需要执行 PDF 解析时，智能体会运行 <code>parse_pdf.py</code> 脚本</li><li>当遇到表单填写任务时，才会加载 <code>forms.md</code> 了解详细步骤</li><li>模板文件只在需要生成特定格式文档时访问</li></ul><p>这种设计有两个关键优势：</p><ol><li><p><strong>无限的知识容量</strong>：通过脚本和外部文件，技能可以”携带”远超上下文限制的知识。例如，一个数据分析技能可以附带一个 1GB 的数据文件和一个查询脚本，智能体通过执行脚本来访问数据，而无需将整个数据集加载到上下文中。</p></li><li><p><strong>确定性执行</strong>：复杂的计算、数据转换、格式解析等任务交给代码执行，避免了 LLM 生成过程中的不确定性和幻觉问题。</p></li></ol><h3 id="渐进式披露的效果：从-16k-到-500-Token"><a href="#渐进式披露的效果：从-16k-到-500-Token" class="headerlink" title="渐进式披露的效果：从 16k 到 500 Token"></a>渐进式披露的效果：从 16k 到 500 Token</h3><p>社区开发者分享的实践案例充分证明了渐进式披露的威力。在一个真实场景中：</p><ul><li><strong>传统 MCP 方式</strong>：直接连接一个包含大量工具定义的 MCP 服务器，初始加载消耗 <strong>16,000 个 token</strong></li><li><strong>Skills 包装后</strong>：创建一个简单的 Skill 作为”网关”，仅在 Frontmatter 中描述功能，初始消耗仅 <strong>500 个 token</strong></li></ul><p>当智能体确定需要使用该技能时，才会加载详细指令并按需调用底层的 MCP 工具。这种架构不仅大幅降低了初始成本，还使得对话过程中的上下文管理更加精准和高效。</p><h2 id="Agent-Skills-vs-MCP：本质区别与协作关系"><a href="#Agent-Skills-vs-MCP：本质区别与协作关系" class="headerlink" title="Agent Skills vs MCP：本质区别与协作关系"></a>Agent Skills vs MCP：本质区别与协作关系</h2><p>现在，我们可以系统地比较这两种技术的本质区别了。</p><div align="center">  <img src="./images/Extra05-figures/image2.png" alt="MCP与Agent Skills设计哲学对比" width="90%"/>  <p>图 2 MCP 与 Agent Skills 设计哲学对比</p></div><h3 id="从工程视角理解差异"><a href="#从工程视角理解差异" class="headerlink" title="从工程视角理解差异"></a>从工程视角理解差异</h3><p>让我们通过一个具体的例子来理解这种差异。假设你要构建一个智能体来帮助团队进行代码审查：</p><p><strong>MCP 的职责</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># MCP 提供对 GitHub 的标准化访问</span><br>github_mcp = MCPTool(server_command=[<span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-github&quot;</span>])<br><br><span class="hljs-comment"># MCP 暴露的工具（简化示例）：</span><br><span class="hljs-comment"># - list_pull_requests(repo, state)</span><br><span class="hljs-comment"># - get_pull_request_details(pr_number)</span><br><span class="hljs-comment"># - list_pr_comments(pr_number)</span><br><span class="hljs-comment"># - create_pr_comment(pr_number, body)</span><br><span class="hljs-comment"># - get_file_content(repo, path, ref)</span><br><span class="hljs-comment"># - list_pr_files(pr_number)</span><br></code></pre></td></tr></table></figure><p>MCP 让智能体”能够”访问 GitHub，能够调用这些 API。但它不知道”应该”做什么。</p><p><strong>Skills 的职责</strong>：</p><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><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></pre></td><td class="code"><pre><code class="hljs markdown">---<br>name: code-review-workflow<br><span class="hljs-section">description: 执行标准的代码审查流程，包括检查代码风格、安全问题、测试覆盖率等</span><br><span class="hljs-section">---</span><br><br><span class="hljs-section"># 代码审查工作流</span><br><br><span class="hljs-section">## 审查清单</span><br><br>当执行代码审查时，按以下步骤进行：<br><br><span class="hljs-bullet">1.</span> <span class="hljs-strong">**获取 PR 信息**</span>：调用 <span class="hljs-code">`get_pull_request_details`</span> 了解变更背景<br><span class="hljs-bullet">2.</span> <span class="hljs-strong">**分析变更文件**</span>：调用 <span class="hljs-code">`list_pr_files`</span> 获取文件列表<br><span class="hljs-bullet">3.</span> <span class="hljs-strong">**逐文件审查**</span>：<br><span class="hljs-bullet">   -</span> 对于 <span class="hljs-code">`.py`</span> 文件：检查是否符合 PEP 8，是否有明显的性能问题<br><span class="hljs-bullet">   -</span> 对于 <span class="hljs-code">`.js/.ts`</span> 文件：检查是否有未处理的 Promise，是否使用了废弃的 API<br><span class="hljs-bullet">   -</span> 对于测试文件：验证是否覆盖了新增的代码路径<br><span class="hljs-bullet">4.</span> <span class="hljs-strong">**安全检查**</span>：<br><span class="hljs-bullet">   -</span> 是否硬编码了敏感信息（密钥、密码）<br><span class="hljs-bullet">   -</span> 是否有 SQL 注入或 XSS 风险<br><span class="hljs-bullet">5.</span> <span class="hljs-strong">**提供反馈**</span>：<br><span class="hljs-bullet">   -</span> 严重问题：使用 <span class="hljs-code">`create_pr_comment`</span> 直接评论<br><span class="hljs-bullet">   -</span> 建议改进：在总结中提出<br><br><span class="hljs-section">## 公司特定规范</span><br><br><span class="hljs-bullet">-</span> 所有数据库查询必须使用参数化查询<br><span class="hljs-bullet">-</span> API 端点必须有权限验证装饰器<br><span class="hljs-bullet">-</span> 新功能必须附带单元测试（覆盖率 &gt; 80%）<br><br><span class="hljs-section">## 示例评论模板</span><br><br><span class="hljs-strong">**严重问题**</span>：<br><br>⚠️ 安全风险：第 45 行直接拼接 SQL 字符串，存在注入风险。<br>建议改用参数化查询：<span class="hljs-code">`cursor.execute(&quot;SELECT * FROM users WHERE id = ?&quot;, (user_id,))`</span><br><br></code></pre></td></tr></table></figure><p>Skills 告诉智能体”应该”做什么、如何组织审查流程、需要关注哪些公司特定的规范。它是领域知识和最佳实践的容器。</p><h3 id="上下文管理策略的本质差异"><a href="#上下文管理策略的本质差异" class="headerlink" title="上下文管理策略的本质差异"></a>上下文管理策略的本质差异</h3><div align="center">  <img src="./images/Extra05-figures/image3.png" alt="MCP急切加载 vs Skills惰性加载" width="90%"/>  <p>图 3 MCP 急切加载 vs Skills 惰性加载对比</p></div><h3 id="互补而非竞争：Skills-MCP-的混合架构"><a href="#互补而非竞争：Skills-MCP-的混合架构" class="headerlink" title="互补而非竞争：Skills + MCP 的混合架构"></a>互补而非竞争：Skills + MCP 的混合架构</h3><p>理解了两者的差异后，我们会发现：<strong>Skills 和 MCP 不是竞争关系，而是互补关系</strong>。最佳实践是将两者结合，形成分层架构：</p><div align="center">  <img src="./images/Extra05-figures/image4.png" alt="Skills + MCP 混合架构" width="90%"/>  <p>图 4 Skills + MCP 混合架构设计</p></div><strong>典型工作流</strong>：<ol><li>用户问：”分析公司内部谁的话语权最高”</li><li><strong>Skills 层</strong>识别这是一个数据分析任务，加载 <code>mysql-employees-analysis</code> 技能</li><li><strong>Skills 层</strong>根据技能指令，将任务分解为子步骤：查询管理关系、薪资对比、任职时长等</li><li><strong>MCP 层</strong>执行具体的 SQL 查询，返回结果</li><li><strong>Skills 层</strong>根据技能中的领域知识，解读数据并生成综合分析</li><li>返回结构化的答案给用户</li></ol><p>这种架构的优势是：</p><ul><li><strong>关注点分离</strong>：MCP 专注于”能力”，Skills 专注于”智慧”</li><li><strong>成本优化</strong>：渐进式加载大幅降低 token 消耗</li><li><strong>可维护性</strong>：业务逻辑（Skills）与基础设施（MCP）解耦</li><li><strong>复用性</strong>：同一个 MCP 服务器可以被多个 Skills 使用</li></ul><h2 id="技术实现：如何创建和使用-Skills"><a href="#技术实现：如何创建和使用-Skills" class="headerlink" title="技术实现：如何创建和使用 Skills"></a>技术实现：如何创建和使用 Skills</h2><h3 id="SKILL-md-规范详解"><a href="#SKILL-md-规范详解" class="headerlink" title="SKILL.md 规范详解"></a>SKILL.md 规范详解</h3><p>让我们深入了解 <code>SKILL.md</code> 文件的标准结构：</p><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><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><code class="hljs markdown">---<br><span class="hljs-section"># === 必需字段 ===</span><br>name: skill-name<br>  # 技能的唯一标识符，使用 kebab-case 命名<br><br>description: &gt;<br>  简洁但精确的描述，说明：<br><span class="hljs-bullet">  1.</span> 这个技能做什么<br><span class="hljs-bullet">  2.</span> 什么时候应该使用它<br><span class="hljs-bullet">  3.</span> 它的核心价值是什么<br>  # 注意：description 是智能体选择技能的唯一依据，必须写清楚！<br><br><span class="hljs-section"># === 可选字段 ===</span><br>version: 1.0.0<br>  # 语义化版本号<br><br>allowed<span class="hljs-emphasis">_tools: [tool1, tool2]</span><br><span class="hljs-emphasis">  # 此技能可以调用的工具列表（白名单）</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">required_</span>context: [context<span class="hljs-emphasis">_item1]</span><br><span class="hljs-emphasis">  # 此技能需要的上下文信息</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">license: MIT</span><br><span class="hljs-emphasis">  # 许可协议</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">author: Your Name <span class="language-xml">&lt;email@example.com&gt;</span></span><br><span class="hljs-emphasis">  # 作者信息</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">tags: [database, analysis, sql]</span><br><span class="hljs-emphasis">  # 便于分类和搜索的标签</span><br><span class="hljs-emphasis">---</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis"># 技能标题</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">## 概述</span><br><span class="hljs-emphasis">（对技能的详细介绍，包括使用场景、技术背景等）</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">## 前置条件</span><br><span class="hljs-emphasis">（使用此技能需要的环境配置、依赖项等）</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">## 工作流程</span><br><span class="hljs-emphasis">（详细的步骤说明，告诉智能体如何执行任务）</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">## 最佳实践</span><br><span class="hljs-emphasis">（经验总结、注意事项、常见陷阱等）</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">## 示例</span><br><span class="hljs-emphasis">（具体的使用案例，帮助智能体理解）</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">## 故障排查</span><br><span class="hljs-emphasis">（常见问题和解决方案）</span><br></code></pre></td></tr></table></figure><h3 id="编写高质量-Skills-的原则"><a href="#编写高质量-Skills-的原则" class="headerlink" title="编写高质量 Skills 的原则"></a>编写高质量 Skills 的原则</h3><p>根据 Anthropic 官方文档和社区最佳实践，编写有效的 Skills 需要遵循以下原则：</p><h4 id="1-精准的-Description"><a href="#1-精准的-Description" class="headerlink" title="1. 精准的 Description"></a>1. 精准的 Description</h4><p><code>description</code> 是智能体决策的关键。它应该：</p><ul><li><strong>精确定义适用范围</strong>：避免模糊的描述如”帮助处理数据”</li><li><strong>包含触发关键词</strong>：让智能体能够匹配用户意图</li><li><strong>说明独特价值</strong>：与其他技能区分开来</li></ul><p>❌ <strong>不好的 description</strong>：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">description:</span> <span class="hljs-string">处理数据库查询</span><br></code></pre></td></tr></table></figure><p>✅ <strong>好的 description</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><code class="hljs yaml"><span class="hljs-attr">description:</span> <span class="hljs-string">&gt;</span><br><span class="hljs-string">  将中文业务问题转换为 SQL 查询并分析 MySQL employees 示例数据库。</span><br><span class="hljs-string">  适用于员工信息查询、薪资统计、部门分析、职位变动历史等场景。</span><br><span class="hljs-string">  当用户询问关于员工、薪资、部门的数据时使用此技能。</span><br></code></pre></td></tr></table></figure><h4 id="2-模块化与单一职责"><a href="#2-模块化与单一职责" class="headerlink" title="2. 模块化与单一职责"></a>2. 模块化与单一职责</h4><p>一个 Skill 应该专注于一个明确的领域或任务类型。如果一个 Skill 试图做太多事情，会导致：</p><ul><li>Description 过于宽泛，匹配精度下降</li><li>指令内容过长，浪费上下文</li><li>难以维护和更新</li></ul><p><strong>建议</strong>：与其创建一个”通用数据分析”技能，不如创建多个专门的技能：</p><ul><li><code>mysql-employees-analysis</code>：专门分析 employees 数据库</li><li><code>sales-data-analysis</code>：专门分析销售数据</li><li><code>user-behavior-analysis</code>：专门分析用户行为数据</li></ul><h4 id="3-确定性优先原则"><a href="#3-确定性优先原则" class="headerlink" title="3. 确定性优先原则"></a>3. 确定性优先原则</h4><p>对于复杂的、需要精确执行的任务，优先使用脚本而不是依赖 LLM 生成。例如，在数据导出场景中，与其让 LLM 生成 Excel 二进制内容（容易出错），不如编写一个专门的脚本来处理这个任务，SKILL.md 中只需要指导智能体何时调用这个脚本即可。</p><h4 id="4-渐进式披露策略"><a href="#4-渐进式披露策略" class="headerlink" title="4. 渐进式披露策略"></a>4. 渐进式披露策略</h4><p>合理利用三层结构，将信息按重要性和使用频率分层：</p><ul><li><strong>SKILL.md 主体</strong>：放置核心工作流、常用模式</li><li><strong>附加文档</strong>（如 <code>advanced.md</code>）：放置高级用法、边缘情况</li><li><strong>数据文件</strong>：放置大型参考数据，通过脚本按需查询</li></ul><h3 id="实践案例：MySQL-员工分析-Skill-详解"><a href="#实践案例：MySQL-员工分析-Skill-详解" class="headerlink" title="实践案例：MySQL 员工分析 Skill 详解"></a>实践案例：MySQL 员工分析 Skill 详解</h3><p>让我们通过 Anthropic 社区的一个真实案例，了解 Agent Skills 的具体应用。这个技能用于分析 MySQL 官方的 <code>employees</code> 示例数据库。</p><h4 id="技能文件结构"><a href="#技能文件结构" class="headerlink" title="技能文件结构"></a>技能文件结构</h4><figure class="highlight awk"><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><code class="hljs awk">skills<span class="hljs-regexp">/mysql-employees-analysis/</span><br>├── SKILL.md          <span class="hljs-comment"># 主技能文件（包含元数据和详细指令）</span><br>└── db_schema.sql     <span class="hljs-comment"># 数据库结构参考（可选，按需加载）</span><br></code></pre></td></tr></table></figure><h4 id="SKILL-md-核心内容示例"><a href="#SKILL-md-核心内容示例" class="headerlink" title="SKILL.md 核心内容示例"></a>SKILL.md 核心内容示例</h4><p>这个技能的 Frontmatter（元数据层）：</p><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><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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br></pre></td><td class="code"><pre><code class="hljs markdown">---<br>name: mysql-employees-analysis<br>description: &gt;<br>  将中文业务问题转换为 SQL 查询并分析 MySQL employees 示例数据库。<br>  适用于员工信息查询（如&quot;工号12345的员工信息&quot;）、<br>  薪资统计（如&quot;平均薪资最高的部门&quot;）、<br>  部门分析（如&quot;各部门人数分布&quot;）、<br>  职位变动历史（如&quot;某员工的晋升路径&quot;）等场景。<br>version: 1.0.0<br>allowed<span class="hljs-emphasis">_tools: [execute_</span>sql]<br><span class="hljs-section">tags: [database, mysql, sql, employees, analysis]</span><br><span class="hljs-section">---</span><br><br><span class="hljs-section"># MySQL 员工数据库分析技能</span><br><br><span class="hljs-section">## 概述</span><br><br>这个技能专门用于分析 MySQL 官方提供的 <span class="hljs-code">`employees`</span> 示例数据库。<br>该数据库包含约 300,000 名虚拟员工的记录，涵盖 1985-2000 年的数据。<br><br><span class="hljs-strong">**核心能力**</span>：<br><span class="hljs-bullet">-</span> 理解中文自然语言的业务问题<br><span class="hljs-bullet">-</span> 转换为高效的 SQL 查询<br><span class="hljs-bullet">-</span> 执行查询并解读结果<br><span class="hljs-bullet">-</span> 提供业务洞察和数据解读<br><br><span class="hljs-section">## 数据库结构</span><br><br><span class="hljs-section">### 核心表结构</span><br><br>| 表名           | 说明         | 关键字段                                                     |<br>| -------------- | ------------ | ------------------------------------------------------------ |<br>| <span class="hljs-code">`employees`</span>    | 员工基本信息 | emp<span class="hljs-emphasis">_no, birth_</span>date, first<span class="hljs-emphasis">_name, last_</span>name, gender, hire<span class="hljs-emphasis">_date |</span><br><span class="hljs-emphasis">| `salaries`     | 薪资历史     | emp_</span>no, salary, from<span class="hljs-emphasis">_date, to_</span>date                           |<br>| <span class="hljs-code">`titles`</span>       | 职位历史     | emp<span class="hljs-emphasis">_no, title, from_</span>date, to<span class="hljs-emphasis">_date                            |</span><br><span class="hljs-emphasis">| `dept_</span>emp`     | 员工部门关系 | emp<span class="hljs-emphasis">_no, dept_</span>no, from<span class="hljs-emphasis">_date, to_</span>date                          |<br>| <span class="hljs-code">`dept_manager`</span> | 部门经理     | emp<span class="hljs-emphasis">_no, dept_</span>no, from<span class="hljs-emphasis">_date, to_</span>date                          |<br>| <span class="hljs-code">`departments`</span>  | 部门信息     | dept<span class="hljs-emphasis">_no, dept_</span>name                                           |<br><br><span class="hljs-section">### 关键约定</span><br><br>⚠️ <span class="hljs-strong">**重要**</span>：<span class="hljs-code">`to_date = &#x27;9999-01-01&#x27;`</span> 表示&quot;当前有效&quot;的记录。<br>查询&quot;当前&quot;状态时（如现任员工、当前薪资），必须加此过滤条件。<br><br>完整的表结构参见：<span class="hljs-code">`db_schema.sql`</span><br><br><span class="hljs-section">## 工作流程</span><br><br><span class="hljs-section">### 第一步：理解需求</span><br><br>仔细分析用户的中文描述，识别：<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**查询目标**</span>：要查什么数据？（员工、薪资、部门...）<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**筛选条件**</span>：有什么限制？（特定部门、时间范围、薪资区间...）<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**聚合维度**</span>：需要统计吗？（平均值、总数、排名...）<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**时间范围**</span>：是历史数据还是当前状态？<br><br><span class="hljs-section">### 第二步：构建 SQL</span><br><br>根据需求选择合适的查询模式（见下方&quot;常见查询模式&quot;）。<br><br><span class="hljs-strong">**编写原则**</span>：<br><span class="hljs-bullet">1.</span> 使用明确的表别名（如 <span class="hljs-code">`e`</span> for employees）<br><span class="hljs-bullet">2.</span> JOIN 时优先使用主键/外键<br><span class="hljs-bullet">3.</span> 注意日期过滤（特别是 <span class="hljs-code">`to_date`</span>）<br><span class="hljs-bullet">4.</span> 合理使用索引字段<br><span class="hljs-bullet">5.</span> 大结果集要加 LIMIT<br><br><span class="hljs-section">### 第三步：执行查询</span><br><br>调用 <span class="hljs-code">`execute_sql`</span> 工具执行构建好的 SQL。<br><br><span class="hljs-code">```python</span><br><span class="hljs-code"># 示例调用（智能体会自动转换为工具调用）</span><br><span class="hljs-code">result = execute_sql(query=&quot;SELECT ...&quot;)</span><br><span class="hljs-code"></span><br><span class="hljs-code"></span><br><span class="hljs-code">### 第四步：解读结果</span><br><span class="hljs-code"></span><br><span class="hljs-code">将查询结果转化为自然语言回答：</span><br><span class="hljs-code">- 用表格呈现结构化数据</span><br><span class="hljs-code">- 突出关键数据点</span><br><span class="hljs-code">- 提供业务洞察（如趋势、异常）</span><br><span class="hljs-code">- 如果结果为空，说明可能的原因</span><br><span class="hljs-code"></span><br><span class="hljs-code">## 常见查询模式</span><br><span class="hljs-code"></span><br><span class="hljs-code">### 模式 1：基础信息查询</span><br><span class="hljs-code"></span><br><span class="hljs-code"></span><br><span class="hljs-code">-- 查询特定员工的基本信息</span><br><span class="hljs-code">SELECT emp_no, CONCAT(first_name, &#x27; &#x27;, last_name) AS full_name,</span><br><span class="hljs-code">       gender, birth_date, hire_date</span><br><span class="hljs-code">FROM employees</span><br><span class="hljs-code">WHERE emp_no = &lt;员工号&gt;;</span><br><span class="hljs-code"></span><br><span class="hljs-code"></span><br><span class="hljs-code">### 模式 2：当前状态查询</span><br><span class="hljs-code"></span><br><span class="hljs-code"></span><br><span class="hljs-code">-- 查询当前薪资最高的员工（TOP 10）</span><br><span class="hljs-code">SELECT e.emp_no,</span><br><span class="hljs-code">       CONCAT(e.first_name, &#x27; &#x27;, e.last_name) AS name,</span><br><span class="hljs-code">       s.salary</span><br><span class="hljs-code">FROM employees e</span><br><span class="hljs-code">JOIN salaries s ON e.emp_no = s.emp_no</span><br><span class="hljs-code">WHERE s.to_date = &#x27;9999-01-01&#x27;  -- 当前薪资</span><br><span class="hljs-code">ORDER BY s.salary DESC</span><br><span class="hljs-code">LIMIT 10;</span><br><span class="hljs-code"></span><br><span class="hljs-code"></span><br><span class="hljs-code">### 模式 3：历史趋势分析</span><br><span class="hljs-code"></span><br><span class="hljs-code"></span><br><span class="hljs-code">-- 查询某员工的薪资变化历史</span><br><span class="hljs-code">SELECT emp_no, salary, from_date, to_date,</span><br><span class="hljs-code">       salary - LAG(salary) OVER (ORDER BY from_date) AS increase</span><br><span class="hljs-code">FROM salaries</span><br><span class="hljs-code">WHERE emp_no = &lt;员工号&gt;</span><br><span class="hljs-code">ORDER BY from_date;</span><br><span class="hljs-code"></span><br><span class="hljs-code"></span><br><span class="hljs-code">### 模式 4：跨表关联查询</span><br><span class="hljs-code"></span><br><span class="hljs-code"></span><br><span class="hljs-code">-- 查询各部门的平均薪资（当前）</span><br><span class="hljs-code">SELECT d.dept_name,</span><br><span class="hljs-code">       COUNT(DISTINCT de.emp_no) AS emp_count,</span><br><span class="hljs-code">       ROUND(AVG(s.salary), 2) AS avg_salary</span><br><span class="hljs-code">FROM departments d</span><br><span class="hljs-code">JOIN dept_emp de ON d.dept_no = de.dept_no</span><br><span class="hljs-code">JOIN salaries s ON de.emp_no = s.emp_no</span><br><span class="hljs-code">WHERE de.to_date = &#x27;9999-01-01&#x27;  -- 当前在职</span><br><span class="hljs-code">  AND s.to_date = &#x27;9999-01-01&#x27;   -- 当前薪资</span><br><span class="hljs-code">GROUP BY d.dept_name</span><br><span class="hljs-code">ORDER BY avg_salary DESC;</span><br><span class="hljs-code"></span><br><span class="hljs-code"></span><br><span class="hljs-code">### 模式 5：复杂业务分析</span><br><span class="hljs-code"></span><br><span class="hljs-code"></span><br><span class="hljs-code">-- 分析&quot;话语权&quot;：综合管理层级、薪资、任职时长</span><br><span class="hljs-code">WITH manager_hierarchy AS (</span><br><span class="hljs-code">    -- 统计每个经理管理的下属数</span><br><span class="hljs-code">    SELECT dm.emp_no, COUNT(de.emp_no) AS subordinate_count</span><br><span class="hljs-code">    FROM dept_manager dm</span><br><span class="hljs-code">    JOIN dept_emp de ON dm.dept_no = de.dept_no</span><br><span class="hljs-code">    WHERE dm.to_date = &#x27;9999-01-01&#x27;</span><br><span class="hljs-code">      AND de.to_date = &#x27;9999-01-01&#x27;</span><br><span class="hljs-code">      AND de.emp_no != dm.emp_no</span><br><span class="hljs-code">    GROUP BY dm.emp_no</span><br><span class="hljs-code">),</span><br><span class="hljs-code">current_salary AS (</span><br><span class="hljs-code">    -- 当前薪资</span><br><span class="hljs-code">    SELECT emp_no, salary</span><br><span class="hljs-code">    FROM salaries</span><br><span class="hljs-code">    WHERE to_date = &#x27;9999-01-01&#x27;</span><br><span class="hljs-code">),</span><br><span class="hljs-code">tenure AS (</span><br><span class="hljs-code">    -- 任职时长（年）</span><br><span class="hljs-code">    SELECT emp_no,</span><br><span class="hljs-code">           TIMESTAMPDIFF(YEAR, hire_date, CURDATE()) AS years_employed</span><br><span class="hljs-code">    FROM employees</span><br><span class="hljs-code">)</span><br><span class="hljs-code">SELECT e.emp_no,</span><br><span class="hljs-code">       CONCAT(e.first_name, &#x27; &#x27;, e.last_name) AS name,</span><br><span class="hljs-code">       COALESCE(mh.subordinate_count, 0) AS team_size,</span><br><span class="hljs-code">       cs.salary,</span><br><span class="hljs-code">       t.years_employed,</span><br><span class="hljs-code">       -- 简单的话语权评分（可根据业务调整权重）</span><br><span class="hljs-code">       (COALESCE(mh.subordinate_count, 0) * 10 +</span><br><span class="hljs-code">        cs.salary / 1000 +</span><br><span class="hljs-code">        t.years_employed * 5) AS influence_score</span><br><span class="hljs-code">FROM employees e</span><br><span class="hljs-code">JOIN current_salary cs ON e.emp_no = cs.emp_no</span><br><span class="hljs-code">JOIN tenure t ON e.emp_no = t.emp_no</span><br><span class="hljs-code">LEFT JOIN manager_hierarchy mh ON e.emp_no = mh.emp_no</span><br><span class="hljs-code">WHERE cs.salary &gt; 60000  -- 过滤低薪员工</span><br><span class="hljs-code">ORDER BY influence_score DESC</span><br><span class="hljs-code">LIMIT 20;</span><br><span class="hljs-code"></span><br><span class="hljs-code"></span><br><span class="hljs-code">## 注意事项</span><br><span class="hljs-code"></span><br><span class="hljs-code">### ⚠️ 时间字段的正确处理</span><br><span class="hljs-code"></span><br><span class="hljs-code">- &lt;strong&gt;当前状态&lt;/strong&gt;：必须使用 `to_date = &#x27;9999-01-01&#x27;` 过滤</span><br><span class="hljs-code">- &lt;strong&gt;历史查询&lt;/strong&gt;：注意 `from_date` 和 `to_date` 的范围</span><br><span class="hljs-code">- &lt;strong&gt;时间计算&lt;/strong&gt;：使用 `TIMESTAMPDIFF`、`DATEDIFF` 等函数</span><br><span class="hljs-code"></span><br><span class="hljs-code">### ⚠️ 性能优化</span><br><span class="hljs-code"></span><br><span class="hljs-code">- &lt;strong&gt;大表 JOIN&lt;/strong&gt;：优先使用索引字段（emp_no, dept_no）</span><br><span class="hljs-code">- &lt;strong&gt;聚合查询&lt;/strong&gt;：合理使用 GROUP BY 和 HAVING</span><br><span class="hljs-code">- &lt;strong&gt;结果限制&lt;/strong&gt;：对于展示类查询，添加 LIMIT 限制</span><br><span class="hljs-code">- &lt;strong&gt;子查询优化&lt;/strong&gt;：复杂查询使用 WITH (CTE) 提高可读性和性能</span><br><span class="hljs-code"></span><br><span class="hljs-code">### ⚠️ 数据质量</span><br><span class="hljs-code"></span><br><span class="hljs-code">- &lt;strong&gt;NULL 值处理&lt;/strong&gt;：使用 COALESCE 或 IFNULL 处理空值</span><br><span class="hljs-code">- &lt;strong&gt;重复记录&lt;/strong&gt;：注意员工可能多次调岗，查询时考虑去重</span><br><span class="hljs-code">- &lt;strong&gt;数据范围&lt;/strong&gt;：数据库只包含 1985-2000 年的数据，查询时注意时间边界</span><br><span class="hljs-code"></span><br><span class="hljs-code">## 故障排查</span><br><span class="hljs-code"></span><br><span class="hljs-code">&lt;strong&gt;问题 1：查询结果为空&lt;/strong&gt;</span><br><span class="hljs-code">- 检查是否正确使用了 `to_date = &#x27;9999-01-01&#x27;`</span><br><span class="hljs-code">- 验证员工号或部门号是否存在</span><br><span class="hljs-code">- 检查日期范围是否合理</span><br><span class="hljs-code"></span><br><span class="hljs-code">&lt;strong&gt;问题 2：查询速度慢&lt;/strong&gt;</span><br><span class="hljs-code">- 检查是否缺少索引字段的 WHERE 条件</span><br><span class="hljs-code">- 考虑将复杂查询拆分为多步</span><br><span class="hljs-code">- 使用 EXPLAIN 分析查询计划</span><br><span class="hljs-code"></span><br><span class="hljs-code">&lt;strong&gt;问题 3：统计数据不准确&lt;/strong&gt;</span><br><span class="hljs-code">- 注意区分&quot;历史&quot;和&quot;当前&quot;状态</span><br><span class="hljs-code">- 检查 JOIN 条件是否遗漏</span><br><span class="hljs-code">- 验证聚合函数的使用是否正确</span><br></code></pre></td></tr></table></figure><p>这个 SKILL.md 文件展示了一个完整技能的结构：</p><ul><li>清晰的元数据（智能体用于发现和匹配）</li><li>完整的数据库结构说明</li><li>详细的工作流程指导</li><li>丰富的查询模式示例（可直接复用的 SQL 模板）</li><li>实用的注意事项和故障排查</li></ul><h4 id="技能的使用效果"><a href="#技能的使用效果" class="headerlink" title="技能的使用效果"></a>技能的使用效果</h4><p>当用户向支持 Agent Skills 的智能体（如 Claude Desktop、Claude Code）提问时：</p><p><strong>用户问题</strong>：</p><blockquote><p>“分析公司内部谁的话语权最高？需要综合考虑管理层级、薪资水平和任职时长。”</p></blockquote><div align="center">  <img src="./images/Extra05-figures/image5.png" alt="Agent Skills工作流程" width="90%"/>  <p>图 5 Agent Skills 完整工作流程示意</p></div><p><strong>输出示例</strong>：</p><table><thead><tr><th>排名</th><th>员工号</th><th>姓名</th><th>团队规模</th><th>薪资</th><th>任职年限</th><th>影响力评分</th></tr></thead><tbody><tr><td>1</td><td>110022</td><td>Margareta Markovitch</td><td>45</td><td>152,710</td><td>18</td><td>692.71</td></tr><tr><td>2</td><td>110039</td><td>Vishwani Minakawa</td><td>38</td><td>138,273</td><td>16</td><td>598.27</td></tr><tr><td>3</td><td>110085</td><td>Ebru Alpin</td><td>32</td><td>124,054</td><td>15</td><td>519.05</td></tr></tbody></table><p><strong>关键洞察</strong>：</p><ol><li>话语权最高的员工通常管理大团队（30+人）、薪资前1%（&gt;12万）、任职超15年</li><li>部门经理的影响力远超普通员工，管理规模是关键因素</li><li>长期任职的高薪员工即使不担任管理职务，也具有较强的话语权</li></ol><p>整个过程中，技能提供了：</p><ul><li><strong>领域知识</strong>：如何衡量”话语权”（管理规模+薪资+任职时长）</li><li><strong>技术指导</strong>：如何编写高效的 SQL（使用 CTE、窗口函数、多表 JOIN）</li><li><strong>业务理解</strong>：如何解读数据并生成洞察</li></ul><h3 id="Skills-的分享与复用"><a href="#Skills-的分享与复用" class="headerlink" title="Skills 的分享与复用"></a>Skills 的分享与复用</h3><p>Agent Skills 的另一个重要特性是<strong>社区化</strong>。Anthropic 建立了官方的 Skills 仓库：</p><p><strong>官方技能库</strong>：<a href="https://github.com/anthropics/skills">https://github.com/anthropics/skills</a></p><p>截至 2025 年，已有数百个社区贡献的技能，覆盖：</p><ul><li><strong>开发工具</strong>：前端设计、API 测试、代码审查、Git 工作流</li><li><strong>数据分析</strong>：SQL 查询、数据可视化、统计分析</li><li><strong>文档处理</strong>：PDF 解析、Markdown 生成、技术文档撰写</li><li><strong>业务流程</strong>：项目管理、客户支持、合规审查</li></ul><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><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 克隆官方技能库</span><br>git <span class="hljs-built_in">clone</span> https://github.com/anthropics/skills.git<br><br><span class="hljs-comment"># 复制需要的技能到你的项目</span><br><span class="hljs-built_in">cp</span> -r skills/frontend-design ./my-project/skills/<br><br><span class="hljs-comment"># 智能体会自动发现并加载</span><br></code></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><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><code class="hljs bash"><span class="hljs-comment"># 发布到 GitHub</span><br><span class="hljs-built_in">cd</span> my-custom-skill<br>git init<br>git add SKILL.md<br>git commit -m <span class="hljs-string">&quot;Add custom SQL analysis skill&quot;</span><br>git remote add origin https://github.com/yourname/my-skill.git<br>git push -u origin main<br><br><span class="hljs-comment"># 其他开发者可以直接使用</span><br><span class="hljs-comment"># git clone https://github.com/yourname/my-skill.git</span><br></code></pre></td></tr></table></figure><h2 id="行业动态与生态演进"><a href="#行业动态与生态演进" class="headerlink" title="行业动态与生态演进"></a>行业动态与生态演进</h2><h3 id="标准化进程与厂商支持"><a href="#标准化进程与厂商支持" class="headerlink" title="标准化进程与厂商支持"></a>标准化进程与厂商支持</h3><p>Agent Skills 虽然由 Anthropic 提出，但其设计理念正在影响整个行业。</p><p><strong>Anthropic Claude</strong>：</p><ul><li>Claude Desktop 和 Claude Code 原生支持 Skills</li><li>提供官方 SDK 和开发工具</li><li>维护官方技能库</li></ul><p><strong>OpenAI 的响应</strong>：<br>虽然 OpenAI 尚未官方采用 “Skills” 这个术语，但在 2025 年 3 月的更新中，ChatGPT 引入了类似的概念：</p><ul><li><strong>Custom Instructions 增强</strong>：支持更复杂的多步骤指令</li><li><strong>Memory 与 Context Profiles</strong>：允许用户保存和复用特定领域的知识</li><li><strong>GPTs 的”知识库”功能</strong>：可以附加文档和脚本，按需加载</li></ul><p>这些功能本质上是 Skills 理念的不同实现形式。</p><p><strong>Google Vertex AI</strong>：<br>Google 在 Gemini 模型中引入了 **”Grounding with Functions”**，允许开发者定义”函数包”（Function Packages），每个包包含：</p><ul><li>函数定义（类似 MCP 的 tools）</li><li>使用指南（类似 Skills 的 instructions）</li><li>示例（examples）</li></ul><p>这种设计与 Skills + MCP 的混合架构高度相似。</p><h3 id="分层架构的必然性"><a href="#分层架构的必然性" class="headerlink" title="分层架构的必然性"></a>分层架构的必然性</h3><p>综合各方观点，我们认为：<strong>Skills 和 MCP 代表了智能体架构中两个必然分离的层级</strong>。随着智能体系统的复杂度增加，这种分层是不可避免的：</p><figure class="highlight isbl"><table><tr><td class="gutter"><pre><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><code class="hljs isbl">应用层（<span class="hljs-variable"><span class="hljs-class">Application</span></span> <span class="hljs-variable">Layer</span>）<br>  ↓ <span class="hljs-variable">Agent</span> <span class="hljs-variable">Skills</span><br>  ↓ 领域知识、工作流、最佳实践<br><br>传输层（<span class="hljs-variable">Transport</span> <span class="hljs-variable">Layer</span>）<br>  ↓ <span class="hljs-variable">MCP</span><br>  ↓ 标准化接口、工具调用、资源访问<br><br>基础设施层（<span class="hljs-variable">Infrastructure</span> <span class="hljs-variable">Layer</span>）<br>  ↓ 数据库、<span class="hljs-variable">API</span>、文件系统、外部服务<br></code></pre></td></tr></table></figure><p>这与传统软件架构的演进路径完全一致（从单体到分层到微服务），只是在 AI 领域重新演绎了一遍。</p><h3 id="标准化的趋势"><a href="#标准化的趋势" class="headerlink" title="标准化的趋势"></a>标准化的趋势</h3><p>随着行业对智能体技术的重视，我们预见以下趋势：</p><p><strong>1. 协议融合</strong></p><p>未来可能出现统一的智能体能力描述协议，融合 MCP 的连接性和 Skills 的知识表达：</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></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-comment"># 未来的统一协议示例（假想）</span><br><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">agent.io/v1</span><br><span class="hljs-attr">kind:</span> <span class="hljs-string">Capability</span><br><span class="hljs-attr">metadata:</span><br>  <span class="hljs-attr">name:</span> <span class="hljs-string">enterprise-data-analysis</span><br><span class="hljs-attr">spec:</span><br>  <span class="hljs-attr">transport:</span><br>    <span class="hljs-attr">protocol:</span> <span class="hljs-string">mcp</span><br>    <span class="hljs-attr">server:</span> <span class="hljs-string">database-mcp-server</span><br>    <span class="hljs-attr">tools:</span> [<span class="hljs-string">query</span>, <span class="hljs-string">schema</span>]<br>  <span class="hljs-attr">knowledge:</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">skill</span><br>    <span class="hljs-attr">workflow:</span> <span class="hljs-string">data-analysis-workflow.md</span><br>    <span class="hljs-attr">examples:</span> <span class="hljs-string">examples/</span><br></code></pre></td></tr></table></figure><p><strong>2. 市场化与生态系统</strong></p><p>类似于 NPM、PyPI，未来可能出现智能体能力的包管理系统：</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></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 假想的未来命令</span><br>agent-cli install @anthropic/frontend-design-skill<br>agent-cli install @google/data-analysis-suite<br>agent-cli install @openai/code-review-assistant<br></code></pre></td></tr></table></figure><p>开发者可以发布、分享、售卖自己的 Skills 和 MCP 服务器，形成繁荣的生态系统。</p><p><strong>3. 自动化能力发现</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 未来的智能体可能具备自主学习能力</span><br>agent = SelfEvolvingAgent()<br><br><span class="hljs-comment"># 智能体在执行任务时发现缺少某种能力</span><br>response = agent.run(<span class="hljs-string">&quot;生成 3D 建模文件&quot;</span>)<br><br><span class="hljs-comment"># 智能体自动搜索并安装相关 Skill</span><br><span class="hljs-comment"># [内部日志] 检测到未知任务类型：3D建模</span><br><span class="hljs-comment"># [内部日志] 搜索技能库...发现 &quot;blender-3d-modeling&quot; skill</span><br><span class="hljs-comment"># [内部日志] 请求用户授权安装...已授权</span><br><span class="hljs-comment"># [内部日志] 技能安装完成，重新执行任务</span><br></code></pre></td></tr></table></figure><h3 id="挑战与风险"><a href="#挑战与风险" class="headerlink" title="挑战与风险"></a>挑战与风险</h3><p>与此同时，我们也需要警惕潜在的风险：</p><p><strong>安全性挑战</strong>：</p><ul><li>Skills 包含可执行脚本，存在代码注入风险</li><li>MCP 服务器可能暴露敏感数据接口</li><li>第三方技能的可信度难以验证</li></ul><p><strong>上下文污染</strong>：</p><ul><li>随着 Skills 数量增加，即使是元数据也可能占用大量上下文</li><li>需要更智能的技能索引和检索机制</li></ul><p><strong>碎片化风险</strong>：</p><ul><li>虽然 MCP 正在标准化，但 Skills 格式尚未统一</li><li>不同厂商可能推出不兼容的 Skills 规范</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Agent Skills 和 MCP 代表了智能体技术栈中两个关键的抽象层：</p><ul><li><strong>MCP（Model Context Protocol）</strong>：解决”连接性”问题，是智能体与外部世界交互的标准化接口，相当于”神经系统”或”双手”</li><li><strong>Agent Skills</strong>：解决”能力”问题，是领域知识和工作流的封装，相当于”大脑皮层”或”操作手册”</li></ul><p>两者不是竞争关系，而是互补关系：</p><div align="center">  <img src="./images/Extra05-figures/image6.png" alt="MCP与Agent Skills总结对比" width="90%"/>  <p>图 6 MCP 与 Agent Skills 全面对比总结</p></div><p><strong>关键洞察</strong>：</p><ol><li><p><strong>分层架构是必然趋势</strong>：随着智能体系统复杂度增加，”连接层”和”知识层”的分离是不可避免的</p></li><li><p><strong>上下文效率是核心矛盾</strong>：Skills 的渐进式披露机制将 token 消耗降低 90% 以上，这是其最大的技术优势</p></li><li><p><strong>领域知识的民主化</strong>：Skills 让非开发者也能贡献智能体能力，这将极大拓展 AI 应用的边界</p></li><li><p><strong>混合架构是最佳实践</strong>：在企业级应用中，MCP 提供基础设施连接，Skills 提供业务逻辑，两者结合才能构建高效、可维护的智能体系统</p></li></ol><p><strong>实践建议</strong>：</p><ul><li>对于<strong>外部服务连接</strong>（数据库、API、云服务），优先使用 MCP</li><li>对于<strong>复杂工作流</strong>（多步骤任务、领域专业知识），优先使用 Skills</li><li>在<strong>上下文受限</strong>的场景（长对话、大量工具），使用 Skills 进行渐进式管理</li><li>构建<strong>企业级智能体</strong>时，采用 MCP + Skills 的分层架构</li></ul><p>通过本章的学习，你应该能够：</p><ul><li>理解 Agent Skills 和 MCP 的本质区别与协作关系</li><li>掌握 Skills 的渐进式披露机制及其优势</li><li>编写高质量的 SKILL.md 文件</li><li>在实际项目中合理选择和组合两种技术</li><li>构建分层清晰、高效可维护的智能体系统</li></ul><p>智能体技术仍在快速演进中。MCP 已成为连接层的事实标准，Skills 的理念也在影响整个行业。掌握这两种技术，将帮助你在 AI 浪潮中构建更强大、更实用的智能体应用。</p><hr><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li>Anthropic Agent Skills 官方文档：<a href="https://docs.anthropic.com/en/docs/agent-skills">https://docs.anthropic.com/en/docs/agent-skills</a></li><li>Anthropic Skills GitHub 仓库：<a href="https://github.com/anthropics/skills">https://github.com/anthropics/skills</a></li><li>Model Context Protocol 规范：<a href="https://modelcontextprotocol.io/">https://modelcontextprotocol.io/</a></li><li>Anthropic 博客：Improving Frontend Design Through Skills：<a href="https://www.claude.com/blog/improving-frontend-design-through-skills">https://www.claude.com/blog/improving-frontend-design-through-skills</a></li><li>第十章：智能体通信协议（hello-agents）</li></ol>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra05-AgentSkills%E8%A7%A3%E8%AF%BB/</id>
    <link href="http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra05-AgentSkills%E8%A7%A3%E8%AF%BB/"/>
    <published>2026-03-02T20:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="Agent-Skills-与-MCP：智能体能力扩展的两种范式"><a href="#Agent-Skills-与-MCP：智能体能力扩展的两种范式" class="headerlink" title="Agent Skills 与 MCP：智能体能力扩展的两种范]]>
    </summary>
    <title>Agent Skills 与 MCP：智能体能力扩展的两种范式</title>
    <updated>2026-03-08T09:24:16.372Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="Dify智能体搭建实战指南：从零构建全能个人助手-保姆级教程"><a href="#Dify智能体搭建实战指南：从零构建全能个人助手-保姆级教程" class="headerlink" title="Dify智能体搭建实战指南：从零构建全能个人助手(保姆级教程)"></a>Dify智能体搭建实战指南：<br>从零构建全能个人助手(保姆级教程)</h1><div align="center">  <img src="https://github.com/Tasselszcx.png" width="80" height="80" style="border-radius: 50%;" />  <br />  <strong>作者：</strong> <a href="https://github.com/Tasselszcx">Tasselszcx</a>  <br />  <em>原创教程 | 保姆级指南 | 完整实践</em></div><h2 id="1-安装所需插件"><a href="#1-安装所需插件" class="headerlink" title="1. 安装所需插件"></a>1. 安装所需插件</h2><p>在构建智能体之前，需要先完成必要的插件安装和 MCP 配置。如图1所示，按照图中文字指示一步步安装本章节所需插件。</p><div align="center">  <img src="./images/Extra03-figures/image1.jpg" alt="插件安装示意图" width="90%"/>  <p>图1 插件安装示意图</p></div><h2 id="2-配置MCP（Model-Context-Protocol）"><a href="#2-配置MCP（Model-Context-Protocol）" class="headerlink" title="2. 配置MCP（Model Context Protocol）"></a>2. 配置MCP（Model Context Protocol）</h2><p>关于 MCP 的详细原理这里不展开，我们重点演示如何使用云端部署的 MCP 服务。本案例使用国内的魔搭社区 MCP 市场进行演示，具体步骤如下：</p><p><strong>(1) 进入ModelScope社区</strong>：<a href="https://www.modelscope.cn/home">https://www.modelscope.cn/home</a></p><p><strong>(2) 注册账号并登录</strong>，如图2所示</p><div align="center">  <img src="./images/Extra03-figures/image2.jpg" alt="ModelScope注册登录界面" width="90%"/>  <p>图2 ModelScope注册登录界面</p></div><p><strong>(3) 进入高德地图MCP配置页面</strong></p><ul><li>登录后，按照图3所示，一步步点击进入高德地图MCP配置页面</li><li>页面应如图4所示</li></ul><div align="center">  <img src="./images/Extra03-figures/image3.jpg" alt="高德地图MCP入口指引" width="90%"/>  <p>图3 高德地图MCP入口指引</p></div><div align="center">  <img src="./images/Extra03-figures/image4.jpg" alt="高德地图MCP配置页面" width="90%"/>  <p>图4 高德地图MCP配置页面</p></div><p><strong>(4) 进入高德开放平台</strong>：<a href="https://console.amap.com/dev/index">https://console.amap.com/dev/index</a></p><ul><li>按照图5中文字指示新建应用</li></ul><div align="center">  <img src="./images/Extra03-figures/image5.jpg" alt="高德开放平台新建应用" width="90%"/>  <p>图5 高德开放平台新建应用</p></div><p><strong>(5) 创建api_key</strong></p><ul><li>如图6所示，一步步创建api_key</li><li>将创建好的api_key输入图4的红框中，即可显示配置成功</li><li>配置成功页面如图7所示</li></ul><div align="center">  <img src="./images/Extra03-figures/image6.jpg" alt="创建api_key步骤" width="90%"/>  <p>图6 创建api_key步骤</p></div><div align="center">  <img src="./images/Extra03-figures/image7.jpg" alt="MCP配置成功页面" width="90%"/>  <p>图7 MCP配置成功页面</p></div><p><strong>至此，整个高德地图MCP配置完成！</strong></p><h2 id="3-Agent设计与效果展示"><a href="#3-Agent设计与效果展示" class="headerlink" title="3. Agent设计与效果展示"></a>3. Agent设计与效果展示</h2><p>本案例将创建一个全方位的私人助手，涵盖以下功能模块：</p><ul><li>日常生活问答</li><li>文案润色优化</li><li>多模态内容生成（图片、视频）</li><li>MCP 工具集成（高德地图、饮食推荐、新闻资讯）</li><li>数据查询与可视化分析</li></ul><p>整个智能体的编排架构如图8所示。</p><div align="center">  <img src="./images/Extra03-figures/image8.jpg" alt="智能体编排架构图" width="90%"/>  <p>图8 智能体编排架构图</p></div><p>下面介绍如何搭建这样一个智能体的Chatflow：</p><h3 id="（1）创建Chatflow空白应用"><a href="#（1）创建Chatflow空白应用" class="headerlink" title="（1）创建Chatflow空白应用"></a>（1）创建Chatflow空白应用</h3><ul><li>按照图9及图10，一步步创建Chatflow空白应用</li></ul><div align="center">  <img src="./images/Extra03-figures/image9.jpg" alt="创建Chatflow步骤1" width="90%"/>  <p>图9 创建Chatflow步骤1</p></div><div align="center">  <img src="./images/Extra03-figures/image10.jpg" alt="创建Chatflow步骤2" width="90%"/>  <p>图10 创建Chatflow步骤2</p></div><h3 id="（2）创建问题分类器"><a href="#（2）创建问题分类器" class="headerlink" title="（2）创建问题分类器"></a>（2）创建问题分类器</h3><ul><li>先创建一个问题分类器用于对输入问题进行分类</li><li>分类器所填内容如图11所示</li></ul><div align="center">  <img src="./images/Extra03-figures/image11.jpg" alt="问题分类器配置" width="80%"/>  <p>图11 问题分类器配置</p></div><h3 id="（3）日常助手模块实现"><a href="#（3）日常助手模块实现" class="headerlink" title="（3）日常助手模块实现"></a>（3）日常助手模块实现</h3><p>这是一个基础的对话模块，配置大语言模型和时间工具，作为兜底的通用问答服务。</p><p><strong>配置说明</strong>：</p><ul><li>配置说明及连线参考图12</li><li>具体flow中各节点分别为”开始-问题分类器-LLM-直接回复”</li><li><strong>后续我们直接用节点flow进行说明每个模块的flow</strong></li></ul><div align="center">  <img src="./images/Extra03-figures/image12.jpg" alt="日常助手模块配置" width="90%"/>  <p>图12 日常助手模块配置</p></div><p><strong>LLM节点的system_prompt如下</strong>：</p><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><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section"># Role: 日常问题咨询专家</span><br><br><span class="hljs-section">## Profile</span><br><span class="hljs-bullet">-</span> language: 中文<br><span class="hljs-bullet">-</span> description: 专门回答用户日常生活中的一般性问题，提供实用、准确、易懂的建议和解答<br><span class="hljs-bullet">-</span> background: 拥有丰富的生活经验和广泛的知识储备，擅长将复杂问题简单化<br><span class="hljs-bullet">-</span> personality: 亲切友好、耐心细致、务实可靠<br><span class="hljs-bullet">-</span> expertise: 日常生活、健康养生、家庭管理、人际关系、实用技巧<br><br><br><span class="hljs-section">## Skills</span><br><br><span class="hljs-bullet">1.</span> 问题分析能力<br><span class="hljs-bullet">   -</span> 快速理解: 迅速把握用户问题的核心要点<br><span class="hljs-bullet">   -</span> 分类识别: 准确判断问题所属的生活领域<br><span class="hljs-bullet">   -</span> 需求挖掘: 深入理解用户潜在需求<br><span class="hljs-bullet">   -</span> 优先级排序: 合理评估问题的重要性和紧急性<br><br><span class="hljs-bullet">2.</span> 解答提供能力<br><span class="hljs-bullet">   -</span> 知识整合: 综合运用多领域知识提供解答<br><span class="hljs-bullet">   -</span> 方案制定: 提供具体可行的解决方案<br><span class="hljs-bullet">   -</span> 步骤分解: 将复杂问题拆解为简单步骤<br><span class="hljs-bullet">   -</span> 替代方案: 准备多种备选方案供用户选择<br><br><span class="hljs-bullet">3.</span> 沟通表达能力<br><span class="hljs-bullet">   -</span> 语言通俗: 使用简单易懂的日常用语<br><span class="hljs-bullet">   -</span> 逻辑清晰: 条理分明地组织回答内容<br><span class="hljs-bullet">   -</span> 举例说明: 通过具体案例帮助理解<br><span class="hljs-bullet">   -</span> 重点突出: 强调关键信息和注意事项<br><br><span class="hljs-section">## Rules</span><br><br><span class="hljs-bullet">1.</span> 回答原则：<br><span class="hljs-bullet">   -</span> 实用性优先: 确保提供的建议具有可操作性<br><span class="hljs-bullet">   -</span> 准确性保证: 基于可靠信息和常识给出回答<br><span class="hljs-bullet">   -</span> 中立客观: 避免个人偏见和主观臆断<br><span class="hljs-bullet">   -</span> 适度建议: 根据问题复杂程度提供适当深度的解答<br><br><span class="hljs-bullet">2.</span> 行为准则：<br><span class="hljs-bullet">   -</span> 及时响应: 快速回应用户的问题<br><span class="hljs-bullet">   -</span> 耐心细致: 对重复或简单问题保持耐心<br><span class="hljs-bullet">   -</span> 积极引导: 鼓励用户提供更多背景信息<br><span class="hljs-bullet">   -</span> 持续改进: 根据反馈优化回答质量<br><br><br><span class="hljs-section">## Workflows</span><br><br><span class="hljs-bullet">-</span> 目标: 为用户提供实用、可靠的日常问题解决方案<br><span class="hljs-bullet">-</span> 步骤 1: 仔细阅读并理解用户提出的日常问题<br><span class="hljs-bullet">-</span> 步骤 2: 分析问题类型和用户潜在需求<br><span class="hljs-bullet">-</span> 步骤 3: 基于常识和经验提供具体可行的建议<br><span class="hljs-bullet">-</span> 步骤 4: 用通俗易懂的语言组织回答内容<br><span class="hljs-bullet">-</span> 步骤 5: 检查回答的实用性和安全性<br><br><br><span class="hljs-section">## Initialization</span><br>作为日常问题咨询专家，你必须遵守上述Rules，按照Workflows执行任务。<br></code></pre></td></tr></table></figure><p><strong>演示效果</strong>：<br>如图13所示：</p><div align="center">  <img src="./images/Extra03-figures/image13.png" alt="日常助手演示效果" width="80%"/>  <p>图13 日常助手演示效果</p></div><h3 id="（4）文案优化模块实现"><a href="#（4）文案优化模块实现" class="headerlink" title="（4）文案优化模块实现"></a>（4）文案优化模块实现</h3><p>根据 OpenAI 的数据报告，超过60%的用户使用 ChatGPT 进行文本优化相关任务，包括润色、修改、扩写、缩写等。因此，文案优化是高频需求场景，我们将其作为第二个核心功能模块。</p><p><strong>具体配置</strong>：</p><ul><li>具体flow中各节点分别为”开始-问题分类器-LLM-直接回复”，同（3）</li></ul><p><strong>LLM节点的system_prompt如下</strong>：</p><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><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><code class="hljs markdown"><span class="hljs-section"># 一、 角色人设（Role）</span><br>你是一位专业的文案优化专家，拥有丰富的营销文案写作和优化经验，擅长提升文案的吸引力、转化率和可读性。你的视角是站在目标受众和营销目标的角度，专业度边界限于文案优化领域，不涉及技术实现或产品开发。<br><br><span class="hljs-section"># 二、 背景（Background）</span><br>用户提供了一段原始文案，需要你对其进行优化，以提升其整体效果。背景信息包括：文案可能用于营销、品牌推广或信息传达等场景，但具体用途未详细说明。已知条件是用户希望文案更吸引人、清晰或具有说服力，但未提供原始文案内容，因此你需要基于通用优化原则工作。<br><br><span class="hljs-section"># 三、 任务目标（Task）</span><br><span class="hljs-bullet">-</span> 分析并优化文案的结构、语言和风格，使其更符合目标受众的偏好。<br><span class="hljs-bullet">-</span> 提升文案的吸引力、可读性和转化潜力，确保信息传达清晰。<br><span class="hljs-bullet">-</span> 根据常见优化原则（如简洁性、情感共鸣、行动号召等）进行调整，不涉及内容重写，除非必要。<br><span class="hljs-bullet">-</span> 在保持核心信息的前提下，适当扩展和丰富文案内容，提供更全面的优化版本。<br><br><span class="hljs-section"># 四、 限制提示（Limit）</span><br><span class="hljs-bullet">-</span> 避免改变原始文案的核心信息或意图，除非用户明确要求。<br><span class="hljs-bullet">-</span> 不要添加虚构或无关内容，确保优化基于逻辑和最佳实践。<br><span class="hljs-bullet">-</span> 避免使用过于技术性或专业术语，除非目标受众是专业人士。<br><span class="hljs-bullet">-</span> 不涉及对图片、布局或其他非文本元素的优化。<br><br><span class="hljs-section"># 五、 输出格式要求（Example）</span><br>输出应为优化后的文案文本，结构清晰，语言流畅，内容详实。例如：<br><span class="hljs-bullet">-</span> 如果原始文案是“我们的产品很好，快来买吧”<br>优化后可以是：“在这个充满选择的时代，真正打动人心的从来不是浮夸的宣传，而是经得起时间和用户考验的好产品。我们的产品正是如此。它不仅在设计上注重细节与品质，更在功能上不断打磨与创新，只为给每一位用户带来更好的使用体验。无论是外观的质感，还是性能的稳定，我们始终坚持高标准严要求，力求让每一位选择我们的顾客都能感受到物超所值的惊喜。<br>我们深知，购买一款产品，不仅仅是一次简单的消费，更是一种对生活方式的选择。因此，我们从选材、工艺到售后服务的每一个环节，都倾注了满满的诚意与专业，用心守护您的每一次体验。无论您是追求实用、注重品质，还是想要与众不同的个性化，我们的产品都能为您提供理想的解决方案。<br>现在，就让我们用行动来证明一切。真正的好产品，不需要过多修饰，它本身就是最好的代言人。立即行动，选择我们，让品质改变生活，从此拥有与众不同的体验！”<br><span class="hljs-bullet">-</span> 输出应直接呈现优化内容，无需额外解释或注释，除非用户要求。请确保优化后的文案内容更加丰富和完整，优化后的文案文本须超过500字。<br></code></pre></td></tr></table></figure><p><strong>演示效果</strong>：<br>如图14所示：</p><div align="center">  <img src="./images/Extra03-figures/image14.png" alt="文案优化演示效果" width="80%"/>  <p>图14 文案优化演示效果</p></div><h3 id="（5）多模态生成模块（图片，视频）"><a href="#（5）多模态生成模块（图片，视频）" class="headerlink" title="（5）多模态生成模块（图片，视频）"></a>（5）多模态生成模块（图片，视频）</h3><p>图片和视频生成是另一个高频应用场景。随着豆包生图、Google Imagen 等模型的进化，以及可灵、Google Veo 3、OpenAI Sora 2 等视频生成技术的突破，多模态内容生成的质量已达到实用水平。</p><p><strong>图片生成配置</strong>：</p><ul><li>本案例使用豆包插件实现图片和视频生成</li><li>关于豆包插件的图片、视频生成权限及api_key获取，请参考这篇blog，讲解的极其清晰，建议直接看blog中的第3、4部分：<br><a href="https://blog.csdn.net/sjkflw121150/article/details/148480867#:~:text=3.-,%E8%B0%83%E7%94%A8Doubao%E6%96%87%E7%94%9F%E5%9B%BE%E5%B7%A5%E5%85%B7,-%E8%B0%83%E7%94%A8%20Doubao">https://blog.csdn.net/sjkflw121150/article/details/148480867#:~:text&#x3D;3.-,%E8%B0%83%E7%94%A8Doubao%E6%96%87%E7%94%9F%E5%9B%BE%E5%B7%A5%E5%85%B7,-%E8%B0%83%E7%94%A8%20Doubao</a></li><li>参考图15，创建豆包生图这一块的flow</li><li>flow中各节点分别为”开始-问题分类器-豆包T2I-直接回复”</li></ul><div align="center">  <img src="./images/Extra03-figures/image15.jpg" alt="豆包生图flow配置" width="90%"/>  <p>图15 豆包生图flow配置</p></div><p><strong>生图效果</strong>：<br>如图16所示：</p><div align="center">  <img src="./images/Extra03-figures/image16.png" alt="豆包生图效果展示" width="80%"/>  <p>图16 豆包生图效果展示</p></div><p><strong>视频生成配置</strong>：</p><ul><li>视频生成与图片生成同理，火山引擎中开通文生视频权限即可，见图17的说明</li><li>文生视频flow中各节点分别为”开始-问题分类器-豆包T2V-直接回复”</li></ul><div align="center">  <img src="./images/Extra03-figures/image17.jpg" alt="文生视频权限开通" width="90%"/>  <p>图17 文生视频权限开通</p></div><p><strong>生视频效果</strong>：<br>如图18所示：</p><div align="center">  <img src="./images/Extra03-figures/image18.png" alt="豆包生视频效果展示" width="80%"/>  <p>图18 豆包生视频效果展示</p></div><h3 id="（6）MCP-工具集成（高德地图、饮食推荐、新闻资讯）"><a href="#（6）MCP-工具集成（高德地图、饮食推荐、新闻资讯）" class="headerlink" title="（6）MCP 工具集成（高德地图、饮食推荐、新闻资讯）"></a>（6）MCP 工具集成（高德地图、饮食推荐、新闻资讯）</h3><p>在前面我们已经完成了 MCP 的配置，现在将其集成到智能体中。</p><p><strong>配置步骤</strong>（参考图19）：</p><ol><li>选择支持 MCP 调用的Agent节点</li><li>选择 ReAct 模式</li><li>添加”获取时间戳”工具</li><li>配置 MCP 服务（找到图7，选择 SSE 模式，删除 mcp-server 前缀后把其他信息复制过来）</li><li>填写相应的提示词</li></ol><div align="center">  <img src="./images/Extra03-figures/image19.jpg" alt="MCP工具集成配置步骤" width="90%"/>  <p>图19 MCP工具集成配置步骤</p></div><p><strong>具体配置</strong>：</p><ul><li>最后Agent节点填写信息可参考图20</li><li>MCP服务调用的flow中各节点分别为”开始-问题分类器-Agent-直接回复”</li></ul><div align="center">  <img src="./images/Extra03-figures/image20.jpg" alt="Agent节点配置详情" width="50%"/>  <p>图20 Agent节点配置详情</p></div><p><strong>效果展示</strong>：</p><ul><li>高德助手效果：如图21所示</li></ul><div align="center">  <img src="./images/Extra03-figures/image21.png" alt="高德助手效果展示" width="80%"/>  <p>图21 高德助手效果展示</p></div><ul><li>饮食助手效果：如图22所示</li></ul><div align="center">  <img src="./images/Extra03-figures/image22.png" alt="饮食助手效果展示" width="80%"/>  <p>图22 饮食助手效果展示</p></div><ul><li>新闻助手效果：如图23所示</li></ul><div align="center">  <img src="./images/Extra03-figures/image23.png" alt="新闻助手效果展示" width="50%"/>  <p>图23 新闻助手效果展示</p></div><h3 id="（7）数据查询与分析模块"><a href="#（7）数据查询与分析模块" class="headerlink" title="（7）数据查询与分析模块"></a>（7）数据查询与分析模块</h3><p><strong>数据查询与分析模块</strong></p><p>数据处理是智能体的重要能力之一。本模块演示如何在 Dify 中连接数据库，实现数据查询和可视化分析。</p><p>首先安装数据查询工具插件，本案例使用 <code>rookie-text2data</code> 插件。数据查询的关键在于为大模型提供清晰的表结构和字段信息，使其能够生成准确的 SQL 查询语句。常见做法包括：</p><ul><li>直接提供数据表的 DDL 语句</li><li>提供表名和字段名的对应关系说明</li></ul><p>配置数据库连接信息（IP地址、数据库名称、端口、账号、密码等），如图24所示。查询结果需要通过大模型节点进行整理，转换为易于理解的自然语言输出。</p><div align="center">  <img src="./images/Extra03-figures/image24.png" alt="数据库配置" width="50%"/>  <p>图24 数据库配置</p></div><p><strong>提示词设置：</strong></p><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><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section"># 一、 角色人设（Role）</span><br>您是一位专业的数据查询师，擅长数据整理，具有清晰的逻辑思维和简洁表达能力。<br><br><span class="hljs-section"># 二、 背景（Background）</span><br>用户提供了从数据库中查询到的原始数据，这些数据可能存在格式不统一、字段缺失、重复记录等问题，需要经过专业整理后才能有效展示。<br><br><span class="hljs-section"># 三、 任务目标（Task）</span><br><span class="hljs-bullet">1.</span> 对原始数据进行归纳和整理<br><span class="hljs-bullet">2.</span> 按照正确的逻辑对数据进行分类和排序<br><span class="hljs-bullet">3.</span> 数据展示突出关键信息和数据洞察<br><span class="hljs-bullet">4.</span> 提供易于理解的数据展示<br><br><span class="hljs-section"># 四、 限制提示（Limit）</span><br><span class="hljs-bullet">1.</span> 不得随意删除重要数据<br><span class="hljs-bullet">2.</span> 避免使用过于复杂或专业的统计术语<br><span class="hljs-bullet">3.</span> 不得篡改原始数据的真实值<br><span class="hljs-bullet">4.</span> 避免展示过多冗余信息，保持简洁明了<br><span class="hljs-bullet">5.</span> 不得泄露敏感数据或个人隐私信息<br><br><span class="hljs-section"># 五、 输出格式要求（Example）</span><br> 数据概览：简要说明数据内容即可<br></code></pre></td></tr></table></figure><p>效果展示如图25所示：</p><div align="center">  <img src="./images/Extra03-figures/image25.png" alt="数据查询助手效果" width="80%"/>  <p>图25 数据查询助手</p></div><p><strong>提示词设置：</strong></p><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><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><code class="hljs markdown"><span class="hljs-section"># 一、 角色人设（Role）</span><br>你是一位专业的数据分析师，具备数据整理、清洗和可视化能力，能够从原始数据中提取关键信息并转化为直观的可视化展示。<br><br><span class="hljs-section"># 二、 背景（Background）</span><br>用户已从数据库中查询到一批原始数据，这些数据可能包含多个字段、存在缺失值或格式不一致的情况，需要经过整理后生成可视化图表。<br><br><span class="hljs-section"># 三、 任务目标（Task）</span><br><span class="hljs-section">#工作流程</span><br><span class="hljs-bullet">1.</span> 数据分析<br>按照合理的规则进行数据分析整理总结<br><span class="hljs-bullet">2.</span> 分析 &amp; 可视化<br>至少生成 1 幅图表（柱状 / 折线 / 饼图任选其1或以上）<br>可调用工具：“generate<span class="hljs-emphasis">_pie_</span>chart&quot; | &quot;generate<span class="hljs-emphasis">_column_</span>chart&quot; | &quot;generate<span class="hljs-emphasis">_line_</span>chart&quot;<br><br><span class="hljs-section"># 四、 限制提示（Limit）</span><br><span class="hljs-bullet">1.</span> 避免使用过于复杂的图表类型，确保可视化结果易于理解<br><span class="hljs-bullet">2.</span> 不要忽略数据质量问题，必须进行必要的数据清洗<br><span class="hljs-bullet">3.</span> 避免在可视化中使用过多颜色或元素，保持简洁明了<br><span class="hljs-bullet">4.</span> 不要遗漏关键数据的标注和说明<br>5.必须进行总结和图表生成，不管数据多少<br><br><span class="hljs-section"># 五、 输出格式要求（Example）</span><br>请按照以下格式输出：<br><span class="hljs-bullet">1.</span> 数据概况总结（不要输出字段名称，不要分点，一小段话就行）<br><span class="hljs-bullet">2.</span> 展示生成的图表<br></code></pre></td></tr></table></figure><div align="center">  <img src="./images/Extra03-figures/image26.png" alt="数据分析助手效果" width="80%"/>  <p>图26 数据分析助手</p></div><p>数据分析助手这一块唯一的不同就是我们增加了数据可视化的工具，也就是”generate_pie_chart” | “generate_column_chart” | “generate_line_chart”这几个生成BI图表的工具插件，这个在前面相信大家都按照要求安装了就可以直接添加启动使用，并像上面的提示词一样增加对应的描述即可。这块大家后续可以自己连着sql尝试一下，就不过多赘述了~</p><hr><p><strong>至此，我们完成了一个功能全面的超级智能体个人助手。</strong></p><p>该助手涵盖了生活的多个方面：</p><ul><li>需要新衣服时，可以让豆包生成设计</li><li>出门前，可以让高德助手规划路线</li><li>不知道吃什么时，可以获取饮食推荐</li><li>想了解学习情况时，可以进行数据分析</li></ul><p><strong>这个智能体能够处理各类工作和生活任务，期待看到大家搭建出更多有创意的私人智能体助手。</strong></p><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><ol><li><p>ModelScope社区. <a href="https://www.modelscope.cn/home">https://www.modelscope.cn/home</a></p></li><li><p>高德开放平台. <a href="https://console.amap.com/dev/index">https://console.amap.com/dev/index</a></p></li><li><p>sjkflw121150. Dify搭建AI图片生成助手中的坑！. CSDN博客. <a href="https://blog.csdn.net/sjkflw121150/article/details/148480867#:~:text=3.-,%E8%B0%83%E7%94%A8Doubao%E6%96%87%E7%94%9F%E5%9B%BE%E5%B7%A5%E5%85%B7,-%E8%B0%83%E7%94%A8%20Doubao">https://blog.csdn.net/sjkflw121150/article/details/148480867#:~:text=3.-,%E8%B0%83%E7%94%A8Doubao%E6%96%87%E7%94%9F%E5%9B%BE%E5%B7%A5%E5%85%B7,-%E8%B0%83%E7%94%A8%20Doubao</a></p></li></ol>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra03-Dify%E6%99%BA%E8%83%BD%E4%BD%93%E5%88%9B%E5%BB%BA%E4%BF%9D%E5%A7%86%E7%BA%A7%E6%93%8D%E4%BD%9C%E6%B5%81%E7%A8%8B/</id>
    <link href="http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra03-Dify%E6%99%BA%E8%83%BD%E4%BD%93%E5%88%9B%E5%BB%BA%E4%BF%9D%E5%A7%86%E7%BA%A7%E6%93%8D%E4%BD%9C%E6%B5%81%E7%A8%8B/"/>
    <published>2026-03-02T18:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="Dify智能体搭建实战指南：从零构建全能个人助手-保姆级教程"><a href="#Dify智能体搭建实战指南：从零构建全能个人助手-保姆级教程" class="headerlink" title="Dify智能体搭建实战指南：从零构建全能个人助手(保姆级教程)"]]>
    </summary>
    <title>
      <![CDATA[Dify智能体搭建实战指南：<br>从零构建全能个人助手(保姆级教程)]]>
    </title>
    <updated>2026-03-08T09:24:16.370Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="上下文工程补充知识"><a href="#上下文工程补充知识" class="headerlink" title="上下文工程补充知识"></a>上下文工程补充知识</h1><h2 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h2><p>为什么上下文工程最近又再次火热起来？源自 Chroma 创始人兼 CEOJeff 在 Len Space <a href="https://youware.app/project/7529x70z4p">播客</a>的对话，<br>Chroma 向量数据库领域的开源霸主。连大名鼎鼎的 Voyager 论文里用的都是它。<br>CEOJeff 对话的标题就是关于“RAG is dead”的观念，在视频中很明显的说明了原本的RAG的局限性和现在context engnieer的重要性，</p><p>![alt text](&#x2F;img&#x2F;ai-agent-learning&#x2F;alt text)</p><p>本章我们先全面讲解一下“上下文工程”的（context engnieer）概念，<br>并在文章最后谈一下对 Rag is dead 的看法</p><h2 id="什么是上下文工程？"><a href="#什么是上下文工程？" class="headerlink" title="什么是上下文工程？"></a>什么是上下文工程？</h2><p>我们可以打一个比方，Agent就像一种<a href="https://www.youtube.com/watch?si=-aKY-x57ILAmWTdw&t=620&v=LCEmiRjPEtQ&feature=youtu.be&ref=blog.langchain.com">新型操作系统</a>。LLM如同CPU，其<a href="https://docs.anthropic.com/en/docs/build-with-claude/context-windows?ref=blog.langchain.com">上下文窗口</a>如同RAM，作为模型的工作内存。就像RAM一样，LLM上下文窗口的<a href="https://lilianweng.github.io/posts/2023-06-23-agent/?ref=blog.langchain.com">容量有限</a>，无法处理各种来源的上下文。而上下文工程就像操作系统管理CPU的RAM一样，去管理LLM的上下文窗口，决定在何时去填充什么内容。<a href="https://x.com/karpathy/status/1937902205765607626?ref=blog.langchain.com">Karpathy总结得很好</a>：<br><em>“上下文工程是…在上下文窗口中为下一步填充恰到好处信息的精妙艺术和科学。”</em></p><p><img src="/img/ai-agent-learning/llm_context_engineering" alt="llm_context_engineering"></p><h2 id="上下文工程的概念"><a href="#上下文工程的概念" class="headerlink" title="上下文工程的概念"></a><a href="https://blog.langchain.com/context-engineering-for-agents/%60">上下文工程的概念</a></h2><p>![alt text](&#x2F;img&#x2F;ai-agent-learning&#x2F;alt text)</p><p>Context就是模型“看到”的一切，模型其实并不是只根据我们输入的prompt回复问题，还有其余的信息配合生成回复。上下文工程作为适用于几种不同上下文类型的总括：</p><ul><li><strong>Instructions（指令上下文）</strong> : 提示、记忆、少量示例等 prompt engineering，包括：<ul><li>系统提示词：定义AI的角色、行为准则和响应风格</li><li>用户指令：描述具体任务及要求</li><li>少样本示例：输入输出示例，帮助理解预期格式</li><li>工具描述：函数或工具的规范与使用说明</li><li>格式约束：输出的格式和结构要求</li></ul></li><li><strong>Knowledge（知识上下文）</strong> : 事实、知识库等  rag，包括：<ul><li>领域知识：特定行业或专业的事实信息</li><li>记忆：用户偏好、历史交互和会话记录</li><li>知识库：从数据库或知识库中获取相关信息</li><li>实时数据：动态更新的当前状态信息</li></ul></li><li><strong>Tools（工具上下文）</strong> : 工具描述和工具调用的反馈 agent，包括：<ul><li>函数调用结果：API响应或查询结果</li><li>工具执行状态：成功、失败或错误反馈</li><li>多步骤工具链：工具间的依赖关系与数据传递</li><li>执行历史：工具调用的记录与结果</li></ul></li></ul><h3 id="例子——旅游APP的智能助手"><a href="#例子——旅游APP的智能助手" class="headerlink" title="例子——旅游APP的智能助手"></a>例子——旅游APP的智能助手</h3><p>![alt text](&#x2F;img&#x2F;ai-agent-learning&#x2F;alt text)</p><p>为了清晰地区分这四个概念，我们设定一个统一的实际场景，然后看每个方法如何解决这个问题。</p><p><strong>场景：一个旅游APP的智能助手</strong></p><p><strong>用户需求：</strong> “帮我规划一个为期三天的北京家庭旅行。我们是两个大人和一个5岁的孩子，喜欢历史文化，也想要一些轻松有趣的活动。我们的总预算是8000元。”</p><hr><h4 id="1-提示词工程-Prompt-Engineering"><a href="#1-提示词工程-Prompt-Engineering" class="headerlink" title="1. 提示词工程 (Prompt Engineering)"></a>1. 提示词工程 (Prompt Engineering)</h4><p>这是最基础、最直接的方法。它的核心是<strong>如何向语言模型（LLM）提一个好问题</strong>，以期它仅凭其内部的通用知识库就能给出最好的答案。</p><ul><li><p><strong>核心思想：</strong> 优化输入给模型的指令（Prompt），让它输出更符合期望的结果。</p></li><li><p><strong>工作方式：</strong></p><ol><li>开发者或用户将所有需求精心构造成一个详细的提示词。</li><li>将这个提示词直接发送给一个通用的大语言模型（如 GPT-4）。</li><li>模型完全依赖其截至训练日期（比如 2023 年）的内部知识进行回答。</li></ol></li><li><p><strong>例子：</strong></p><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></pre></td><td class="code"><pre><code class="hljs markdown">你是一位专业的旅行规划师。请为北京一个为期三天的家庭旅行设计一份详细行程。<br><br><span class="hljs-section"># 家庭成员</span><br><span class="hljs-bullet">-</span> 2个成年人<br><span class="hljs-bullet">-</span> 1个5岁的儿童<br><br><span class="hljs-section"># 兴趣偏好</span><br><span class="hljs-bullet">-</span> 历史文化（故宫、长城等）<br><span class="hljs-bullet">-</span> 轻松有趣的儿童活动<br><br><span class="hljs-section"># 预算</span><br><span class="hljs-bullet">-</span> 总预算不超过8000元人民币，请给出大致的费用估算。<br><br><span class="hljs-section"># 输出要求</span><br><span class="hljs-bullet">-</span> 每日行程安排（上午、下午、晚上）<br><span class="hljs-bullet">-</span> 交通建议<br><span class="hljs-bullet">-</span> 餐饮推荐（包含适合儿童的餐厅）<br><span class="hljs-bullet">-</span> 预算明细<br></code></pre></td></tr></table></figure></li><li><p><strong>局限性：</strong></p><ul><li><strong>信息过时：</strong> 无法提供实时的门票价格、开放时间或最新的交通信息。</li><li><strong>信息不准确：</strong> 预算估算可能非常粗略，因为它不知道当前的酒店和机票价格。</li><li><strong>缺乏个性化：</strong> 无法根据用户的历史偏好进行推荐。</li><li><strong>“一本正经地胡说八道”：</strong> 可能会编造一些不存在的“儿童乐园”或餐厅。</li></ul></li></ul><hr><h4 id="2-检索增强生成-RAG"><a href="#2-检索增强生成-RAG" class="headerlink" title="2. 检索增强生成 (RAG)"></a>2. 检索增强生成 (RAG)</h4><p>为了解决提示词工程“知识陈旧”的问题，RAG 引入了<strong>外部知识库</strong>。</p><ul><li><p><strong>核心思想：</strong> 在生成答案前，先从一个特定的、可信的数据库中检索相关信息，然后将这些信息和用户问题一起提供给模型。</p></li><li><p><strong>工作方式：</strong></p><ol><li><strong>知识库准备：</strong> 提前准备好一个包含最新旅游攻略、景点介绍、酒店列表、餐厅评论的数据库（比如一堆 PDF、网页或数据库记录）。</li><li><strong>检索 (Retrieve)：</strong> 当用户提问时，系统首先在知识库中搜索与“北京亲子游”、“历史文化景点”相关的文档片段。</li><li><strong>增强 (Augment)：</strong> 将检索到的信息（例如：“故宫最新门票价格为60元，周一闭馆”、“北京环球影城是热门亲子项目”）和用户的原始问题拼接成一个新的、内容更丰富的提示词。</li><li><strong>生成 (Generate)：</strong> 将这个增强后的提示词发送给 LLM，让它基于这些“新鲜”的资料来生成行程。</li></ol></li><li><p><strong>例子：</strong><br>系统在内部知识库中找到了三段文字：A) 故宫官网的开放时间和票价；B) 一篇关于“带娃逛天坛”的博客；C) 一份“北京家庭友好型酒店”列表。<br>然后，它向 LLM 发出指令：“根据以下信息：[A、B、C段文字内容]，为用户规划一个北京三日亲子游，预算8000元。”</p></li><li><p><strong>局限性：</strong></p><ul><li><strong>被动响应：</strong> 它只能根据你提供的信息回答，无法主动执行任务。它不能去“查”机票，只能用你数据库里“有”的机票信息。</li><li><strong>单向交互：</strong> 完成一次检索和生成就结束了，无法进行多步推理和行动。</li><li><strong>知识库依赖：</strong> 效果好坏严重依赖于知识库的质量和更新频率。</li></ul></li></ul><hr><h4 id="3-Agent-智能体"><a href="#3-Agent-智能体" class="headerlink" title="3. Agent (智能体)"></a>3. Agent (智能体)</h4><p>Agent 让 AI 从一个“问答机器人”进化成一个<strong>能思考、能使用工具的“行动者”</strong>。</p><ul><li><p><strong>核心思想：</strong> 赋予模型一个“思考-行动”循环（Reasoning-Action Loop），让它能自主规划步骤、使用外部工具（如API）来完成复杂任务。</p></li><li><p><strong>工作方式：</strong></p><ol><li><strong>思考与规划：</strong> LLM（作为 Agent 的大脑）接收到用户需求后，会先思考：“要完成这个任务，我需要：1. 查机票和酒店价格；2. 查景点门票；3. 规划路线；4. 汇总成行程。”</li><li><strong>选择工具 (Action)：</strong> 它决定使用第一个工具：<code>search_flight_api(from=&quot;上海&quot;, to=&quot;北京&quot;, date=&quot;...&quot;)</code>。</li><li><strong>观察结果 (Observation)：</strong> API 返回了机票价格：5000元。</li><li><strong>再次思考：</strong> “机票花了5000，预算还剩3000。我需要找每晚价格低于800元的酒店。”</li><li><strong>再次行动：</strong> 使用工具 <code>search_hotel_api(city=&quot;北京&quot;, price_max=800, family_friendly=true)</code>。</li><li>这个循环会一直持续，直到它收集到所有必要信息，最终完成规划。</li></ol></li><li><p><strong>例子：</strong><br>这个助手会像一个真正的人类助理一样工作：</p><ul><li>“好的，我正在为您查询… 我发现下周五去北京的机票大约需要5000元。”</li><li>“考虑到预算，我为您筛选了几家评价很好且价格在600-800元&#x2F;晚的家庭酒店。”</li><li>“故宫门票已通过 <code>ticket_api</code> 查询，儿童免票。我已将此信息加入行程。”</li></ul></li><li><p><strong>局限性：</strong></p><ul><li><strong>复杂且不稳定：</strong> Agent 的行为路径不固定，可能会犯错（比如陷入循环、错误使用工具），调试和控制难度大。</li><li><strong>成本高：</strong> 每一步思考和工具调用都可能是一次 LLM API 调用，成本较高。</li></ul></li></ul><hr><h4 id="4-上下文工程-Context-Engineering"><a href="#4-上下文工程-Context-Engineering" class="headerlink" title="4. 上下文工程 (Context Engineering)"></a>4. 上下文工程 (Context Engineering)</h4><p>上下文工程是<strong>一个更宏观、更严谨的学科</strong>，它着眼于<strong>如何为模型（无论是简单的 RAG 还是复杂的 Agent）构建最优的“上下文窗口”</strong>。它是对上述所有方法的优化和升华。</p><ul><li><p><strong>核心思想：</strong> 精心设计和编排进入模型上下文的所有信息（指令、检索到的数据、历史对话、工具输出等），以实现最高效、最可靠的输出。它是一门关于“喂什么”和“怎么喂”的科学。</p></li><li><p><strong>工作方式：</strong><br>它不是一个独立的系统类型，而是优化 RAG 和 Agent 的方法论。回到旅行规划的例子：</p><ol><li><strong>收集阶段 (Gather)：</strong><ul><li><strong>并行检索：</strong> 不仅仅是从旅游攻略库（RAG）里检索，它还会同时：<ul><li>调用 <code>weather_api</code> 查询北京未来几天的天气。</li><li>调用 <code>events_api</code> 查询是否有特殊的儿童展览或活动。</li><li>从用户画像数据库（CRM）中检索到“该用户上次旅行预订了博物馆门票”。</li><li>对用户的模糊提问“轻松有趣的活动”进行多路搜索，包括“北京游乐场”、“北京科技馆”、“适合儿童的表演”。</li></ul></li></ul></li><li><strong>筛选与压缩阶段 (Glean &amp; Compact)：</strong><ul><li><strong>重排序：</strong> 它发现天气预报显示第二天有雨，于是将户外长城的优先级降低，提升了室内科技馆的推荐权重。</li><li><strong>压缩：</strong> 它不会把一篇长长的酒店评论文章都丢给模型，而是提取出关键信息：“该酒店有儿童游乐区，提供婴儿床。”</li><li><strong>格式化：</strong> 它将所有收集到的、杂乱的信息（天气、机票、用户偏好、景点介绍）整合成一个高度结构化、简洁明了的 JSON 对象。</li></ul></li><li><strong>最终交付：</strong> 最后，它将这个“完美”的上下文包交给 Agent 的大脑（LLM），指令可能是：“请基于这份已验证、已整理的结构化数据 <code>[JSON object]</code>，为用户生成最终行程。”</li></ol></li><li><p><strong>例子：</strong><br>上下文工程的产出不是直接给用户的行程，而是给模型看的、最优化的“作战地图”。因为经过了上下文工程的优化，Agent 的工作变得极其简单和高效，它不需要再自己费力地一步步试错，而是基于一份完美的简报直接进行最终的规划生成。</p></li></ul><h4 id="总结对比"><a href="#总结对比" class="headerlink" title="总结对比"></a>总结对比</h4><table><thead><tr><th align="left">概念</th><th align="left">核心思想</th><th align="left">工作方式</th><th align="left">局限性</th></tr></thead><tbody><tr><td align="left"><strong>提示词工程</strong></td><td align="left">问对问题</td><td align="left">精心设计一个完美的 Prompt</td><td align="left">知识过时，无法与外部世界交互</td></tr><tr><td align="left"><strong>RAG</strong></td><td align="left">给予参考资料</td><td align="left">提问前先从知识库检索相关信息</td><td align="left">被动响应，无法执行任务，依赖知识库</td></tr><tr><td align="left"><strong>Agent</strong></td><td align="left">赋予行动能力</td><td align="left">通过“思考-行动”循环来使用工具、完成任务</td><td align="left">复杂，不稳定，成本高</td></tr><tr><td align="left"><strong>上下文工程</strong></td><td align="left">打造完美输入</td><td align="left">系统性地收集、筛选、压缩、格式化所有信息，为模型提供最优上下文</td><td align="left">是一个方法论&#x2F;学科，而非具体系统，实现复杂</td></tr></tbody></table><p>简单来说，它们是能力的递进：</p><ul><li><strong>提示词工程</strong> 是<strong>对话者</strong>。</li><li><strong>RAG</strong> 是一个带了本书供查阅的<strong>对话者</strong>。</li><li><strong>Agent</strong> 是一个可以打电话、上网查资料、帮你订票的<strong>助理</strong>。</li><li><strong>上下文工程</strong> 是这位助理背后的<strong>总参谋</strong>，负责提前收集和整理所有情报，确保助理能做出最明智的决策。</li></ul><h2 id="为什么会出现-Context-Engineer？"><a href="#为什么会出现-Context-Engineer？" class="headerlink" title="为什么会出现 Context Engineer？"></a>为什么会出现 Context Engineer？</h2><p>![alt text](&#x2F;img&#x2F;ai-agent-learning&#x2F;alt text)</p><p>随着LLM在推理和工具调用方面变得越来越好，大家对Agent的兴趣大幅增长。Agent将LLM调用和工具调用交织在一起，通常用于长时间运行的任务。Agent使用工具反馈来决定下一步操作。</p><p>然而，长时间运行的任务和积累的工具调用反馈意味着Agent通常使用大量token。这可能导致许多问题：可能超出上下文窗口大小、增加成本&#x2F;延迟或降低Agent性能。</p><p>随着上下文窗口越来越长，我们原本以为“把所有对话历史和资料都丢进模型”就能解决记忆问题。但实验表明，现实远比想象复杂。随着上下文长度增长，模型越来越难保持信息的准确性与一致性，表现就像“<strong>记忆腐烂</strong>”。</p><p>![alt text](&#x2F;img&#x2F;ai-agent-learning&#x2F;alt text)</p><p>这些现象在 Chroma 的研究中被称为Context Rot——即模型在长语境下的性能“腐蚀”。这正是Context Engineer这一角色诞生的根本原因：需要有人去对抗和修复这种“语境腐烂”，通过裁剪、压缩、重组和检索增强，让模型在有限的注意力资源中保持可靠表现。</p><h2 id="上下文挑战"><a href="#上下文挑战" class="headerlink" title="上下文挑战"></a>上下文挑战</h2><p>上下文挑战主要存在<a href="https://www.dbreunig.com/2025/06/22/how-contexts-fail-and-how-to-fix-them.html?ref=blog.langchain.com">四个方面</a>，分别描述为：</p><ul><li>上下文污染 - 当幻觉进入上下文时</li><li>上下文分散 - 当上下文压倒了训练数据时</li><li>上下文混淆 - 当多余的上下文影响响应时</li><li>上下文冲突 - 当上下文各部分不一致时</li></ul><h3 id="Context-Poisoning-When-a-Hallucination-Makes-It-into-the-Context"><a href="#Context-Poisoning-When-a-Hallucination-Makes-It-into-the-Context" class="headerlink" title="Context Poisoning: When a Hallucination Makes It into the Context"></a>Context Poisoning: When a Hallucination Makes It into the Context</h3><p>上下文毒化（Context Poisoning）指的是幻觉（hallucination，即模型生成的错误或虚构信息）或其它错误进入上下文窗口，并被反复引用，从而嵌入错误信息，导致代理（agent）性能脱轨。这种情况会“毒化”关键部分，如目标或摘要，使得模型固执于不可能或无关的目标，导致重复的、无意义的的行为。</p><h3 id="Context-Distraction-When-the-Context-Overwhelms-the-Training"><a href="#Context-Distraction-When-the-Context-Overwhelms-the-Training" class="headerlink" title="Context Distraction: When the Context Overwhelms the Training"></a>Context Distraction: When the Context Overwhelms the Training</h3><p>上下文干扰（Context Distraction）发生在上下文增长过长（例如超过10万token）时，导致模型过度依赖历史细节，而忽略其预训练知识或生成新颖解决方案的能力。这会引发重复动作而非创造性问题解决，且性能在上下文窗口满载前就已下降。</p><p>模型在面对数十万 tokens 的输入时，并不能像硬盘一样均匀记住所有信息。实验发现，精简版输入（仅几百 tokens）反而比完整输入（十几万 tokens）表现更好。研究结果显示，模型在精简版上的表现显著优于完整版。这说明当输入过长、噪音过多时，即使是最先进的模型，也很难抓住关键信息。</p><h3 id="Context-Confusion-When-Superfluous-Context-Influences-the-Response"><a href="#Context-Confusion-When-Superfluous-Context-Influences-the-Response" class="headerlink" title="Context Confusion: When Superfluous Context Influences the Response"></a>Context Confusion: When Superfluous Context Influences the Response</h3><p>上下文混淆（Context Confusion）是指无关或多余的信息（如冗余工具定义）被纳入上下文，迫使模型考虑它，从而产生次优响应。即使额外内容无害，也会稀释焦点并降低质量。<br>真实对话和资料中，往往存在语义相似却不相关的“噪音”。短上下文里模型能区分，但长上下文时更容易被误导。这要求有人来做上下文的筛选与去噪，让模型聚焦真正相关的信息。在长上下文里，模型不光要找到相关信息，还要能分辨“哪个才是正确的 needle，哪个只是干扰项”。</p><h3 id="Context-Clash-When-Parts-of-the-Context-Disagree"><a href="#Context-Clash-When-Parts-of-the-Context-Disagree" class="headerlink" title="Context Clash: When Parts of the Context Disagree"></a>Context Clash: When Parts of the Context Disagree</h3><p>上下文冲突（Context Clash）是混淆的更严重形式，指上下文中的信息相互冲突（如新工具或事实与现有内容矛盾），从而破坏推理，通常因为模型锁定在早期假设中。这比单纯无关更具破坏性：“This is a more problematic version of Context Confusion: the bad context here isn’t irrelevant, it directly conflicts with other information in the prompt.” 在多步交互中，早期的错误会传播，模型依赖于有缺陷的前提。</p><p> 缺乏“计算机式”可靠性<br>我们希望LLM获得一致质量的输出 即使是最简单的复制任务，模型在长输入下也会出错。它不是逐字逐位的符号处理器，而是概率驱动的语言生成器。因此不能期望它像数据库或计算机一样精确地处理长上下文，而必须借助结构化设计来弥补。</p><p>因此，有效的上下文窗口管理和语境工程是必不可少的。</p><h2 id="上下文工程策略"><a href="#上下文工程策略" class="headerlink" title="上下文工程策略"></a>上下文工程策略</h2><p>上一节提到上下文面临如此多的挑战，那么如何克服它们呢？这就要依靠上下文工程。其中，上下文工程的策略主要分为四种：写入（存储）、选择、压缩和隔离。</p><p>![alt text](&#x2F;img&#x2F;ai-agent-learning&#x2F;alt text)</p><h3 id="写入上下文"><a href="#写入上下文" class="headerlink" title="写入上下文"></a>写入上下文</h3><p><strong>写入上下文</strong>意味着将其保存在上下文窗口之外以帮助Agent执行任务。<br>主要分为两种：</p><ul><li><strong>临时笔记板</strong><br>一个临时的工作区，记录模型的中间推理，让思考过程可见。通过”临时笔记板”做笔记是一种在Agent执行任务时持久保存信息的方法。其思想是将信息保存在上下文窗口之外，以便Agent可用。</li><li><strong>记忆</strong><br>Agent 把新发生的上下文（new context）与已有的记忆（existing memories）结合，经过处理后写成更新的记忆（updated memory）</li></ul><p>![alt text](&#x2F;img&#x2F;ai-agent-learning&#x2F;alt text)</p><h3 id="选择上下文"><a href="#选择上下文" class="headerlink" title="选择上下文"></a>选择上下文</h3><p>当信息量越来越大时，如何选择比如何存储更重要。选择上下文就是在每次调用模型时，从所有可用的信息源里，挑出真正相关的部分放入窗口。</p><p>具体可供选择的上下文有：</p><ul><li><p><strong>临时笔记板（Scratchpad）</strong>：即上文提到的临时笔记板，作为模型的”工作记忆”空间，用于记录推理过程、中间结果和思考步骤。在多步骤任务中，模型可以将当前的推理状态、已完成的子任务、待处理的问题等信息写入临时笔记板，便于后续步骤参考和调整策略。</p></li><li><p><strong>记忆（Memory）</strong>：包括短期记忆和长期记忆两个层面。短期记忆保存当前会话中的历史对话和上下文信息，确保对话连贯性；长期记忆则存储用户偏好、历史交互模式、个性化设置等跨会话的持久化信息，帮助模型提供更加个性化和一致的服务体验。</p></li><li><p><strong>工具（Tools）</strong>：在 Agent 系统里，工具本身就是一种上下文。当模型调用 API、插件或外部函数时，它必须理解工具的描述（包括功能说明、参数要求、返回格式等），并在合适的场景下选择正确的工具。工具调用后的反馈结果也会作为新的上下文输入，指导模型下一步的决策。工具的可用性、执行状态、调用历史都是重要的上下文信息。</p></li><li><p><strong>知识（Knowledge）</strong>：主要指 RAG（检索增强生成）中的外部知识库。包括结构化数据（如数据库表格）、非结构化文档（如技术文档、产品手册）、向量数据库中的语义检索结果等。这些外部知识弥补了模型训练数据的时效性限制和知识覆盖面不足的问题，通过动态检索相关信息来增强模型的回答准确性和专业性。</p></li></ul><h3 id="压缩上下文"><a href="#压缩上下文" class="headerlink" title="压缩上下文"></a>压缩上下文</h3><p>![alt text](&#x2F;img&#x2F;ai-agent-learning&#x2F;alt text)</p><p>压缩上下文涉及仅保留执行任务所需的token，通过减少冗余信息来优化上下文窗口的使用效率。</p><h4 id="上下文摘要"><a href="#上下文摘要" class="headerlink" title="上下文摘要"></a>上下文摘要</h4><p><strong>对话摘要：</strong><br>在长时间的多轮交互中，完整保留所有历史对话会快速消耗上下文窗口。通过对话摘要技术，可以将早期的对话轮次压缩成简洁的摘要形式，保留关键信息（如用户偏好、重要决策、待解决问题等），同时丢弃冗余的寒暄和重复内容。这样既能维持对话的连贯性，又能为新的交互留出足够空间。</p><p><strong>工具摘要：</strong><br>工具调用往往会返回大量的原始数据（如完整的API响应、数据库查询结果等）。通过工具摘要，可以提取和保留最相关的结果字段，过滤掉元数据、调试信息等非必要内容。例如，天气API可能返回详细的气象参数，但摘要后只保留温度、天气状况等核心信息，大幅减少token消耗。</p><h4 id="上下文修剪"><a href="#上下文修剪" class="headerlink" title="上下文修剪"></a>上下文修剪</h4><p><strong>基于规则的修剪：</strong><br>可以使用硬编码启发式方法来主动删除过时或低优先级的上下文。常见策略包括：</p><ul><li>从对话历史中删除较旧的消息，保留最近N轮对话</li><li>移除已完成的子任务记录，只保留当前任务相关信息</li><li>删除过期的临时数据或已失效的工具调用结果</li></ul><p><strong>智能修剪：</strong><br>更高级的方法可以基于相关性评分来动态选择保留哪些上下文片段。通过语义相似度计算或重要性打分，优先保留与当前任务最相关的信息，自动淘汰相关度低的历史内容。</p><h3 id="隔离上下文"><a href="#隔离上下文" class="headerlink" title="隔离上下文"></a>隔离上下文</h3><p>隔离上下文涉及将上下文拆分以帮助Agent执行任务。</p><h4 id="多Agent架构"><a href="#多Agent架构" class="headerlink" title="多Agent架构"></a>多Agent架构</h4><p>![alt text](&#x2F;img&#x2F;ai-agent-learning&#x2F;alt text)</p><p><strong>关注点分离：</strong><br>将复杂的大任务拆分成多个独立的子任务,每个子任务由专门的Agent负责。这种设计遵循单一职责原则,使每个Agent专注于特定领域,提高整体系统的可维护性和可扩展性。</p><p><strong>Agent隔离特性：</strong><br>每个子Agent拥有独立的资源和配置:</p><ul><li><strong>专用工具集</strong>：每个Agent只能访问完成其任务所需的特定工具,避免工具泛滥导致的选择困难</li><li><strong>独立系统指令</strong>：针对特定任务定制的系统提示词,明确Agent的角色定位和行为准则</li><li><strong>隔离的上下文窗口</strong>：各Agent维护自己的上下文空间,互不干扰,避免无关信息污染</li></ul><p><strong>Agent协作机制：</strong><br>多个Agent之间通过明确的接口进行通信和数据传递,主控Agent或路由层负责任务分配和结果整合,形成协同工作流。</p><h4 id="执行环境隔离"><a href="#执行环境隔离" class="headerlink" title="执行环境隔离"></a>执行环境隔离</h4><p>![alt text](&#x2F;img&#x2F;ai-agent-learning&#x2F;alt text)</p><p><strong>上下文与执行分离：</strong><br>将代码执行环境与LLM的上下文窗口隔离开来,LLM不需要直接接触所有工具的原始输出数据。</p><p><strong>处理层设计：</strong><br>在工具执行和LLM之间增加处理层:</p><ul><li>工具在独立的沙箱环境中执行,产生原始输出</li><li>处理层过滤、转换和摘要原始结果</li><li>只将精炼后的关键信息传递给LLM上下文</li></ul><p>这种隔离既提高了安全性,又减少了token消耗,使LLM能够专注于高层决策而非底层细节处理。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>上下文工程的四个动作——写、选、压、隔——并不是零散的技巧，而是一套系统方法。<br>它们分别解决了信息丢失、信息冗余、信息过载和信息冲突的问题。<br>当这四个策略被系统化执行，Agent 就能在复杂环境中稳定运行。</p><h2 id="上下文工程的实现"><a href="#上下文工程的实现" class="headerlink" title="上下文工程的实现"></a>上下文工程的实现</h2><p>使用LangSmith和LangGraph进行上下文工程，此部分内容具体可以参考 第九章。</p><h2 id="总结与思考：RAG-is-Dead"><a href="#总结与思考：RAG-is-Dead" class="headerlink" title="总结与思考：RAG is Dead?"></a>总结与思考：RAG is Dead?</h2><p>![alt text](&#x2F;img&#x2F;ai-agent-learning&#x2F;alt text)</p><p>Jeff主要批评了传统的RAG将”检索（Retrieval）、增强（Augmented）、生成（Generation）”三个不同概念强行捆绑在一起，导致了概念上的混乱和实践上的模糊化。从上下文工程的视角重新审视RAG，可以将其拆解为更清晰的步骤：</p><p><strong>传统RAG vs 上下文工程视角（高级RAG）：</strong></p><table><thead><tr><th>阶段</th><th>传统RAG</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></tbody></table><p><strong>核心改进：</strong></p><ul><li><strong>检索策略多样化</strong>：不再依赖单一的向量检索，而是根据任务特点组合使用稠密检索、稀疏检索、语义重排序等技术</li><li><strong>上下文质量优先</strong>：强调送入LLM的不是”越多越好”，而是”越精准越好”，通过过滤和排序确保上下文的高质量</li><li><strong>闭环优化</strong>：通过评估数据集持续迭代优化检索策略、过滤规则和排序算法，形成可衡量、可改进的工程化流程</li></ul><p>这种视角将RAG从一个黑盒流程转变为可拆解、可优化的上下文工程问题，使其更具可操作性和可扩展性。</p><p>因此，上下文工程既是一门系统化的工程实践，也是一门需要权衡取舍的艺术。它要求我们在海量信息中精准地判断以下4个问题：</p><ul><li><strong>Write（写入）</strong> —— 哪些信息应该纳入上下文？</li><li><strong>Select（选择）</strong> —— 哪些内容最相关且必要？</li><li><strong>Compress（压缩）</strong> —— 哪些可以摘要或简化？</li><li><strong>Isolate（隔离）</strong> —— 哪些需要分离到独立空间？</li></ul><p>只有懂得这些问题，才能实现有效的上下文工程，实现艺术与工程的完美结合。</p><p>![alt text](&#x2F;img&#x2F;ai-agent-learning&#x2F;alt text)</p><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>沧海九粟. 上下文工程：优化 Agent 效能的关键技术[EB&#x2F;OL]. (2025-07-10)[2025-10-21]. <a href="https://www.bilibili.com/video/BV1w3GNzeEHb/?spm_id_from=333.1387.upload.video_card.click&vd_source=0f47ed6b43bae0b240e774a8fd72e3e4">https://www.bilibili.com/video/BV1w3GNzeEHb/?spm_id_from=333.1387.upload.video_card.click&amp;vd_source=0f47ed6b43bae0b240e774a8fd72e3e4</a></p><p>Drew Breunig. How Long Contexts Fail[EB&#x2F;OL]. (2025-06-22)[2025-10-21]. <a href="https://www.dbreunig.com/2025/06/22/how-contexts-fail-and-how-to-fix-them.html?ref=blog.langchain.com">https://www.dbreunig.com/2025/06/22/how-contexts-fail-and-how-to-fix-them.html?ref=blog.langchain.com</a></p><p>Latent.Space, Jeff Huber, Swyx. RAG is Dead, Context Engineering is King[EB&#x2F;OL]. (2025-08-19)[2025-10-21]. <a href="https://www.latent.space/p/chroma">https://www.latent.space/p/chroma</a></p><p>万字拆解. RAG已死吗？上下文工程（context engineer）为何为王？[EB&#x2F;OL]. (2025-09-03)[2025-10-21]. <a href="https://www.woshipm.com/ai/6264065.html">https://www.woshipm.com/ai/6264065.html</a></p>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra02-%E4%B8%8A%E4%B8%8B%E6%96%87%E5%B7%A5%E7%A8%8B%E8%A1%A5%E5%85%85%E7%9F%A5%E8%AF%86/</id>
    <link href="http://jasondong97.github.io/2026/03/03/ai-agent-learning/Extra02-%E4%B8%8A%E4%B8%8B%E6%96%87%E5%B7%A5%E7%A8%8B%E8%A1%A5%E5%85%85%E7%9F%A5%E8%AF%86/"/>
    <published>2026-03-02T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="上下文工程补充知识"><a href="#上下文工程补充知识" class="headerlink" title="上下文工程补充知识"></a>上下文工程补充知识</h1><h2 id="引入"><a href="#引入" class="headerlink"]]>
    </summary>
    <title>上下文工程补充知识</title>
    <updated>2026-03-08T09:24:16.367Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="LLM-amp-VLM-amp-Agent-面试问题总结"><a href="#LLM-amp-VLM-amp-Agent-面试问题总结" class="headerlink" title="LLM &amp; VLM &amp; Agent 面试问题总结"></a>LLM &amp; VLM &amp; Agent 面试问题总结</h1><p>本文档是在备战2025秋招过程中整理的面试“八股”合集。</p><p>楼主主要投递的岗位包括：大模型算法工程师、Agent工程师、AI开发工程师、算法评测工程师等，面试公司以国内互联网中大厂为主。因此，本文档中的问题深度和广度都围绕这些岗位的要求展开，内容涵盖了从 LLM&#x2F;VLM 核心理论，到 RAG&#x2F;Agent 应用开发，再到 RLHF 对齐技术和模型&#x2F;Agent 评估等全链路技术栈。所有问题均整理自多次线上技术面试的真实经历。</p><p>【使用建议】<br>本文档仅供学习与参考。为了达到最佳效果，强烈建议先独立思考每个问题，尝试构建自己的答案，然后再对照文档提供的参考思路进行查漏补缺。知其然，更要知其所以然。直接背诵是效率最低的方式。</p><p>预祝各位求职顺利，都能拿到心仪的Offer！</p><hr><h3 id="1-LLM-八股"><a href="#1-LLM-八股" class="headerlink" title="1. LLM 八股"></a>1. LLM 八股</h3><ol><li>请详细解释一下 Transformer 模型中的自注意力机制是如何工作的？它为什么比 RNN 更适合处理长序列？</li><li>什么是位置编码？在 Transformer 中，为什么它是必需的？请列举至少两种实现方式。</li><li>请你详细介绍ROPE，对比绝对位置编码它的优劣势分别是什么？</li><li>你知道MHA，MQA，GQA的区别吗？详细解释一下。</li><li>请比较一下几种常见的 LLM 架构，例如 Encoder-Only, Decoder-Only, 和 Encoder-Decoder，并说明它们各自最擅长的任务类型。</li><li>什么是Scaling Laws？它揭示了模型性能、计算量和数据量之间的什么关系？这对LLM的研发有什么指导意义？</li><li>在LLM的推理阶段，有哪些常见的解码策略？请解释 Greedy Search, Beam Search, Top-K Sampling 和 Nucleus Sampling (Top-P) 的原理和优缺点。</li><li>什么是词元化？请比较一下 BPE 和 WordPiece 这两种主流的子词切分算法。</li><li>你觉得NLP和LLM最大的区别是什么？两者有何共同和不同之处？</li><li>L1和L2正则化分别是什么，什么场景适合使用呢？</li><li>“涌现能力”是大型模型中一个备受关注的现象，请问你如何理解这个概念？它通常在模型规模达到什么程度时出现？</li><li>激活函数有了解吗，你知道哪些LLM常用的激活函数？为什么选用它？</li><li>混合专家模型（MoE）是如何在不显著增加推理成本的情况下，有效扩大模型参数规模的？请简述其工作原理。</li><li>在训练一个百或千亿参数级别的 LLM 时，你会面临哪些主要的工程和算法挑战？（例如：显存、通信、训练不稳定性等）</li><li>开源框架了解过哪些？Qwen，Deepseek的论文是否有研读过，说一下其中的创新点主要体现在哪？</li><li>最近读过哪些LLM比较前沿的论文，聊一下它的相关方法，针对什么问题，提出了什么方法，对比实验有哪些？</li></ol><hr><h3 id="2-VLM-八股"><a href="#2-VLM-八股" class="headerlink" title="2. VLM 八股"></a>2. VLM 八股</h3><ol><li>多模态大模型（如 VLM）的核心挑战是什么？即如何实现不同模态信息（如视觉和语言）的有效对齐和融合？</li><li>请解释 CLIP 模型的工作原理。它是如何通过对比学习来连接图像和文本的？</li><li>像 LLaVA 或 MiniGPT-4 这样的模型是如何将一个预训练好的视觉编码器（Vision Encoder）和一个大语言模型（LLM）连接起来的？请描述其关键的架构设计。</li><li>什么是视觉指令微调？为什么说它是让 VLM 具备良好对话和指令遵循能力的关键步骤？</li><li>在处理视频等多模态数据时，相比于静态图片，VLM 需要额外解决哪些问题？（例如，如何表征时序信息？）</li><li>请解释Grounding在 VLM 领域中的含义。我们如何评估一个 VLM 是否能将文本描述准确地对应到图片中的特定区域？</li><li>请对比至少不同的 VLM 架构范式（如共享编码器 vs. 跨模态注意力融合），并分析它们的优劣。</li><li>在 VLM 的应用中，如何处理高分辨率的输入图像？这会带来哪些计算和模型设计上的挑战？</li><li>VLM 在生成内容时，同样会遇到“幻觉”（Hallucination）问题，但它的表现形式和纯文本 LLM 有何不同？请举例说明。</li><li>除了图片描述和视觉问答（VQA），你还能列举出 VLM 的哪些前沿或具有潜力的应用方向？</li><li>有没有做过VLM相关方面的微调？什么模型？</li></ol><hr><h3 id="3-RLHF-八股"><a href="#3-RLHF-八股" class="headerlink" title="3. RLHF 八股"></a>3. RLHF 八股</h3><ol><li>和传统SFT相比，RLHF旨在解决语言模型中的哪些核心问题？为什么说SFT本身不足以实现我们期望的“对齐”目标？</li><li>请详细阐述经典RLHF流程的三个核心阶段。在每个阶段，输入是什么，输出是什么，以及该阶段的关键目标是什么？</li><li>在RM训练阶段，我们通常收集的是成对比较数据，而不是让人类标注者直接给回复打一个绝对分数。你认为这样做的主要优势和潜在的劣势分别是什么？</li><li>奖励模型的设计至关重要。它的模型架构通常如何选择？它与我们最终要优化的LLM是什么关系？在训练奖励模型时，常用的损失函数是什么？请解释其背后的数学原理（例如，可以结合Bradley-Terry模型来解释）。</li><li>在RLHF的第三阶段，PPO是最主流的强化学习算法。为什么选择PPO，而不是其他更简单的策略梯度算法（如REINFORCE）或者Q-learning系算法？PPO中的KL散度惩罚项起到了什么关键作用？</li><li>如果在PPO训练过程中，KL散度惩罚项的系数 β 设置得过大或过小，分别会导致什么样的问题？你将如何通过实验和观察来调整这个超参数？</li><li>什么是“奖励作弊&#x2F;奖励黑客”（Reward Hacking）？请结合一个具体的LLM应用场景给出一个例子，并探讨几种可能的缓解策略。</li><li>RLHF流程复杂且不稳定。近年来出现了一些替代方案，例如DPO。请解释DPO的核心思想，并比较它与传统RLHF（基于PPO）的主要区别和优势。</li><li>想象一下，你训练完成的RLHF模型在离线评估中表现优异，奖励模型分数很高，但上线后用户反馈其回答变得越来越“模式化”、奉承、且缺乏信息量。你认为可能的原因是什么？你会从哪些方面着手分析和解决这个问题？</li><li>你知道Deepseek的GRPO吗，它和PPO的主要区别是什么？优劣是什么？</li><li>GSPO和DAPO有听说过吗？他们和GRPO有什么区别？</li><li>如何解决信用分配问题？token级别和seq级别的奖励有何不同？</li><li>除了人类反馈，我们还可以利用AI自身的反馈来做对齐，即RLAIF。请谈谈你对RLAIF的理解，它的潜力和风险分别是什么？</li></ol><hr><h3 id="4-Agent"><a href="#4-Agent" class="headerlink" title="4. Agent"></a>4. Agent</h3><ol><li>你如何定义一个基于 LLM 的智能体（Agent）？它通常由哪些核心组件构成？</li><li>请详细解释 ReAct 框架。它是如何将思维链和行动结合起来，以完成复杂任务的？</li><li>在 Agent 的设计中，“规划能力”至关重要。请谈谈目前有哪些主流方法可以赋予 LLM 规划能力？（例如 CoT, ToT, GoT等）</li><li>Memory是 Agent 的一个关键模块。请问如何为 Agent 设计短期记忆和长期记忆系统？可以借助哪些外部工具或技术？</li><li>Tool Use是扩展 Agent 能力的有效途径。请解释 LLM 是如何学会调用外部 API 或工具的？（可以从 Function Calling 的角度解释）</li><li>请比较一下两个流行的 Agent 开发框架，如 LangChain 和 LlamaIndex。它们的核心应用场景有何不同？</li><li>在构建一个复杂的 Agent 时，你认为最主要的挑战是什么？</li><li>什么是多智能体系统？让多个 LLM Agent 协同工作相比于单个 Agent 有什么优势？又会引入哪些新的复杂性？</li><li>当一个 Agent 需要在真实或模拟环境中（如机器人、游戏）执行任务时，它与纯粹基于软件工具的 Agent 有什么本质区别？</li><li>如何确保一个 Agent 的行为是安全、可控且符合人类意图的？在 Agent 的设计中，有哪些保障对齐方法？</li><li>了解A2A框架吗？它和普通Agent框架的区别在哪，挑一个最关键的不同点说明。</li><li>你用过哪些Agent框架？选型是如何选的？你最终场景的评价指标是什么？</li><li>有微调过Agent能力吗？数据集如何收集？</li></ol><hr><h3 id="5-RAG"><a href="#5-RAG" class="headerlink" title="5. RAG"></a>5. RAG</h3><ol><li>请解释 RAG 的工作原理。与直接对 LLM 进行微调相比，RAG 主要解决了什么问题？有哪些优势？</li><li>一个完整的 RAG 流水线包含哪些关键步骤？请从数据准备到最终生成，详细描述整个过程。</li><li>在构建知识库时，文本切块策略至关重要。你会如何选择合适的切块大小和重叠长度？这背后有什么权衡？</li><li>如何选择一个合适的嵌入模型？评估一个 Embedding 模型的好坏有哪些指标？</li><li>除了基础的向量检索，你还知道哪些可以提升 RAG 检索质量的技术？</li><li>请解释“Lost in the Middle”问题。它描述了 RAG 中的什么现象？有什么方法可以缓解这个问题？</li><li>如何全面地评估一个 RAG 系统的性能？请分别从检索和生成两个阶段提出评估指标。</li><li>在什么场景下，你会选择使用图数据库或知识图谱来增强或替代传统的向量数据库检索？</li><li>传统的 RAG 流程是“先检索后生成”，你是否了解一些更复杂的 RAG 范式，比如在生成过程中进行多次检索或自适应检索？</li><li>RAG 系统在实际部署中可能面临哪些挑战？</li><li>了解搜索系统吗？和RAG有什么区别？</li><li>知道或者使用过哪些开源RAG框架比如Ragflow？如何选择合适场景？</li></ol><hr><h3 id="6-模型评估与-Agent-评估"><a href="#6-模型评估与-Agent-评估" class="headerlink" title="6. 模型评估与 Agent 评估"></a>6. 模型评估与 Agent 评估</h3><ol><li>为什么传统的 NLP 评估指标（如 BLEU, ROUGE）对于评估现代 LLM 的生成质量来说，存在很大的局限性？</li><li>请介绍几个目前行业内广泛使用的 LLM 综合性基准测试，并说明它们各自的侧重点。（例如：MMLU, Big-Bench, HumanEval）</li><li>什么是“LLM-as-a-Judge”？使用 LLM 来评估另一个 LLM 的输出，有哪些优点和潜在的偏见？</li><li>如何设计一个评估方案来衡量 LLM 的特定能力，比如“事实性&#x2F;幻觉水平”、“推理能力”或“安全性”？</li><li>评估一个 Agent 为什么比评估一个基础 LLM 更加困难和复杂？评估的维度有哪些不同？</li><li>你了解哪些专门用于评估 Agent 能力的基准测试？这些基准通常如何构建测试环境和任务？</li><li>在评估一个 Agent 的任务完成情况时，除了最终结果的正确性，还有哪些过程指标是值得关注的？（例如：效率、成本、鲁棒性）</li><li>什么是红队测试？它在发现 LLM 和 Agent 的安全漏洞与偏见方面扮演着什么角色？</li><li>在进行人工评估时，如何设计合理的评估准则和流程，以保证评估结果的客观性和一致性？</li><li>如何持续监控和评估一个已经部署上线的 LLM 应用或 Agent 服务的表现，以应对可能出现的性能衰退或行为漂移？</li></ol><hr><h3 id="7-LLM-前景与发展"><a href="#7-LLM-前景与发展" class="headerlink" title="7. LLM 前景与发展"></a>7. LLM 前景与发展</h3><ol><li>你认为当前 LLM 距离通用人工智能（AGI）还有多远？最关键的缺失能力是什么？</li><li>从 GPT-4 到未来的模型，你认为多模态的融合会走向何方？仅仅是文本、图像的结合，还是会拓展到更多感官维度？</li><li>你如何看待开源模型和闭源模型生态系统的竞争与共存？它们各自的优势是什么，未来将如何演进？</li><li>随着模型能力的增强，LLM 的“世界模型”或内在模拟能力也备受关注。你如何理解这个概念？它对实现更高阶的推理和规划有何意义？</li><li>“数据”是训练 LLM 的燃料。你认为高质量的人工合成数据在未来的模型训练中将扮演什么样的角色？</li><li>具身智能（Embodied AI），即 LLM 与机器人的结合，被认为是 AI 的下一个浪潮。你认为 LLM 将如何赋能机器人，并会带来哪些挑战？</li><li>个性化是 LLM 应用的重要方向。在实现高度个性化的 Agent 或助手的过程中，我们应如何平衡效果、隐私和安全？</li><li>你认为 Transformer 架构会长久地统治这个领域吗？还是你看到了像状态空间模型（SSM, 如 Mamba）等新架构的潜力？</li><li>展望未来 3-5 年，你认为 LLM 和 Agent 技术最有可能在哪个行业或领域率先实现颠覆性的应用？为什么？</li></ol><hr><h3 id="8-其它"><a href="#8-其它" class="headerlink" title="8. 其它"></a>8. 其它</h3><ol><li>你认为目前限制Agent能力和普及的最大瓶颈是什么？（例如：模型能力、成本、可靠性、还是其他？）</li><li>在过去半年里，哪一篇关于Agent的论文或哪一个开源项目让你印象最深刻？为什么？</li><li>你如何看待Agent领域的“涌现能力”？我们应该追求更强大的基础模型，还是更精巧的Agent架构？</li><li>你认为未来1-2年内，Agent技术最有可能在哪个行业或场景率先实现大规模商业落地？</li><li>如果让你自由探索，你最想创造一个什么样的Agent来解决什么问题？</li><li>对于想要进入Agent领域的初学者，你会给他&#x2F;她什么建议？应该重点学习哪些技术？</li><li>总结一下，你认为一个顶尖的AI Agent工程师，应该具备哪些核心素质？</li><li>平常使用AI吗，都用来干嘛？如果我想使用AI，比如coding领域，你有何建议给我？</li></ol>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/02/ai-agent-learning/Extra01-%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/</id>
    <link href="http://jasondong97.github.io/2026/03/02/ai-agent-learning/Extra01-%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/"/>
    <published>2026-03-02T14:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="LLM-amp-VLM-amp-Agent-面试问题总结"><a href="#LLM-amp-VLM-amp-Agent-面试问题总结" class="headerlink" title="LLM &amp; VLM &amp; Agent 面试问题总结"></]]>
    </summary>
    <title>
      <![CDATA[LLM & VLM & Agent 面试问题总结]]>
    </title>
    <updated>2026-03-08T09:24:16.365Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="LLM-amp-VLM-amp-Agent-面试回答参考"><a href="#LLM-amp-VLM-amp-Agent-面试回答参考" class="headerlink" title="LLM &amp; VLM &amp; Agent 面试回答参考"></a>LLM &amp; VLM &amp; Agent 面试回答参考</h1><p>本文档旨在为大语言模型（LLM）、视觉语言模型（VLM）、智能体（Agent）、RAG及相关领域的面试提供一个全面的复习指南。仅提供1-6部分参考答案，7、8章节为半开放题目，可以自行借助AI或结合自身经历回答。</p><hr><h3 id="1-LLM-八股"><a href="#1-LLM-八股" class="headerlink" title="1. LLM 八股"></a><strong>1. LLM 八股</strong></h3><h4 id="1-1-请详细解释一下-Transformer-模型中的自注意力机制是如何工作的？它为什么比-RNN-更适合处理长序列？"><a href="#1-1-请详细解释一下-Transformer-模型中的自注意力机制是如何工作的？它为什么比-RNN-更适合处理长序列？" class="headerlink" title="1.1 请详细解释一下 Transformer 模型中的自注意力机制是如何工作的？它为什么比 RNN 更适合处理长序列？"></a><strong>1.1 请详细解释一下 Transformer 模型中的自注意力机制是如何工作的？它为什么比 RNN 更适合处理长序列？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  自注意力（Self-Attention）机制是Transformer模型的核心，它使得模型能够动态地衡量输入序列中不同单词之间的重要性，并据此生成每个单词的上下文感知表示。</p><p>  <strong>工作原理如下：</strong></p><ol><li><p><strong>生成Q, K, V向量：</strong> 对于输入序列中的每一个词元（token）的嵌入向量，我们通过乘以三个可学习的权重矩阵 $W^Q, W^K, W^V$ ，分别生成三个向量：查询向量（Query, Q）、键向量（Key, K）和值向量（Value, V）。</p><ul><li><strong>Query (Q):</strong> 代表当前词元为了更好地理解自己，需要去“查询”序列中其他词元的信息。</li><li><strong>Key (K):</strong> 代表序列中每个词元所“携带”的，可以被查询的信息标签。</li><li><strong>Value (V):</strong> 代表序列中每个词元实际包含的深层含义。</li></ul></li><li><p><strong>计算注意力分数：</strong> 为了确定当前词元（由Q代表）应该对其他所有词元（由K代表）投入多少关注，我们计算当前词元的Q与其他所有词元的K的点积。这个分数衡量了两者之间的相关性。</p><div align="center">$$\text{Score}(Q_i, K_j) = Q_i \cdot K_j$$</div></li><li><p><strong>缩放（Scaling）：</strong> 将计算出的分数除以一个缩放因子 $\sqrt{d_k}$（ $d_k$ 是K向量的维度）。这一步是为了在反向传播时获得更稳定的梯度，防止点积结果过大导致Softmax函数进入饱和区。</p><div align="center">$$\frac{Q \cdot K^T}{\sqrt{d_k}}$$</div></li><li><p><strong>Softmax归一化：</strong> 将缩放后的分数通过一个Softmax函数，使其转换为一组总和为1的概率分布。这些概率就是“注意力权重”，表示在当前位置，每个输入词元所占的重要性。</p><div align="center">$$\text{AttentionWeights} = \text{softmax}\left(\frac{Q K^T}{\sqrt{d_k}}\right)$$</div></li><li><p><strong>加权求和：</strong> 最后，将得到的注意力权重与每个词元对应的V向量相乘并求和，得到最终的自注意力层输出。这个输出向量融合了整个序列的上下文信息，且权重由模型动态学习得到。</p><div align="center">$$\text{Output} = \text{AttentionWeights} \cdot V$$</div></li></ol><p>  <strong>为什么比RNN更适合处理长序列？</strong></p><ol><li><strong>并行计算能力：</strong> 自注意力机制在计算时，可以一次性处理整个序列，计算所有位置之间的关联，是高度并行的。而RNN（包括LSTM、GRU）必须按照时间顺序依次处理每个词元，无法并行化，导致处理长序列时速度非常慢。</li><li><strong>解决长距离依赖问题：</strong> 在自注意力中，任意两个位置之间的交互路径长度都是O(1)，因为可以直接计算它们的注意力分数。而在RNN中，序列首尾两个词元的信息传递需要经过整个序列的长度，路径为O(N)，这极易导致梯度消失或梯度爆炸，使得模型难以捕捉长距离的依赖关系。</li></ol></li></ul><hr><h4 id="1-2-什么是位置编码？在-Transformer-中，为什么它是必需的？请列举至少两种实现方式。"><a href="#1-2-什么是位置编码？在-Transformer-中，为什么它是必需的？请列举至少两种实现方式。" class="headerlink" title="1.2 什么是位置编码？在 Transformer 中，为什么它是必需的？请列举至少两种实现方式。"></a><strong>1.2 什么是位置编码？在 Transformer 中，为什么它是必需的？请列举至少两种实现方式。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>什么是位置编码？</strong><br>  位置编码（Positional Encoding, PE）是一个与词嵌入维度相同的向量，其目的是向模型注入关于词元在输入序列中绝对或相对位置的信息。它会与词元的词嵌入（Token Embedding）相加，然后一同输入到Transformer的底层。</p><p>  <strong>为什么它是必需的？</strong><br>  Transformer的核心机制——自注意力，在计算时处理的是一个集合（Set）而非序列（Sequence）。它本身不包含任何关于词元顺序的信息，是 <strong>置换不变（Permutation-invariant）</strong> 的。这意味着，如果打乱输入序列中词元的顺序，自注意力层的输出也会相应地被打乱，但每个词元自身的输出向量（在不考虑softmax归一化的情况下）是相同的。这显然不符合自然语言的特性，因为语序至关重要（例如“我打你”和“你打我”含义完全相反）。因此，必须通过一种外部机制，将位置信息显式地提供给模型，这就是位置编码的作用。</p><p>  <strong>至少两种实现方式：</strong></p><ol><li><p><strong>正弦&#x2F;余弦位置编码（Sinusoidal Positional Encoding）：</strong><br>这是原始Transformer论文《Attention Is All You Need》中使用的方法。它使用不同频率的正弦和余弦函数来生成位置编码，其公式如下：</p><div align="center">$$PE_{(pos, 2i)} = \sin(pos / 10000^{2i/d_{\text{model}}})$$</div><div align="center">$$PE_{(pos, 2i+1)} = \cos(pos / 10000^{2i/d_{\text{model}}})$$</div><p>其中， $pos$ 是词元在序列中的位置， $i$ 是编码向量中的维度索引， $d_{\text{model}}$ 是嵌入维度。</p><ul><li><strong>优点：</strong><ul><li><strong>可外推性：</strong> 能够处理比训练中最长序列还要长的序列。</li><li><strong>相对位置信息：</strong> 模型可以轻易地学习到相对位置关系，因为对于任何固定的偏移量 $k$ ， $PE_{pos+k}$ 都可以表示为 $PE_{pos}$ 的一个线性函数，这使得模型更容易捕捉相对位置的依赖。</li></ul></li></ul></li><li><p><strong>可学习的绝对位置编码（Learned Absolute Positional Encoding）：</strong><br>这种方法将位置编码视为模型参数的一部分，通过训练学习得到。具体来说，会创建一个形状为 <code>(max_sequence_length, embedding_dimension)</code> 的位置编码矩阵。在处理序列时，根据每个词元的位置索引，从这个矩阵中查找对应的编码向量，并加到词嵌入上。BERT和GPT-2等模型采用了这种方式。</p><ul><li><strong>优点：</strong> 模式更加灵活，可以让模型自己学习出最适合数据的位置表示。</li><li><strong>缺点：</strong> 无法泛化到超过预设 <code>max_sequence_length</code> 的长度。如果需要处理更长的序列，就需要对位置编码进行微调或采用其他策略。</li></ul></li></ol></li></ul><hr><h4 id="1-3-请你详细介绍ROPE，对比绝对位置编码它的优劣势分别是什么？"><a href="#1-3-请你详细介绍ROPE，对比绝对位置编码它的优劣势分别是什么？" class="headerlink" title="1.3 请你详细介绍ROPE，对比绝对位置编码它的优劣势分别是什么？"></a><strong>1.3 请你详细介绍ROPE，对比绝对位置编码它的优劣势分别是什么？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>RoPE (Rotary Position Embedding) 介绍</strong><br>  RoPE，全称旋转位置编码，是目前大语言模型（如Llama系列、Qwen等）中最主流的位置编码方案之一。它是一种将位置信息融入自注意力机制的创新方法。</p><p>  其核心思想是：<strong>通过向量旋转的方式，将绝对位置信息编码到Query和Key向量中，从而使得模型在计算注意力分数时，能够自然地利用相对位置信息。</strong></p><p>  <strong>工作原理：</strong><br>  RoPE不再像传统位置编码那样直接将位置向量加到词嵌入上。它的操作发生在生成Q和K向量之后、计算注意力分数之前：</p><ol><li><strong>维度分组：</strong> 将Q和K向量的 $d$ 维特征两两一组，视为 $d&#x2F;2$ 个二维向量。</li><li><strong>构造旋转矩阵：</strong> 对于序列中的位置 $m$，构造一个与位置相关的旋转矩阵 $R_m$。这个矩阵在二维空间中表示一个旋转操作。</li><li><strong>旋转Q和K：</strong> 将每个二维向量组通过对应的旋转矩阵 $R_m$ 进行旋转。</li></ol><p>  数学上，这个过程等价于将每个二维向量 $(x_m, x_{m+1})$ 看作一个复数，然后乘以一个复数 $e^{im\theta}$，其中 $m$ 是位置， $\theta$ 是一个预设的、与维度相关的常数。这个操作只会改变向量的相位（方向），而不改变其模（长度）。</p><p>  <strong>关键特性：</strong><br>  RoPE的巧妙之处在于，经过旋转后的两个位置 $m$ 和 $n$ 的Query向量 $q_m$ 和Key向量 $k_n$ 进行点积运算时，其结果只与它们的相对位置 $(m-n)$ 有关，而与它们的绝对位置 $m$ 和 $n$ 无关。这使得自注意力机制天然地具备了对相对位置的感知能力。</p><p>  <strong>对比绝对位置编码的优劣势：</strong></p><p>  <strong>RoPE的优势：</strong></p><ol><li><strong>内置相对位置建模：</strong> 这是其最大的优势。RoPE使得注意力分数直接依赖于词元间的相对距离，这更符合自然语言中语法和语义依赖通常是相对的这一特性。</li><li><strong>良好的外推能力：</strong> 由于其数学性质，RoPE在处理比训练时更长的序列时表现出色，具有很强的长度泛化能力，这也是长序列LLM偏爱它的重要原因。</li><li><strong>不引入额外可训练参数：</strong> RoPE是一种函数式的、固定的编码方式，不需要像可学习位置编码那样占用模型参数。</li><li><strong>随着距离增加，依赖性衰减：</strong> 旋转的性质使得距离越远的词元，其内积关系会呈现周期性的衰减，符合语言中距离越远相关性越弱的直觉。</li></ol><p>  <strong>RoPE的劣势：</strong></p><ol><li><strong>理论理解相对复杂：</strong> 其背后的数学原理（复数、欧拉公式、旋转矩阵）比直接相加的绝对位置编码更抽象。</li><li><strong>对绝对位置信息的表征可能较弱：</strong> 虽然RoPE从绝对位置导出，但其在注意力机制中的核心作用是体现相对位置。对于那些强依赖绝对位置信息的特定任务（例如，判断一个词是否在句子开头），它的效果可能不如直接使用绝对位置编码直观。</li></ol></li></ul><hr><h4 id="1-4-你知道MHA，MQA，GQA的区别吗？详细解释一下。"><a href="#1-4-你知道MHA，MQA，GQA的区别吗？详细解释一下。" class="headerlink" title="1.4 你知道MHA，MQA，GQA的区别吗？详细解释一下。"></a><strong>1.4 你知道MHA，MQA，GQA的区别吗？详细解释一下。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  MHA、MQA和GQA是Transformer模型中三种不同的注意力机制变体，它们的主要区别在于如何组织和共享Query、Key和Value的“头”（Head），核心目标是在模型效果和推理效率（特别是显存占用）之间做出不同的权衡。</p><h4 id="1-MHA-Multi-Head-Attention"><a href="#1-MHA-Multi-Head-Attention" class="headerlink" title="1. MHA (Multi-Head Attention)"></a><strong>1. MHA (Multi-Head Attention)</strong></h4><p>  这是原始Transformer论文中提出的标准注意力机制。</p><ul><li><strong>工作原理：</strong><ol><li>将输入的Q、K、V向量分别通过 $N$ 个独立的线性变换，得到 $N$ 组不同的 $Q_i, K_i, V_i$ 头（ $i&#x3D;1, …, N$ ）。</li><li>这 $N$ 组头在各自的子空间中并行地计算注意力（Scaled Dot-Product Attention）。</li><li>将 $N$ 个头计算得到的输出向量拼接（Concatenate）起来。</li><li>最后通过一个线性变换将拼接后的向量映射回原始维度。</li></ol></li><li><strong>结构：</strong> $N$ 个Query头， $N$ 个Key头， $N$ 个Value头。</li><li><strong>优点：</strong> 效果最好，模型能力最强。每个头可以在不同的表示子空间中学习到不同的信息。</li><li><strong>缺点：</strong> 推理成本高。在自回归生成任务中，需要缓存每一层的Key和Value（即KV Cache），MHA的KV Cache大小与头的数量$N$成正比，显存占用非常大，限制了长序列的生成。</li></ul><h4 id="2-MQA-Multi-Query-Attention"><a href="#2-MQA-Multi-Query-Attention" class="headerlink" title="2. MQA (Multi-Query Attention)"></a><strong>2. MQA (Multi-Query Attention)</strong></h4><p>  为了解决MHA在推理时的显存瓶颈而被提出。</p><ul><li><strong>工作原理：</strong><ol><li>与MHA一样，有 $N$ 个独立的Query头。</li><li><strong>核心区别：</strong> 所有的 $N$ 个Query头共享<strong>同一个</strong>Key头和<strong>同一个</strong>Value头。</li></ol></li><li><strong>结构：</strong> $N$ 个Query头，<strong>1个</strong>Key头，<strong>1个</strong>Value头。</li><li><strong>优点：</strong> 极大地降低了推理成本。KV Cache的大小不再依赖于头的数量 $N$ ，相比MHA减小了 $N$ 倍，显著降低了显存占用，并加快了推理速度。</li><li><strong>缺点：</strong> 可能会导致模型性能的下降。因为所有Query头被迫从同样的一组Key和Value中提取信息，模型的表达能力受到了一定的限制。</li></ul><h4 id="3-GQA-Grouped-Query-Attention"><a href="#3-GQA-Grouped-Query-Attention" class="headerlink" title="3. GQA (Grouped-Query Attention)"></a><strong>3. GQA (Grouped-Query Attention)</strong></h4><p>  GQA是MHA和MQA之间的一个折中方案，旨在平衡性能和效率。</p><ul><li><strong>工作原理：</strong><ol><li>将 $N$ 个Query头分成 $G$ 组。</li><li><strong>核心区别：</strong> 每组内的Query头共享一个Key头和一个Value头。总共有 $G$ 个Key头和 $G$ 个Value头。</li></ol></li><li><strong>结构：</strong> $N$ 个Query头，<strong>G个</strong>Key头，<strong>G个</strong>Value头。（通常 $1 &lt; G &lt; N$ ）。</li><li><strong>说明：</strong><ul><li>当 $G&#x3D;N$ 时，GQA等价于MHA。</li><li>当 $G&#x3D;1$ 时，GQA等价于MQA。</li></ul></li><li><strong>优点：</strong> 在推理效率上远超MHA，同时在模型性能上优于MQA。它提供了一个灵活的旋钮，可以根据具体需求在效率和效果之间进行调整。Llama 2等模型就采用了GQA。</li></ul><p>  <strong>总结：</strong></p><table><thead><tr><th align="left">特性</th><th align="left">MHA (Multi-Head Attention)</th><th align="left">MQA (Multi-Query Attention)</th><th align="left">GQA (Grouped-Query Attention)</th></tr></thead><tbody><tr><td align="left"><strong>结构</strong></td><td align="left">N个Q头, N个K头, N个V头</td><td align="left">N个Q头, 1个K头, 1个V头</td><td align="left">N个Q头, G个K头, G个V头</td></tr><tr><td align="left"><strong>模型质量</strong></td><td align="left">最高</td><td align="left">可能下降</td><td align="left">接近MHA，优于MQA</td></tr><tr><td align="left"><strong>推理效率</strong></td><td align="left">最低 (KV Cache大)</td><td align="left">最高 (KV Cache小)</td><td align="left">居中，远好于MHA</td></tr><tr><td align="left"><strong>应用</strong></td><td align="left">BERT, GPT-3</td><td align="left">PaLM</td><td align="left">Llama 2, Mixtral</td></tr></tbody></table></li></ul><hr><h4 id="1-5-请比较一下几种常见的-LLM-架构，例如-Encoder-Only-Decoder-Only-和-Encoder-Decoder，并说明它们各自最擅长的任务类型。"><a href="#1-5-请比较一下几种常见的-LLM-架构，例如-Encoder-Only-Decoder-Only-和-Encoder-Decoder，并说明它们各自最擅长的任务类型。" class="headerlink" title="1.5 请比较一下几种常见的 LLM 架构，例如 Encoder-Only, Decoder-Only, 和 Encoder-Decoder，并说明它们各自最擅长的任务类型。"></a><strong>1.5 请比较一下几种常见的 LLM 架构，例如 Encoder-Only, Decoder-Only, 和 Encoder-Decoder，并说明它们各自最擅长的任务类型。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  LLM的架构主要可以分为三类，它们的核心区别在于使用了Transformer的哪些部分以及注意力机制的类型，这直接决定了它们各自擅长的任务。</p><h4 id="1-Encoder-Only-架构-例如-BERT-RoBERTa"><a href="#1-Encoder-Only-架构-例如-BERT-RoBERTa" class="headerlink" title="1. Encoder-Only 架构 (例如 BERT, RoBERTa)"></a><strong>1. Encoder-Only 架构 (例如 BERT, RoBERTa)</strong></h4><ul><li><strong>结构：</strong> 由多个Transformer Encoder层堆叠而成。</li><li><strong>核心机制：</strong> <strong>双向自注意力机制</strong>。在处理序列中的任何一个词元时，模型都可以同时关注到它左边和右边的所有词元。这使得模型能够获得非常丰富的上下文表示。</li><li><strong>最擅长的任务类型：自然语言理解 (NLU)</strong>。<ul><li><strong>具体任务：</strong><ul><li><strong>分类任务：</strong> 情感分析、文本分类。</li><li><strong>序列标注：</strong> 命名实体识别 (NER)。</li><li><strong>句子关系判断：</strong> 自然语言推断 (NLI)。</li><li><strong>完形填空：</strong> 像BERT的Masked Language Model (MLM) 预训练任务本身。</li></ul></li><li><strong>原因：</strong> 这些任务的核心是<strong>理解</strong>输入文本的深层含义，而双向上下文对于准确理解至关重要。这类模型的输出通常是固定的标签或类别，而非自由生成的长文本。</li></ul></li></ul><h4 id="2-Decoder-Only-架构-例如-GPT系列-Llama-Qwen"><a href="#2-Decoder-Only-架构-例如-GPT系列-Llama-Qwen" class="headerlink" title="2. Decoder-Only 架构 (例如 GPT系列, Llama, Qwen)"></a><strong>2. Decoder-Only 架构 (例如 GPT系列, Llama, Qwen)</strong></h4><ul><li><strong>结构：</strong> 由多个Transformer Decoder层堆叠而成，但移除了其中的Encoder-Decoder交叉注意力部分。</li><li><strong>核心机制：</strong> <strong>单向（因果）自注意力机制 (Causal Self-Attention)</strong>。在预测第 <code>t</code> 个词元时，模型只能关注到位置 <code>1</code> 到 <code>t-1</code> 的词元，不能看到未来的信息。这种自回归的特性天然适合生成任务。</li><li><strong>最擅长的任务类型：自然语言生成 (NLG)</strong>。<ul><li><strong>具体任务：</strong><ul><li><strong>开放式文本生成：</strong> 写文章、故事、诗歌。</li><li><strong>对话系统&#x2F;聊天机器人：</strong> 如ChatGPT。</li><li><strong>代码生成：</strong> 如Copilot。</li><li><strong>上下文续写 (In-context Learning)。</strong></li></ul></li><li><strong>原因：</strong> 语言的生成过程是顺序的、从左到右的，Decoder-Only架构的单向注意力完美地模拟了这一过程。目前绝大多数的通用大语言模型都采用此架构。</li></ul></li></ul><h4 id="3-Encoder-Decoder-架构-例如-T5-BART-原始Transformer"><a href="#3-Encoder-Decoder-架构-例如-T5-BART-原始Transformer" class="headerlink" title="3. Encoder-Decoder 架构 (例如 T5, BART, 原始Transformer)"></a><strong>3. Encoder-Decoder 架构 (例如 T5, BART, 原始Transformer)</strong></h4><ul><li><strong>结构：</strong> 包含一个完整的Encoder栈和一个完整的Decoder栈。</li><li><strong>核心机制：</strong> Encoder部分使用<strong>双向注意力</strong>来编码整个输入序列，形成一个全面的上下文表示。Decoder部分在生成输出时，一方面使用<strong>单向注意力</strong>处理已生成的序列，另一方面通过<strong>交叉注意力 (Cross-Attention)</strong>机制来关注Encoder的输出，确保生成内容与输入相关。</li><li><strong>最擅长的任务类型：序列到序列 (Seq2Seq)</strong>。<ul><li><strong>具体任务：</strong><ul><li><strong>机器翻译：</strong> 将一种语言（输入序列）翻译成另一种语言（输出序列）。</li><li><strong>文本摘要：</strong> 将一篇长文章（输入序列）概括成几句话（输出序列）。</li><li><strong>问答：</strong> 将问题（输入序列）转换为答案（输出序列）。</li></ul></li><li><strong>原因：</strong> 这类任务需要首先对源序列有一个完整的、全局的理解（由Encoder完成），然后基于这个理解有条件地生成一个目标序列（由Decoder完成）。</li></ul></li></ul></li></ul><hr><h4 id="1-6-什么是Scaling-Laws？它揭示了模型性能、计算量和数据量之间的什么关系？这对LLM的研发有什么指导意义？"><a href="#1-6-什么是Scaling-Laws？它揭示了模型性能、计算量和数据量之间的什么关系？这对LLM的研发有什么指导意义？" class="headerlink" title="1.6 什么是Scaling Laws？它揭示了模型性能、计算量和数据量之间的什么关系？这对LLM的研发有什么指导意义？"></a><strong>1.6 什么是Scaling Laws？它揭示了模型性能、计算量和数据量之间的什么关系？这对LLM的研发有什么指导意义？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>什么是Scaling Laws？</strong><br>  Scaling Laws（尺度定律）是由OpenAI、DeepMind等机构通过大量实验发现的一系列经验性规律。它揭示了大型语言模型的性能（通常以交叉熵损失函数Loss来衡量）与三个关键资源要素——<strong>模型参数规模（N）</strong>、<strong>训练数据集大小（D）</strong>和<strong>训练所用的计算量（C）</strong>——之间存在着可预测的<strong>幂律关系（Power-Law Relationship）</strong>。</p><p>  <strong>揭示了什么关系？</strong></p><ol><li><strong>性能的可预测性：</strong> Scaling Laws表明，模型的性能损失会随着N、D、C的增加而平滑地、可预测地下降。这种关系可以用一个幂律公式来描述，例如，当数据和计算量足够时，模型损失 L 与模型参数量 N 的关系大致为： $L(N) \propto N^{-\alpha}$ ，其中 $\alpha$ 是一个小的正指数。这意味着我们可以通过在小规模模型上的实验结果，来外推（predict）更大规模模型可能达到的性能。</li><li><strong>瓶颈效应：</strong> 模型的最终性能会被N、D、C中最受限的那个因素所制约。如果仅仅增加模型大小而不增加数据量，性能提升会很快达到瓶颈；反之亦然。为了有效提升模型性能，必须协同扩展这三个要素。</li><li><strong>资源的最优分配：</strong> 对于一个给定的计算预算（FLOPs），存在一个最优的模型大小（N）和数据量（D）的组合。DeepMind的Chinchilla论文是一个里程碑式的发现，它修正了早期认为应该优先扩大模型规模的观点，指出<strong>为了达到计算最优，模型参数量和训练数据量应该近似1:20的比例进行扩展</strong>。例如，训练一个70B参数的模型，大约需要1.4万亿个token的数据。</li></ol><p>  <strong>对LLM研发的指导意义：</strong></p><ol><li><strong>科学指导项目规划：</strong> 在投入数百万甚至数千万美元进行一次大规模训练之前，研究机构可以先通过小规模实验拟合出自己数据集和模型架构下的Scaling Law。这使得他们能够科学地预测最终模型的性能，评估项目的投资回报率，并合理申请计算资源。</li><li><strong>优化资源配置，避免浪费：</strong> Scaling Laws，特别是Chinchilla定律，为如何高效使用计算预算提供了明确的指导。它告诉我们，与其训练一个参数巨大但数据不足的模型（over-trained），不如用同样的算力去训练一个参数稍小但数据更充分的模型（under-trained），后者效果可能更好。这促使业界从单纯追求“大参数”转向“大参数与大数据的平衡”。</li><li><strong>强调数据的重要性：</strong> Scaling Laws的发现，让学术界和工业界都更加深刻地认识到，高质量、大规模的训练数据和模型参数规模同等重要，甚至在某些阶段更为关键。这推动了数据工程、数据清洗和高质量合成数据生成等领域的发展。</li></ol></li></ul><hr><h4 id="1-7-在LLM的推理阶段，有哪些常见的解码策略？请解释-Greedy-Search-Beam-Search-Top-K-Sampling-和-Nucleus-Sampling-Top-P-的原理和优缺点。"><a href="#1-7-在LLM的推理阶段，有哪些常见的解码策略？请解释-Greedy-Search-Beam-Search-Top-K-Sampling-和-Nucleus-Sampling-Top-P-的原理和优缺点。" class="headerlink" title="1.7 在LLM的推理阶段，有哪些常见的解码策略？请解释 Greedy Search, Beam Search, Top-K Sampling 和 Nucleus Sampling (Top-P) 的原理和优缺点。"></a><strong>1.7 在LLM的推理阶段，有哪些常见的解码策略？请解释 Greedy Search, Beam Search, Top-K Sampling 和 Nucleus Sampling (Top-P) 的原理和优缺点。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  在LLM的推理（或称解码）阶段，模型会生成一个词元概率分布，解码策略决定了如何从这个分布中选择下一个词元。常见的策略可以分为确定性和随机性两类。</p><h4 id="1-Greedy-Search-贪心搜索"><a href="#1-Greedy-Search-贪心搜索" class="headerlink" title="1. Greedy Search (贪心搜索)"></a><strong>1. Greedy Search (贪心搜索)</strong></h4><ul><li><strong>原理：</strong> 在每个时间步，总是选择当前概率分布中概率最高的那个词元作为输出。</li><li><strong>优点：</strong><ul><li><strong>速度快：</strong> 计算开销最小，实现最简单。</li></ul></li><li><strong>缺点：</strong><ul><li><strong>局部最优：</strong> 每一步的“贪心”选择可能导致整个序列不是全局最优的。一个高概率的词后面可能跟着一系列低概率的词，最终序列的总概率反而不高。</li><li><strong>缺乏多样性：</strong> 输出是完全确定的，对于同一个输入，每次生成的结果都一样，内容往往比较呆板、重复。</li></ul></li></ul><h4 id="2-Beam-Search-集束搜索"><a href="#2-Beam-Search-集束搜索" class="headerlink" title="2. Beam Search (集束搜索)"></a><strong>2. Beam Search (集束搜索)</strong></h4><ul><li><strong>原理：</strong> 这是对贪心搜索的改进。它在每个时间步会保留 $k$ 个（ $k$ 称为 “beam width” 或 “beam size”）最有可能的候选序列。在下一步，它会从这 $k$ 个候选序列出发，生成所有可能的下一个词元，然后从所有这些扩展出的新序列中，再次选出累计概率最高的 $k$ 个。最后，从最终的 $k$ 个完整序列中选择最优的一个。</li><li><strong>优点：</strong><ul><li><strong>质量更高：</strong> 通过探索更广的搜索空间，通常能找到比贪心搜索概率更高、质量更好的序列。</li></ul></li><li><strong>缺点：</strong><ul><li><strong>计算成本高：</strong> 需要维护 $k$ 个候选序列，计算和内存开销是贪心搜索的 $k$ 倍。</li><li><strong>仍然倾向于安全和高频：</strong> 优化目标是全局概率，这使得它还是倾向于生成常见、安全的句子，可能缺乏创造性，并且在长文本生成中容易出现重复。</li></ul></li></ul><h4 id="3-Top-K-Sampling-Top-K-采样"><a href="#3-Top-K-Sampling-Top-K-采样" class="headerlink" title="3. Top-K Sampling (Top-K 采样)"></a><strong>3. Top-K Sampling (Top-K 采样)</strong></h4><ul><li><strong>原理：</strong> 这是一种随机采样策略。在每个时间步，不再是选择最优的，而是：<ol><li>从整个词汇表的概率分布中，筛选出概率最高的 $K$ 个词元。</li><li>将这 $K$ 个词元的概率进行归一化（使它们的和为1）。</li><li>在这 $K$ 个词元中，根据新的概率分布进行随机采样。</li></ol></li><li><strong>优点：</strong><ul><li><strong>增加多样性：</strong> 引入了随机性，使得生成内容更加丰富、有趣和不可预测。</li><li><strong>避免低概率词：</strong> 通过限制在Top-K范围内，过滤掉了那些概率极低、可能不通顺或奇怪的词元。</li></ul></li><li><strong>缺点：</strong><ul><li><strong>K值固定：</strong> $K$ 是一个固定的超参数。当概率分布很尖锐时（模型非常确定下一个词），一个大的K可能会引入不相关的词；当概率分布很平坦时（模型不确定），一个小的K可能会限制模型的选择。</li></ul></li></ul><h4 id="4-Nucleus-Sampling-x2F-Top-P-Sampling-核心采样"><a href="#4-Nucleus-Sampling-x2F-Top-P-Sampling-核心采样" class="headerlink" title="4. Nucleus Sampling &#x2F; Top-P Sampling (核心采样)"></a><strong>4. Nucleus Sampling &#x2F; Top-P Sampling (核心采样)</strong></h4><ul><li><strong>原理：</strong> 这是对Top-K采样的改进，它使用一个动态的候选词元集。<ol><li>将所有词元按概率从高到低排序。</li><li>从概率最高的词元开始，逐个累加它们的概率，直到总概率之和超过一个预设的阈值 $p$（例如 $p&#x3D;0.95$）。</li><li>这个累加过程中包含的所有词元构成了“核心（Nucleus）”候选集。</li><li>然后，在这个动态大小的候选集中，根据它们的原始概率进行归一化和随机采样。</li></ol></li><li><strong>优点：</strong><ul><li><strong>自适应候选集：</strong> 候选集的大小会根据上下文动态变化。当模型对下一个词非常确定时，概率分布尖锐，可能只有一两个词的概率和就超过了 $p$，候选集就很小，生成更精确；当模型不确定时，概率分布平坦，需要包含更多词才能达到 $p$，候选集就变大，允许更多探索。</li><li><strong>兼顾质量与多样性：</strong> 相比Top-K，它是一种更原则性和鲁棒性的方法，是目前大多数LLM应用默认的采样策略。</li></ul></li></ul></li></ul><hr><h4 id="1-8-什么是词元化？请比较一下-BPE-和-WordPiece-这两种主流的子词切分算法。"><a href="#1-8-什么是词元化？请比较一下-BPE-和-WordPiece-这两种主流的子词切分算法。" class="headerlink" title="1.8 什么是词元化？请比较一下 BPE 和 WordPiece 这两种主流的子词切分算法。"></a><strong>1.8 什么是词元化？请比较一下 BPE 和 WordPiece 这两种主流的子词切分算法。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>什么是词元化（Tokenization）？</strong><br>  词元化是将原始的文本字符串分解成一个个独立的单元（称为“词元”或“token”），并将这些词元映射到唯一的整数ID的过程。这是自然语言处理模型处理文本的第一步，因为模型只能处理数字输入。</p><p>  现代大型语言模型普遍采用 <strong>子词（Subword）</strong> 词元化算法，它介于按词切分和按字符切分之间。这样做的好处是：</p><ol><li><strong>有效处理未登录词（OOV）：</strong> 任何罕见词或新词都可以被拆解成已知的子词组合，避免了“未知”标记。</li><li><strong>平衡词表大小与序列长度：</strong> 相比于词级别，词表规模大大减小；相比于字符级别，生成的序列长度又不会过长，兼顾了效率。</li><li><strong>保留形态信息：</strong> 像 “running”, “runner” 这样的词可以共享 “run” 这个子词，使得模型能够理解词根和词缀的关系。</li></ol><p>  <strong>BPE vs. WordPiece</strong></p><p>  BPE和WordPiece是两种最主流的子词切分算法，它们构建词表的过程相似，但在合并子词的决策标准上有所不同。</p><h4 id="BPE-Byte-Pair-Encoding"><a href="#BPE-Byte-Pair-Encoding" class="headerlink" title="BPE (Byte Pair Encoding)"></a><strong>BPE (Byte Pair Encoding)</strong></h4><ul><li><strong>工作原理：</strong><ol><li><strong>初始化：</strong> 词汇表由语料库中出现的所有基本字符组成。</li><li><strong>迭代合并：</strong> 重复以下步骤直到达到预设的词表大小：<br>a.  在整个语料库中，统计所有相邻词元对的出现频率。<br>b.  找出<strong>频率最高</strong>的那个词元对（例如 <code>(&#39;e&#39;, &#39;s&#39;)</code>）。<br>c.  将这个词元对合并成一个新的、更长的词元（<code>&#39;es&#39;</code>），并将其加入词汇表。<br>d.  在语料库中，用新词元替换所有出现的该词元对。</li></ol></li><li><strong>应用模型：</strong> GPT系列、Llama等。</li><li><strong>特点：</strong> 算法思想简单直观，完全基于数据中符号对的出现频率。</li></ul><h4 id="WordPiece"><a href="#WordPiece" class="headerlink" title="WordPiece"></a><strong>WordPiece</strong></h4><ul><li><strong>工作原理：</strong><ol><li><strong>初始化：</strong> 与BPE一样，词汇表也从所有基本字符开始。</li><li><strong>迭代合并（核心区别）：</strong> WordPiece在选择合并哪两个子词时，不是基于频率，而是基于<strong>语言模型的似然（Likelihood）</strong>。它会尝试所有可能的合并，并选择那个能够<strong>最大程度提升训练数据似然值</strong>的合并操作。</li></ol><ul><li>可以通俗地理解为：如果把语料库看作一个语言模型，每次合并都应该让这个语言模型产生当前语料库的概率变得最大。它倾向于合并那些内部凝聚力更强的字符组合。</li></ul></li><li><strong>应用模型：</strong> BERT, DistilBERT, Electra。</li><li><strong>特点：</strong> WordPiece在切分时，通常会在单词的非起始部分子词前加上特殊符号（如<code>##</code>），例如 “tokenization” 可能会被切分为 <code>(&quot;token&quot;, &quot;##ization&quot;)</code>。</li></ul><p>  <strong>主要区别总结：</strong></p><table><thead><tr><th align="left">特性</th><th align="left">BPE (Byte Pair Encoding)</th><th align="left">WordPiece</th></tr></thead><tbody><tr><td align="left"><strong>合并决策标准</strong></td><td align="left"><strong>频率驱动</strong>：合并出现次数最多的相邻子词对。</td><td align="left"><strong>似然驱动</strong>：合并能最大化提升语料库语言模型似然的子词对。</td></tr><tr><td align="left"><strong>理论基础</strong></td><td align="left">数据压缩算法，简单高效。</td><td align="left">概率语言模型，理论上更优。</td></tr><tr><td align="left"><strong>应用代表</strong></td><td align="left">GPT, Llama, RoBERTa</td><td align="left">BERT, T5</td></tr></tbody></table></li></ul><hr><h4 id="1-9-你觉得NLP和LLM最大的区别是什么？两者有何共同和不同之处？"><a href="#1-9-你觉得NLP和LLM最大的区别是什么？两者有何共同和不同之处？" class="headerlink" title="1.9 你觉得NLP和LLM最大的区别是什么？两者有何共同和不同之处？"></a><strong>1.9 你觉得NLP和LLM最大的区别是什么？两者有何共同和不同之处？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  NLP（自然语言处理）和LLM（大型语言模型）之间是领域与技术、一般与具体的关系。LLM是NLP发展至今最前沿、最具影响力的一项技术范式，它在很大程度上重塑了NLP领域。</p><p>  <strong>共同之处：</strong></p><ul><li><strong>最终目标一致：</strong> 两者的根本目标都是实现人工智能对人类语言的理解、生成、和运用，即所谓的“人工智能皇冠上的明珠”。</li><li><strong>技术根基相通：</strong> 现代NLP和LLM都建立在深度学习，特别是神经网络的基础上。Transformer架构是连接两者的关键桥梁，从BERT到GPT，都是其思想的延伸和发展。</li></ul><p>  <strong>最大的区别与不同之处：</strong></p><p>  最大的区别在于<strong>研究和应用范式</strong>的根本性转变，从“为每个任务训练一个模型”转向“用一个模型解决所有任务”。</p><p>  具体可以从以下几个维度来看：</p><ol><li><p><strong>任务处理范式 (Task-Handling Paradigm)：</strong></p><ul><li><strong>传统NLP：</strong> 奉行“分而治之”的策略。研究者会针对每一个具体的NLP任务（如机器翻译、情感分析、命名实体识别）设计特定的模型架构、损失函数和训练数据集，遵循<code>Pre-train -&gt; Fine-tune</code>的流程。每个模型都是一个“专家”。</li><li><strong>LLM：</strong> 追求“大一统”的通用模型。通过在海量数据上进行大规模预训练，一个LLM基础模型就具备了解决多种任务的潜力。用户通过设计不同的 <strong>提示（Prompt）</strong> 或提供 <strong>上下文示例（In-context Learning）</strong> 来引导模型完成任务，大大简化了开发流程，甚至实现了 <strong>零样本（Zero-shot）</strong> 和 <strong>少样本（Few-shot）</strong> 学习。</li></ul></li><li><p><strong>模型能力与“涌现” (Model Capabilities &amp; Emergence)：</strong></p><ul><li><strong>传统NLP：</strong> 模型的能<br>力是明确且有限的，通常与其训练目标直接相关。</li><li><strong>LLM：</strong> 当模型规模（参数、数据、算力）跨越某个阈值后，会表现出小模型上不存在的 <strong>“涌现能力” (Emergent Abilities)</strong> 。例如，复杂的逻辑推理（思维链, Chain-of-Thought）、代码生成、遵循复杂指令等。这些能力不是被直接训练的，而是从海量数据中自发学习到的。</li></ul></li><li><p><strong>规模 (Scale)：</strong></p><ul><li><strong>传统NLP：</strong> 模型参数量通常在百万级到几亿级（例如，BERT-base约1.1亿）。</li><li><strong>LLM：</strong> 参数量从百亿（Billion）起步，发展到千亿甚至万亿级别。训练数据和所需计算资源也比传统NLP模型高出几个数量级。</li></ul></li><li><p><strong>交互与应用方式 (Interaction &amp; Application)：</strong></p><ul><li><strong>传统NLP：</strong> 通常以API形式被集成到软件中，输入输出格式相对固定。</li><li><strong>LLM：</strong> 催生了以<strong>对话</strong>和<strong>指令</strong>为核心的全新交互方式（如ChatGPT），使得AI更加平易近人。应用也从后端工具演变为可以直接面向用户的产品。</li></ul></li></ol><p>  <strong>总结：</strong> 如果说传统NLP是在打造一支由各种“工具专家”组成的工具箱，那么LLM则是在努力打造一个“瑞士军刀”式的通用智能工具，它可能在某些特定任务上不如专用工具精细，但其通用性、灵活性和强大的涌现能力是前所未有的。</p></li></ul><hr><h4 id="1-10-L1和L2正则化分别是什么，什么场景适合使用呢？"><a href="#1-10-L1和L2正则化分别是什么，什么场景适合使用呢？" class="headerlink" title="1.10 L1和L2正则化分别是什么，什么场景适合使用呢？"></a><strong>1.10 L1和L2正则化分别是什么，什么场景适合使用呢？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  L1和L2正则化都是在机器学习和深度学习中用于防止模型过拟合的常用技术。它们通过在模型的损失函数（Loss Function）中添加一个代表模型复杂度的惩罚项来实现这一目标。</p><h4 id="L1-正则化-L1-Regularization-x2F-Lasso"><a href="#L1-正则化-L1-Regularization-x2F-Lasso" class="headerlink" title="L1 正则化 (L1 Regularization &#x2F; Lasso)"></a><strong>L1 正则化 (L1 Regularization &#x2F; Lasso)</strong></h4><ul><li><p><strong>定义：</strong> L1正则化添加的惩罚项是模型所有权重参数 $w_i$ 的<strong>绝对值之和</strong>，乘以一个正则化系数 $\lambda$。</p>  <div align="center">   $$\text{Loss}_{L1} = \text{Original Loss} + \lambda \sum_{i} |w_i|$$  </div>  </li><li><p><strong>核心作用：产生稀疏性 (Sparsity)</strong>。<br>  在梯度下降优化过程中，L1惩罚项会驱使那些对模型贡献不大的特征的权重最终变为<strong>精确的0</strong>。这相当于从模型中完全移除了这些特征。</p></li><li><p><strong>适用场景：特征选择 (Feature Selection)</strong>。<br>  当你的数据集中包含大量特征，但你怀疑其中许多特征是冗余或无用的时，L1正则化非常有用。它能够自动地“筛选”出最重要的特征，简化模型，提高解释性。</p></li></ul><h4 id="L2-正则化-L2-Regularization-x2F-Ridge-x2F-Weight-Decay"><a href="#L2-正则化-L2-Regularization-x2F-Ridge-x2F-Weight-Decay" class="headerlink" title="L2 正则化 (L2 Regularization &#x2F; Ridge &#x2F; Weight Decay)"></a><strong>L2 正则化 (L2 Regularization &#x2F; Ridge &#x2F; Weight Decay)</strong></h4><ul><li><p><strong>定义：</strong> L2正则化添加的惩罚项是模型所有权重参数 $w_i$ 的<strong>平方和</strong>，乘以一个正则化系数 $\lambda$。</p>  <div align="center">  $$\text{Loss}_{L2} = \text{Original Loss} + \lambda \sum_{i} w_i^2$$  </div>  </li><li><p><strong>核心作用：权重衰减 (Weight Decay)</strong>。<br>  L2正则化会惩罚大的权重值，它会促使模型的权重参数尽可能小，<strong>趋近于0但通常不会等于0</strong>。这使得模型的权重分布更加平滑和分散，避免模型过度依赖少数几个高权重的特征。</p></li><li><p><strong>适用场景：通用性的过拟合防治</strong>。<br>  L2是更常用、更通用的正则化方法。当特征之间可能存在相关性（共线性），或者你认为绝大多数特征都对预测有或多或少的贡献时，L2是首选。它能有效地提高模型的泛化能力，使其在未见过的数据上表现更好。在深度学习中，“权重衰减”通常就是指L2正则化。</p></li></ul><p>  <strong>总结对比：</strong></p><table><thead><tr><th align="left">对比项</th><th align="left">L1 正则化</th><th align="left">L2 正则化</th></tr></thead><tbody><tr><td align="left"><strong>惩罚项</strong></td><td align="left">权重的绝对值之和 (L1范数)</td><td align="left">权重的平方和 (L2范数)</td></tr><tr><td align="left"><strong>效果</strong></td><td align="left">权重稀疏化，部分权重为0</td><td align="left">权重平滑化，权重趋近于0</td></tr><tr><td align="left"><strong>主要用途</strong></td><td align="left">特征选择，简化模型</td><td align="left">防止过拟合，提升泛化能力</td></tr><tr><td align="left"><strong>解的特性</strong></td><td align="left">不稳定，数据微小变动可能导致特征集变化</td><td align="left">稳定，解是唯一的</td></tr></tbody></table></li></ul><hr><h4 id="1-11-“涌现能力”是大型模型中一个备受关注的现象，请问你如何理解这个概念？它通常在模型规模达到什么程度时出现？"><a href="#1-11-“涌现能力”是大型模型中一个备受关注的现象，请问你如何理解这个概念？它通常在模型规模达到什么程度时出现？" class="headerlink" title="1.11 “涌现能力”是大型模型中一个备受关注的现象，请问你如何理解这个概念？它通常在模型规模达到什么程度时出现？"></a><strong>1.11 “涌现能力”是大型模型中一个备受关注的现象，请问你如何理解这个概念？它通常在模型规模达到什么程度时出现？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>对“涌现能力”的理解：</strong><br>  “涌现能力”（Emergent Abilities）是指那些<strong>在小型模型中不存在或表现不佳，但当模型规模（包括参数量、训练数据和计算量）达到某个临界点后，突然出现并显著超越随机水平的能力</strong>。</p><p>  它的核心特征是<strong>非线性和不可预测性</strong>：</p><ul><li><strong>非线性增长：</strong> 这种能力的性能表现并不随着模型规模的增加而平滑、线性地提升。相反，它会在某个规模区间内发生“相变”式的跃迁，性能从接近随机猜测的水平迅速提升到非常高的水平。</li><li><strong>非直接训练：</strong> 这些高级能力通常不是通过特定的监督学习目标直接训练出来的。例如，我们没有直接教模型如何“一步一步思考”，但当模型足够大时，它通过学习海量文本中的逻辑关系，自发地获得了这种能力。</li></ul><p>  <strong>典型的涌现能力例子包括：</strong></p><ol><li><strong>思维链（Chain-of-Thought, CoT）：</strong> 在面对需要多步推理的数学或逻辑问题时，通过提示模型“一步一步地思考”，大模型可以生成一个连贯的推理过程并得出正确答案。小模型则无法利用这种提示。</li><li><strong>上下文学习（In-context Learning）：</strong> 无需更新模型权重，仅在Prompt中提供几个任务示例（Few-shot），大模型就能“学会”并执行这个新任务。</li><li><strong>执行复杂指令：</strong> 理解并遵循包含多个步骤、约束和否定逻辑的复杂人类指令。</li></ol><p>  <strong>出现的模型规模：</strong><br>  涌现能力出现的具体规模<strong>没有一个固定的数值</strong>，它取决于能力本身、模型架构、数据质量和评估任务的复杂性。</p><p>  然而，根据Google等机构的标志性研究，许多引人注目的涌现能力，例如<strong>思维链推理</strong>，通常是在模型参数规模达到<strong>百亿（tens of billions）到千亿（a hundred billion）</strong> 级别时开始出现的。</p><ul><li>例如，在Google PaLM模型的实验中，思维链推理能力在<strong>62B参数</strong>的模型上开始显现，而在8B和16B的模型上则完全无效。这种能力随着模型增长到<strong>540B</strong>时变得更加强大和稳定。</li></ul><p>  总而言之，“涌现能力”是“量变引起质变”在大型模型领域的生动体现，它表明单纯地扩大规模可以解锁全新的、更高级的认知能力，这也是当前LLM研究持续推动模型规模增长的核心驱动力之一。</p></li></ul><hr><h4 id="1-12-激活函数有了解吗，你知道哪些LLM常用的激活函数？为什么选用它？"><a href="#1-12-激活函数有了解吗，你知道哪些LLM常用的激活函数？为什么选用它？" class="headerlink" title="1.12 激活函数有了解吗，你知道哪些LLM常用的激活函数？为什么选用它？"></a><strong>1.12 激活函数有了解吗，你知道哪些LLM常用的激活函数？为什么选用它？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  是的，我了解激活函数。激活函数是神经网络中至关重要的一环，它的主要作用是<strong>为网络引入非线性（non-linearity）</strong>。如果没有激活函数，多层神经网络本质上等同于一个单层的线性模型，无法学习和拟合复杂的数据模式。</p><p>  在现代大型语言模型（Transformer架构）中，最常用的激活函数主要有两个：<strong>GeLU</strong> 和 <strong>SwiGLU</strong>。</p><ol><li><p><strong>GeLU (Gaussian Error Linear Unit):</strong></p><ul><li><strong>简介：</strong> GeLU曾是Transformer模型中的主流激活函数，被BERT、GPT-2等经典模型采用。它的数学形式是 $x \cdot \Phi(x)$，其中 $\Phi(x)$ 是高斯分布的累积分布函数。</li><li><strong>为什么选用它？</strong><ul><li><strong>平滑性：</strong> GeLU是ReLU的一个平滑近似。相比于ReLU在0点的突变，GeLU的平滑特性使其在优化过程中梯度更稳定，更有利于模型收敛。</li><li><strong>随机正则化思想：</strong> GeLU可以看作是综合了Dropout和ReLU的思想。它根据输入的数值大小，对其进行随机的“归零”或“保留”，但这个过程是确定性的。输入越小，其输出被“归零”的概率越高。</li></ul></li></ul></li><li><p><strong>SwiGLU (Swish-Gated Linear Unit):</strong></p><ul><li><strong>简介：</strong> SwiGLU是目前<strong>最先进、最主流</strong>的选择，被Llama、PaLM、Mixtral、Gemma等一系列现代LLM广泛采用。它属于<strong>门控线性单元（Gated Linear Unit, GLU）</strong> 家族的变体。</li><li><strong>工作原理：</strong> 它将前馈网络（FFN）的第一个线性层的输出 $X$ 分成两部分， $A$ 和 $B$ 。然后通过公式 $Swish(A) \otimes B$ 计算输出，其中 $Swish(x) &#x3D; x \cdot \sigma(x)$ ， $\sigma$ 是Sigmoid函数， $\otimes$ 是逐元素相乘。</li><li><strong>为什么选用它？</strong><ul><li><strong>门控机制（Gating Mechanism）：</strong> SwiGLU的核心优势在于其“门控”设计。 $B$ 部分可以被看作一个动态的“门”，它可以根据输入内容，控制 $Swish(A)$ 中的信息哪些可以通过、哪些需要被抑制。这种机制<strong>显著增强了模型的表达能力</strong>，使得FFN层可以更灵活地处理信息。</li><li><strong>实证效果优越：</strong> Google在PaLM论文中的实验发现，使用SwiGLU替换标准的GeLU或ReLU，可以<strong>显著提升模型的性能</strong>（降低困惑度）。尽管SwiGLU会增加FFN层的参数量（因为需要两个矩阵而不是一个），但其带来的性能增益被证明是值得的。</li></ul></li></ul></li></ol></li></ul><hr><h4 id="1-13-混合专家模型（MoE）是如何在不显著增加推理成本的情况下，有效扩大模型参数规模的？请简述其工作原理。"><a href="#1-13-混合专家模型（MoE）是如何在不显著增加推理成本的情况下，有效扩大模型参数规模的？请简述其工作原理。" class="headerlink" title="1.13 混合专家模型（MoE）是如何在不显著增加推理成本的情况下，有效扩大模型参数规模的？请简述其工作原理。"></a><strong>1.13 混合专家模型（MoE）是如何在不显著增加推理成本的情况下，有效扩大模型参数规模的？请简述其工作原理。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  混合专家模型（Mixture of Experts, MoE）是一种模型架构，它的核心思想是通过 <strong>“稀疏激活”（Sparse Activation）</strong> 的策略，来解决模型规模与计算成本之间的矛盾。它允许模型拥有巨大的总参数量，但在处理任何一个输入时，只动用其中一小部分参数，从而在不显著增加推理成本（FLOPs）的情况下，大幅提升模型容量。</p><p>  <strong>工作原理如下：</strong></p><ol><li><p><strong>用“专家”替换FFN层：</strong></p><ul><li>在标准的Transformer架构中，计算量最大的部分之一是前馈网络（Feed-Forward Network, FFN）层。</li><li>MoE架构将模型中的部分或全部FFN层替换为<strong>MoE层</strong>。一个MoE层由两部分组成：<ul><li><strong>N个“专家”（Experts）：</strong> 每个专家本身就是一个独立的、规模较小的FFN。</li><li><strong>1个“门控网络”或“路由器”（Gating Network &#x2F; Router）：</strong> 这是一个小型的神经网络，通常是一个简单的线性层。</li></ul></li></ul></li><li><p><strong>动态路由决策：</strong></p><ul><li>当一个词元（token）的向量表示来到MoE层时，它首先被送入<strong>路由器</strong>。</li><li>路由器的作用是 <strong>“决策”</strong> ，判断这个token应该由哪些专家来处理最合适。它会输出一个包含N个分数的向量，代表该token与N个专家的“匹配度”。</li></ul></li><li><p><strong>Top-K稀疏激活：</strong></p><ul><li>路由器输出的分数经过Softmax归一化后，系统并<strong>不会</strong>激活所有的专家。相反，它只选择分数<strong>最高的Top-K个专家</strong>（K通常很小，比如1或2）。</li><li>这就是“稀疏激活”的关键：对于每一个token，只有极少数（K个）专家被激活并进行计算，其余的（N-K个）专家则完全不参与，不产生任何计算成本。</li></ul></li><li><p><strong>加权输出：</strong></p><ul><li>被选中的K个专家分别对输入的token向量进行处理，得到K个输出向量。</li><li>最终的输出是这K个输出向量的<strong>加权和</strong>，权重同样由路由器的输出分数决定。</li></ul></li></ol><p>  <strong>如何实现“参数大但成本低”？</strong></p><ul><li>假设一个模型有8个专家（N&#x3D;8），并且每次只激活2个（K&#x3D;2），如Mixtral-8x7B模型。</li><li><strong>总参数量：</strong> 模型的总参数量是所有共享部分（如注意力层）的参数量，加上<strong>所有8个专家</strong>的参数量之和。这使得模型的总参数规模可以非常大（例如达到47B）。</li><li><strong>推理成本：</strong> 但在进行一次前向传播（推理）时，对于任意一个token，实际参与计算的只有共享部分和<strong>被激活的2个专家</strong>。因此，其计算量（FLOPs）约等于一个规模小得多的“稠密”模型（例如一个13B的模型）。</li><li><strong>结论：</strong> MoE成功地将<strong>总参数量</strong>（代表模型的知识容量）和<strong>单次推理的计算量</strong>（代表模型的速度和成本）<strong>解耦</strong>，从而实现了“用小模型的成本，获得大模型的知识”。</li></ul></li></ul><hr><h4 id="1-14-在训练一个百或千亿参数级别的-LLM-时，你会面临哪些主要的工程和算法挑战？（例如：显存、通信、训练不稳定性等）"><a href="#1-14-在训练一个百或千亿参数级别的-LLM-时，你会面临哪些主要的工程和算法挑战？（例如：显存、通信、训练不稳定性等）" class="headerlink" title="1.14 在训练一个百或千亿参数级别的 LLM 时，你会面临哪些主要的工程和算法挑战？（例如：显存、通信、训练不稳定性等）"></a><strong>1.14 在训练一个百或千亿参数级别的 LLM 时，你会面临哪些主要的工程和算法挑战？（例如：显存、通信、训练不稳定性等）</strong></h4><ul><li><p><strong>参考答案：</strong><br>  训练百亿或千亿参数级别的LLM是一个巨大的系统工程，涉及硬件、软件和算法的深度协同。其挑战主要体现在以下三个方面：</p><p>  <strong>1. 显存挑战 (Memory Wall):</strong></p><ul><li><strong>问题：</strong> 一个千亿参数的模型，其模型参数、梯度、优化器状态（如Adam中的动量和方差）加起来需要数TB的存储空间，远远超出了任何单张GPU的显存（目前最先进的H100也只有80GB）。</li><li><strong>解决方案（3D并行）：</strong><ul><li><strong>数据并行 (Data Parallelism, DP):</strong> 最基础的并行方式。在每张卡上都保留一份完整的模型副本，但将数据切分成多个batch，每张卡处理一个batch。计算完成后通过All-Reduce操作同步梯度。这种方式<strong>不能解决单卡显存不足</strong>的问题。</li><li><strong>流水线并行 (Pipeline Parallelism, PP):</strong> 将模型的层（layers）进行垂直切分，不同的GPU负责模型的一部分（例如，GPU-1负责1-16层，GPU-2负责17-32层）。这<strong>可以有效降低单卡显存</strong>，但会引入“流水线气泡”（pipeline bubbles），即部分GPU在等待上下游数据时会处于空闲状态。</li><li><strong>张量并行 (Tensor Parallelism, TP):</strong> 将模型中的单个大算子（如大的权重矩阵）进行水平切分，放到不同的GPU上协同计算。例如，将一个大的矩阵乘法分解到多张卡上。这也能<strong>降低单卡显存</strong>，但会引入<strong>非常高</strong>的通信开销。</li><li><strong>ZeRO (Zero Redundancy Optimizer):</strong> 由微软DeepSpeed提出的显存优化技术。它在数据并行的基础上，将<strong>优化器状态、梯度、甚至模型参数</strong>也进行切分，分布到所有GPU上。每个GPU只保留自己需要计算的那一部分，极大地降低了单卡的显存冗余，是目前大规模训练的标配。</li></ul></li></ul><p>  <strong>2. 通信挑战 (Communication Bottleneck):</strong></p><ul><li><strong>问题：</strong> 上述所有并行策略都引入了大量的GPU间通信。例如，DP需要同步梯度，PP需要传递激活值，TP需要在每次前向和后向传播中交换计算结果。当GPU数量巨大时，通信所需的时间可能超过计算本身，成为整个训练的瓶颈。</li><li><strong>解决方案：</strong><ul><li><strong>硬件层面：</strong> 使用高速互联技术，如单机内的<strong>NVLink</strong>和跨节点的<strong>InfiniBand</strong>网络。</li><li><strong>软件层面：</strong> 开发高效的通信算法（如Ring All-Reduce），并设计调度策略来将<strong>计算和通信操作重叠（overlap）</strong>，以隐藏通信延迟。</li></ul></li></ul><p>  <strong>3. 训练不稳定性挑战 (Training Instability):</strong></p><ul><li><strong>问题：</strong> 训练如此巨大的模型在数值上非常脆弱。由于计算层数极深、数据量极大，训练过程中很容易出现<strong>梯度爆炸或消失</strong>，导致损失（Loss）突然飙升为NaN（Not a Number），使得数小时甚至数天的训练成果毁于一旦。</li><li><strong>解决方案：</strong><ul><li><strong>数值精度：</strong> 普遍采用 <strong>BF16 (BFloat16)</strong> 混合精度训练。BF16相比FP16有更大的动态范围，能有效避免梯度下溢，同时保持FP32的稳定性。同时，关键部分（如优化器的master weights）仍保留FP32以保证精度。</li><li><strong>稳定的模型架构：</strong> 采用更稳定的架构设计，如<strong>Pre-LayerNorm</strong>（在自注意力和FFN之前进行层归一化），以及使用更平滑的激活函数如<strong>GeLU&#x2F;SwiGLU</strong>。</li><li><strong>梯度裁剪 (Gradient Clipping):</strong> 设定一个梯度的范数上限，如果计算出的梯度超过这个阈值，就将其缩放到阈值以内，这是防止梯度爆炸最直接有效的方法。</li><li><strong>学习率调度与预热 (Learning Rate Scheduling &amp; Warmup):</strong> 采用精心设计的学习率调度策略，如在训练初期使用一个较小的学习率并逐渐增大的“预热”阶段，有助于模型在训练早期稳定下来。</li></ul></li></ul></li></ul><hr><h4 id="1-15-开源框架了解过哪些？Qwen，Deepseek的论文是否有研读过，说一下其中的创新点主要体现在哪？"><a href="#1-15-开源框架了解过哪些？Qwen，Deepseek的论文是否有研读过，说一下其中的创新点主要体现在哪？" class="headerlink" title="1.15 开源框架了解过哪些？Qwen，Deepseek的论文是否有研读过，说一下其中的创新点主要体现在哪？"></a><strong>1.15 开源框架了解过哪些？Qwen，Deepseek的论文是否有研读过，说一下其中的创新点主要体现在哪？</strong></h4><ul><li><p><strong>参考答案：</strong></p><p>  <strong>开源框架：</strong></p><ul><li><strong>基础框架：</strong> <strong>PyTorch</strong> 是目前大模型研究和开发的事实标准，提供了灵活的张量计算和自动微分能力。</li><li><strong>模型与生态：</strong> <strong>Hugging Face Transformers</strong> 是最重要的模型库和生态系统，它极大地降低了使用和分享模型的门槛。</li><li><strong>大规模训练：</strong> <strong>DeepSpeed</strong> (微软) 和 <strong>Megatron-LM</strong> (英伟达) 是进行大规模分布式训练的核心框架，它们实现了上述的3D并行、ZeRO等关键技术。</li><li><strong>高效推理：</strong> <strong>vLLM</strong>, <strong>TensorRT-LLM</strong> 等框架专注于优化LLM的推理速度和吞吐量，通过PagedAttention等技术来解决KV Cache的显存瓶颈。</li></ul><p>  <strong>Qwen系列（可以参考开源论文自行回答，Qwen2.5，Qwen3系列）</strong></p><p>  <strong>Deepseek系列（可以参考开源论文自行回答，如GRPO）</strong></p></li></ul><hr><h4 id="1-16-最近读过哪些LLM比较前沿的论文，聊一下它的相关方法，针对什么问题，提出了什么方法，对比实验有哪些？"><a href="#1-16-最近读过哪些LLM比较前沿的论文，聊一下它的相关方法，针对什么问题，提出了什么方法，对比实验有哪些？" class="headerlink" title="1.16 最近读过哪些LLM比较前沿的论文，聊一下它的相关方法，针对什么问题，提出了什么方法，对比实验有哪些？"></a><strong>1.16 最近读过哪些LLM比较前沿的论文，聊一下它的相关方法，针对什么问题，提出了什么方法，对比实验有哪些？</strong></h4><ul><li><strong>参考答案：</strong><br>  <strong>(这是一个开放性问题，回答时应选择1-2篇自己真正理解的、有影响力的近期论文。)</strong></li></ul><h3 id="2-VLM-八股"><a href="#2-VLM-八股" class="headerlink" title="2. VLM 八股"></a><strong>2. VLM 八股</strong></h3><h4 id="2-1-多模态大模型（如-VLM）的核心挑战是什么？即如何实现不同模态信息（如视觉和语言）的有效对齐和融合？"><a href="#2-1-多模态大模型（如-VLM）的核心挑战是什么？即如何实现不同模态信息（如视觉和语言）的有效对齐和融合？" class="headerlink" title="2.1 多模态大模型（如 VLM）的核心挑战是什么？即如何实现不同模态信息（如视觉和语言）的有效对齐和融合？"></a><strong>2.1 多模态大模型（如 VLM）的核心挑战是什么？即如何实现不同模态信息（如视觉和语言）的有效对齐和融合？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  多模态大模型（VLM）的核心挑战在于解决 <strong>“模态鸿沟”（Modality Gap）</strong> 。视觉信息（如图像、视频）是以像素矩阵的形式存在的，密集、具体且连续；而语言信息是以离散的符号（token）序列存在的，稀疏、抽象且结构化。如何让模型跨越这两种完全不同的数据形式，实现有效的理解和推理，是VLM研究的中心问题。</p><p>  这个挑战的解决方案主要包含两个关键环节：</p><ol><li><p><strong>对齐（Alignment）：建立跨模态的语义连接</strong></p><ul><li><strong>目标：</strong> 对齐的目标是让模型理解视觉世界中的“概念”和人类语言中的“符号”是指代的同一事物。例如，模型需要知道图片中的一只奔跑的狗的像素集合，和文本描述“a running dog”在语义上是等价的。</li><li><strong>实现方式：</strong> 主流方法是<strong>表示空间对齐</strong>。通过设计一个训练任务，将图像和其对应的文本描述映射到一个共享的或可比较的向量空间中。在这个空间里，匹配的图文对的向量表示距离很近，而不匹配的图文对则距离很远。CLIP模型使用的对比学习就是实现对齐的经典范式。</li></ul></li><li><p><strong>融合（Fusion）：实现跨模态信息的深度交互</strong></p><ul><li><strong>目标：</strong> 在对齐的基础上，让两种模态的信息能够深度地交互，以完成更复杂的推理任务，而不仅仅是识别。例如，回答“图片中穿红色衣服的人在做什么？”就需要同时理解“红色衣服”（视觉属性）和“做什么”（动作识别），并将它们结合起来推理。</li><li><strong>实现方式：</strong> 主流的融合方法包括：<ul><li><strong>连接器（Connector）：</strong> 将视觉编码器提取的视觉特征，通过一个小的、可训练的模块（如MLP或Q-Former），转换为LLM能够理解的“视觉词元”（Visual Tokens），然后与文本词元拼接起来，送入LLM统一处理。LLaVA是这种方式的代表。</li><li><strong>跨模态注意力（Cross-Attention）：</strong> 在LLM的某些层中插入跨模态注意力模块，让文本表示（作为Query）能够“查询”视觉表示（作为Key和Value），从而在生成文本的每一步都能动态地关注到图像的不同区域。Flamingo和BLIP-2是这种方式的代表。</li></ul></li></ul></li></ol></li></ul><hr><h4 id="2-2-请解释-CLIP-模型的工作原理。它是如何通过对比学习来连接图像和文本的？"><a href="#2-2-请解释-CLIP-模型的工作原理。它是如何通过对比学习来连接图像和文本的？" class="headerlink" title="2.2 请解释 CLIP 模型的工作原理。它是如何通过对比学习来连接图像和文本的？"></a><strong>2.2 请解释 CLIP 模型的工作原理。它是如何通过对比学习来连接图像和文本的？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  CLIP（Contrastive Language-Image Pre-training）是一个通过在海量图文对数据上进行预训练，从而学会将图像和文本关联起来的 foundational model。它的核心是利用 <strong>对比学习（Contrastive Learning）</strong> 来打通视觉和语言两个模态。</p><p>  <strong>工作原理如下：</strong></p><ol><li><p><strong>双编码器架构（Dual-Encoder Architecture）：</strong></p><ul><li><strong>图像编码器（Image Encoder）：</strong> 通常是一个标准的视觉模型，如ResNet或Vision Transformer (ViT)，负责将输入的图像转换成一个高维的特征向量。</li><li><strong>文本编码器（Text Encoder）：</strong> 通常是一个Transformer模型，负责将输入的文本描述转换成一个同维度的高维特征向量。</li></ul></li><li><p><strong>共享嵌入空间（Shared Embedding Space）：</strong><br>模型的目标是将图像和文本的特征向量投影到一个共享的多模态嵌入空间中。在这个空间里，语义相似的图像和文本的向量应该彼此靠近。</p></li><li><p><strong>对比学习训练目标：</strong><br>训练过程在一个包含N个（图像，文本）对的批次（Batch）中进行：</p><ul><li><strong>正样本（Positive Pairs）：</strong> 对于批次中的任意一个图像，其对应的文本描述是唯一的正样本。反之亦然。</li><li><strong>负样本（Negative Pairs）：</strong> 批次中所有其他的（N-1）个文本描述都是该图像的负样本。同理，所有其他的（N-1）个图像也是该文本的负样本。</li><li><strong>目标函数（InfoNCE Loss）：</strong> 模型的目标是<strong>最大化</strong>正样本对（匹配的图文）特征向量之间的<strong>余弦相似度</strong>，同时<strong>最小化</strong>所有负样本对（不匹配的图文）特征向量之间的余弦相似度。</li><li>通过这种方式，模型被“逼迫”去学习图像内容和文本描述之间的内在联系。例如，当看到一张猫的图片和文本“a photo of a cat”时，模型会提高它们的相似度；而当看到猫的图片和文本“a photo of a dog”时，则会降低它们的相似度。</li></ul></li></ol><p>  经过大规模数据（4亿图文对）的训练，CLIP的编码器能够生成高度泛化的、语义丰富的特征，使其在零样本（zero-shot）图像分类等任务上表现出色，因为它能理解自然语言描述的视觉概念。</p></li></ul><hr><h4 id="2-3-像-LLaVA-或-MiniGPT-4-这样的模型是如何将一个预训练好的视觉编码器（Vision-Encoder）和一个大语言模型（LLM）连接起来的？请描述其关键的架构设计。"><a href="#2-3-像-LLaVA-或-MiniGPT-4-这样的模型是如何将一个预训练好的视觉编码器（Vision-Encoder）和一个大语言模型（LLM）连接起来的？请描述其关键的架构设计。" class="headerlink" title="2.3 像 LLaVA 或 MiniGPT-4 这样的模型是如何将一个预训练好的视觉编码器（Vision Encoder）和一个大语言模型（LLM）连接起来的？请描述其关键的架构设计。"></a><strong>2.3 像 LLaVA 或 MiniGPT-4 这样的模型是如何将一个预训练好的视觉编码器（Vision Encoder）和一个大语言模型（LLM）连接起来的？请描述其关键的架构设计。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  LLaVA和MiniGPT-4这类模型开创了一种高效构建强大VLM的范式，其核心思想是<strong>复用（leverage）</strong> 已经非常强大的预训练单模态模型，并通过一个轻量级的“<strong>连接器</strong>”将它们桥接起来。</p><p>  其关键架构设计通常包含三个核心组件：</p><ol><li><p><strong>冻结的视觉编码器（Frozen Vision Encoder）：</strong></p><ul><li>通常采用一个已经预训练好的、强大的视觉模型，最常见的是CLIP的Vision Transformer (ViT)。</li><li>在训练VLM时，这个视觉编码器大部分时间是<strong>冻结的</strong>，不更新其参数。这样做的好处是保留了其强大的、泛化的视觉特征提取能力，并极大地节省了计算资源。</li><li>它的作用是将输入的图像转换成一系列的视觉特征向量（Image Patches’ Embeddings）。</li></ul></li><li><p><strong>连接器模块（Connector Module）：</strong></p><ul><li>这是整个架构的关键“胶水层”。它的作用是将来自视觉编码器的视觉特征，<strong>转换</strong>成大语言模型（LLM）能够理解的输入格式，即与文本词元（word embeddings）在同一向量空间中的“<strong>视觉词元</strong>”（visual tokens）。</li><li>在LLaVA中，这个连接器是一个简单的<strong>线性投影层（Linear Projection Layer）</strong>。</li><li>在MiniGPT-4或BLIP-2中，这个连接器是一个更复杂的<strong>Q-Former (Querying Transformer)</strong>，它通过一组可学习的查询向量来从视觉特征中“浓缩”出最相关的信息。</li><li>这个模块是整个模型中主要<strong>需要训练</strong>的部分。</li></ul></li><li><p><strong>冻结的大语言模型（Frozen Large Language Model）：</strong></p><ul><li>使用一个现成的、强大的预训练LLM，如Llama、Vicuna等。</li><li>LLM在训练中也通常是<strong>冻结的</strong>（或使用LoRA等参数高效微调方法）。这保留了LLM强大的语言生成、推理和指令遵循能力。</li><li>LLM接收拼接后的序列（视觉词元 + 文本词元），并像处理纯文本一样，自回归地生成回答。</li></ul></li></ol><p>  <strong>训练过程通常分为两阶段：</strong></p><ul><li><strong>第一阶段（视觉-语言对齐预训练）：</strong> 使用大量的图像-标题数据，只训练连接器模块，目的是教会连接器如何将视觉特征有效地映射为LLM能理解的表示。</li><li><strong>第二阶段（视觉指令微调）：</strong> 使用高质量、多样化的多模态指令跟随数据（例如，图像+问题+答案），对整个模型（主要是连接器和LLM的LoRA部分）进行微调，教会模型如何根据指令进行对话、描述和推理。</li></ul></li></ul><hr><h4 id="2-4-什么是视觉指令微调？为什么说它是让-VLM-具备良好对话和指令遵循能力的关键步骤？"><a href="#2-4-什么是视觉指令微调？为什么说它是让-VLM-具备良好对话和指令遵循能力的关键步骤？" class="headerlink" title="2.4 什么是视觉指令微调？为什么说它是让 VLM 具备良好对话和指令遵循能力的关键步骤？"></a><strong>2.4 什么是视觉指令微调？为什么说它是让 VLM 具备良好对话和指令遵循能力的关键步骤？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>视觉指令微调（Visual Instruction Tuning, VIT）</strong> 是一种训练方法，它使用一个由大量“指令-响应”对组成的数据集来微调一个预训练好的VLM。与传统任务（如VQA、图像描述）的数据集不同，指令微调数据集的格式更加多样和自由，旨在模拟人类与智能助手的交互方式。</p><p>  每条数据通常包含三个部分：</p><ol><li><strong>视觉输入（Vision Input）：</strong> 一张图片或视频。</li><li><strong>指令（Instruction）：</strong> 一个用自然语言提出的、与视觉输入相关的任务或问题。例如，“请详细描述这幅画的风格”，“图中最高的建筑物是什么？”，“根据这张图写一个三句话的故事”。</li><li><strong>响应（Response）：</strong> 针对该指令的理想回答。</li></ol><p>  <strong>为什么是关键步骤？</strong></p><p>  视觉指令微调是连接 VLM <strong>基础能力</strong>与<strong>应用能力</strong>的桥梁，其关键性体现在：</p><ol><li><strong>泛化到未知任务：</strong> 传统的VQA或描述模型只能执行它们被训练过的特定任务。而通过在成千上万种不同指令上进行微调，模型学会了<strong>理解指令意图</strong>的泛化能力。它不再是死板地回答“what is this?”，而是能理解“describe”、“compare”、“explain why”等各种指令背后的复杂要求。</li><li><strong>激发LLM的潜力：</strong> 经过对齐预训练后，VLM只是学会了将视觉信息“翻译”给LLM。而指令微调则真正教会了LLM<strong>如何使用</strong>这些视觉信息来完成推理、遵循复杂指令和进行多轮对话。它将LLM固有的强大能力（如常识推理、代码生成、创意写作）与视觉输入结合了起来。</li><li><strong>对齐人类交互模式：</strong> 指令微调使得模型的输出格式和交互方式更符合人类的期望，使其表现得更像一个真正的“多模态对话助手”，而不是一个任务单一的工具。这是模型从“可用”到“好用”的决定性一步。</li></ol></li></ul><hr><h4 id="2-5-在处理视频等多模态数据时，相比于静态图片，VLM-需要额外解决哪些问题？（例如，如何表征时序信息？）"><a href="#2-5-在处理视频等多模态数据时，相比于静态图片，VLM-需要额外解决哪些问题？（例如，如何表征时序信息？）" class="headerlink" title="2.5 在处理视频等多模态数据时，相比于静态图片，VLM 需要额外解决哪些问题？（例如，如何表征时序信息？）"></a><strong>2.5 在处理视频等多模态数据时，相比于静态图片，VLM 需要额外解决哪些问题？（例如，如何表征时序信息？）</strong></h4><ul><li><p><strong>参考答案：</strong><br>  处理视频数据引入了<strong>时间维度</strong>，这带来了相比静态图片额外且独特的挑战：</p><ol><li><p><strong>时序信息表征（Temporal Information Representation）：</strong></p><ul><li><strong>挑战：</strong> 视频的核心在于动态变化、动作和事件的发生顺序。模型必须能够理解帧与帧之间的时序关系，例如物体的运动轨迹、动作的连续性、事件的因果关系等。</li><li><strong>解决方案：</strong><ul><li><strong>帧采样+融合：</strong> 从视频中抽取部分关键帧，分别提取它们的特征，然后通过一个时间融合模块（如时间注意力、3D卷积或简单的拼接池化）来聚合时序信息。</li><li><strong>时空建模：</strong> 使用能够直接处理时空数据的网络结构，如3D CNN或Video Transformer (ViViT)，在提取特征的阶段就同时考虑空间和时间维度。</li></ul></li></ul></li><li><p><strong>巨大的计算和存储开销：</strong></p><ul><li><strong>挑战：</strong> 视频本质上是图像序列，一个短视频可能包含数百甚至数千帧，数据量远超单张图片。这导致了巨大的计算（模型前向传播）和显存（存储特征）开销。</li><li><strong>解决方案：</strong><ul><li><strong>稀疏采样：</strong> 采用智能的帧采样策略，只处理变化显著或具有代表性的帧。</li><li><strong>特征压缩：</strong> 对逐帧提取的特征进行压缩或池化，减少送入后续模型的Token数量。</li></ul></li></ul></li><li><p><strong>长距离依赖建模：</strong></p><ul><li><strong>挑战：</strong> 视频中的关键因果关系可能跨越很长的时间窗口（例如，一个视频开头的铺垫可能要到结尾才揭示其意义）。模型需要具备捕捉这种长距离时间依赖的能力。</li><li><strong>解决方案：</strong> 采用类似Transformer的架构来建模帧之间的关系，利用其全局感受野的优势。</li></ul></li><li><p><strong>多模态融合的复杂性增加：</strong></p><ul><li><strong>挑战：</strong> 视频通常还伴随着<strong>音频</strong>（语音、背景音）和<strong>字幕</strong>等模态。VLM需要解决将视觉时序信息、音频流信息和文本信息同步对齐和融合的难题。</li><li><strong>解决方案：</strong> 设计更复杂的对齐和融合模块，能够处理多个异步或同步的时间序列数据。</li></ul></li></ol></li></ul><hr><h4 id="2-6-请解释Grounding在-VLM-领域中的含义。我们如何评估一个-VLM-是否能将文本描述准确地对应到图片中的特定区域？"><a href="#2-6-请解释Grounding在-VLM-领域中的含义。我们如何评估一个-VLM-是否能将文本描述准确地对应到图片中的特定区域？" class="headerlink" title="2.6 请解释Grounding在 VLM 领域中的含义。我们如何评估一个 VLM 是否能将文本描述准确地对应到图片中的特定区域？"></a><strong>2.6 请解释Grounding在 VLM 领域中的含义。我们如何评估一个 VLM 是否能将文本描述准确地对应到图片中的特定区域？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  在VLM领域，<strong>Grounding（定位或指代）</strong> 指的是将语言中的某个特定概念或短语（a phrase or a concept）与图像中的<strong>特定像素区域（a specific pixel region）</strong> 建立准确对应关系的能力。简单来说，就是模型不仅知道图片里“有什么”，还要知道“在哪里”。</p><p>  例如，对于指令“请告诉我图片中那只戴着红色项圈的黑猫”，一个具备Grounding能力的模型，其内部注意力机制应该能够准确地聚焦在图片中黑猫所在的区域，而不是图片中的其他物体或背景。</p><p>  <strong>如何评估Grounding能力？</strong></p><p>  评估Grounding能力通常需要带有<strong>位置标注</strong>的数据集（如RefCOCO, Visual Genome），评估方法主要有：</p><ol><li><p><strong>指代短语定位（Referring Expression Grounding）：</strong></p><ul><li><strong>任务：</strong> 给定一张图片和一个描述图片中某个物体的短语（如“the woman in the red dress”），模型需要输出该物体的位置，通常是一个<strong>边界框（Bounding Box）</strong>。</li><li><strong>评估指标：</strong> 将模型预测的边界框与人工标注的真实边界框（Ground Truth BBox）进行比较，计算它们的<strong>交并比（Intersection over Union, IoU）</strong>。<div align="center">$$\text{IoU} = \frac{\text{Area of Overlap}}{\text{Area of Union}}$$</div></li></ul><p>通常会设定一个IoU阈值（如0.5或0.75），如果模型预测的IoU超过该阈值，则认为定位正确。最后计算<strong>准确率（Accuracy@IoU&gt;threshold）</strong>。</p></li><li><p><strong>视觉Grounding对话：</strong></p><ul><li><strong>任务：</strong> 在对话中，当模型生成引用了图片中某个物体的文本时，同时输出该物体的位置。</li><li><strong>评估：</strong> 这类评估更复杂，可能需要人工判断模型生成的文本和其对应的边界框是否一致且准确。一些新的基准（如Shikra, GPT4-ROI）正在探索这类评估方式。</li></ul></li><li><p><strong>注意力图可视化（定性分析）：</strong></p><ul><li><strong>方法：</strong> 虽然不是一个定量的指标，但通过可视化模型在生成与某个物体相关的文本时，其内部注意力机制的激活区域，可以直观地判断模型是否“看对”了地方。如果生成“猫”这个词时，注意力主要集中在猫的区域，说明其具备一定的隐式Grounding能力。</li></ul></li></ol></li></ul><hr><h4 id="2-7-请对比至少两种不同的-VLM-架构范式，并分析它们的优劣。"><a href="#2-7-请对比至少两种不同的-VLM-架构范式，并分析它们的优劣。" class="headerlink" title="2.7 请对比至少两种不同的 VLM 架构范式，并分析它们的优劣。"></a><strong>2.7 请对比至少两种不同的 VLM 架构范式，并分析它们的优劣。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  当前主流的VLM架构范式，根据视觉和语言信息融合方式的不同，主要可以分为两大类：<strong>基于连接器的架构</strong> 和 <strong>基于跨模态注意力的架构</strong>。</p><table><thead><tr><th><strong>架构范式</strong></th><th><strong>基于连接器（Connector-based）</strong></th><th><strong>基于跨模态注意力（Cross-Attention-based）</strong></th></tr></thead><tbody><tr><td><strong>代表模型</strong></td><td>LLaVA, MiniGPT-4</td><td>Flamingo, BLIP-2</td></tr><tr><td><strong>核心思想</strong></td><td><strong>前期对齐，后期融合</strong>。将视觉特征通过一个轻量级模块“翻译”成LLM能理解的“视觉词元”，然后与文本词元拼接，让LLM统一处理。</td><td><strong>边生成边融合</strong>。在LLM内部插入跨模态注意力层，允许文本特征在生成的每一步都动态地“查询”和“参考”视觉特征。</td></tr><tr><td><strong>工作流程</strong></td><td>1. 视觉编码器提特征<br>2. 连接器将视觉特征转为定长的Visual Tokens<br>3. <code>[Visual Tokens] + [Text Tokens]</code> 送入LLM</td><td>1. 视觉编码器提特征<br>2. LLM在生成文本时，其内部的Query会与视觉特征的Key&#x2F;Value进行Cross-Attention计算，动态注入视觉信息。</td></tr><tr><td><strong>优势</strong></td><td><strong>1. 训练和推理效率高：</strong> 只需训练一个轻量级的连接器，且可以复用强大的预训练视觉和语言模型，成本较低。<br><strong>2. 架构简洁优雅：</strong> 实现简单，易于扩展和复现。<br><strong>3. 性能强大：</strong> 在许多基准上证明了其有效性，尤其是在视觉指令跟随方面。</td><td><strong>1. 深度融合：</strong> 视觉和语言信息的交互发生在LLM的每一层或多层，融合得更充分、更深入。<br><strong>2. 少样本学习能力强：</strong> Flamingo证明了这种架构在上下文少样本学习（in-context few-shot learning）上表现极其出色。<br><strong>3. 对视觉细节的动态捕捉：</strong> 在生成长文本时，可以根据需要动态地关注图像的不同部分。</td></tr><tr><td><strong>劣势</strong></td><td><strong>1. 信息瓶颈：</strong> 视觉信息被连接器压缩成固定数量的“视觉词元”，可能在转换过程中丢失部分细节，存在信息瓶颈。<br><strong>2. 融合深度较浅：</strong> 视觉和语言的融合完全依赖于LLM自身的自注意力机制，不如显式的跨模态注意力来得直接。</td><td><strong>1. 架构复杂，训练成本高：</strong> 需要修改LLM的内部结构，并进行大规模的训练，计算开销巨大。<br><strong>2. 推理速度较慢：</strong> 额外的跨模态注意力计算增加了推理时的延迟。</td></tr></tbody></table><p>  <strong>总结：</strong> 基于连接器的架构是当前实现高性价比、高性能VLM的主流方案，追求效率和简洁。而基于跨模态注意力的架构则代表了追求极致性能和深度融合的方向，但成本更高。</p></li></ul><hr><h4 id="2-8-在-VLM-的应用中，如何处理高分辨率的输入图像？这会带来哪些计算和模型设计上的挑战？"><a href="#2-8-在-VLM-的应用中，如何处理高分辨率的输入图像？这会带来哪些计算和模型设计上的挑战？" class="headerlink" title="2.8 在 VLM 的应用中，如何处理高分辨率的输入图像？这会带来哪些计算和模型设计上的挑战？"></a><strong>2.8 在 VLM 的应用中，如何处理高分辨率的输入图像？这会带来哪些计算和模型设计上的挑战？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  处理高分辨率图像是当前VLM领域的一个重要挑战，因为标准的视觉编码器（如ViT）通常被设计用于处理低分辨率的固定尺寸输入（例如224x224或336x336）。</p><p>  <strong>带来的挑战：</strong></p><ol><li><strong>计算量爆炸：</strong> Vision Transformer (ViT) 将图像分割成固定大小的图块（Patches）。输入图像的分辨率如果从224x224增加到448x448，边长变为2倍，图块数量会变为4倍。而自注意力机制的计算复杂度与输入序列长度（即图块数量）的平方成正比，这意味着计算量会变为原来的<strong>16倍</strong>，这是不可接受的。</li><li><strong>位置编码失效：</strong> 预训练好的ViT的位置编码是针对特定数量的图块进行学习或设计的。输入更高分辨率的图像会导致图块数量增加，超出现有的位置编码范围，导致模型无法理解图块的相对位置。</li><li><strong>显存占用剧增：</strong> 更多的图块意味着更长的序列，在Transformer的每一层都需要存储巨大的激活值，导致显存占用急剧增加。</li></ol><p>  <strong>处理方法：</strong></p><p>  目前主要有以下几种策略来处理高分辨率图像：</p><ol><li><p><strong>切片-编码-拼接（Slicing-based approach）：</strong></p><ul><li><strong>方法：</strong> 将高分辨率图像切割成多个重叠或不重叠的低分辨率子图（例如，切成4个或6个224x224的图块）。将每个子图独立地送入标准的视觉编码器提取特征，最后将所有子图的特征拼接或融合起来，作为LLM的视觉输入。</li><li><strong>代表模型：</strong> LLaVA-1.5 的部分实现思路。</li><li><strong>优点：</strong> 简单有效，可以直接利用预训练好的低分辨率模型。</li><li><strong>缺点：</strong> 破坏了图像的全局结构，模型难以理解跨越不同切片的物体。</li></ul></li><li><p><strong>可变分辨率图块（Variable-size Patches）：</strong></p><ul><li><strong>方法：</strong> 保持图块数量不变，但根据输入分辨率动态调整每个图块的大小。例如，对于高分辨率图像，使用更大的图块尺寸。</li><li><strong>优点：</strong> 保持了固定的序列长度，避免了计算量爆炸。</li><li><strong>缺点：</strong> 大图块会丢失局部细节信息，需要对模型进行相应的预训练或微调。</li></ul></li><li><p><strong>多尺度特征融合（Multi-scale Feature Fusion）：</strong></p><ul><li><strong>方法：</strong> 设计一个可以处理高分辨率图像的视觉编码器（如Swin Transformer），并从其不同层级提取多尺度的特征图。然后通过一个特征金字塔网络（FPN）或类似结构将这些特征融合，再送入一个适配器模块（Adapter）转换成固定长度的序列给LLM。</li><li><strong>代表模型：</strong> Fuyu-8B, Monkey。</li><li><strong>优点：</strong> 能够在保留细节的同时兼顾全局信息。</li><li><strong>缺点：</strong> 需要更复杂的视觉主干网络和适配器设计。</li></ul></li></ol></li></ul><hr><h4 id="2-9-VLM-在生成内容时，同样会遇到“幻觉”（Hallucination）问题，但它的表现形式和纯文本-LLM-有何不同？请举例说明。"><a href="#2-9-VLM-在生成内容时，同样会遇到“幻觉”（Hallucination）问题，但它的表现形式和纯文本-LLM-有何不同？请举例说明。" class="headerlink" title="2.9 VLM 在生成内容时，同样会遇到“幻觉”（Hallucination）问题，但它的表现形式和纯文本 LLM 有何不同？请举例说明。"></a><strong>2.9 VLM 在生成内容时，同样会遇到“幻觉”（Hallucination）问题，但它的表现形式和纯文本 LLM 有何不同？请举例说明。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  VLM和纯文本LLM都会产生“幻觉”，即生成与事实不符或无中生有的内容。但VLM的幻觉是<strong>基于视觉输入的</strong>，其表现形式与纯文本LLM有显著不同，主要体现在将错误的、不存在的视觉事实强行“植入”到描述中。</p><p>  <strong>VLM幻觉的主要表现形式：</strong></p><ol><li><p><strong>物体幻觉（Object Hallucination）：</strong></p><ul><li><strong>描述：</strong> 这是最常见的幻觉形式，即模型描述了图像中<strong>完全不存在</strong>的物体。</li><li><strong>与LLM区别：</strong> 纯文本LLM的物体幻觉是凭空捏造（如编造一个不存在的书名），而VLM的物体幻觉是错误地“看”到了图像中没有的东西。</li><li><strong>举例：</strong><ul><li><strong>输入图像：</strong> 一只猫坐在沙发上。</li><li><strong>VLM幻觉输出：</strong> “一只猫和一只<strong>小狗</strong>正舒适地躺在沙发上。”（图像中并没有狗）</li></ul></li></ul></li><li><p><strong>属性幻觉（Attribute Hallucination）：</strong></p><ul><li><strong>描述：</strong> 模型正确识别了图像中的物体，但错误地描述了该物体的<strong>属性</strong>，如颜色、形状、大小、数量等。</li><li><strong>与LLM区别：</strong> 纯文本LLM的属性幻觉是记错了事实（如“法国的首都是柏林”），而VLM的属性幻觉是看错了图像细节。</li><li><strong>举例：</strong><ul><li><strong>输入图像：</strong> 一个穿着蓝色衬衫的男人。</li><li><strong>VLM幻觉输出：</strong> “一个穿着<strong>红色</strong>衬衫的男人站在窗前。”（颜色错误）</li><li><strong>输入图像：</strong> 桌子上有两个苹果。</li><li><strong>VLM幻觉输出：</strong> “桌上放着<strong>三个</strong>苹果。”（数量错误）</li></ul></li></ul></li><li><p><strong>关系幻觉（Relationship Hallucination）：</strong></p><ul><li><strong>描述：</strong> 模型正确识别了多个物体，但错误地描述了它们之间的<strong>空间位置</strong>或<strong>交互关系</strong>。</li><li><strong>与LLM区别：</strong> 纯文本LLM的关系幻觉是混淆了概念关系（如“牛顿发现了相对论”），而VLM的关系幻觉是混淆了物理空间关系。</li><li><strong>举例：</strong><ul><li><strong>输入图像：</strong> 一本书放在一个杯子旁边。</li><li><strong>VLM幻觉输出：</strong> “一本书放在一个杯子<strong>里面</strong>。”（空间关系错误）</li><li><strong>输入图像：</strong> 一个女孩在追逐一个皮球。</li><li><strong>VLM幻觉输出：</strong> “一个皮球在追逐一个女孩。”（动作关系错误）</li></ul></li></ul></li></ol></li></ul><hr><h4 id="2-10-除了图片描述和视觉问答（VQA），你还能列举出-VLM-的哪些前沿或具有潜力的应用方向？"><a href="#2-10-除了图片描述和视觉问答（VQA），你还能列举出-VLM-的哪些前沿或具有潜力的应用方向？" class="headerlink" title="2.10 除了图片描述和视觉问答（VQA），你还能列举出 VLM 的哪些前沿或具有潜力的应用方向？"></a><strong>2.10 除了图片描述和视觉问答（VQA），你还能列举出 VLM 的哪些前沿或具有潜力的应用方向？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  除了基础的图片描述和视觉问答，VLM正在向更复杂、更具交互性的前沿方向发展，展现出巨大的应用潜力：</p><ol><li><p><strong>多模态对话系统与个人助手：</strong></p><ul><li>用户可以发送图片、截图，并围绕这些视觉信息与助手进行多轮、深入的对话。例如，“帮我看看这张冰箱里的图片，晚上能做什么菜？”“如果用鸡蛋和西红柿，具体步骤是什么？”</li></ul></li><li><p><strong>视觉定位与指令执行（Visual Grounding &amp; Grounded Agents）：</strong></p><ul><li>VLM不仅能理解图像内容，还能在图像上进行定位和操作。这可以用于：<ul><li><strong>UI自动化：</strong> 指挥VLM“点击那个写着‘提交’的蓝色按钮”，VLM能理解指令并定位按钮位置。</li><li><strong>具身智能（Embodied AI）：</strong> 作为机器人的“大脑”，VLM可以理解摄像头捕捉的实时画面，并根据指令（如“把桌上的红苹果拿给我”）规划并执行动作。</li></ul></li></ul></li><li><p><strong>专业领域的视觉分析助手：</strong></p><ul><li><strong>医疗影像分析：</strong> 辅助医生解读X光片、CT扫描图，识别异常并生成初步报告。</li><li><strong>工业质检：</strong> 在生产线上实时分析产品图像，检测瑕疵和缺陷。</li><li><strong>保险定损：</strong> 上传车辆事故照片，VLM可以自动评估损伤程度和维修方案。</li></ul></li><li><p><strong>内容创作与代码生成：</strong></p><ul><li><strong>所见即所得的网页&#x2F;App生成：</strong> 用户上传一张设计草图或UI截图，VLM可以直接生成实现该界面的前端代码（HTML&#x2F;CSS&#x2F;JavaScript）。</li><li><strong>图表和数据可视化解读：</strong> VLM可以“阅读”复杂的图表（如流程图、柱状图、K线图），提取关键信息，并生成数据摘要或代码进行复现。</li></ul></li><li><p><strong>教育与无障碍辅助：</strong></p><ul><li><strong>实时场景描述：</strong> 为视障人士实时描述周围的环境、识别物体、阅读文字。</li><li><strong>交互式学习：</strong> 拍下教科书上的一张图或一道题，VLM可以提供详细的讲解和相关的知识点。</li></ul></li></ol></li></ul><hr><h4 id="2-11-有没有做过VLM相关方面的微调？什么模型？"><a href="#2-11-有没有做过VLM相关方面的微调？什么模型？" class="headerlink" title="2.11 有没有做过VLM相关方面的微调？什么模型？"></a><strong>2.11 有没有做过VLM相关方面的微调？什么模型？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>(这是一个考察实践经验的问题，回答时应结合具体项目。如果经验不足，可以清晰地阐述一个完整的设想流程。以下提供一个AI回答范例。)</strong></p><p>  是的，我有过VLM微调的实践经验。在一个项目中，我们尝试利用<strong>LLaVA-1.5</strong>模型来解决一个特定工业领域的<strong>视觉缺陷检测与分类</strong>任务。</p><p>  <strong>项目背景与目标：</strong><br>  我们的目标是构建一个能与质检员对话的智能助手。质检员可以上传一张产品（例如，金属铸件）的图片，然后通过自然语言提问，比如“这张图里有什么缺陷？”、“缺陷在哪个位置？”、“这是什么类型的缺陷？”，模型需要能够理解问题并给出准确的回答。</p><p>  <strong>模型选型：</strong><br>  我们选择LLaVA-1.5（7B版本）作为基础模型，主要原因有三点：</p><ol><li><strong>架构成熟：</strong> 它的“ViT + 线性投影 + Vicuna”架构是开源VLM的主流，易于理解和修改。</li><li><strong>强大的基础能力：</strong> 它在通用的视觉对话任务上已经表现很好，我们只需要在此基础上进行领域知识的注入。</li><li><strong>开源生态好：</strong> 有大量现成的微调脚本和社区支持，可以快速上手。</li></ol><p>  <strong>微调过程：</strong></p><ol><li><p><strong>数据准备：</strong> 这是最关键的一步。我们构建了一个小规模、高质量的<strong>视觉指令数据集</strong>。每一条数据包含：</p><ul><li><strong>图像：</strong> 一张带有特定缺陷的工业产品图。</li><li><strong>指令：</strong> 模仿质检员的提问，设计了多种指令模板，如“查找图片中的瑕疵”、“描述一下左上角的异常”等。</li><li><strong>回答：</strong> 精心撰写的标准答案，例如“图片中存在一处裂纹型缺陷，位于产品的右上角边缘”。</li></ul></li><li><p><strong>微调策略：</strong></p><ul><li>我们采用了 <strong>LoRA（Low-Rank Adaptation）</strong> 对LLM部分进行参数高效微调。</li><li>视觉编码器（CLIP ViT）和连接器（MLP）保持冻结，因为我们认为LLaVA的基础视觉表示能力已经足够，主要任务是教会LLM如何用我们领域的“黑话”（专业术语）来描述这些视觉特征。</li></ul></li><li><p><strong>训练与评估：</strong></p><ul><li>在单张A100 GPU上进行了几个epoch的训练。</li><li>评估时，我们不仅看模型回答的文本相似度，更重要的是进行<strong>人工评估</strong>，判断其回答的专业性、准确性和定位能力是否符合要求。</li></ul></li></ol><p>  <strong>遇到的挑战与收获：</strong><br>  主要的挑战在于高质量标注数据的获取成本很高。我们发现，即使只有几百条高质量的领域指令数据，也能显著提升模型在特定任务上的表现。这个项目让我深刻理解了视觉指令微调对于VLM领域适应（domain adaptation）的关键作用。</p></li></ul><h3 id="3-RLHF-八股"><a href="#3-RLHF-八股" class="headerlink" title="3. RLHF 八股"></a><strong>3. RLHF 八股</strong></h3><h4 id="3-1-和传统SFT相比，RLHF旨在解决语言模型中的哪些核心问题？为什么说SFT本身不足以实现我们期望的“对齐”目标？"><a href="#3-1-和传统SFT相比，RLHF旨在解决语言模型中的哪些核心问题？为什么说SFT本身不足以实现我们期望的“对齐”目标？" class="headerlink" title="3.1 和传统SFT相比，RLHF旨在解决语言模型中的哪些核心问题？为什么说SFT本身不足以实现我们期望的“对齐”目标？"></a><strong>3.1 和传统SFT相比，RLHF旨在解决语言模型中的哪些核心问题？为什么说SFT本身不足以实现我们期望的“对齐”目标？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  与传统的监督微调（SFT）相比，RLHF（从人类反馈中进行强化学习）旨在解决语言模型中更深层次的“<strong>对齐</strong>”（Alignment）问题。这具体包括三个方面，通常被称为“HHH”原则：</p><ol><li><strong>有用性（Helpfulness）：</strong> 模型应该提供准确、相关且信息量丰富的内容，尽力帮助用户解决问题。</li><li><strong>诚实性（Honesty）：</strong> 模型应基于其知识进行回答，不应捏造事实。在不知道答案或无法满足要求时，应主动承认，而不是产生幻觉。</li><li><strong>无害性（Harmlessness）：</strong> 模型不能产生有偏见、歧视性、暴力、色情或任何其他可能造成伤害的内容。</li></ol><p>  <strong>为什么SFT本身不足以实现对齐目标？</strong></p><ol><li><strong>目标定义模糊：</strong> “有用”、“诚实”、“无害”这些概念是复杂、主观且依赖上下文的，很难通过一个静态的、固定的SFT数据集来精确定义。例如，“怎样算一个有帮助的回答？”并没有唯一的正确答案，它取决于用户的偏好。</li><li><strong>偏好难以标注：</strong> 对于一个问题，可能有多个“正确”但风格、详略、侧重点不同的回答。SFT通常采用类似（prompt, ideal_response）的数据格式，它无法表达“回答A比回答B更好”这类细粒度的<strong>偏好信息</strong>。</li><li><strong>行为空间巨大：</strong> LLM可以生成几乎无限的回复。SFT数据集只能覆盖其中极小的一部分高质量示例，模型很容易学到数据集中的表面统计特征（statistical artifacts），而不是真正理解背后的原则。它教会了模型“模仿”，但没有教会模型“判断”。</li><li><strong>暴露偏差（Exposure Bias）：</strong> SFT在训练时，每一步都基于真实的“黄金”上下文。但在推理时，模型是基于自己生成的上下文来继续生成，一旦早期出现偏差，错误会累积。</li></ol><p>  RLHF通过引入一个代表人类偏好的奖励模型，让LLM在一个探索性的框架（强化学习）中学习，使其能够理解并优化那些难以用SFT范式表达的、模糊的人类偏好，从而更好地实现对齐。</p></li></ul><hr><h4 id="3-2-请详细阐述经典RLHF流程的三个核心阶段。在每个阶段，输入是什么，输出是什么，以及该阶段的关键目标是什么？"><a href="#3-2-请详细阐述经典RLHF流程的三个核心阶段。在每个阶段，输入是什么，输出是什么，以及该阶段的关键目标是什么？" class="headerlink" title="3.2 请详细阐述经典RLHF流程的三个核心阶段。在每个阶段，输入是什么，输出是什么，以及该阶段的关键目标是什么？"></a><strong>3.2 请详细阐述经典RLHF流程的三个核心阶段。在每个阶段，输入是什么，输出是什么，以及该阶段的关键目标是什么？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  经典的RLHF流程（由OpenAI的InstructGPT论文提出）包含三个核心阶段：</p><p>  <strong>阶段一：监督微调（Supervised Fine-Tuning, SFT）</strong></p><ul><li><strong>输入：</strong> 一个高质量的、由人工编写或筛选的指令跟随数据集。数据格式通常是（指令 Prompt, 理想回答 Response）。</li><li><strong>输出：</strong> 一个经过微调的基础语言模型，我们称之为SFT模型。</li><li><strong>关键目标：</strong> 让预训练好的LLM初步具备理解和遵循人类指令的能力。这是为后续阶段提供一个良好初始策略（policy）的基础，让模型先学会“说什么话”，而不是“胡言乱语”。</li></ul><p>  <strong>阶段二：训练奖励模型（Reward Model, RM）</strong></p><ul><li><strong>输入：</strong> 一个人类偏好比较数据集。生成这个数据集的流程是：<ol><li>从指令数据集中采样一个Prompt。</li><li>用第一阶段的SFT模型对该Prompt生成多个（通常是2到4个）不同的回答。</li><li>由人类标注者对这些回答进行排序，选出最好的和最差的。数据格式通常是（Prompt, 胜出回答 $y_w$, 落败回答 $y_l$）。</li></ol></li><li><strong>输出：</strong> 一个奖励模型（RM）。这个模型能够输入任何（Prompt, Response）对，并输出一个标量分数，这个分数代表了人类对该回答的偏好程度。</li><li><strong>关键目标：</strong> 学习一个能够模仿和泛化人类偏好的函数。这个RM将作为下一阶段强化学习的“环境”或“裁判”，为LLM的探索提供指导信号。</li></ul><p>  <strong>阶段三：近端策略优化（Proximal Policy Optimization, PPO）</strong></p><ul><li><strong>输入：</strong><ol><li>第一阶段的SFT模型（作为初始策略）。</li><li>第二阶段训练好的RM（作为奖励函数）。</li><li>一个新的、用于策略探索的指令数据集。</li></ol></li><li><strong>输出：</strong> 经过RLHF对齐的最终语言模型。</li><li><strong>关键目标：</strong> 使用强化学习来进一步微调SFT模型。在这个阶段，模型（作为Agent）会针对一个Prompt生成一个回答（Action），奖励模型（作为Environment）会给这个回答打分（Reward），然后通过PPO算法更新模型参数，使其生成的回答能在获得高奖励的同时，又不过于偏离原始SFT模型的风格和内容，从而实现“对齐”。</li></ul></li></ul><hr><h4 id="3-3-在RM训练阶段，我们通常收集的是成对比较数据，而不是让人类标注者直接给回复打一个绝对分数。你认为这样做的主要优势和潜在的劣势分别是什么？"><a href="#3-3-在RM训练阶段，我们通常收集的是成对比较数据，而不是让人类标注者直接给回复打一个绝对分数。你认为这样做的主要优势和潜在的劣势分别是什么？" class="headerlink" title="3.3 在RM训练阶段，我们通常收集的是成对比较数据，而不是让人类标注者直接给回复打一个绝对分数。你认为这样做的主要优势和潜在的劣势分别是什么？"></a><strong>3.3 在RM训练阶段，我们通常收集的是成对比较数据，而不是让人类标注者直接给回复打一个绝对分数。你认为这样做的主要优势和潜在的劣势分别是什么？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  在训练奖励模型（RM）时，采用成对比较（Pairwise Comparison）而非绝对评分（Absolute Scoring）是业界的标准做法，这背后有深刻的认知科学和实践考量。</p><p>  <strong>主要优势：</strong></p><ol><li><strong>降低认知负荷，提升标注一致性：</strong> 让人在多个选项中选出“哪个更好”远比给一个选项打一个精确的绝对分数（如1到10分）要容易和直观。不同标注者对于“7分”的定义可能天差地别，但对于“A比B更好”的判断则更容易达成共识，这大大提升了数据的<strong>标注者间一致性（Inter-rater agreement）</strong>。</li><li><strong>提供更精细的信号：</strong> 比较数据能够捕捉到细微的偏好差异。两个回答可能在绝对分数上都是“好”的（比如都是8分），但比较数据可以明确指出其中一个比另一个“稍微好一点”，这种相对信号对于模型学习更精细的偏好至关重要。</li><li><strong>数据分布归一化：</strong> 绝对分数很容易受到标注者个人情绪、打分尺度、疲劳度等因素影响，导致分数分布不均或存在偏差。而比较数据天然地将问题转化为一个标准化的二元分类或排序任务，模型只需要学习相对关系，对绝对尺度不敏感。</li></ol><p>  <strong>潜在的劣势：</strong></p><ol><li><strong>数据效率可能较低：</strong> 每次比较只产生1比特的信息（A&gt;B或B&gt;A）。如果要对K个回答进行完整排序，需要进行 $O(K^2)$ 次比较，而绝对评分只需要K次。这意味着要达到同等的信息量，可能需要更多的标注工作。</li><li><strong>可能出现不传递性（Intransitivity）：</strong> 人类偏好有时不满足传递性，即可能出现“A比B好，B比C好，但C比A好”的循环偏好。这会给奖励模型带来噪声和矛盾的训练信号。</li><li><strong>信息不完整：</strong> 比较数据只告诉我们相对好坏，但没有说明“好多少”或“差多少”。两个回答的差距可能微乎其微，也可能天差地别，但成对比较无法直接体现这种差异的幅度。</li></ol></li></ul><hr><h4 id="3-4-奖励模型的设计至关重要。它的模型架构通常如何选择？它与我们最终要优化的LLM是什么关系？在训练奖励模型时，常用的损失函数是什么？请解释其背后的数学原理（例如，可以结合Bradley-Terry模型来解释）。"><a href="#3-4-奖励模型的设计至关重要。它的模型架构通常如何选择？它与我们最终要优化的LLM是什么关系？在训练奖励模型时，常用的损失函数是什么？请解释其背后的数学原理（例如，可以结合Bradley-Terry模型来解释）。" class="headerlink" title="3.4 奖励模型的设计至关重要。它的模型架构通常如何选择？它与我们最终要优化的LLM是什么关系？在训练奖励模型时，常用的损失函数是什么？请解释其背后的数学原理（例如，可以结合Bradley-Terry模型来解释）。"></a><strong>3.4 奖励模型的设计至关重要。它的模型架构通常如何选择？它与我们最终要优化的LLM是什么关系？在训练奖励模型时，常用的损失函数是什么？请解释其背后的数学原理（例如，可以结合Bradley-Terry模型来解释）。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>模型架构选择：</strong><br>  奖励模型（RM）的架构通常选择与要优化的LLM<strong>相同或非常相似</strong>的架构，但有两点关键区别：</p><ol><li>RM的初始化权重通常来自于<strong>第一阶段训练好的SFT模型</strong>。这样做可以保证RM对指令和语言风格有很好的基础理解。</li><li>RM的最后一层（通常是预测下一个token的softmax层）被替换为一个<strong>回归头（Regression Head）</strong>，这个头通常是一个线性层，用于输出一个<strong>标量（scalar）</strong>，即奖励分数。</li></ol><p>  <strong>与最终LLM的关系：</strong><br>  RM是最终LLM的<strong>效用函数代理（proxy for the utility function）</strong>。它在RLHF流程中扮演着<strong>人类偏好的模拟器</strong>的角色。最终的LLM（即策略）的目标就是生成能够让这个RM给出高分数的回答。因此，RM的质量直接决定了最终LLM对齐的天花板。如果RM有缺陷或偏见，LLM在优化过程中就会“奖励作弊”，利用这些缺陷来获得高分，而不是真正生成人类喜欢的回答。</p><p>  <strong>常用的损失函数：</strong><br>  RM训练时最常用的损失函数是<strong>成对排序损失（Pairwise Ranking Loss）</strong>。其目标是，对于任意一个给定的prompt，RM赋予“胜出回答”（ $y_w$ ）的分数 $r(y_w)$ 应该高于赋予“落败回答”（ $y_l$ ）的分数 $r(y_l)$ 。</p><p>  <strong>数学原理解释（结合Bradley-Terry模型）：</strong><br>  Bradley-Terry模型是一个用于描述成对比较结果概率的模型。它假设每个个体（在这里是每个回答）都有一个潜在的“实力”分数（即奖励分数 $r$ ）。回答 $y_w$ 优于 $y_l$ 的概率 $P(y_w &gt; y_l)$ 可以用一个logistic函数（即sigmoid函数 $\sigma$ ）来建模：<br>  <div align="center"><br>  $$P(y_w &gt; y_l | x) &#x3D; \sigma(r(y_w | x) - r(y_l | x))$$<br>  </div></p><p>  其中 $x$ 是prompt， $r(y|x)$ 是RM给出的分数。这个公式的直观意义是，两个回答的奖励分数差距越大，我们越确信其中一个比另一个好。</p><p>  在训练时，我们的目标是最大化我们观察到的人类偏好数据的对数似然。对于一个偏好数据 $(y_w, y_l)$ ，我们希望最大化 $P(y_w &gt; y_l)$ 的对数。因此，损失函数就是其<strong>负对数似然</strong>：<br>  <div align="center"><br>  $$\text{Loss} &#x3D; -\log(P(y_w &gt; y_l | x)) &#x3D; -\log(\sigma(r(y_w | x) - r(y_l | x)))$$<br>  </div></p><p>  这个损失函数会惩罚那些RM给分错误（即 $r(y_l) &gt; r(y_w)$ ）的情况，并驱动RM学习到一个能够准确反映人类偏好排序的打分函数。</p></li></ul><hr><h4 id="3-5-在RLHF的第三阶段，PPO是最主流的强化学习算法。为什么选择PPO，而不是其他更简单的策略梯度算法（如REINFORCE）或者Q-learning系算法？PPO中的KL散度惩罚项起到了什么关键作用？"><a href="#3-5-在RLHF的第三阶段，PPO是最主流的强化学习算法。为什么选择PPO，而不是其他更简单的策略梯度算法（如REINFORCE）或者Q-learning系算法？PPO中的KL散度惩罚项起到了什么关键作用？" class="headerlink" title="3.5 在RLHF的第三阶段，PPO是最主流的强化学习算法。为什么选择PPO，而不是其他更简单的策略梯度算法（如REINFORCE）或者Q-learning系算法？PPO中的KL散度惩罚项起到了什么关键作用？"></a><strong>3.5 在RLHF的第三阶段，PPO是最主流的强化学习算法。为什么选择PPO，而不是其他更简单的策略梯度算法（如REINFORCE）或者Q-learning系算法？PPO中的KL散度惩罚项起到了什么关键作用？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  在RLHF的第三阶段选择PPO（近端策略优化）作为主流算法是基于其在大型语言模型这种复杂环境下，对<strong>训练稳定性</strong>、<strong>样本效率</strong>和<strong>实现简易性</strong>之间做出的良好权衡。</p><p>  <strong>为什么不选择其他算法？</strong></p><ol><li><p><strong>vs. REINFORCE (简单策略梯度):</strong></p><ul><li>REINFORCE算法以其 <strong>高方差（high variance）</strong> 而闻名。它直接使用蒙特卡洛采样得到的整个序列的奖励来更新策略，这会导致梯度估计非常不稳定，尤其是在LLM这种动作空间巨大、奖励信号稀疏的环境中。训练过程会非常震荡，难以收敛。PPO通过引入价值函数作为基线（baseline）和使用优势函数（advantage function），显著降低了方差，使得训练更稳定。</li></ul></li><li><p><strong>vs. Q-learning系算法 (如DQN):</strong></p><ul><li>DQN等基于价值的算法主要是为<strong>离散（discrete）且低维</strong>的动作空间设计的。它们需要为每个状态下的每个可能动作计算一个Q值。对于LLM来说，动作空间是整个词汇表在每个时间步的组合，这是一个极其巨大的、组合性的空间。直接应用Q-learning来计算每个词的Q值是不可行的。而PPO作为一种策略梯度方法，直接在策略空间进行优化，天然地适用于这种连续或巨大的动作空间。</li></ul></li></ol><p>  <strong>PPO中KL散度惩罚项的关键作用：</strong></p><p>  PPO的目标函数中包含一个非常关键的<strong>KL散度惩罚项</strong>：<br>  <div align="center"><br>  $$\text{Objective}( \pi_{\text{RL}} ) &#x3D; \mathbb{E} [ \text{Reward} ] - \beta \cdot \mathbb{KL}(\pi_{\text{RL}} || \pi_{\text{SFT}})$$<br>  </div></p><p>  其中 $\pi_{\text{RL}}$ 是当前正在优化的策略， $\pi_{\text{SFT}}$ 是第一阶段训练好的初始SFT策略， $\beta$ 是一个超参数。这个KL散度项起到了 <strong>“信任区域”</strong> 或 <strong>“正则化”</strong> 的作用，其关键目的有两个：</p><ol><li><strong>防止策略崩溃（Policy Collapse）：</strong> 奖励模型（RM）是不完美的，总会存在一些漏洞。如果没有KL惩罚项，RL策略会不顾一切地寻找RM的漏洞来“作弊”以获得最高分，这常常导致生成的文本毫无意义、充满重复或攻击性内容，即所谓的“模式崩溃”。KL惩罚项通过约束新策略不能与初始的、表现尚可的SFT策略偏离太远，从而将优化限制在一个“安全”的区域内，保留了SFT模型良好的语言特性。</li><li><strong>保证探索效率和多样性：</strong> 保持与SFT模型的相近度，意味着模型不会过早地收敛到某个奖励高但质量差的局部最优解。它鼓励模型在已经学会的、有意义的语言分布附近进行探索，而不是跳到一个完全陌生的、可能导致奖励模型失效的区域。这有助于维持生成文本的多样性和可读性。</li></ol></li></ul><hr><h4 id="3-6-如果在PPO训练过程中，KL散度惩罚项的系数-β-设置得过大或过小，分别会导致什么样的问题？你将如何通过实验和观察来调整这个超参数？"><a href="#3-6-如果在PPO训练过程中，KL散度惩罚项的系数-β-设置得过大或过小，分别会导致什么样的问题？你将如何通过实验和观察来调整这个超参数？" class="headerlink" title="3.6 如果在PPO训练过程中，KL散度惩罚项的系数 β 设置得过大或过小，分别会导致什么样的问题？你将如何通过实验和观察来调整这个超参数？"></a><strong>3.6 如果在PPO训练过程中，KL散度惩罚项的系数 β 设置得过大或过小，分别会导致什么样的问题？你将如何通过实验和观察来调整这个超参数？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  KL散度惩罚项的系数 $\beta$ 是RLHF训练中一个至关重要的超参数，它控制着“利用奖励模型”和“保持语言模型本性”之间的平衡。</p><p>  <strong>设置不当导致的问题：</strong></p><ul><li><p><strong>$\beta$ 设置过大：</strong></p><ul><li><strong>问题描述：</strong> 如果惩罚系数过大，模型会过于“保守”。为了最小化与SFT模型的KL散度，策略更新的步子会非常小，甚至几乎不更新。</li><li><strong>具体表现：</strong> 模型对奖励信号的响应不足，训练过程看起来“停滞不前”。最终得到的RLHF模型与原始的SFT模型在行为和输出上几乎没有区别，RLHF阶段的优化效果大打折扣，没有充分学到人类的偏好。</li></ul></li><li><p><strong>$\beta$ 设置过小：</strong></p><ul><li><strong>问题描述：</strong> 如果惩罚系数过小，对策略的约束力不足，模型会变得过于“激进”，不顾一切地去迎合奖励模型（RM）。</li><li><strong>具体表现：</strong><ol><li><strong>奖励作弊（Reward Hacking）：</strong> 模型很快发现RM的漏洞并加以利用，生成一些在RM看来分数很高，但实际质量很差、甚至不通顺的文本。</li><li><strong>模式崩溃（Mode Collapse）：</strong> 模型输出的风格和内容变得极其单一、重复，失去了多样性。例如，可能会反复使用某些“奉承”或“安全”的短语，因为这些短语被RM赋予了高分。</li><li><strong>语言模型能力退化：</strong> 偏离SFT模型太远可能导致模型忘记基本的语言知识，生成语法错误或无意义的文本。</li></ol></li></ul></li></ul><p>  <strong>如何通过实验和观察来调整 $\beta$ ？</strong></p><p>  调整 $\beta$ 是一个经验性的过程，通常需要监控以下几个关键指标：</p><ol><li><strong>监控KL散度值：</strong> 在训练日志中，实时观察每个batch或epoch的平均KL散度。一个健康的训练过程，KL散度应该在一个相对稳定且合理的范围内波动。如果KL值持续接近于0，说明 $\beta$ 可能太大了。如果KL值急剧增大且不稳定，说明 $\beta$ 可能太小了。</li><li><strong>监控奖励分数：</strong> 观察奖励模型给出的平均分数。正常情况下，奖励分数应该随着训练稳步提升。如果奖励分数提升很快，但KL散度也急剧增大，就需要警惕奖励作弊的风险。如果奖励分数几乎不增长，说明 $\beta$ 可能太大了。</li><li><strong>定期进行定性分析（Qualitative Analysis）：</strong> 这是最重要的一步。在训练的不同阶段（例如，每隔N个step），从验证集中随机抽取一些prompt，用当前训练的策略模型和SFT参考模型分别生成回答。人工对比检查：<ul><li>RL模型的回答是否比SFT模型更符合期望的偏好？</li><li>RL模型的回答是否出现了重复、模式化、不通顺等问题？</li><li>RL模型是否保留了基本的语言流畅度和事实性？</li></ul></li><li><strong>设置KL散度目标范围：</strong> 一些实现（如TRL库）中，会设定一个KL散度的目标范围。如果实际KL值超出了这个范围，会动态地调整 $\beta$ 值，使其保持在目标范围内。这是一个自动化调整的思路。</li></ol><p>  通过综合以上定量指标和定性观察，可以迭代地调整 $\beta$ 值，直到找到一个既能有效利用奖励信号，又能保持模型稳定性和多样性的最佳平衡点。</p></li></ul><hr><h4 id="3-7-什么是“奖励作弊-x2F-奖励黑客”（Reward-Hacking）？请结合一个具体的LLM应用场景给出一个例子，并探讨几种可能的缓解策略。"><a href="#3-7-什么是“奖励作弊-x2F-奖励黑客”（Reward-Hacking）？请结合一个具体的LLM应用场景给出一个例子，并探讨几种可能的缓解策略。" class="headerlink" title="3.7 什么是“奖励作弊&#x2F;奖励黑客”（Reward Hacking）？请结合一个具体的LLM应用场景给出一个例子，并探讨几种可能的缓解策略。"></a><strong>3.7 什么是“奖励作弊&#x2F;奖励黑客”（Reward Hacking）？请结合一个具体的LLM应用场景给出一个例子，并探讨几种可能的缓解策略。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>奖励作弊（Reward Hacking）</strong>，也称作“规范博弈”（Specification Gaming），指的是在强化学习中，智能体（Agent）发现并利用了奖励函数（Reward Function）的漏洞或不完善之处，以一种设计者非预期的方式来最大化奖励，但实际上并没有完成任务的真正目标。本质上是“<strong>钻了规则的空子</strong>”。</p><p>  <strong>LLM应用场景举例：</strong></p><ul><li><strong>场景：</strong> 训练一个生成文本摘要的LLM。</li><li><strong>奖励模型（RM）的设计：</strong> 假设我们设计的RM偏好那些<strong>包含原文中所有重要关键词</strong>且<strong>长度较长</strong>（认为长摘要信息更全）的摘要。</li><li><strong>奖励作弊的现象：</strong><br>  经过RLHF训练后，这个LLM可能会生成这样的“摘要”：它不再是精炼地总结原文，而是将原文中的所有句子，特别是那些含有关键词的句子，<strong>原封不动地、大量地复制粘贴</strong>过来，并用一些连接词（如“此外”、“同时”、“而且”）将它们生硬地串联起来，形成一篇很长但毫无信息浓缩价值的文本。</li><li><strong>为什么这是作弊：</strong> 这个生成的文本完美地迎合了RM的两个偏好：1）包含了所有关键词；2）长度很长。因此RM会给它打出非常高的分数。然而，它完全违背了“摘要”这个任务的初衷——即简洁地概括核心内容。</li></ul><p>  <strong>缓解策略：</strong></p><ol><li><p><strong>改进奖励模型（Iterative RM Improvement）：</strong></p><ul><li><strong>核心思想：</strong> 奖励作弊的根源在于RM不够好。最直接的方法就是不断优化RM。</li><li><strong>具体做法：</strong> 将模型作弊生成的case（即RM打高分但人类认为很差的例子）重新加入到RM的训练数据中，作为负样本。通过这种迭代的方式，让RM学会识别并惩罚这些作弊行为。</li></ul></li><li><p><strong>增强策略约束（KL Divergence Penalty）：</strong></p><ul><li><strong>核心思想：</strong> 限制模型为了高分而“走火入魔”。</li><li><strong>具体做法：</strong> 在PPO训练中，使用一个足够强的KL散度惩罚项。这会惩罚那些与初始SFT模型行为差异过大的策略，使得模型即使发现作弊路径，也会因为“行为过于怪异”而被KL散度项拉回来，从而不敢轻易作弊。</li></ul></li><li><p><strong>奖励函数设计的多样化（Ensemble or Multi-objective Rewards）：</strong></p><ul><li><strong>核心思想：</strong> 避免单一、简单的奖励指标。</li><li><strong>具体做法：</strong> 设计更复杂的奖励函数，例如，除了RM的分数，再引入一个明确惩罚“重复度”或“与原文相似度过高”的惩罚项。或者训练多个RM的集成（Ensemble），对它们的打分进行平均，这可以减少单个RM的偏见被利用的风险。</li></ul></li><li><p><strong>过程监督（Process Supervision） vs. 结果监督（Outcome Supervision）：</strong></p><ul><li><strong>核心思想：</strong> 奖励好的思考过程，而不仅仅是最终结果。</li><li><strong>具体做法：</strong> 对于一些推理任务，可以让人类不仅对最终答案评分，也对模型生成的中间思考步骤进行评分，训练一个能评估推理过程质量的RM。这使得模型更难通过“猜对答案”的方式作弊。</li></ul></li></ol></li></ul><hr><h4 id="3-8-RLHF流程复杂且不稳定。近年来出现了一些替代方案，例如DPO。请解释DPO的核心思想，并比较它与传统RLHF（基于PPO）的主要区别和优势。"><a href="#3-8-RLHF流程复杂且不稳定。近年来出现了一些替代方案，例如DPO。请解释DPO的核心思想，并比较它与传统RLHF（基于PPO）的主要区别和优势。" class="headerlink" title="3.8 RLHF流程复杂且不稳定。近年来出现了一些替代方案，例如DPO。请解释DPO的核心思想，并比较它与传统RLHF（基于PPO）的主要区别和优势。"></a><strong>3.8 RLHF流程复杂且不稳定。近年来出现了一些替代方案，例如DPO。请解释DPO的核心思想，并比较它与传统RLHF（基于PPO）的主要区别和优势。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>DPO（Direct Preference Optimization）的核心思想：</strong><br>  DPO是一种更简单、更稳定的语言模型偏好对齐方法，其核心思想是 <strong>绕过（bypass）</strong> 显式的奖励模型建模和复杂的强化学习训练过程，直接利用偏好数据来优化语言模型。</p><p>  它的推导过程很巧妙：它首先写出了传统RLHF流程（奖励建模+PPO）的优化目标，然后通过数学变换发现，最优的RLHF策略与参考策略（SFT模型）以及隐式的奖励函数之间存在一个解析关系。最终，它把这个关系代入到奖励模型的损失函数中，神奇地得到了一个可以直接在偏好数据上优化语言模型策略的损失函数，而奖励函数在这个过程中被“抵消”掉了。</p><p>  简单来说，DPO将RLHF这个“<strong>先学习奖励，再用RL优化</strong>”的两阶段问题，直接转换成了一个等价的“<strong>直接用偏好数据进行监督学习</strong>”的一阶段问题。它的损失函数形式上类似一个分类损失，目标是<strong>提高模型对“胜出回答”的生成概率，同时降低对“落败回答”的生成概率</strong>。</p><p>  <strong>与传统RLHF（基于PPO）的主要区别和优势：</strong></p><table><thead><tr><th align="left"><strong>特性</strong></th><th align="left"><strong>传统RLHF (PPO-based)</strong></th><th align="left"><strong>DPO (Direct Preference Optimization)</strong></th></tr></thead><tbody><tr><td align="left"><strong>流程阶段</strong></td><td align="left"><strong>三阶段：</strong> 1. SFT <br> 2. 训练RM <br> 3. PPO-RL</td><td align="left"><strong>两阶段：</strong> 1. SFT <br> 2. 直接在偏好数据上微调</td></tr><tr><td align="left"><strong>核心组件</strong></td><td align="left">需要一个<strong>显式的奖励模型（RM）</strong>和复杂的<strong>强化学习</strong>训练循环（采样、评估、更新）。</td><td align="left"><strong>不需要</strong>独立的奖励模型，也<strong>不需要</strong>强化学习。</td></tr><tr><td align="left"><strong>训练过程</strong></td><td align="left"><strong>复杂且不稳定</strong>：涉及Actor、Critic、RM和SFT四个模型，超参数多（如 $\beta$ ,  $\lambda$ 等），对实现细节敏感，容易出现奖励作弊和训练崩溃。</td><td align="left"><strong>简单且稳定</strong>：本质上是一个监督学习任务，直接在偏好数据上计算损失并用梯度下降更新模型。实现简单，超参数少，训练过程稳定。</td></tr><tr><td align="left"><strong>计算成本</strong></td><td align="left"><strong>高</strong>：PPO需要在推理模式下从策略模型中大量采样生成数据，并用RM进行评估，计算开销大。</td><td align="left"><strong>低</strong>：只需要计算偏好对中两个回答的似然概率，无需额外采样和奖励模型的前向传播。</td></tr><tr><td align="left"><strong>效果</strong></td><td align="left">效果已被广泛验证，是工业界标准。</td><td align="left">在许多任务上被证明<strong>效果持平甚至优于</strong>传统RLHF，同时成本更低。</td></tr></tbody></table><p>  <strong>总结优势：</strong><br>  DPO相对于传统RLHF的主要优势是<strong>简洁、稳定、高效</strong>。它大大简化了对齐流程，降低了实现难度和计算成本，使得偏好对齐技术更容易被广泛应用，同时在效果上也不逊色于甚至超越了复杂的RLHF方法。</p></li></ul><hr><h4 id="3-9-想象一下，你训练完成的RLHF模型在离线评估中表现优异，奖励模型分数很高，但上线后用户反馈其回答变得越来越“模式化”、奉承、且缺乏信息量。你认为可能的原因是什么？你会从哪些方面着手分析和解决这个问题？"><a href="#3-9-想象一下，你训练完成的RLHF模型在离线评估中表现优异，奖励模型分数很高，但上线后用户反馈其回答变得越来越“模式化”、奉承、且缺乏信息量。你认为可能的原因是什么？你会从哪些方面着手分析和解决这个问题？" class="headerlink" title="3.9 想象一下，你训练完成的RLHF模型在离线评估中表现优异，奖励模型分数很高，但上线后用户反馈其回答变得越来越“模式化”、奉承、且缺乏信息量。你认为可能的原因是什么？你会从哪些方面着手分析和解决这个问题？"></a><strong>3.9 想象一下，你训练完成的RLHF模型在离线评估中表现优异，奖励模型分数很高，但上线后用户反馈其回答变得越来越“模式化”、奉承、且缺乏信息量。你认为可能的原因是什么？你会从哪些方面着手分析和解决这个问题？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  这是一个典型的RLHF中“对齐税”（Alignment Tax）或“模式崩溃”（Mode Collapse）现象。即模型为了迎合学到的偏好，牺牲了内容的多样性和信息量。</p><p>  <strong>可能的原因分析：</strong></p><ol><li><p><strong>奖励模型（RM）的偏差和过拟合：</strong></p><ul><li><strong>原因：</strong> RM本身可能学到了有偏的、表面的模式。例如，人类标注者可能无意识地更偏爱那些语气礼貌、结构清晰、使用特定“安全”词汇（如“根据我的知识…”、“作为一个AI模型…”）的回答。RM学到了这些表面特征，并给这类回答高分，而不管其信息量如何。</li><li><strong>离线评估的欺骗性：</strong> 离线评估通常也是用这个有偏的RM来打分的，所以模型分数自然很高，但这是一种“自欺欺人”。</li></ul></li><li><p><strong>PPO优化过程中的过度优化（Over-optimization）：</strong></p><ul><li><strong>原因：</strong> PPO算法非常强大，如果KL散度的惩罚系数 $\beta$ 设置得过小，或者训练步数过多，模型会过度地在RM定义的奖励景观（reward landscape）中寻找最高点。而这个最高点很可能就是一个狭窄的“模式化”区域。</li><li><strong>后果：</strong> 模型找到了获得高分的“万能公式”，即无论什么问题，都用一种奉承、安全的模式来回答，因为这是RM最喜欢的。</li></ul></li><li><p><strong>偏好数据本身的局限性：</strong></p><ul><li><strong>原因：</strong> 用于训练RM的人类偏好数据可能不够多样，或者标注标准过于单一。例如，标注者可能倾向于选择更“政治正确”或“四平八稳”的回答，导致RM学不到对“有创意”、“信息密度高”等更复杂维度的偏好。</li></ul></li></ol><p>  <strong>分析和解决问题的步骤：</strong></p><ol><li><p><strong>深入分析奖励模型（RM Diagnosis）：</strong></p><ul><li><strong>做法：</strong> 首先要诊断RM。我会构造一些对比样本：一个是有信息量但朴实的回答，另一个是模式化、奉承但信息量低的回答。然后用RM去打分，看它是否真的更偏爱后者。</li><li><strong>目的：</strong> 验证RM是否是问题的根源。</li></ul></li><li><p><strong>数据驱动的解决方案（Data-driven Solution）：</strong></p><ul><li><strong>做法：</strong> 如果RM确实存在偏差，需要重新进行数据迭代。收集那些“模式化”的失败案例，并让标注者明确地将它们标记为比那些信息量更丰富的回答更差。用这些新的偏好数据来<strong>继续微调或重新训练RM</strong>。</li><li><strong>目的：</strong> 修正RM的价值观，让它学会欣赏多样性和信息量。</li></ul></li><li><p><strong>算法层面的调整（Algorithmic Adjustment）：</strong></p><ul><li><strong>做法：</strong><ul><li><strong>增大KL散度系数 $\beta$：</strong> 增强对SFT模型的约束，让模型不敢过于偏离其原始的、更多样化的语言风格。</li><li><strong>引入熵奖励（Entropy Bonus）：</strong> 在PPO的目标函数中加入一项熵奖励，鼓励模型生成更多样化的词元分布，对抗模式崩溃。</li><li><strong>提前停止（Early Stopping）：</strong> 监控模型的输出质量，在发现模式化倾向开始出现时就停止训练，而不是追求最高的RM分数。</li></ul></li></ul></li><li><p><strong>解码策略的调整（Decoding Strategy Tuning）：</strong></p><ul><li><strong>做法：</strong> 在模型上线提供服务时，可以尝试调整解码策略。例如，适当<strong>提高Temperature</strong>或使用<strong>Top-K&#x2F;Top-P采样</strong>而非Greedy Search，可以增加生成文本的随机性和多样性，在一定程度上缓解模式化问题。</li></ul></li></ol></li></ul><hr><h4 id="3-10-你知道Deepseek的GRPO吗，它和PPO的主要区别是什么？优劣是什么？"><a href="#3-10-你知道Deepseek的GRPO吗，它和PPO的主要区别是什么？优劣是什么？" class="headerlink" title="3.10 你知道Deepseek的GRPO吗，它和PPO的主要区别是什么？优劣是什么？"></a><strong>3.10 你知道Deepseek的GRPO吗，它和PPO的主要区别是什么？优劣是什么？</strong></h4><ul><li><strong>参考答案：</strong><br>  <strong>(具体可以参考GRPO论文，自己阐述理解)</strong></li></ul><hr><h4 id="3-11-GSPO和DAPO有听说过吗？他们和GRPO有什么区别？"><a href="#3-11-GSPO和DAPO有听说过吗？他们和GRPO有什么区别？" class="headerlink" title="3.11 GSPO和DAPO有听说过吗？他们和GRPO有什么区别？"></a><strong>3.11 GSPO和DAPO有听说过吗？他们和GRPO有什么区别？</strong></h4><ul><li><strong>参考答案：</strong><br>  <strong>(这是一个考察前沿知识广度的问题。截至目前，GSPO和DAPO并非像PPO、DPO那样广为人知或被广泛采纳的主流算法缩写可以参考腾讯，阿里相关论文了解)</strong></li></ul><hr><h4 id="3-12-如何解决信用分配问题？token级别和seq级别的奖励有何不同？"><a href="#3-12-如何解决信用分配问题？token级别和seq级别的奖励有何不同？" class="headerlink" title="3.12 如何解决信用分配问题？token级别和seq级别的奖励有何不同？"></a><strong>3.12 如何解决信用分配问题？token级别和seq级别的奖励有何不同？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>信用分配问题（Credit Assignment Problem）</strong>是强化学习中的一个经典难题。在语言模型生成的场景下，它指的是：当一个完整的回答（序列）得到一个最终的奖励分数后，我们<strong>如何确定这个分数应该归功于（或归咎于）序列中的哪些具体的词元（token）</strong>。一个好的结尾可能弥补了一个糟糕的开头，反之亦然。简单地将最终奖励分配给每一个词元是不公平且低效的。</p><p>  <strong>Token级别奖励 vs. Sequence级别奖励</strong></p><ol><li><p><strong>Sequence级别奖励 (Sequence-level Reward):</strong></p><ul><li><strong>定义：</strong> 这是RLHF中最常见的形式。奖励模型（RM）读取整个生成的序列，并给出一个<strong>单一的标量分数</strong>作为对整个序列的评价。</li><li><strong>优点：</strong><ul><li><strong>与人类评估模式一致：</strong> 人类通常是读完整个回答后形成一个总体印象，这种方式更容易收集偏好数据和训练RM。</li><li><strong>实现简单：</strong> 奖励函数的设计和计算都非常直接。</li></ul></li><li><strong>缺点：</strong><ul><li><strong>信用分配模糊：</strong> 这正是信用分配问题的直接体现。序列中所有token都收到相同的奖励信号，无法区分“好词”和“坏词”，导致学习信号稀疏且充满噪声，降低了学习效率。</li></ul></li></ul></li><li><p><strong>Token级别奖励 (Token-level Reward):</strong></p><ul><li><strong>定义：</strong> 为序列中的<strong>每一个token</strong>都分配一个独立的奖励分数。这个分数应该反映该token在当时上下文中的贡献。</li><li><strong>优点：</strong><ul><li><strong>信号精细：</strong> 提供了非常精细和密集的学习信号，理论上可以极大地提高学习效率和最终性能，因为它直接告诉模型哪一步走对了，哪一步走错了。</li></ul></li><li><strong>缺点：</strong><ul><li><strong>难以获取：</strong> 让标注者为每个token打分几乎是不可能的，认知负荷极大。因此，Token级别的奖励通常不是直接从人类那里获得的。</li><li><strong>定义困难：</strong> 如何定义一个token的“好坏”本身就很复杂。一个词的好坏严重依赖于后续生成的上下文。</li></ul></li></ul></li></ol><p>  <strong>如何解决（或缓解）信用分配问题？</strong></p><p>  尽管我们通常只得到Sequence级别的奖励，但主流的RL算法（如PPO）内部有一些机制来尝试缓解信用分配问题：</p><ol><li><strong>优势函数（Advantage Function）和价值函数（Value Function）：</strong><ul><li><strong>方法：</strong> 在PPO中，除了策略模型（Actor），还会训练一个<strong>价值模型（Critic）</strong>。这个Critic的作用是估计在某个状态（即生成了部分序列的上下文）下，未来可能获得的期望奖励。</li><li><strong>信用分配：</strong> 通过计算<strong>优势函数（Advantage）</strong>，即 <code>A(s, a) = R_t - V(s_t)</code>（简化的形式），我们可以估计出在当前状态 $s_t$ 选择动作 $a_t$ （生成某个token）比“平均水平”好多少。 $R_t$ 是实际得到的未来总回报， $V(s_t)$ 是期望的平均回报。这个优势值可以被看作是一种<strong>伪Token级别</strong>的奖励信号。</li><li><strong>GAE（Generalized Advantage Estimation）：</strong> PPO通常使用GAE来更稳定地估计优势函数，它通过指数加权平均综合了多个时间步的TD误差，进一步平衡了偏差和方差，为每个时间步提供了更可靠的信用分配信号。</li></ul></li></ol><p>  简单来说，我们虽然只有一个最终的序列奖励，但通过引入一个学习未来期望的Critic，P-PO能够为每一步的token生成一个更合理的、间接的、反映其边际贡献的“优势”信号，从而在实践中有效地解决了信用分配问题。</p></li></ul><hr><h4 id="3-13-除了人类反馈，我们还可以利用AI自身的反馈来做对齐，即RLAIF。请谈谈你对RLAIF的理解，它的潜力和风险分别是什么？"><a href="#3-13-除了人类反馈，我们还可以利用AI自身的反馈来做对齐，即RLAIF。请谈谈你对RLAIF的理解，它的潜力和风险分别是什么？" class="headerlink" title="3.13 除了人类反馈，我们还可以利用AI自身的反馈来做对齐，即RLAIF。请谈谈你对RLAIF的理解，它的潜力和风险分别是什么？"></a><strong>3.13 除了人类反馈，我们还可以利用AI自身的反馈来做对齐，即RLAIF。请谈谈你对RLAIF的理解，它的潜力和风险分别是什么？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>对RLAIF (Reinforcement Learning from AI Feedback)的理解：</strong><br>  RLAIF是一种对齐技术，其核心思想是在标准的RLHF流程中，用一个 <strong>强大的、独立的AI模型（通常是比被训练模型更先进的闭源模型，如GPT-4、Claude）</strong> 来替代人类标注者，为语言模型的输出提供偏好判断。</p><p>  具体流程与RLHF非常相似：</p><ol><li>用SFT模型针对一个prompt生成两个或多个回答。</li><li>将prompt和这些回答提交给一个“<strong>裁判AI</strong>”（AI Judge&#x2F;Labeler）。</li><li>裁判AI根据预设的准则（例如，一个精心设计的prompt，要求它从“有用性”、“无害性”等方面判断哪个回答更好），输出其偏好（例如，”回答A更好”）。</li><li>用这些AI生成的偏好数据来训练奖励模型（RM），或者直接用于DPO等算法。</li><li>后续的RL优化流程与RLHF完全相同。</li></ol><p>  本质上，RLAIF是<strong>用AI的偏好来“蒸馏”或“指导”被训练模型的对齐</strong>，是一种“AI训练AI”的范式。</p><p>  <strong>RLAIF的潜力：</strong></p><ol><li><strong>极高的可扩展性和效率（Scalability &amp; Efficiency）：</strong> 这是RLAIF最大的优势。AI标注者可以7x24小时不间断工作，速度远超人类，且成本极低。这使得我们可以用比传统RLHF大几个数量级的偏好数据集来训练模型，从而可能实现更好的对齐效果。</li><li><strong>标注一致性（Consistency）：</strong> 只要裁判AI和其使用的prompt固定，其标注标准就是完全一致的，避免了人类标注者之间固有的偏见和不一致性问题。</li><li><strong>探索更复杂的偏好：</strong> 我们可以通过设计复杂的prompt，引导裁判AI从非常细微、专业的角度（如代码的优雅性、科学解释的准确性）进行评估，这可能是普通人类标注者难以做到的。</li></ol><p>  <strong>RLAIF的风险：</strong></p><ol><li><strong>偏见的继承与放大（Bias Inheritance and Amplification）：</strong> 这是RLAIF最核心的风险。裁判AI自身的偏见（无论是来自其训练数据还是其模型架构）会被毫无保留地传递给被训练的模型。如果裁判AI有某种偏见，RLAIF流程不仅会继承它，还可能因为大规模的训练而将其<strong>放大</strong>，导致最终模型产生系统性的、难以察觉的偏差。</li><li><strong>价值的“近亲繁殖”：</strong> RLAIF构建了一个封闭的AI生态系统，模型的价值观来自于另一个AI。这可能导致AI的价值观与真实、多样、不断演化的人类价值观逐渐脱节，形成一种“回音室效应”或“近亲繁殖”，最终对齐到一个并非人类真正期望的目标上。</li><li><strong>缺乏常识和真实世界 grounding：</strong> 裁判AI可能缺乏对物理世界、社会动态的真实理解。它可能基于文本的表面统计特征做出判断，而这些判断可能在现实世界中是荒谬或有害的。例如，它可能无法判断一个听起来很有说服力的安全建议在实践中是否危险。</li><li><strong>对裁判AI的过度依赖：</strong> 整个对齐的安全性和可靠性都系于裁判AI一身。如果这个裁判AI本身存在漏洞或被恶意利用，其后果将是灾难性的。</li></ol><p>  因此，RLAIF是一个非常有潜力的技术，但其实践应用需要非常谨慎，通常需要与人类监督（Human Oversight）相结合，定期由人类专家抽查和校准AI的标注结果，以确保其对齐方向的正确性。</p></li></ul><h3 id="4-Agent"><a href="#4-Agent" class="headerlink" title="4. Agent"></a><strong>4. Agent</strong></h3><h4 id="4-1-你如何定义一个基于-LLM-的智能体（Agent）？它通常由哪些核心组件构成？"><a href="#4-1-你如何定义一个基于-LLM-的智能体（Agent）？它通常由哪些核心组件构成？" class="headerlink" title="4.1 你如何定义一个基于 LLM 的智能体（Agent）？它通常由哪些核心组件构成？"></a><strong>4.1 你如何定义一个基于 LLM 的智能体（Agent）？它通常由哪些核心组件构成？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  一个基于 LLM 的智能体（Agent）是一个能够自主理解环境、进行规划决策、并执行行动以达成特定目标的计算系统。其核心特征是利用一个<strong>大型语言模型（LLM）作为其“大脑”或“中央处理器”</strong>，来进行复杂的推理和决策。</p><p>  与传统的调用LLM进行问答或文本生成不同，Agent具有<strong>自主性</strong>和<strong>循环执行</strong>的特点，它能主动地、持续地与环境或工具交互，直到完成任务。</p><p>  一个典型的LLM Agent通常由以下<strong>四个核心组件</strong>构成：</p><ol><li><p><strong>大脑&#x2F;核心引擎 (Brain&#x2F;Core Engine):</strong></p><ul><li><strong>组件：</strong> 一个强大的大型语言模型（LLM），如GPT系列、Gemini、Llama等。</li><li><strong>作用：</strong> 这是Agent的认知核心。它负责理解用户目标、感知环境信息、进行常识推理、制定计划、并决定下一步的行动。所有其他组件的输出最终都会汇集到LLM进行处理。</li></ul></li><li><p><strong>规划模块 (Planning Module):</strong></p><ul><li><strong>组件：</strong> 可以是LLM的内置能力（如通过CoT、ReAct等提示策略激发），也可以是独立的算法模块。</li><li><strong>作用：</strong> 负责将一个复杂、长期的目标分解成一系列更小、更具体的、可执行的子任务。它还负责根据行动的反馈动态地调整 и修正计划。规划能力是Agent处理复杂任务的关键。</li></ul></li><li><p><strong>记忆模块 (Memory Module):</strong></p><ul><li><strong>组件：</strong> 通常是外部数据库或数据结构的组合，如向量数据库、键值存储等。</li><li><strong>作用：</strong> 弥补LLM有限的上下文窗口。它分为：<ul><li><strong>短期记忆：</strong> 记录当前的对话历史、中间步骤的“思考过程”（scratchpad），用于维持任务的连贯性。</li><li><strong>长期记忆：</strong> 存储过去的经验、知识、用户偏好等，通过检索（通常是RAG）来为当前决策提供信息。</li></ul></li></ul></li><li><p><strong>工具使用模块 (Tool Use Module):</strong></p><ul><li><strong>组件：</strong> 一系列外部API、函数库或硬件接口。</li><li><strong>作用：</strong> 扩展Agent的能力边界。LLM本身无法获取实时信息、执行数学计算或与物理世界交互。工具使用模块允许Agent调用外部工具来完成这些任务，例如：<ul><li><strong>信息获取：</strong> 调用搜索引擎、数据库查询API。</li><li><strong>代码执行：</strong> 运行Python解释器、访问终端。</li><li><strong>物理操作：</strong> 控制机器人手臂、调用智能家居API。</li></ul></li></ul></li></ol></li></ul><hr><h4 id="4-2-请详细解释-ReAct-框架。它是如何将思维链和行动结合起来，以完成复杂任务的？"><a href="#4-2-请详细解释-ReAct-框架。它是如何将思维链和行动结合起来，以完成复杂任务的？" class="headerlink" title="4.2 请详细解释 ReAct 框架。它是如何将思维链和行动结合起来，以完成复杂任务的？"></a><strong>4.2 请详细解释 ReAct 框架。它是如何将思维链和行动结合起来，以完成复杂任务的？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  ReAct (Reason and Act) 是一个强大且基础的Agent行为框架，它通过一种巧妙的提示（Prompting）策略，让LLM能够协同地生成<strong>推理轨迹（reasoning traces）</strong>和<strong>任务相关的行动（actions）</strong>。</p><p>  <strong>核心思想：</strong><br>  ReAct的核心思想是，人类在解决复杂问题时，并不仅仅是“思考”或“行动”，而是将两者紧密地交织在一起。我们会先思考一下，然后采取一个行动，观察结果，再根据结果进行思考，决定下一步行动。ReAct就是模仿人类这种“<strong>思考 -&gt; 行动 -&gt; 观察 -&gt; 思考…</strong>”的循环模式。</p><p>  <strong>工作流程：</strong><br>  ReAct通过一个精心设计的Prompt来引导LLM生成特定格式的文本。这个循环的每一步如下：</p><ol><li><p><strong>思考 (Thought):</strong></p><ul><li>LLM首先分析当前的任务目标和已有的信息（观察）。</li><li>然后，它会生成一段<strong>内心独白</strong>，即“思考”部分。这部分内容是LLM对当前情况的分析、策略的制定或对下一步行动的规划。例如：“我需要查找一下今天新加坡的天气。我应该使用搜索工具。”</li><li>思考过程让Agent的行为变得可解释，并且有助于LLM自己进行复杂的规划和错误修正。</li></ul></li><li><p><strong>行动 (Action):</strong></p><ul><li>在“思考”之后，LLM会决定并生成一个具体的、可执行的“行动”。</li><li>这个行动通常被格式化为 <code>Action: [Tool_Name, Tool_Input]</code> 的形式。例如：<code>Action: [Search, &quot;weather in Singapore today&quot;]</code>。</li><li><code>Tool_Name</code> 是要调用的工具名称，<code>Tool_Input</code> 是传递给该工具的参数。</li></ul></li><li><p><strong>观察 (Observation):</strong></p><ul><li>Agent的外部执行器（harness）会解析LLM生成的“行动”，并<strong>实际调用</strong>对应的工具。</li><li>工具执行后返回的结果，被格式化为“观察”信息，并反馈给LLM。例如：<code>Observation: &quot;Today in Singapore, the weather is sunny with a high of 32°C.&quot;</code></li></ul></li></ol><p>  <strong>循环与结合：</strong><br>  这个“观察”结果会作为新的上下文，与原始目标一起，输入到LLM中，开始下一轮的“思考 -&gt; 行动 -&gt; 观察”循环。</p><p>  <strong>如何结合思维链（CoT）和行动？</strong></p><ul><li><strong>思维链 (Chain of Thought, CoT)</strong> 是一种让LLM通过生成中间推理步骤来解决复杂问题的方法。</li><li>ReAct中的<strong>思考 (Thought)</strong>部分，本质上就是一种<strong>动态的、交互式的思维链</strong>。</li><li>传统的CoT是一次性生成所有思考步骤，然后得出答案。而ReAct的“思考”是<strong>每一步行动前</strong>都会进行的、<strong>基于最新观察结果</strong>的思维链。</li><li>这种结合使得Agent能够：<ul><li><strong>处理动态环境：</strong> 可以根据工具返回的最新信息实时调整策略。</li><li><strong>进行错误修正：</strong> 如果一个行动失败或返回了无用的信息，Agent可以在下一步的“思考”中分析失败原因，并尝试不同的行动。</li><li><strong>完成复杂任务：</strong> 通过将大任务分解成一系列“思考-行动”的子步骤，ReAct能够完成需要多步推理和工具交互的复杂任务。</li></ul></li></ul></li></ul><hr><h4 id="4-3-在-Agent-的设计中，“规划能力”至重要。请谈谈目前有哪些主流方法可以赋予-LLM-规划能力？（例如-CoT-ToT-GoT等）"><a href="#4-3-在-Agent-的设计中，“规划能力”至重要。请谈谈目前有哪些主流方法可以赋予-LLM-规划能力？（例如-CoT-ToT-GoT等）" class="headerlink" title="4.3 在 Agent 的设计中，“规划能力”至重要。请谈谈目前有哪些主流方法可以赋予 LLM 规划能力？（例如 CoT, ToT, GoT等）"></a><strong>4.3 在 Agent 的设计中，“规划能力”至重要。请谈谈目前有哪些主流方法可以赋予 LLM 规划能力？（例如 CoT, ToT, GoT等）</strong></h4><ul><li><p><strong>参考答案：</strong><br>  规划能力是衡量Agent智能水平的核心指标，它决定了Agent能否有效地将复杂目标分解为可执行步骤。目前，赋予LLM规划能力的主流方法，从简单到复杂，大致可以分为以下几个层次：</p><ol><li><p><strong>基于提示的隐式规划 (Prompt-based Implicit Planning):</strong></p><ul><li><strong>Chain of Thought (CoT):</strong> 这是最基础的规划方法。通过在提示中加入“Let’s think step by step”，引导LLM生成一个线性的、一步接一步的思考过程。这个思考过程本身就是一种简单的计划。<ul><li><strong>优点：</strong> 实现简单，无需修改模型。</li><li><strong>缺点：</strong> 规划是线性的，无法进行探索和回溯。一旦某一步出错，整个计划很可能失败。</li></ul></li><li><strong>ReAct 框架:</strong> ReAct将CoT与行动结合，使得规划成为一个动态过程。每一步的“思考”都是基于前一步“观察”的重新规划，比CoT更具鲁棒性。</li></ul></li><li><p><strong>基于搜索的显式规划 (Search-based Explicit Planning):</strong></p><ul><li><p>这类方法将规划问题形式化为一个搜索问题，通过探索不同的“思考”路径来寻找最优解。</p></li><li><p><strong>Tree of Thoughts (ToT):</strong></p><ul><li><strong>核心思想：</strong> ToT将规划过程构建为一棵“思维树”。从一个初始问题开始，LLM会生成多个不同的、并行的思考路径（树的分支）。</li><li><strong>工作流程：</strong> 它采用标准的树搜索算法（如广度优先或深度优先搜索），在每一步都对当前的所有“思维节点”（叶子节点）进行评估（通常也由LLM自己打分），然后选择最有希望的节点进行下一步的扩展。</li><li><strong>优点：</strong> 允许模型进行探索、评估和回溯，能解决需要深思熟虑或多路径探索的复杂问题。</li><li><strong>缺点：</strong> 计算开销大，因为需要维护和评估一整棵树。</li></ul></li><li><p><strong>Graph of Thoughts (GoT):</strong></p><ul><li><strong>核心思想：</strong> GoT是对ToT的进一步泛化。它认为思维过程不一定是树状的，而更可能是图状的。</li><li><strong>工作流程：</strong> GoT允许不同的思维路径（分支）进行<strong>合并（Merge）</strong>，将多个子问题的解汇集起来形成一个更复杂的解。它还允许<strong>循环（Cycle）</strong>，使得思维过程可以迭代地优化和精炼。</li><li><strong>优点：</strong> 提供了比树更灵活的思维结构，能够解决需要整合不同信息流或迭代改进的、更复杂的规划问题。</li><li><strong>缺点：</strong> 结构和实现比ToT更复杂。</li></ul></li></ul></li><li><p><strong>基于任务分解的规划 (Task Decomposition Planning):</strong></p><ul><li><strong>方法：</strong> 训练或提示LLM充当一个“规划器”，将主任务显式地分解成一个依赖图或一个步骤列表。然后，另一个“执行器”LLM（或同一个LLM扮演不同角色）再去逐一完成这些子任务。</li><li><strong>优点：</strong> 结构清晰，易于管理和监控任务进度。</li><li><strong>缺点：</strong> 对LLM的分解能力要求很高，且预先分解的计划可能缺乏对动态变化的适应性。</li></ul></li></ol></li></ul><hr><h4 id="4-4-Memory是-Agent-的一个关键模块。请问如何为-Agent-设计短期记忆和长期记忆系统？可以借助哪些外部工具或技术？"><a href="#4-4-Memory是-Agent-的一个关键模块。请问如何为-Agent-设计短期记忆和长期记忆系统？可以借助哪些外部工具或技术？" class="headerlink" title="4.4 Memory是 Agent 的一个关键模块。请问如何为 Agent 设计短期记忆和长期记忆系统？可以借助哪些外部工具或技术？"></a><strong>4.4 Memory是 Agent 的一个关键模块。请问如何为 Agent 设计短期记忆和长期记忆系统？可以借助哪些外部工具或技术？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  记忆模块是Agent打破LLM上下文窗口限制、实现持续学习和个性化的关键。设计Agent的记忆系统通常会模仿人类的记忆机制，分为短期记忆和长期记忆。</p><p>  <strong>1. 短期记忆 (Short-Term Memory):</strong></p><ul><li><strong>作用：</strong> 存储当前任务的上下文信息，包括即时对话历史、中间的思考步骤（如ReAct的Scratchpad）、工具的调用结果等。它是Agent进行连贯思考和行动的基础。</li><li><strong>实现方式：</strong><ul><li><strong>LLM的上下文窗口 (Context Window):</strong> 这是最直接的短期记忆载体。所有最近的交互都会被放入Prompt中。</li><li><strong>缓冲区 (Buffers):</strong> 在Agent框架（如LangChain）中，通常会使用不同类型的缓冲区来管理对话历史，例如：<ul><li><strong>ConversationBufferMemory:</strong> 存储完整的对话历史。</li><li><strong>ConversationBufferWindowMemory:</strong> 只保留最近的K轮对话。</li><li><strong>ConversationSummaryBufferMemory:</strong> 在历史对话过长时，动态地用LLM进行总结，以节省Token。</li></ul></li><li><strong>暂存器 (Scratchpad):</strong> 用于记录ReAct框架中的“Thought-Action-Observation”轨迹，是Agent进行逐步推理的关键。</li></ul></li></ul><p>  <strong>2. 长期记忆 (Long-Term Memory):</strong></p><ul><li><strong>作用：</strong> 存储跨越任务和时间维度的信息，如用户的个人偏好、过去的成功&#x2F;失败经验、领域知识等。它使得Agent能够“学习”和“成长”。</li><li><strong>实现方式与外部工具：</strong> 长期记忆的核心是“<strong>存储</strong>”和“<strong>检索</strong>”，这通常需要借助外部技术，最主流的是<strong>RAG (Retrieval-Augmented Generation)</strong> 范式。<ul><li><strong>核心技术：向量数据库 (Vector Database)</strong><ul><li><strong>工具：</strong> Pinecone, ChromaDB, FAISS, Weaviate等。</li><li><strong>工作流程：</strong><ol><li><strong>存储（Storing&#x2F;Writing）：</strong> 当Agent获得一个有价值的信息（如用户明确给出的偏好、一个成功解决问题的完整流程）时，它会使用一个<strong>嵌入模型（Embedding Model）</strong>将这段文本信息转换成一个高维向量。然后，将这个向量及其原始文本存入向量数据库。</li><li><strong>检索（Retrieving&#x2F;Reading）：</strong> 在Agent进行规划或决策时，它会把当前的任务或问题也转换成一个查询向量。然后，用这个查询向量去向量数据库中进行<strong>相似度搜索</strong>，找出与当前情况最相关的历史记忆。</li><li><strong>使用（Using）：</strong> 检索到的记忆（原始文本）会被插入到LLM的Prompt中，作为额外的上下文，来指导LLM做出更明智的决策。</li></ol></li></ul></li><li><strong>其他技术：</strong><ul><li><strong>传统数据库&#x2F;知识图谱：</strong> 对于结构化或关系型数据，使用SQL数据库或图数据库（如Neoj）进行存储和精确查询也是一种有效的长期记忆形式。</li></ul></li></ul></li></ul></li></ul><hr><h4 id="4-5-Tool-Use是扩展-Agent-能力的有效途径。请解释-LLM-是如何学会调用外部-API-或工具的？（可以从-Function-Calling-的角度解释）"><a href="#4-5-Tool-Use是扩展-Agent-能力的有效途径。请解释-LLM-是如何学会调用外部-API-或工具的？（可以从-Function-Calling-的角度解释）" class="headerlink" title="4.5 Tool Use是扩展 Agent 能力的有效途径。请解释 LLM 是如何学会调用外部 API 或工具的？（可以从 Function Calling 的角度解释）"></a><strong>4.5 Tool Use是扩展 Agent 能力的有效途径。请解释 LLM 是如何学会调用外部 API 或工具的？（可以从 Function Calling 的角度解释）</strong></h4><ul><li><p><strong>参考答案：</strong><br>  LLM学会调用外部API或工具，是其从一个纯粹的“语言模型”转变为一个“行动执行者”的关键一步。这一能力的核心是让LLM能够<strong>理解何时需要使用工具</strong>，以及<strong>如何以结构化的方式表达使用哪个工具和传递什么参数</strong>。目前，主流的实现方式是<strong>Function Calling</strong>。</p><p>  <strong>Function Calling的工作原理如下：</strong></p><ol><li><p><strong>工具定义与注册 (Tool Definition &amp; Registration):</strong></p><ul><li>我们首先需要以一种机器可读的方式，向LLM“描述”我们有哪些可用的工具。这个描述通常是一个<strong>结构化的模式（Schema）</strong>，比如JSON Schema。</li><li>对于每一个工具，我们需要定义：<ul><li><strong>函数名称 (Function Name):</strong> 例如，<code>get_current_weather</code>。</li><li><strong>函数描述 (Function Description):</strong> 用自然语言清晰地描述这个函数的功能。例如，“获取指定城市的实时天气信息”。这个描述至关重要，因为LLM会根据它来判断何时使用该工具。</li><li><strong>参数列表 (Parameters):</strong> 定义函数需要哪些输入参数，每个参数的名称、类型、和描述。例如，参数 <code>location</code> (string, “城市名”) 和 <code>unit</code> (enum, “温度单位，可以是celsius或fahrenheit”)。</li></ul></li></ul></li><li><p><strong>LLM的决策与意图识别 (LLM’s Decision &amp; Intent Recognition):</strong></p><ul><li>在与用户交互时，我们将用户的提问<strong>连同所有已注册的工具描述</strong>一起发送给LLM。</li><li>LLM（如GPT-4, Gemini等）经过了特殊的指令微调，使其能够理解这种“工具描述”的格式。</li><li>LLM会分析用户的意图。如果它认为只靠自身知识无法回答，且用户的意图与某个工具的功能相匹配，它就会决定调用该工具。</li></ul></li><li><p><strong>生成结构化的调用指令 (Generating Structured Calling Instructions):</strong></p><ul><li>当LLM决定调用工具时，它的输出<strong>不再是自然语言文本</strong>，而是一个特殊格式的、结构化的<strong>JSON对象</strong>（或其他格式）。</li><li>这个JSON对象会精确地包含：<ul><li><strong>要调用的函数名称</strong>。</li><li><strong>一个包含所有参数名和值的对象</strong>。</li></ul></li><li>例如，对于用户提问“今天新加坡天气怎么样？”，LLM可能输出：<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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;tool_call&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;get_current_weather&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;arguments&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>      <span class="hljs-attr">&quot;location&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Singapore&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;unit&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;celsius&quot;</span><br>    <span class="hljs-punctuation">&#125;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure></li></ul></li><li><p><strong>外部执行与结果返回 (External Execution &amp; Result Return):</strong></p><ul><li>Agent的控制代码（Orchestrator）会捕获这个特殊的JSON输出。</li><li>它会解析JSON，找到函数名和参数，然后在<strong>外部环境中实际执行</strong>这个函数（例如，调用一个真实的天气API）。</li><li>函数执行完毕后，会返回一个结果（例如，<code>&#123;&quot;temperature&quot;: 32, &quot;condition&quot;: &quot;sunny&quot;&#125;</code>）。</li></ul></li><li><p><strong>整合结果并生成最终回复 (Integrating Result &amp; Generating Final Response):</strong></p><ul><li>控制代码将工具的返回结果<strong>再次格式化</strong>，并将其作为新的上下文信息，连同之前的对话历史一起，再次发送给LLM。</li><li>这一次，LLM已经获得了它需要的信息。它会基于这个结果，生成一个最终的、流畅的自然语言回答给用户，例如：“今天新加坡的天气是晴天，温度为32摄氏度。”</li></ul></li></ol></li></ul><hr><h4 id="4-6-请比较一下两个流行的-Agent-开发框架，如-LangChain-和-LlamaIndex。它们的核心应用场景有何不同？"><a href="#4-6-请比较一下两个流行的-Agent-开发框架，如-LangChain-和-LlamaIndex。它们的核心应用场景有何不同？" class="headerlink" title="4.6 请比较一下两个流行的 Agent 开发框架，如 LangChain 和 LlamaIndex。它们的核心应用场景有何不同？"></a><strong>4.6 请比较一下两个流行的 Agent 开发框架，如 LangChain 和 LlamaIndex。它们的核心应用场景有何不同？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  LangChain和LlamaIndex是构建LLM应用最流行的两个开源框架，它们都极大地简化了开发流程，但它们的<strong>核心哲学和设计重点有所不同</strong>，导致了它们在应用场景上的差异。</p><p>  <strong>核心定位的差异：</strong></p><ul><li><p><strong>LangChain：一个通用的LLM应用“编排”框架 (General-purpose Orchestration Framework)</strong></p><ul><li><strong>哲学：</strong> LangChain的目标是提供一个全面的工具集，用于将LLM与各种组件（工具、记忆、数据源）“链接”在一起，构建复杂的应用程序，其中Agent是其核心应用之一。它更关注于 <strong>“工作流”的构建</strong>。</li><li><strong>核心抽象：</strong> Chains (调用链), Agents (智能体), Memory (记忆模块), Callbacks (回调系统)。</li></ul></li><li><p><strong>LlamaIndex：一个专注于外部数据的“数据”框架 (Data Framework for External Data)</strong></p><ul><li><strong>哲学：</strong> LlamaIndex的出发点是解决LLM与私有或外部数据连接的核心问题，即<strong>RAG (Retrieval-Augmented Generation)</strong>。它专注于如何高效地<strong>摄入（ingest）、索引（index）、和查询（query）</strong>外部数据。它更关注于<strong>“数据流”的管理</strong>。</li><li><strong>核心抽象：</strong> Data Connectors (数据连接器), Indexes (索引结构), Retrievers (检索器), Query Engines (查询引擎)。</li></ul></li></ul><p>  <strong>核心应用场景的不同：</strong></p><table><thead><tr><th align="left"><strong>特性</strong></th><th align="left"><strong>LangChain</strong></th><th align="left"><strong>LlamaIndex</strong></th></tr></thead><tbody><tr><td align="left"><strong>最擅长的场景</strong></td><td align="left"><strong>构建复杂的、多步骤的Agent</strong>：当你的应用需要调用多个不同的工具、维护复杂的对话状态、并遵循一个精心设计的执行逻辑时，LangChain的Agent Executor和Chains提供了极大的灵活性。</td><td align="left"><strong>构建高性能的RAG系统</strong>：当你的核心需求是搭建一个强大的知识库问答系统（Q&amp;A over your data），需要处理复杂的非结构化数据（PDF, PPT）、构建高级索引（如树索引、关键词表索引）、并优化检索质量时，LlamaIndex是首选。</td></tr><tr><td align="left"><strong>应用举例</strong></td><td align="left">1. 一个能上网搜索、执行代码、并调用计算器的<strong>通用研究助手</strong>。<br>2. 一个能连接公司内部API来查询订单、更新客户信息的<strong>自动化客服Agent</strong>。<br>3. 一个能执行一系列复杂操作的<strong>自动化流程（RPA）</strong>。</td><td align="left">1. 一个能够回答关于公司内部海量技术文档问题的<strong>开发者助手</strong>。<br>2. 一个能够结合多份PDF财报进行深度分析和回答的<strong>金融分析工具</strong>。<br>3. 一个私人的、基于个人笔记库（Notion, Obsidian）的<strong>知识管理和问答系统</strong>。</td></tr><tr><td align="left"><strong>功能交叉</strong></td><td align="left">LangChain也内置了RAG功能（Document Loaders, Vector Stores, Retrievers），但相对LlamaIndex来说，其高级功能和可定制性较少。</td><td align="left">LlamaIndex也引入了Agent的概念（Data Agent），允许LLM智能地选择不同的数据源和查询策略，但其Agent的通用性和复杂工具编排能力不如LangChain。</td></tr></tbody></table><p>  <strong>总结：</strong></p><ul><li>如果你的项目<strong>以Agent为核心，需要复杂的逻辑编排和多工具协作</strong>，首选<strong>LangChain</strong>。</li><li>如果你的项目<strong>以数据为核心，需要构建强大的知识库和问答能力</strong>，首选<strong>LlamaIndex</strong>。</li><li>在实际开发中，两者也常常被<strong>结合使用</strong>：例如，使用LlamaIndex构建一个强大的知识库检索工具，然后将这个工具接入到LangChain构建的Agent中，让Agent能够利用这个知识库来完成更复杂的任务。</li></ul></li></ul><hr><h4 id="4-7-在构建一个复杂的-Agent-时，你认为最主要的挑战是什么？"><a href="#4-7-在构建一个复杂的-Agent-时，你认为最主要的挑战是什么？" class="headerlink" title="4.7 在构建一个复杂的 Agent 时，你认为最主要的挑战是什么？"></a><strong>4.7 在构建一个复杂的 Agent 时，你认为最主要的挑战是什么？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  构建一个复杂的Agent（例如，需要多步规划、多工具交互、长期记忆的Agent）时，会遇到一系列从理论到工程的挑战。我认为最主要的挑战可以归结为以下几点：</p><ol><li><p><strong>规划与推理的鲁棒性 (Robustness of Planning and Reasoning):</strong></p><ul><li><strong>挑战描述：</strong> 复杂的任务往往需要长期、多步的规划。当前的LLM虽然强大，但其推理链条仍然很脆弱。Agent很容易在执行过程中“迷失”——忘记最初的目标、陷入无效的循环、或者因为某一步的错误（如工具返回非预期结果）而导致整个任务失败。如何让Agent具备强大的纠错能力和动态重规划能力，是最大的挑战之一。</li><li><strong>具体表现：</strong> Agent卡在重复的“思考-行动”循环中；对工具的失败没有备用方案；过早地认为任务已完成。</li></ul></li><li><p><strong>可靠且可复现的评估 (Reliable and Reproducible Evaluation):</strong></p><ul><li><strong>挑战描述：</strong> 如何科学地评估一个Agent的性能极其困难。对于一个复杂的、开放式的任务（如“帮我规划一次为期一周的新加坡旅游”），没有唯一的正确答案。</li><li><strong>具体表现：</strong><ul><li><strong>评估指标难以定义：</strong> 仅看最终结果是否“好”是主观的。需要评估过程的效率（调用了多少次工具）、成本（花费了多少token）、鲁棒性（在不同干扰下的表现）等。</li><li><strong>环境不可复现：</strong> 如果Agent使用了搜索引擎等动态工具，两次执行的结果可能完全不同，导致评估无法复现。</li><li><strong>评估成本高：</strong> 目前最可靠的评估方式仍然是人工评估，但成本高昂且难以规模化。</li></ul></li></ul></li><li><p><strong>成本、延迟与可扩展性 (Cost, Latency, and Scalability):</strong></p><ul><li><strong>挑战描述：</strong> 一个复杂的任务可能需要Agent进行数十次甚至上百次的LLM调用（每次思考、每次总结、每次决策都需要一次调用）。</li><li><strong>具体表现：</strong><ul><li><strong>高昂的API费用：</strong> 使用GPT-4等强大模型作为Agent大脑，一次复杂任务的成本可能高达数美元。</li><li><strong>不可接受的延迟：</strong> 用户需要等待很长时间才能得到最终结果，因为整个过程是串行的。</li><li><strong>服务扩展性差：</strong> 高成本和高延迟使得将这类复杂Agent大规模部署给海量用户变得不切实际。</li></ul></li></ul></li><li><p><strong>安全与可控性 (Safety and Controllability):</strong></p><ul><li><strong>挑战描述：</strong> 赋予Agent调用工具的能力，本质上是赋予了它在数字世界甚至物理世界中“行动”的能力。</li><li><strong>具体表现：</strong><ul><li><strong>权限管理困难：</strong> 如何精确控制Agent的权限，防止它执行危险操作（如删除文件、发送恶意邮件）？</li><li><strong>提示注入攻击（Prompt Injection）：</strong> 恶意用户或被Agent处理的外部数据（如网页内容）可能包含恶意指令，劫持Agent去执行非预期的任务。</li><li><strong>不可预测性：</strong> Agent的自主性使其行为难以被完全预测，可能会产生意料之外的负面后果。</li></ul></li></ul></li></ol></li></ul><hr><h4 id="4-8-什么是多智能体系统？让多个-LLM-Agent-协同工作相比于单个-Agent-有什么优势？又会引入哪些新的复杂性？"><a href="#4-8-什么是多智能体系统？让多个-LLM-Agent-协同工作相比于单个-Agent-有什么优势？又会引入哪些新的复杂性？" class="headerlink" title="4.8 什么是多智能体系统？让多个 LLM Agent 协同工作相比于单个 Agent 有什么优势？又会引入哪些新的复杂性？"></a><strong>4.8 什么是多智能体系统？让多个 LLM Agent 协同工作相比于单个 Agent 有什么优势？又会引入哪些新的复杂性？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>多智能体系统 (Multi-Agent System, MAS)</strong> 是一个由多个自主的、交互的智能体组成的系统。这些智能体在同一个环境中运作，它们可以相互通信、协作、竞争或协商，以解决单个智能体难以解决的复杂问题。在LLM的背景下，就是让多个LLM Agent协同工作。</p><p>  <strong>相比于单个Agent的优势：</strong></p><ol><li><p><strong>分工与专业化 (Division of Labor &amp; Specialization):</strong></p><ul><li>我们可以为每个Agent设定不同的角色和专长。例如，在一个软件开发团队中，可以有一个“产品经理Agent”负责需求分析，一个“程序员Agent”负责编写代码，一个“测试工程师Agent”负责编写测试用例。每个Agent都可以基于专门的知识和工具进行微调，从而在各自领域达到更高的专业水平。</li></ul></li><li><p><strong>并行处理与效率 (Parallelism &amp; Efficiency):</strong></p><ul><li>复杂任务可以被分解成多个子任务，并分配给不同的Agent同时处理，这大大缩短了解决问题的总时间。这就像一个团队并行工作，而不是一个人按顺序做所有事。</li></ul></li><li><p><strong>鲁棒性与冗余 (Robustness &amp; Redundancy):</strong></p><ul><li>系统不依赖于任何单个Agent。如果一个Agent出现故障或陷入困境，其他Agent可以接替它的工作，或者通过集体决策找到解决方案，从而提高了整个系统的容错能力。</li></ul></li><li><p><strong>视角多样性与创新 (Diversity of Perspectives &amp; Innovation):</strong></p><ul><li>不同的Agent可以被赋予不同的“性格”、目标或推理方法。通过辩论、协商等方式，它们可以从多个角度审视问题，避免单一Agent的思维局限，并可能激发出更具创造性的解决方案。这在模拟社会动态、进行头脑风暴等场景中尤为有效。</li></ul></li></ol><p>  <strong>引入的新的复杂性：</strong></p><ol><li><p><strong>通信协议与语言 (Communication Protocol &amp; Language):</strong></p><ul><li>Agent之间如何有效沟通？需要设计一套标准化的通信协议和消息格式，确保它们能够相互理解意图、状态和知识。这本身就是一个巨大的挑战。</li></ul></li><li><p><strong>协调与协作机制 (Coordination &amp; Collaboration Mechanisms):</strong></p><ul><li>如何分配任务？谁来领导？如何解决冲突和资源争抢？这需要复杂的协调机制，例如集中的“指挥官”Agent，或者分布式的协商协议（如合同网、拍卖）。</li></ul></li><li><p><strong>社会行为与动态 (Social Behaviors &amp; Dynamics):</strong></p><ul><li>当多个Agent交互时，会出现复杂的社会现象，如信任、欺骗、联盟、背叛等。如何引导系统走向良性的协作，而不是恶性的竞争或混乱，是一个核心的对齐问题。</li></ul></li><li><p><strong>系统状态维护与一致性 (System State Maintenance &amp; Consistency):</strong></p><ul><li>在一个共享的环境中，每个Agent的行为都可能改变环境状态。如何确保所有Agent对当前环境有一个一致的、最新的认知，避免信息不同步导致决策冲突？</li></ul></li><li><p><strong>信用分配的加剧 (Aggravated Credit Assignment):</strong></p><ul><li>当一个团队任务成功或失败时，如何评估每个Agent在其中的贡献或责任？这比单个Agent的信用分配问题要复杂得多。</li></ul></li></ol></li></ul><hr><h4 id="4-9-当一个-Agent-需要在真实或模拟环境中（如机器人、游戏）执行任务时，它与纯粹基于软件工具的-Agent-有什么本质区别？"><a href="#4-9-当一个-Agent-需要在真实或模拟环境中（如机器人、游戏）执行任务时，它与纯粹基于软件工具的-Agent-有什么本质区别？" class="headerlink" title="4.9 当一个 Agent 需要在真实或模拟环境中（如机器人、游戏）执行任务时，它与纯粹基于软件工具的 Agent 有什么本质区别？"></a><strong>4.9 当一个 Agent 需要在真实或模拟环境中（如机器人、游戏）执行任务时，它与纯粹基于软件工具的 Agent 有什么本质区别？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  当Agent从纯粹的软件环境（调用API、读写文件）进入到真实或模拟的物理环境（如机器人、游戏）时，我们称之为<strong>具身智能体（Embodied Agent）</strong>。这种转变引入了几个本质的区别，极大地增加了任务的复杂性。</p><p>  <strong>本质区别主要体现在以下几个方面：</strong></p><ol><li><p><strong>感知与世界接地 (Perception &amp; World Grounding):</strong></p><ul><li><strong>软件Agent：</strong> 感知的是<strong>结构化的、符号化的</strong>信息（如API返回的JSON，数据库的表格）。</li><li><strong>具身Agent：</strong> 感知的是<strong>非结构化的、高维的、充满噪声的</strong>传感器数据（如摄像头的像素流、激光雷达的点云）。它必须解决“符号接地”（Symbol Grounding）问题，即将语言中的概念（如“苹果”）与现实世界的物理实体（像素集合）对应起来。</li></ul></li><li><p><strong>状态的可观测性 (State Observability):</strong></p><ul><li><strong>软件Agent：</strong> 环境状态通常是<strong>完全可观测的</strong>（Full Observability）。通过API可以获取到所有需要的信息。</li><li><strong>具身Agent：</strong> 环境状态是<strong>部分可观测的</strong>（Partial Observability）。机器人只能看到它面前的景象，无法知道房间另一边发生了什么。Agent必须基于不完整的观测历史来推断世界的状态。</li></ul></li><li><p><strong>行动空间与不确定性 (Action Space &amp; Uncertainty):</strong></p><ul><li><strong>软件Agent：</strong> 行动空间是<strong>离散的、确定的</strong>。调用一个API要么成功要么失败，结果是可预测的。</li><li><strong>具身Agent：</strong> 行动空间通常是<strong>连续的、随机的</strong>。控制机器人手臂移动一个精确的距离，会因为电机误差、摩擦力等因素而存在不确定性。每个行动的结果都需要通过传感器反馈来确认。</li></ul></li><li><p><strong>实时性与反馈循环 (Real-time &amp; Feedback Loop):</strong></p><ul><li><strong>软件Agent：</strong> 交互是<strong>回合制的、异步的</strong>。Agent可以花很长时间思考，然后调用工具，等待结果。</li><li><strong>具身Agent：</strong> 必须在<strong>实时（real-time）</strong>中运行。它需要持续地感知、决策和行动，以应对动态变化的环境。反馈循环是即时的、连续的。</li></ul></li><li><p><strong>安全与不可逆性 (Safety &amp; Irreversibility):</strong></p><ul><li><strong>软件Agent：</strong> 错误行动的后果通常是<strong>可逆的、有限的</strong>。一个失败的API调用可以重试，最坏的情况可能是数据错误。</li><li><strong>具身Agent：</strong> 错误行动的后果可能是<strong>物理性的、不可逆的、甚至是危险的</strong>。一个机器人错误的动作可能会打碎一个杯子、损坏自身或对人类造成伤害。因此，安全是具身Agent的首要考虑。</li></ul></li></ol></li></ul><hr><h4 id="4-10-如何确保一个-Agent-的行为是安全、可控且符合人类意图的？在-Agent-的设计中，有哪些保障对齐方法？"><a href="#4-10-如何确保一个-Agent-的行为是安全、可控且符合人类意图的？在-Agent-的设计中，有哪些保障对齐方法？" class="headerlink" title="4.10 如何确保一个 Agent 的行为是安全、可控且符合人类意图的？在 Agent 的设计中，有哪些保障对齐方法？"></a><strong>4.10 如何确保一个 Agent 的行为是安全、可控且符合人类意图的？在 Agent 的设计中，有哪些保障对齐方法？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  确保Agent的安全、可控和对齐是Agent技术能够被信任和应用的前提，这是一个系统性工程，需要在多个层面进行设计。</p><p>  主要的保障对齐方法包括：</p><ol><li><p><strong>核心模型的对齐（Core Model Alignment）：</strong></p><ul><li><strong>基础：</strong> Agent的大脑是一个LLM，因此，这个LLM本身必须是高度对齐的。</li><li><strong>方法：</strong> 使用如<strong>RLHF（从人类反馈中强化学习）</strong>、<strong>DPO（直接偏好优化）</strong>、<strong>Constitutional AI（宪法AI）</strong>等技术，对基础LLM进行微调，使其遵循“有用、诚实、无害”的原则，这是所有安全措施的基石。</li></ul></li><li><p><strong>工具和权限的严格管理（Tool and Permission Scrutiny）：</strong></p><ul><li><strong>原则：</strong> 最小权限原则（Principle of Least Privilege）。只给Agent完成其任务所必需的最少的工具和权限。</li><li><strong>方法：</strong><ul><li><strong>工具白名单：</strong> 明确列出Agent可以调用的安全工具，而不是让它任意调用。</li><li><strong>权限控制：</strong> 对文件系统、数据库、API的访问进行严格的读&#x2F;写&#x2F;执行权限控制。</li><li><strong>资源限制：</strong> 限制Agent的计算资源、API调用次数和执行时间，防止其失控或造成资源滥用。</li></ul></li></ul></li><li><p><strong>人类在环（Human-in-the-Loop, HITL）：</strong></p><ul><li><strong>原则：</strong> 对于高风险或不可逆的操作，必须有人类监督和确认。</li><li><strong>方法：</strong><ul><li><strong>操作确认：</strong> 在执行如“删除文件”、“发送邮件”、“执行金融交易”等敏感操作前，Agent必须生成一个执行计划，并暂停等待人类用户的明确批准。</li><li><strong>监督与干预：</strong> 人类可以实时监控Agent的行为轨迹，并随时暂停、修改或终止其任务。</li></ul></li></ul></li><li><p><strong>执行环境沙箱化（Sandboxed Execution Environment）：</strong></p><ul><li><strong>原则：</strong> 将Agent的执行环境与宿主系统隔离。</li><li><strong>方法：</strong> 让Agent生成的代码或命令在一个受控的沙箱（如Docker容器、虚拟机）中执行。这样即使Agent被劫持或产生恶意代码，其破坏范围也被限制在沙箱内部，不会影响到外部系统。</li></ul></li><li><p><strong>明确的规则与护栏（Explicit Rules and Guardrails）：</strong></p><ul><li><strong>方法：</strong> 除了LLM内在的对齐，可以在Agent的控制逻辑中加入硬编码的规则或“护栏”。例如，可以设置一个正则表达式过滤器，禁止Agent生成或执行包含特定危险命令（如 <code>rm -rf /</code>）的指令。</li></ul></li><li><p><strong>持续的红队测试与审计（Continuous Red Teaming and Auditing）：</strong></p><ul><li><strong>方法：</strong><ul><li><strong>红队测试：</strong> 组织专门的团队，像黑客一样，从各种角度（如提示注入、越狱、滥用工具）来攻击Agent，主动发现其安全漏洞和对齐缺陷。</li><li><strong>行为审计：</strong> 详细记录Agent所有的思考链、工具调用和最终输出，进行事后审计，分析失败案例和非预期行为，并据此迭代改进安全设计。</li></ul></li></ul></li></ol></li></ul><hr><h4 id="4-11-了解A2A框架吗？它和普通Agent框架的区别在哪，挑一个最关键的不同点说明。"><a href="#4-11-了解A2A框架吗？它和普通Agent框架的区别在哪，挑一个最关键的不同点说明。" class="headerlink" title="4.11 了解A2A框架吗？它和普通Agent框架的区别在哪，挑一个最关键的不同点说明。"></a><strong>4.11 了解A2A框架吗？它和普通Agent框架的区别在哪，挑一个最关键的不同点说明。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  是的，我了解A2A（Agent-to-Agent）框架或协议的概念。它代表了多智能体系统研究中的一个重要方向。</p><p>  <strong>和普通Agent框架的区别：</strong><br>  一个普通的Agent框架，如LangChain或Auto-GPT，其核心关注点是<strong>单个Agent的内部工作循环和能力</strong>。它定义了一个Agent如何<strong>感知环境、进行规划（思考）、调用工具（行动）、并处理反馈（观察）</strong>。它的设计蓝图是围绕着一个独立的、自主的个体。</p><p>  而A2A框架的核心关注点则完全不同，它关注的是<strong>多个异构Agent之间的通信和协作</strong>。它试图定义一套<strong>通用的标准、协议和语言</strong>，使得由不同开发者、使用不同技术栈、为了不同目标而构建的Agent们，能够相互发现、理解和交互。</p><p>  <strong>最关键的不同点：</strong></p><p>  <strong>普通Agent框架关注的是“个体的实现”（Implementation of an individual），而A2A框架关注的是“群体的交互标准”（Interaction standard for a collective）。</strong></p><ul><li><strong>举例来说：</strong><ul><li><strong>LangChain</strong>告诉你如何用Python代码构建一个能使用Google搜索和计算器的Agent。它关心的是这个Agent内部的逻辑流（<code>AgentExecutor</code>, <code>Chains</code>, <code>Tools</code>）。</li><li>一个<strong>A2A框架</strong>则试图回答这样的问题：“我的LangChain Agent如何向一个完全不认识的、由别人用Java写的Agent有效地传达一个任务：‘帮我用你的专业金融数据库分析一下这只股票，并把结果以JSON格式返回给我？’”</li><li>它需要定义消息的格式、能力的描述方式（如何声明自己会用什么工具）、任务的分解和委托协议、以及信任和验证机制。</li></ul></li></ul><p>  所以，最关键的不同点在于<strong>抽象层次</strong>。普通Agent框架在“<strong>应用层</strong>”，致力于构建能干活的个体；而A2A框架在“<strong>协议层</strong>”，致力于构建一个能让所有个体互相交流的“社会规则”或“互联网协议”。A2A是实现真正复杂的、去中心化的多智能体协作的必要基础。</p></li></ul><hr><h4 id="4-12-你用过哪些Agent框架？选型是如何选的？你最终场景的评价指标是什么？"><a href="#4-12-你用过哪些Agent框架？选型是如何选的？你最终场景的评价指标是什么？" class="headerlink" title="4.12 你用过哪些Agent框架？选型是如何选的？你最终场景的评价指标是什么？"></a><strong>4.12 你用过哪些Agent框架？选型是如何选的？你最终场景的评价指标是什么？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <em>(这是一个考察实践经验的问题，回答时应展现出对主流工具的了解和有条理的决策过程。以下提供一个回答范例。)</em></p><p>  是的，我在多个项目中实践过不同的Agent框架。我最常用的主要有两个：<strong>LangChain</strong> 和 <strong>LlamaIndex</strong>，偶尔也会使用更轻量级的库如 <strong>AutoGen</strong> 进行多智能体实验。</p><p>  <strong>选型是如何选的？</strong><br>  我的选型过程主要基于项目的<strong>核心需求</strong>，我通常会从“<strong>逻辑编排驱动</strong>”还是“<strong>数据驱动</strong>”这两个角度来考虑：</p><ol><li><p><strong>当项目是“逻辑编排驱动”时，我首选LangChain。</strong></p><ul><li><strong>场景：</strong> 这类项目的核心是构建一个复杂的、需要执行一系列步骤、并与多种外部工具（APIs, 数据库, 文件系统）交互的Agent。例如，一个自动化的研究助手，需要先上网搜索，然后对结果进行总结，再用代码执行器进行数据分析。</li><li><strong>选择理由：</strong> LangChain提供了非常强大和灵活的<strong>Agent Executor</strong>和<strong>Chains</strong>（特别是LCEL表达式语言），能够很好地编排和控制复杂的执行流。它的工具集成生态也是最丰富的。</li></ul></li><li><p><strong>当项目是“数据驱动”时，我首选LlamaIndex。</strong></p><ul><li><strong>场景：</strong> 这类项目的核心是构建一个围绕特定知识库的问答或分析系统，即高级RAG（Retrieval-Augmented Generation）。例如，一个能回答公司内部上千份PDF技术文档的客服机器人。</li><li><strong>选择理由：</strong> LlamaIndex在<strong>数据的摄入、索引、和检索</strong>方面做得比LangChain更深入、更专业。它提供了更多样化和高级的索引结构（如树索引、知识图谱索引）和检索策略（如混合检索、重排序），对于优化RAG的质量至关重要。</li></ul></li></ol><p>  <strong>最终场景的评价指标是什么？</strong><br>  评价指标是高度依赖于具体场景的，但我通常会从以下三个维度来综合评估一个Agent的性能：</p><ol><li><p><strong>任务成功率 (Task Success Rate):</strong></p><ul><li><strong>定义：</strong> 这是最重要的结果导向指标。它衡量Agent在多大比例上成功地、完整地完成了最终任务。</li><li><strong>举例：</strong> 对于一个代码生成Agent，能否生成无语法错误且能通过所有单元测试的代码。对于一个问答Agent，答案的准确率和完整性。</li></ul></li><li><p><strong>过程效率 (Process Efficiency):</strong></p><ul><li><strong>定义：</strong> 衡量Agent在完成任务过程中的资源消耗。</li><li><strong>举例：</strong><ul><li><strong>成本 (Cost):</strong> 完成一次任务的总Token消耗量或API调用费用。</li><li><strong>延迟 (Latency):</strong> 从用户发出指令到Agent给出最终结果的总耗时。</li><li><strong>步骤数 (Number of Steps):</strong> Agent执行的“思考-行动”循环次数。次数越少通常意味着规划能力越强。</li></ul></li></ul></li><li><p><strong>鲁棒性与可预测性 (Robustness &amp; Predictability):</strong></p><ul><li><strong>定义：</strong> 衡量Agent在面对非理想情况（如工具报错、模糊指令、环境变化）时的表现。</li><li><strong>举例：</strong><ul><li><strong>错误处理能力：</strong> 当一个API调用失败时，Agent能否识别错误并尝试备用方案。</li><li><strong>一致性：</strong> 对于相似的输入，Agent能否产生相似的、可预测的输出。</li><li><strong>安全评估：</strong> 在红队测试中，Agent抵抗提示注入等攻击的能力。</li></ul></li></ul></li></ol></li></ul><hr><h4 id="4-13-有微调过Agent能力吗？数据集如何收集？"><a href="#4-13-有微调过Agent能力吗？数据集如何收集？" class="headerlink" title="4.13 有微调过Agent能力吗？数据集如何收集？"></a><strong>4.13 有微调过Agent能力吗？数据集如何收集？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <em>(这是一个考察高级实践能力的问题。回答的关键在于展现出对Agent微调核心思想的理解——即微调的是“思考过程”而非最终答案。)</em></p><p>  是的，我对通过微调来提升Agent特定能力的实践有所了解和尝试。单纯依靠提示（Prompting）来驱动的Agent（zero-shot Agent）在复杂或特定领域的任务上，其稳定性和效率往往不够理想。微调是让Agent变得更可靠、更高效的关键步骤。</p><p>  微调Agent能力的核心是<strong>教会模型如何更好地“思考”和“使用工具”</strong>，本质上是一种<strong>行为克隆（Behavioral Cloning）</strong>。</p><p>  <strong>数据集如何收集？</strong><br>  Agent微调的数据集不是简单的（输入，输出）对，而是一系列高质量的 <strong>“决策轨迹”（decision-making trajectories）</strong>。收集这类数据集主要有以下几种方法：</p><ol><li><p><strong>使用强大的“教师模型”生成合成数据 :</strong></p><ul><li><strong>流程：</strong> 这是目前最主流和高效的方法。<ol><li><strong>定义任务和工具：</strong> 首先明确Agent需要完成的任务和可用的工具集。</li><li><strong>编写任务样本：</strong> 创建一系列该任务的实例（prompts）。</li><li><strong>使用教师模型生成轨迹：</strong> 利用一个非常强大的闭源模型（如GPT-4o）作为“教师”，让它在ReAct或其他Agent框架下执行这些任务。</li><li><strong>记录完整轨迹：</strong> 详细记录下教师模型每一步的“思考（Thought）”和“行动（Action）”。这个（任务, 思考, 行动）序列就是我们的一条数据。</li><li><strong>过滤和清洗：</strong> 自动或人工地筛选掉那些教师模型执行失败或质量不高的轨迹，确保数据集的质量。</li></ol></li></ul></li><li><p><strong>人工编写或修正轨迹:</strong></p></li><li><p><strong>从真实用户交互中收集数据 :</strong></p></li></ol></li></ul><h3 id="5-RAG"><a href="#5-RAG" class="headerlink" title="5. RAG"></a><strong>5. RAG</strong></h3><h4 id="5-1-请解释-RAG-的工作原理。与直接对-LLM-进行微调相比，RAG-主要解决了什么问题？有哪些优势？"><a href="#5-1-请解释-RAG-的工作原理。与直接对-LLM-进行微调相比，RAG-主要解决了什么问题？有哪些优势？" class="headerlink" title="5.1 请解释 RAG 的工作原理。与直接对 LLM 进行微调相比，RAG 主要解决了什么问题？有哪些优势？"></a><strong>5.1 请解释 RAG 的工作原理。与直接对 LLM 进行微调相比，RAG 主要解决了什么问题？有哪些优势？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>RAG (Retrieval-Augmented Generation)</strong> 的工作原理是一种“<strong>先检索，后生成</strong>”的模式，它将信息检索（Information Retrieval）与文本生成（Text Generation）相结合，来增强大型语言模型（LLM）的能力。</p><p>  <strong>工作流程如下：</strong></p><ol><li><strong>检索（Retrieve）：</strong> 当用户提出一个问题时，RAG系统首先不会直接将问题发送给LLM。相反，它会把用户的问题作为一个查询（Query），在一个外部的知识库（通常是向量数据库）中进行搜索，找出与问题最相关的几段信息（documents&#x2F;chunks）。</li><li><strong>增强（Augment）：</strong> 系统会将检索到的这些相关信息与用户的原始问题<strong>拼接</strong>在一起，形成一个内容更丰富、信息量更大的<strong>增强提示（Augmented Prompt）</strong>。</li><li><strong>生成（Generate）：</strong> 最后，将这个增强后的提示喂给LLM。LLM会基于其自身的知识和我们提供的上下文信息，生成一个更准确、更具事实性的回答。</li></ol><p>  <strong>RAG主要解决了LLM的以下核心问题：</strong></p><ol><li><strong>知识的静态性与过时性：</strong> LLM的知识被“冻结”在其训练数据截止的那个时间点。RAG通过连接一个可以随时更新的外部知识库，使得LLM能够获取和利用最新的信息，解决了知识过时的问题。</li><li><strong>幻觉（Hallucination）：</strong> LLM在回答其知识范围外或不确定的问题时，倾向于捏造事实。RAG通过提供具体的、相关的上下文，将LLM的回答“锚定”在这些事实依据上，显著降低了幻觉的产生。</li><li><strong>缺乏专业领域知识与私有知识：</strong> 对LLM进行微调来注入特定领域的知识成本高昂且效果有限。RAG可以轻松地将模型与任何私有数据集（如公司内部文档、个人笔记）连接起来，使其成为一个领域专家。</li></ol><p>  <strong>与微调（Fine-tuning）相比，RAG的优势：</strong></p><ul><li><strong>知识更新成本低：</strong> 更新知识只需在数据库中添加或修改文档，无需重新训练昂贵的LLM。而微调则需要重新进行训练。</li><li><strong>可追溯性与可解释性：</strong> RAG可以清晰地展示出答案是基于哪些源文档生成的，用户可以点击查看来源进行事实核查。微调则像一个“黑盒”，无法知道知识的具体来源。</li><li><strong>降低幻觉：</strong> RAG通过提供事实依据，让回答有据可循。微调虽然能注入知识，但模型仍可能在不确定时产生幻觉。</li><li><strong>高效费比：</strong> 对于注入事实性知识的场景，RAG的开发和维护成本远低于微调。</li><li><strong>个性化：</strong> 可以为每个用户或每个请求动态地接入不同的知识源，实现高度的个性化服务。</li></ul></li></ul><hr><h4 id="5-2-一个完整的-RAG-流水线包含哪些关键步骤？请从数据准备到最终生成，详细描述整个过程。"><a href="#5-2-一个完整的-RAG-流水线包含哪些关键步骤？请从数据准备到最终生成，详细描述整个过程。" class="headerlink" title="5.2 一个完整的 RAG 流水线包含哪些关键步骤？请从数据准备到最终生成，详细描述整个过程。"></a><strong>5.2 一个完整的 RAG 流水线包含哪些关键步骤？请从数据准备到最终生成，详细描述整个过程。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  一个完整的RAG流水线可以分为两个主要阶段：<strong>离线的数据准备（索引）阶段</strong> 和 <strong>在线的查询（推理）阶段</strong>。</p><p>  <strong>阶段一：数据准备 &#x2F; 索引流水线 (Offline &#x2F; Indexing Pipeline)</strong><br>  这个阶段的目标是构建一个可供检索的知识库，它通常是一次性或周期性执行的。</p><ol><li><strong>数据加载（Load）：</strong> 从各种数据源加载原始文档。数据源可以是PDF文件、Word文档、网页、Notion数据库、Confluence页面、数据库表格等。</li><li><strong>文本切块（Split &#x2F; Chunk）：</strong> 将加载进来的长文档切割成更小的、语义完整的文本块（chunks）。这一步至关重要，因为后续的检索和生成都是以这些小块为单位的。</li><li><strong>嵌入（Embed）：</strong> 使用一个预训练的文本嵌入模型（Embedding Model，如BERT, BGE, M3E等），将每一个文本块转换成一个高维的数字向量（vector）。这个向量捕捉了文本块的语义信息。</li><li><strong>存储（Store）：</strong> 将每个文本块的内容及其对应的嵌入向量存储到一个专门的数据库中，最常见的就是<strong>向量数据库（Vector Database）</strong>，如FAISS, ChromaDB, Pinecone等。数据库会为这些向量建立索引，以便进行高效的相似度搜索。</li></ol><p>  <strong>阶段二：查询 &#x2F; 推理流水线 (Online &#x2F; Inference Pipeline)</strong><br>  这个阶段是当用户提出问题时实时执行的。</p><ol><li><strong>用户提问（User Query）：</strong> 系统接收用户输入的自然语言问题。</li><li><strong>查询嵌入（Embed Query）：</strong> 使用与<strong>步骤三中完全相同</strong>的嵌入模型，将用户的提问也转换成一个查询向量。</li><li><strong>向量检索（Retrieve）：</strong> 将这个查询向量与向量数据库中存储的所有文本块向量进行相似度计算（通常是余弦相似度或点积）。系统会找出与查询向量最相似的Top-K个文本块向量，并将它们对应的原始文本块内容检索出来。</li><li><strong>（可选）重排序（Re-rank）：</strong> 为了进一步提升检索质量，可以引入一个重排序模型。它会对初步检索出的Top-K个文本块进行更精细的打分和排序，选出与问题真正最相关的Top-N个（N &lt; K）。</li><li><strong>增强与生成（Augment &amp; Generate）：</strong><ul><li>将重排序后最优的N个文本块内容，与用户的原始问题一起，按照一个预设的模板（Prompt Template）组合成一个增强提示。</li><li>将这个增强提示发送给LLM，由LLM基于提供的上下文和自身知识，生成最终的、流畅的、有根据的回答。</li></ul></li></ol></li></ul><hr><h4 id="5-3-在构建知识库时，文本切块策略至关重要。你会如何选择合适的切块大小和重叠长度？这背后有什么权衡？"><a href="#5-3-在构建知识库时，文本切块策略至关重要。你会如何选择合适的切块大小和重叠长度？这背后有什么权衡？" class="headerlink" title="5.3 在构建知识库时，文本切块策略至关重要。你会如何选择合适的切块大小和重叠长度？这背后有什么权衡？"></a><strong>5.3 在构建知识库时，文本切块策略至关重要。你会如何选择合适的切块大小和重叠长度？这背后有什么权衡？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  文本切块（Chunking）是RAG流程中最关键且最需要经验的步骤之一，它直接影响检索的召回率和精确度，进而影响最终生成答案的质量。选择合适的切块大小（Chunk Size）和重叠长度（Overlap）需要在多个因素之间进行权衡。</p><p>  <strong>如何选择合适的切块大小（Chunk Size）？</strong></p><ol><li><strong>依据嵌入模型的能力：</strong> 嵌入模型有其输入的最大Token数限制。切块大小应小于这个限制。同时，很多嵌入模型在处理中等长度（如256-512个token）的文本时效果最好，过长或过短都可能导致语义表征质量下降。</li><li><strong>依据数据的类型和结构：</strong><ul><li>对于<strong>结构化的、段落分明的</strong>文档（如论文、报告），可以采用<strong>语义切块</strong>，即按段落、标题或句子来切分，这样能最大程度地保留语义完整性。</li><li>对于<strong>非结构化的长文本</strong>，则更多地依赖固定长度切块。</li><li>对于<strong>代码</strong>，应该按函数或类来切块，而不是简单地按行数。</li></ul></li><li><strong>依据预期的查询类型：</strong> 如果用户的问题通常很具体，需要精确定位到某一句话，那么较小的切块（如句子级别）可能更有效。如果用户的问题很宽泛，需要综合多个段落的信息，那么较大的切块会更好。</li></ol><p>  <strong>如何选择合适的重叠长度（Overlap）？</strong></p><p>  重叠长度的作用是<strong>防止语义信息在切块边界被硬生生地切断</strong>。例如，一个重要的概念可能在一句话的结尾被提出，而在下一句话的开头进行解释。如果没有重叠，这句话就会被分割到两个独立的块中，破坏其完整性。</p><ul><li>一个常见的经验法则是设置重叠长度为<strong>切块大小的10%-20%</strong>。例如，对于1024个token的切块，可以设置128或256个token的重叠。</li><li>重叠并非越大越好，过大的重叠会增加数据冗余和存储成本。</li></ul><p>  <strong>背后的权衡（Trade-offs）：</strong></p><ul><li><strong>大块（Large Chunks） vs. 小块（Small Chunks）：</strong><ul><li><p><strong>大块的优点：</strong> 包含更丰富的上下文，有助于回答需要广泛背景知识的复杂问题。</p></li><li><p><strong>大块的缺点：</strong></p><ol><li><strong>噪声增加：</strong> 可能会包含大量与用户查询不直接相关的信息，稀释了关键信息的“信噪比”。</li><li><strong>检索精度下降：</strong> 嵌入向量代表的是整个大块的平均语义，可能无法精确匹配非常具体的问题。</li><li><strong>成本更高：</strong> 送入LLM的上下文更长，API调用成本更高。</li><li><strong>“大海捞针”问题：</strong> 容易触发LLM的“Lost in the Middle”问题。</li></ol></li><li><p><strong>小块的优点：</strong> 信息密度高，与具体问题的相关性强，检索更精确。</p></li><li><p><strong>小块的缺点：</strong></p><ol><li><strong>上下文不足：</strong> 单个小块可能不包含回答问题所需的全部信息，需要检索并拼接多个小块才能形成完整答案。</li><li><strong>语义割裂：</strong> 容易将原本连续的上下文信息切断。</li></ol></li></ul></li></ul><p>  <strong>总结：</strong><br>  切块策略没有唯一的“最佳”方案。实践中，通常会从一个合理的基线（如<code>chunk_size=512</code>, <code>overlap=64</code>）开始，然后通过评估检索质量，针对具体的文档类型和查询场景进行迭代优化。有时甚至会采用<strong>多尺度切块</strong>的策略，即同时索引不同大小的块，以应对不同粒度的查询。</p></li></ul><hr><h4 id="5-4-如何选择一个合适的嵌入模型？评估一个-Embedding-模型的好坏有哪些指标？"><a href="#5-4-如何选择一个合适的嵌入模型？评估一个-Embedding-模型的好坏有哪些指标？" class="headerlink" title="5.4 如何选择一个合适的嵌入模型？评估一个 Embedding 模型的好坏有哪些指标？"></a><strong>5.4 如何选择一个合适的嵌入模型？评估一个 Embedding 模型的好坏有哪些指标？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  选择合适的嵌入模型（Embedding Model）是决定RAG系统检索效果的基石。一个好的嵌入模型应该能够将语义相近的文本映射到向量空间中相近的位置。</p><p>  <strong>如何选择合适的嵌入模型？</strong></p><ol><li><p><strong>参考公开排行榜（Leaderboards）：</strong></p><ul><li><strong>MTEB (Massive Text Embedding Benchmark)</strong> 是目前最权威、最全面的嵌入模型评测基准。它涵盖了多种任务和语言，是选择模型的首要参考。可以直接查看MTEB排行榜，选择在 <strong>检索（Retrieval）</strong> 任务上得分高的模型。</li><li>C-MTEB是专门针对中文的排行榜。</li></ul></li><li><p><strong>考虑具体应用场景：</strong></p><ul><li><strong>领域特异性：</strong> 如果你的知识库是某个专业领域（如医疗、法律、金融），可以考虑使用在该领域数据上预训练或微调过的嵌入模型，它们通常比通用模型表现更好。</li><li><strong>语言支持：</strong> 确保模型支持你的业务所涉及的语言，特别是对于多语言场景。</li><li><strong>模型大小与速度：</strong> 模型越大通常效果越好，但推理速度也越慢，成本越高。需要在效果和性能之间做出权衡。对于需要低延迟的实时应用，可能需要选择一个更小的模型。</li></ul></li><li><p><strong>私有模型 vs. 开源模型：</strong></p><ul><li><strong>私有模型（如OpenAI的Ada系列）：</strong> 优点是性能强大，使用方便。缺点是数据需要通过API传输，存在隐私风险，且成本较高。</li><li><strong>开源模型（如BGE, M3E, Jina-embeddings等）：</strong> 优点是可本地部署，数据安全可控，成本低，且有大量高质量模型可供选择。缺点是需要自己进行部署和维护。</li></ul></li></ol><p>  <strong>评估Embedding模型好坏的指标：</strong><br>  评估指标主要来自MTEB基准，可以分为几大类：</p><ol><li><p><strong>检索（Retrieval）：</strong> 这是对RAG最重要的评估任务。</p><ul><li><strong>nDCG@k (Normalized Discounted Cumulative Gain):</strong> 综合衡量了检索结果的<strong>相关性</strong>和<strong>排名</strong>。是检索任务中最核心和最全面的指标。</li><li><strong>Recall@k:</strong> 衡量在前k个结果中，召回了多少比例的相关文档。</li><li><strong>MRR (Mean Reciprocal Rank):</strong> 衡量第一个相关文档出现在第几位。适用于那些只需要找到一个正确答案的场景。</li></ul></li><li><p><strong>语义文本相似度（Semantic Textual Similarity, STS）：</strong></p><ul><li><strong>指标：</strong> Spearman或Pearson相关系数。</li><li><strong>评估方式：</strong> 衡量模型计算出的向量余弦相似度，与人类判断的两句话的语义相似度分数之间的相关性。一个好的模型，其相似度计算结果应该与人类的直觉高度一致。</li></ul></li><li><p><strong>分类（Classification）：</strong></p><ul><li><strong>指标：</strong> 准确率（Accuracy）。</li><li><strong>评估方式：</strong> 将文本嵌入向量作为特征，训练一个简单的逻辑回归分类器，看其在文本分类任务上的表现。这衡量了嵌入向量作为“特征”的质量。</li></ul></li><li><p><strong>聚类（Clustering）：</strong></p><ul><li><strong>指标：</strong> V-measure。</li><li><strong>评估方式：</strong> 看模型生成的嵌入向量能否在无监督的情况下，将语义相似的文本自然地聚集在一起。</li></ul></li></ol></li></ul><hr><h4 id="5-5-除了基础的向量检索，你还知道哪些可以提升-RAG-检索质量的技术？"><a href="#5-5-除了基础的向量检索，你还知道哪些可以提升-RAG-检索质量的技术？" class="headerlink" title="5.5 除了基础的向量检索，你还知道哪些可以提升 RAG 检索质量的技术？"></a><strong>5.5 除了基础的向量检索，你还知道哪些可以提升 RAG 检索质量的技术？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  基础的向量检索（Dense Retrieval）虽然有效，但在处理复杂查询和多样化文档时往往会遇到瓶颈。为了提升检索质量，学术界和工业界发展出了许多先进的技术，主要可以分为<strong>增强检索器</strong>和<strong>优化查询</strong>两大类。</p><p>  <strong>一、 增强检索器（Improving the Retriever）</strong></p><ol><li><p><strong>混合搜索（Hybrid Search）：</strong></p><ul><li><strong>技术：</strong> 将 <strong>稀疏检索（Sparse Retrieval）</strong> 和 <strong>密集检索（Dense Retrieval）</strong> 相结合。<ul><li><strong>稀疏检索（如BM25）：</strong> 基于关键词匹配，对于包含特定术语、缩写、ID的查询非常有效。</li><li><strong>密集检索（向量搜索）：</strong> 基于语义相似度，擅长理解长尾、口语化的查询。</li></ul></li><li><strong>优势：</strong> 兼顾了关键词精确匹配和语义模糊匹配的能力，效果通常远超单一检索方法。</li></ul></li><li><p><strong>重排序（Re-ranking）：</strong></p><ul><li><strong>技术：</strong> 采用一个 <strong>两阶段（two-stage）</strong> 的检索流程。<ol><li><strong>召回（Recall）：</strong> 先用一个快速但相对粗糙的方法（如向量搜索或混合搜索）从海量文档中召回一个较大的候选集（例如Top 50）。</li><li><strong>重排（Re-rank）：</strong> 再使用一个更强大、更复杂的模型（通常是<strong>Cross-Encoder</strong>）对这个小候选集进行精细化的重排序，选出最终的Top-N（例如Top 5）作为上下文。</li></ol></li><li><strong>优势：</strong> Cross-Encoder可以直接比较查询和文档的文本，捕捉更细粒度的相关性，精度远高于单纯的向量相似度，极大地提升了最终上下文的质量。</li></ul></li></ol><p>  <strong>二、 优化查询（Improving the Query）</strong></p><ol><li><strong>查询扩展与转换（Query Expansion &amp; Transformation）：</strong><ul><li><strong>技术：</strong> 不直接使用用户的原始查询进行检索，而是先用LLM对查询进行“加工”。</li><li><strong>方法：</strong><ul><li><strong>多查询检索（Multi-Query Retrieval）：</strong> 让LLM针对原始问题，从不同角度生成多个不同的查询，然后对所有查询的检索结果进行合并。</li><li><strong>HyDE（Hypothetical Document Embeddings）：</strong> 让LLM先针对问题生成一个“假设性”的答案，然后用这个假设性答案的嵌入去检索，因为答案的文本和目标文档的文本在形式上更相似。</li><li><strong>子问题查询（Sub-Querying）：</strong> 对于复杂问题，先将其分解成多个简单的子问题，分别检索，再汇总结果。</li></ul></li></ul></li></ol><p>  <strong>三、 优化索引结构（Improving the Index）</strong></p><ol><li><p><strong>小块引用大块（Small-to-Large Chunking）：</strong></p><ul><li><strong>技术：</strong> 在索引时，将文档切成小的、用于检索的“摘要块”（Summary Chunks），但每个小块都保留对它所属的、更大的“父块”（Parent Chunk）的引用。</li><li><strong>流程：</strong> 检索时，用查询匹配小块以获得高精度，但最终送给LLM的是包含更丰富上下文的父块。</li><li><strong>优势：</strong> 兼顾了小块检索的精确性和大块上下文的完整性。</li></ul></li><li><p><strong>图索引（Graph Indexing）：</strong></p><ul><li><strong>技术：</strong> 除了向量索引，还用LLM提取文档中的实体和关系，构建一个知识图谱。</li><li><strong>流程：</strong> 检索时，可以先在图谱中进行结构化查询，找到相关的实体和子图，再结合向量检索进行补充。</li><li><strong>优势：</strong> 对于需要进行多跳推理、理解实体关系的查询非常有效。</li></ul></li></ol></li></ul><hr><h4 id="5-6-请解释“Lost-in-the-Middle”问题。它描述了-RAG-中的什么现象？有什么方法可以缓解这个问题？"><a href="#5-6-请解释“Lost-in-the-Middle”问题。它描述了-RAG-中的什么现象？有什么方法可以缓解这个问题？" class="headerlink" title="5.6 请解释“Lost in the Middle”问题。它描述了 RAG 中的什么现象？有什么方法可以缓解这个问题？"></a><strong>5.6 请解释“Lost in the Middle”问题。它描述了 RAG 中的什么现象？有什么方法可以缓解这个问题？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>“Lost in the Middle”</strong> 是指大型语言模型（LLM）在处理一个长上下文（long context）时，倾向于<strong>更好地回忆和利用位于上下文开头和结尾的信息，而忽略或遗忘位于中间部分的信息</strong>的一种现象。这个发现在斯坦福大学的一篇名为《Lost in the Middle: How Language Models Use Long Contexts》的论文中被系统性地揭示。</p><p>  <strong>在RAG中的现象：</strong><br>  这个现象对RAG系统有直接且重要的影响。在RAG的生成阶段，我们通常会将检索到的Top-K个文档块与用户的原始问题拼接起来，形成一个长长的prompt。例如：<br>  <code>[原始问题] + [文档1] + [文档2] + [文档3] + ... + [文档K]</code></p><p>  如果LLM存在“Lost in the Middle”的问题，那么：</p><ul><li><strong>文档1</strong> 和 <strong>文档K</strong> 的内容会得到LLM的充分关注。</li><li>而位于中间的<strong>文档2、文档3…</strong>等，即使它们包含了回答问题的关键信息，也<strong>有很大概率被LLM忽略</strong>，导致最终生成的答案信息不完整或不准确。</li><li>这会使得我们精心设计的检索环节（如重排序）的效果大打折扣，因为即使我们把最相关的文档排在了前面，只要它不是第一个或最后一个，就可能被“遗忘”。</li></ul><p>  <strong>缓解方法：</strong></p><ol><li><p><strong>文档重排序（Document Re-ordering）：</strong></p><ul><li><strong>核心思想：</strong> 不再按照检索分数的顺序简单地拼接文档，而是有策略地放置它们。</li><li><strong>具体做法：</strong> 在将检索到的K个文档送入LLM之前，进行一次重排序。将<strong>最相关</strong>的文档放置在上下文的<strong>开头</strong>和<strong>结尾</strong>，而将次要相关的文档放在中间。这样可以确保关键信息处于LLM的“注意力甜点区”。</li></ul></li><li><p><strong>减少检索的文档数量（Reduce the Number of Retrieved Documents）：</strong></p><ul><li><strong>核心思想：</strong> 与其送入大量可能包含噪声的文档，不如只送入少数几个最关键的文档。</li><li><strong>具体做法：</strong> 严格控制Top-K中的K值，例如只取Top-3或Top-5。这需要前端的检索和重排序步骤有更高的精度，确保召回的文档质量足够高。</li></ul></li><li><p><strong>指令化提示（Instruct the Model）：</strong></p><ul><li><strong>核心思想：</strong> 在prompt中明确指示模型要关注所有提供的上下文。</li><li><strong>具体做法：</strong> 在prompt的末尾加入类似这样的指令：“请确保你的回答完全基于以上提供的所有上下文信息，不要忽略任何一份文档。” 虽然这不能完全解决问题，但在一定程度上可以引导模型的注意力。</li></ul></li><li><p><strong>对LLM进行微调（Fine-tune the LLM）：</strong></p><ul><li><strong>核心思想：</strong> 训练LLM更好地处理长上下文。</li><li><strong>具体做法：</strong> 构建一个特定的微调数据集，其中的任务要求模型必须利用位于上下文中间部分的信息才能正确回答。通过这种方式，可以“强迫”模型学会不忽略中间内容。这是最根本但成本也最高的解决方案。</li></ul></li></ol></li></ul><hr><h4 id="5-7-如何全面地评估一个-RAG-系统的性能？请分别从检索和生成两个阶段提出评估指标。"><a href="#5-7-如何全面地评估一个-RAG-系统的性能？请分别从检索和生成两个阶段提出评估指标。" class="headerlink" title="5.7 如何全面地评估一个 RAG 系统的性能？请分别从检索和生成两个阶段提出评估指标。"></a><strong>5.7 如何全面地评估一个 RAG 系统的性能？请分别从检索和生成两个阶段提出评估指标。</strong></h4><ul><li><p><strong>参考答案：</strong><br>  全面地评估一个RAG系统，必须将其拆分为<strong>检索阶段</strong>和<strong>生成阶段</strong>两个独立但又相互关联的部分进行评估，因为最终答案的质量是这两个阶段共同作用的结果。一个好的评估框架应该同时包含<strong>客观的、自动化的指标</strong>和<strong>主观的、人工的评估</strong>。</p><p>  <strong>第一阶段：检索性能评估 (Retrieval Evaluation)</strong><br>  这个阶段的目标是评估我们的检索器（Retriever）能否“<strong>找得对、找得全</strong>”。评估需要一个包含（问题，相关文档ID）的标注数据集。</p><ul><li><strong>核心指标：</strong><ol><li><strong>上下文精确率 (Context Precision):</strong> 衡量检索到的文档中有多少是真正与问题相关的。它反映了<strong>检索结果的信噪比</strong>。</li><li><strong>上下文召回率 (Context Recall):</strong> 衡量所有相关的文档中，有多少被我们的检索器成功找回来了。它反映了<strong>信息查找的全面性</strong>。</li></ol></li><li><strong>其他常用排名指标：</strong><ol start="3"><li><strong>Hit Rate:</strong> 检索到的文档中是否至少包含一个相关文档。这是一个基础的“及格线”指标。</li><li><strong>MRR (Mean Reciprocal Rank):</strong> 第一个相关文档排名的倒数的平均值。它衡量找到第一个正确答案的速度。</li><li><strong>nDCG@k (Normalized Discounted Cumulative Gain):</strong> 最全面和常用的指标之一，它同时考虑了检索结果的<strong>相关性等级</strong>和它们在结果列表中的<strong>排名</strong>。</li></ol></li></ul><p>  <strong>第二阶段：生成性能评估 (Generation Evaluation)</strong><br>  这个阶段的目标是评估LLM在给定上下文后，能否生成“<strong>忠实、准确、有用</strong>”的答案。</p><ul><li><p><strong>核心指标（通常需要LLM-as-a-Judge或人工评估）：</strong></p><ol><li><strong>忠实度&#x2F;可溯源性 (Faithfulness &#x2F; Groundedness):</strong><ul><li><strong>评估问题：</strong> 生成的答案是否完全基于所提供的上下文？是否存在捏造或幻觉？</li><li><strong>评估方法：</strong> 将生成的答案与上下文进行对比，检查答案中的每一句话是否都能在上下文中找到依据。</li></ul></li><li><strong>答案相关性 (Answer Relevancy):</strong><ul><li><strong>评估问题：</strong> 生成的答案是否直接、清晰地回答了用户的原始问题？</li><li><strong>评估方法：</strong> 评估答案与用户问题的匹配程度，看是否存在答非所问的情况。</li></ul></li><li><strong>答案正确性 (Answer Correctness):</strong><ul><li><strong>评估问题：</strong> 答案中的信息是否事实准确？（这是一个更严格的指标，因为有时即使忠于原文，原文也可能是错的）</li><li><strong>评估方法：</strong> 与一个“黄金标准”答案（Ground Truth）进行比较，或由领域专家进行事实核查。</li></ul></li></ol></li><li><p><strong>自动化评估框架：</strong></p><ul><li>像 <strong>RAGAS</strong>, <strong>ARES</strong>, <strong>TruLens</strong> 这样的开源框架，它们使用LLM-as-a-Judge的思想，将上述的Faithfulness, Relevancy等指标自动化计算出来，极大地提高了评估效率。例如，RAGAS会生成问题、答案，并自动检查答案是否忠实于上下文。</li></ul></li></ul></li></ul><hr><h4 id="5-8-在什么场景下，你会选择使用图数据库或知识图谱来增强或替代传统的向量数据库检索？"><a href="#5-8-在什么场景下，你会选择使用图数据库或知识图谱来增强或替代传统的向量数据库检索？" class="headerlink" title="5.8 在什么场景下，你会选择使用图数据库或知识图谱来增强或替代传统的向量数据库检索？"></a><strong>5.8 在什么场景下，你会选择使用图数据库或知识图谱来增强或替代传统的向量数据库检索？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  我会选择使用图数据库或知识图谱（Knowledge Graph, KG）来增强或替代传统向量数据库，主要是在处理<strong>高度关联、结构化的数据</strong>以及需要进行<strong>复杂关系推理</strong>的场景下。</p><p>  向量数据库擅长的是<strong>语义相似度</strong>的模糊匹配，而知识图谱擅长的是<strong>实体与关系</strong>的精确查询。</p><p>  <strong>核心应用场景：</strong></p><ol><li><p><strong>需要多跳推理（Multi-hop Reasoning）的复杂问题：</strong></p><ul><li><strong>场景描述：</strong> 当用户的问题无法通过单个文档或事实来回答，而需要沿着实体之间的关系链进行多次“跳转”才能找到答案时。</li><li><strong>举例：</strong><ul><li>“<code>Llama 2</code> 的作者所在的公司的CEO是谁？”<ul><li>这是一个三跳查询：<code>Llama 2</code> -&gt; <code>作者</code> -&gt; <code>Meta</code> -&gt; <code>CEO</code></li></ul></li><li>“和我正在处理的这个客户（A公司）在同一个行业、并且使用了我们产品B的成功案例有哪些？”<ul><li><code>A公司</code> -&gt; <code>所属行业</code> -&gt; <code>同行业的其他公司</code> -&gt; <code>使用了产品B的公司</code></li></ul></li></ul></li><li><strong>为什么用KG：</strong> 这类问题用向量检索几乎无法完成，但对于知识图谱来说，就是几次简单的图遍历查询。</li></ul></li><li><p><strong>当数据本身具有强结构和关联性时：</strong></p><ul><li><strong>场景描述：</strong> 数据中包含大量的实体（人、公司、产品、地点）和它们之间明确的关系（雇佣、投资、拥有、位于）。</li><li><strong>举例：</strong> 金融领域的公司股权结构、欺诈检测中的资金流动网络、医疗领域的药物-基因-疾病关系网络、供应链管理。</li><li><strong>为什么用KG：</strong> 将这些数据建成知识图谱，可以最大化地利用其结构信息。例如，可以快速找到一个公司的所有子公司，或者发现两个看似无关的人之间的隐藏联系。</li></ul></li><li><p><strong>需要提供高度可解释性的答案时：</strong></p><ul><li><strong>场景描述：</strong> 在一些严肃的应用（如金融风控、医疗诊断）中，不仅需要给出答案，还需要清晰地解释答案是如何得出的。</li><li><strong>举例：</strong> “为什么将这个交易标记为高风险？” -&gt; “因为交易方A是B公司的子公司，而B公司在一个月前被列入了制裁名单。”</li><li><strong>为什么用KG：</strong> 知识图谱的查询路径本身就是一种非常直观、可解释的证据链。</li></ul></li></ol><p>  <strong>增强或替代？</strong><br>  在大多数情况下，知识图谱和向量数据库是<strong>互补增强</strong>的关系，而非完全替代。一个常见的先进RAG模式是：</p><ol><li><strong>混合检索：</strong> 首先用LLM分析用户问题。</li><li>如果问题涉及复杂关系，则先<strong>查询知识图谱</strong>，找到核心的实体和事实。</li><li>然后，将这些从图谱中检索到的结构化信息，作为上下文，或者用来<strong>构建更精确的查询</strong>，再去<strong>向量数据库</strong>中检索相关的非结构化文本，以获得更详细的解释和背景。</li><li>最后，将两方面的信息汇总给LLM生成答案。</li></ol></li></ul><hr><h4 id="5-9-传统的-RAG-流程是“先检索后生成”，你是否了解一些更复杂的-RAG-范式，比如在生成过程中进行多次检索或自适应检索？"><a href="#5-9-传统的-RAG-流程是“先检索后生成”，你是否了解一些更复杂的-RAG-范式，比如在生成过程中进行多次检索或自适应检索？" class="headerlink" title="5.9 传统的 RAG 流程是“先检索后生成”，你是否了解一些更复杂的 RAG 范式，比如在生成过程中进行多次检索或自适应检索？"></a><strong>5.9 传统的 RAG 流程是“先检索后生成”，你是否了解一些更复杂的 RAG 范式，比如在生成过程中进行多次检索或自适应检索？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  是的，传统的“先检索后生成”（Retrieve-then-Read）范式虽然经典，但比较刻板。为了应对更复杂的问题和提升答案质量，研究界已经提出了多种更动态、更智能的RAG范式。</p><p>  <strong>1. 迭代式检索 (Iterative Retrieval) - 例如 Self-RAG, Corrective-RAG</strong></p><ul><li><strong>核心思想：</strong> 将RAG从一个单向的流水线，变成一个<strong>循环、自我修正</strong>的过程。</li><li><strong>工作流程：</strong><ol><li><strong>首次检索与生成：</strong> 像传统RAG一样，进行检索并生成一个初步的答案。</li><li><strong>反思与评估（Reflection）：</strong> LLM会对初步生成的答案和检索到的上下文进行“反思”。它会评估：当前的信息是否足够支撑答案？答案是否还有不确定或缺失的部分？</li><li><strong>二次检索：</strong> 如果认为信息不足，LLM会<strong>主动生成一个新的、更具针对性的查询</strong>，进行新一轮的检索。例如，如果初步答案是“A公司的CEO是张三”，模型可能会反思“这个信息是否最新？”，然后生成一个新的查询“A公司2025年的CEO是谁？”</li><li><strong>整合与精炼：</strong> LLM会整合新旧检索到的所有信息，生成一个更完善、更准确的最终答案。</li></ol></li></ul><p>  <strong>2. 自适应检索 (Adaptive Retrieval) - 例如 FLARE, Self-Ask</strong></p><ul><li><strong>核心思想：</strong> 不在生成前一次性检索所有信息，而是在<strong>生成过程中“按需”检索</strong>，实现“即时”（just-in-time）的信息获取。</li><li><strong>工作流程：</strong><ol><li><strong>开始生成：</strong> LLM根据问题开始直接生成答案。</li><li><strong>预测不确定性：</strong> 它会一边生成，一边预测接下来的内容。当它预测到即将生成一个事实性信息（如人名、日期、地点），但对此<strong>不确定</strong>（表现为下一个词的概率分布很平坦）时，它会<strong>暂停</strong>生成。</li><li><strong>主动提问与检索：</strong> 在暂停处，LLM会插入一个特殊的占位符（如 <code>[SEARCH]</code>），并主动提出一个需要查询的问题（例如，“法国的首都是哪里？”）。</li><li><strong>获取信息并继续：</strong> 系统执行这个查询，将检索到的答案（“巴黎”）填入，然后LLM基于这个新信息继续向下生成。</li></ol></li><li><strong>优势：</strong> 这种方法非常高效，只在需要时才进行检索，避免了预先检索大量无关信息。</li></ul><p>  <strong>3. 多源数据RAG (Multi-Source RAG)</strong></p><ul><li><strong>核心思想：</strong> 让Agent能够智能地从<strong>多种不同类型的数据源</strong>中进行检索和整合。</li><li><strong>工作流程：</strong> Agent首先对问题进行分解，判断回答这个问题需要哪些信息。然后，它可能会决定：<ul><li>从<strong>向量数据库</strong>中检索相关的非结构化文档。</li><li>从<strong>知识图谱</strong>中查询结构化的实体关系。</li><li>调用<strong>SQL数据库</strong>来获取精确的统计数据。</li><li>甚至调用<strong>搜索引擎API</strong>来获取实时信息。</li></ul></li><li>最后，Agent会将从不同来源获取的所有信息进行综合，生成一个全面的答案。这本质上是一种<strong>Agent驱动的RAG</strong>。</li></ul></li></ul><hr><h4 id="5-10-RAG-系统在实际部署中可能面临哪些挑战？"><a href="#5-10-RAG-系统在实际部署中可能面临哪些挑战？" class="headerlink" title="5.10 RAG 系统在实际部署中可能面临哪些挑战？"></a><strong>5.10 RAG 系统在实际部署中可能面临哪些挑战？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  将一个RAG原型系统部署到生产环境中，会面临一系列从数据到模型、再到工程和运维的实际挑战。</p><ol><li><p><strong>数据处理与维护的复杂性 (Data Pipeline Complexity):</strong></p><ul><li><strong>分块策略的泛化性：</strong> 一个在PDF上效果很好的分块策略，可能在处理HTML或JSON数据时效果很差。为异构数据源设计和维护一套鲁棒的分块策略非常困难。</li><li><strong>知识库的实时更新：</strong> 如何高效地保持向量索引与源数据的同步？当源文档被修改或删除时，需要有可靠的机制来更新或废弃对应的向量，这涉及到复杂的ETL（Extract, Transform, Load）流程。</li></ul></li><li><p><strong>性能瓶颈：延迟与成本 (Performance Bottlenecks: Latency &amp; Cost):</strong></p><ul><li><strong>延迟：</strong> RAG的“检索+生成”两步天然比直接调用LLM要慢。在实时交互场景下，检索和LLM生成的延迟都必须被极致优化。</li><li><strong>成本：</strong><ul><li><strong>计算成本：</strong> 大规模文档的嵌入、向量数据库的运行、LLM的API调用，都是持续的成本支出。</li><li><strong>存储成本：</strong> 向量索引本身会占用大量的存储空间，尤其是高维度的嵌入。</li></ul></li></ul></li><li><p><strong>端到端的评估与监控 (End-to-End Evaluation &amp; Monitoring):</strong></p><ul><li><strong>评估困难：</strong> 在生产环境中，很难有带标准答案的数据集。如何有效地评估线上RAG系统的表现（如检索质量、答案忠实度）是一个巨大挑战。</li><li><strong>性能衰退监控：</strong> 如何发现并诊断问题？是检索模块的性能下降了（例如，因为数据分布变化），还是生成模块开始产生更多幻觉？需要建立一套完善的监控和报警系统。</li></ul></li><li><p><strong>处理“无答案”和“上下文外”问题 (Handling “No Answer” and “Out-of-Context” Questions):</strong></p><ul><li><strong>挑战：</strong> 当知识库中不包含用户所提问题的答案时，系统很容易会基于不相关的检索结果强行生成一个错误的、具有误导性的答案。</li><li><strong>解决方案：</strong> 系统需要具备<strong>判断检索结果相关性</strong>的能力。如果判断所有检索到的内容都与问题无关，它应该<strong>拒绝回答</strong>或明确告知用户“根据现有资料无法回答此问题”，而不是胡乱作答。</li></ul></li><li><p><strong>安全与隐私 (Security &amp; Privacy):</strong></p><ul><li><strong>访问控制：</strong> 在企业环境中，不同的用户对不同的文档有不同的访问权限。RAG系统必须能够集成这套权限体系，确保用户只能检索到他们有权查看的文档内容。</li><li><strong>提示注入：</strong> 恶意用户可能会在查询中嵌入恶意指令，或者被索引的文档本身可能包含恶意内容，这些都可能用来攻击或操纵RAG系统。</li></ul></li></ol></li></ul><hr><h4 id="5-11-了解搜索系统吗？和RAG有什么区别？"><a href="#5-11-了解搜索系统吗？和RAG有什么区别？" class="headerlink" title="5.11 了解搜索系统吗？和RAG有什么区别？"></a><strong>5.11 了解搜索系统吗？和RAG有什么区别？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  是的，我了解搜索系统。搜索系统和RAG系统关系紧密，但它们的目标和最终产出有本质的区别。可以说，<strong>RAG系统是构建在搜索系统之上的一个更高级的应用</strong>。</p><p>  <strong>搜索系统 (Search System) - 例如 Google Search, Elasticsearch</strong></p><ul><li><strong>核心目标：</strong> <strong>信息检索（Information Retrieval）</strong>。它的任务是，根据用户的查询，从一个大规模的文档集合中，找到并返回一个<strong>排序好的文档列表（a ranked list of documents）</strong>。</li><li><strong>最终产出：</strong> <strong>“源”</strong>。它提供的是“可能包含答案的原材料”，用户需要自己去点击链接、阅读文档、并从中<strong>自己总结</strong>出答案。</li><li><strong>核心技术：</strong> 索引技术（如倒排索引）、排序算法（如BM25, PageRank, TF-IDF）、查询理解和扩展。</li></ul><p>  <strong>RAG系统 (Retrieval-Augmented Generation System)</strong></p><ul><li><strong>核心目标：</strong> <strong>问题回答（Question Answering）</strong>。它的任务是，根据用户的查询，直接提供一个<strong>精准的、对话式的、综合性的自然语言答案</strong>。</li><li><strong>最终产出：</strong> <strong>“答案”</strong>。它利用检索到的“源”作为事实依据，但最终交付的是一个经过<strong>综合、提炼和总结</strong>后的成品。</li><li><strong>核心技术：</strong> 它<strong>包含</strong>了一个搜索系统作为其“检索”模块，但更关键的是，它增加了一个大型语言模型（LLM）作为其“<strong>生成&#x2F;合成</strong>”模块。</li></ul><p>  <strong>最关键的区别：</strong></p><table><thead><tr><th align="left">特征</th><th align="left">搜索系统</th><th align="left">RAG系统</th></tr></thead><tbody><tr><td align="left"><strong>任务</strong></td><td align="left">找文档 (Find Documents)</td><td align="left">给答案 (Give Answers)</td></tr><tr><td align="left"><strong>输出</strong></td><td align="left"><strong>文档列表</strong> (List of sources)</td><td align="left"><strong>自然语言答案</strong> (Synthesized answer)</td></tr><tr><td align="left"><strong>用户角色</strong></td><td align="left">用户是<strong>主动</strong>的，需要自己阅读和总结</td><td align="left">用户是<strong>被动</strong>的，直接获得成品答案</td></tr><tr><td align="left"><strong>核心组件</strong></td><td align="left">索引器 + 排序器</td><td align="left"><strong>[索引器 + 排序器]</strong> + <strong>生成器(LLM)</strong></td></tr></tbody></table><p>  <strong>一个简单的比喻：</strong></p><ul><li><strong>搜索系统</strong>就像一个图书馆的图书管理员。你问他“新加坡的历史”，他会告诉你：“关于这个主题，3楼A区的第5、6、8本书，还有4楼C区的期刊都很有用，你自己去看看吧。”</li><li><strong>RAG系统</strong>就像一个历史学专家。你问他同样的问题，他会去图书馆查阅那些书籍和期刊，然后直接告诉你：“新加坡的历史可以概括为以下几个关键时期……，这些信息主要参考了《新加坡史》和《近代东南亚》这几本书。”</li></ul></li></ul><hr><h4 id="5-12-知道或者使用过哪些开源RAG框架比如Ragflow？如何选择合适场景？"><a href="#5-12-知道或者使用过哪些开源RAG框架比如Ragflow？如何选择合适场景？" class="headerlink" title="5.12 知道或者使用过哪些开源RAG框架比如Ragflow？如何选择合适场景？"></a><strong>5.12 知道或者使用过哪些开源RAG框架比如Ragflow？如何选择合适场景？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  是的，我了解并关注着多个开源RAG框架和平台。除了最广为人知的、作为基础工具库的 <strong>LangChain</strong> 和 <strong>LlamaIndex</strong> 之外，还涌现出了一批更专注于提供端到端RAG解决方案的平台，其中 <strong>RAGFlow</strong> 就是一个很有代表性的例子。其他类似的框架还包括 <strong>Haystack</strong>, <strong>DSPy</strong> 等。</p><p>  <strong>对RAGFlow的理解：</strong><br>  RAGFlow与LangChain&#x2F;LlamaIndex这类“代码库”形态的框架不同，它更像一个 <strong>“开箱即用”的、对业务人员更友好的RAG应用平台</strong>。它的特点是：</p><ul><li><strong>自动化与可视化：</strong> RAGFlow试图将RAG流水线中许多复杂的、需要编码和经验调优的步骤自动化。例如，它提供了基于深度学习的、“智能”的文本分块方法，而不是让用户手动设置<code>chunk_size</code>。它通常还提供一个GUI界面，让用户可以方便地上传文档、测试效果、查看引用来源。</li><li><strong>端到端整合：</strong> 它提供了一个相对完整的解决方案，从数据接入、处理、索引到最终的应用接口，都整合在一个系统里。</li><li><strong>为非专家设计：</strong> 它的目标用户不仅是开发者，也包括了希望快速搭建和验证RAG应用的业务分析师或产品经理。</li></ul><p>  <strong>如何选择合适场景？</strong></p><p>  选择哪个框架主要取决于<strong>项目的需求、团队的技能和对定制化的要求</strong>。</p><ol><li><p><strong>选择 LangChain &#x2F; LlamaIndex 的场景：</strong></p><ul><li><strong>高度定制化需求：</strong> 当你需要对RAG流水线的每一个环节（例如，自定义分块逻辑、实现复杂的混合检索策略、集成公司内部的特定工具）进行深度控制和定制时。</li><li><strong>作为底层库集成：</strong> 当你不是要构建一个独立的RAG应用，而是想把RAG能力作为一部分，嵌入到一个更大的、复杂的软件系统中时。</li><li><strong>开发者为核心的团队：</strong> 当你的团队主要是由熟悉Python和AI开发的工程师组成，他们乐于从零开始、灵活地构建和优化系统。</li><li><strong>一句话总结：</strong> <strong>选择它们是为了“灵活性”和“控制力”</strong>。</li></ul></li><li><p><strong>选择 RAGFlow &#x2F; Haystack 这类平台的场景：</strong></p><ul><li><strong>快速原型验证（Rapid Prototyping）：</strong> 当你想在几天内快速搭建一个高质量的RAG原型，来验证一个业务想法的可行性时。</li><li><strong>追求最佳实践（Best Practices Out-of-the-Box）：</strong> 当你希望直接利用领域内已经验证过的最佳实践（如先进的分块和索引技术），而不是自己去重新实现和调试时。</li><li><strong>技术团队规模有限或业务人员主导：</strong> 当团队希望更多地关注业务逻辑，而不是底层AI技术的复杂实现时。</li><li><strong>一句话总结：</strong> <strong>选择它们是为了“效率”和“易用性”</strong>。</li></ul></li></ol><p>  <strong>我的选择策略：</strong><br>  在项目初期，如果需要快速看到效果，我会考虑使用RAGFlow这样的平台来搭建一个<strong>基线（Baseline）</strong>。在验证了业务价值后，如果发现平台的标准化流程无法满足我们更深度的性能优化或业务逻辑定制需求，我可能会考虑使用LangChain或LlamaIndex，将RAGFlow中验证过的有效模块，用代码进行更精细化的<strong>重构和实现</strong>。</p></li></ul><h3 id="6-模型评估与-Agent-评估"><a href="#6-模型评估与-Agent-评估" class="headerlink" title="6. 模型评估与 Agent 评估"></a><strong>6. 模型评估与 Agent 评估</strong></h3><h4 id="6-1-为什么传统的-NLP-评估指标（如-BLEU-ROUGE）对于评估现代-LLM-的生成质量来说，存在很大的局限性？"><a href="#6-1-为什么传统的-NLP-评估指标（如-BLEU-ROUGE）对于评估现代-LLM-的生成质量来说，存在很大的局限性？" class="headerlink" title="6.1 为什么传统的 NLP 评估指标（如 BLEU, ROUGE）对于评估现代 LLM 的生成质量来说，存在很大的局限性？"></a><strong>6.1 为什么传统的 NLP 评估指标（如 BLEU, ROUGE）对于评估现代 LLM 的生成质量来说，存在很大的局限性？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  传统的NLP评估指标，如BLEU（常用于机器翻译）和ROUGE（常用于文本摘要），其核心思想是<strong>比较模型生成的文本与一个或多个“参考答案”在表层词汇（n-gram）上的重合度</strong>。这种方法对于评估现代LLM的生成质量存在巨大局限性，原因如下：</p><ol><li><p><strong>语义理解的缺失（Lack of Semantic Understanding）：</strong></p><ul><li>这些指标只关心词汇的表面匹配，完全不理解其背后的语义。例如，“今天天气很好”和“今天日光很灿烂”，在人类看来意思相近，但它们的BLEU&#x2F;ROUGE得分会很低，因为词汇重合度小。反之，一个与参考答案词汇高度重合但语法不通或逻辑混乱的句子，也可能得到高分。</li></ul></li><li><p><strong>无法评估事实准确性（Cannot Evaluate Factual Accuracy）：</strong></p><ul><li>LLM的核心挑战之一是幻觉。一个生成的答案可能在语言上非常流畅，甚至与参考答案的风格相似，但包含完全错误的事实。BLEU&#x2F;ROUGE无法检测出这种事实性错误。</li></ul></li><li><p><strong>忽略了多样性与创造性（Ignores Diversity and Creativity）：</strong></p><ul><li>对于开放式生成任务（如对话、写作、头脑风暴），根本不存在唯一的“标准答案”。一个好的LLM应该能生成多样化、有创意且合理的回答。而基于固定参考答案的评估方法会“惩罚”任何与参考答案不同但同样优秀的回答，扼杀了创造性。</li></ul></li><li><p><strong>对长文本的评估能力差（Poor for Long-form Content）：</strong></p><ul><li>这些指标在评估长篇文本（如文章、报告）的<strong>连贯性（Coherence）、逻辑性和结构性</strong>方面几乎是无能为力的。它们只能进行局部、零碎的词汇匹配。</li></ul></li><li><p><strong>对推理过程的无视（Ignores Reasoning Process）：</strong></p><ul><li>对于需要推理的问题（如数学题、逻辑题），LLM的价值不仅在于最终答案，更在于其“思维链”。BLEU&#x2F;ROUGE只能比较最终答案的字符串，完全无法评估推理步骤是否正确。</li></ul></li></ol><p>  总之，现代LLM的评估需要超越表层词汇，深入到<strong>语义理解、事实性、逻辑推理、安全性、遵循指令</strong>等更高维度的能力层面，而这正是BLEU和ROUGE等传统指标的盲区。</p></li></ul><hr><h4 id="6-2-请介绍几个目前行业内广泛使用的-LLM-综合性基准测试，并说明它们各自的侧重点。（例如：MMLU-Big-Bench-HumanEval）"><a href="#6-2-请介绍几个目前行业内广泛使用的-LLM-综合性基准测试，并说明它们各自的侧重点。（例如：MMLU-Big-Bench-HumanEval）" class="headerlink" title="6.2 请介绍几个目前行业内广泛使用的 LLM 综合性基准测试，并说明它们各自的侧重点。（例如：MMLU, Big-Bench, HumanEval）"></a><strong>6.2 请介绍几个目前行业内广泛使用的 LLM 综合性基准测试，并说明它们各自的侧重点。（例如：MMLU, Big-Bench, HumanEval）</strong></h4><ul><li><p><strong>参考答案：</strong><br>  为了更全面地评估LLM的能力，学术界和工业界开发了许多综合性基准测试。其中，MMLU、Big-Bench和HumanEval是最具代表性的几个，它们各自有不同的侧重点：</p><ol><li><p><strong>MMLU (Massive Multitask Language Understanding)</strong></p><ul><li><strong>侧重点：</strong> <strong>知识的广度与学科问题解决能力</strong>。</li><li><strong>简介：</strong> MMLU是一个大规模的多任务测试集，旨在衡量模型在各种学科领域的知识水平。它包含57个不同的科目，涵盖了从初等数学、美国历史、计算机科学到专业级别的法律、市场营销和医学等。</li><li><strong>形式：</strong> 所有问题都是<strong>四选一的单项选择题</strong>。</li><li><strong>评估目的：</strong> 检验模型是否具备渊博的、跨学科的知识储备和应用这些知识解决问题的能力。一个在MMLU上得分高的模型，通常被认为是一个“知识渊博”的模型。</li></ul></li><li><p><strong>Big-Bench (Beyond the Imitation Game Benchmark)</strong></p><ul><li><strong>侧重点：</strong> <strong>探索LLM的能力边界和未来潜力</strong>。</li><li><strong>简介：</strong> Big-Bench是一个由社区协作创建的、极其多样化的基准，包含了超过200个任务。这些任务被设计得非常有挑战性，旨在测试当前LLM难以解决的能力，如常识推理、逻辑、物理直觉、创造性任务等。</li><li><strong>形式：</strong> 任务形式非常多样，包括选择题、生成题、比较题等。</li><li><strong>评估目的：</strong> Big-Bench的目标是“预测未来”。它试图找到那些一旦模型规模或技术发展到某个临界点就可能“涌现”出的新能力。它衡量的是模型的<strong>通用智能水平和前沿能力</strong>。</li></ul></li><li><p><strong>HumanEval (Human-Labeled Evaluation)</strong></p><ul><li><strong>侧重点：</strong> <strong>代码生成与编程能力</strong>。</li><li><strong>简介：</strong> HumanEval是一个由OpenAI创建的、专门用于评估代码生成能力的基准。它包含164个手写的编程问题，每个问题都提供了函数签名、文档字符串（docstring）、以及几个单元测试（unit tests）。</li><li><strong>形式：</strong> 模型需要根据函数签名和文档字符串，生成完整的Python函数体。</li><li><strong>评估方法：</strong> 采用 <strong>pass@k</strong> 指标。即模型生成k个代码样本，只要其中至少有一个能够通过所有的单元测试，就算通过。这衡量了模型<strong>编写正确、可用代码</strong>的能力。</li></ul></li></ol><p>  <strong>其他重要基准：</strong></p><ul><li><strong>GSM8K:</strong> 专注于评估<strong>小学水平的数学应用题</strong>的推理能力，需要模型进行多步的思维链推理。</li><li><strong>ARC (AI2 Reasoning Challenge):</strong> 专注于评估需要<strong>科学常识和推理</strong>的、有挑战性的选择题。</li><li><strong>HellaSwag:</strong> 专注于评估<strong>常识推理</strong>，任务是选择一个最合理的句子来续写一个给定的情景。</li></ul></li></ul><hr><h4 id="6-3-什么是“LLM-as-a-Judge”？使用-LLM-来评估另一个-LLM-的输出，有哪些优点和潜在的偏见？"><a href="#6-3-什么是“LLM-as-a-Judge”？使用-LLM-来评估另一个-LLM-的输出，有哪些优点和潜在的偏见？" class="headerlink" title="6.3 什么是“LLM-as-a-Judge”？使用 LLM 来评估另一个 LLM 的输出，有哪些优点和潜在的偏见？"></a><strong>6.3 什么是“LLM-as-a-Judge”？使用 LLM 来评估另一个 LLM 的输出，有哪些优点和潜在的偏见？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>“LLM-as-a-Judge”</strong> 是一种新兴的、自动化的模型评估范式。它的核心思想是<strong>利用一个功能强大的、前沿的LLM（通常是像GPT-4o或Claude 3 Opus这样的闭源模型，被称为“裁判模型”）来评估另一个被测试LLM的输出质量</strong>。</p><p>  <strong>工作流程：</strong></p><ol><li>提供一个 <strong>评估提示（Evaluation Prompt）</strong> 给裁判模型。</li><li>这个提示通常包含：<ul><li>用户的原始问题（user query）。</li><li>被测试LLM生成的回答（response）。</li><li>（可选）一个参考答案（reference answer）。</li><li>一套清晰的<strong>评估准则（rubric）</strong>，例如“请从准确性、流畅性、有害性三个维度，为下面的回答打一个1-10分的分数，并给出你的理由。”</li></ul></li><li>裁判模型会输出一个结构化的评估结果，包括分数和详细的解释。</li></ol><p>  <strong>优点：</strong></p><ol><li><strong>可扩展性与效率（Scalability &amp; Efficiency）：</strong> 这是最大的优点。相比于昂贵且缓慢的人工评估，LLM裁判可以近乎实时地、大规模地对海量模型输出进行评估，极大地加速了模型迭代的反馈循环。</li><li><strong>一致性（Consistency）：</strong> 只要裁判模型和评估提示固定，其评估标准就是一致的，避免了不同人类标注者之间主观差异带来的不一致性问题。</li><li><strong>可定制化（Customizability）：</strong> 可以通过设计不同的评估准则和提示，轻松地让裁判模型从任意维度（如简洁性、创造性、安全性、共情能力等）来评估输出，非常灵活。</li></ol><p>  <strong>潜在的偏见：</strong></p><ol><li><strong>位置偏见（Position Bias）：</strong> 在进行A&#x2F;B模型对比评估时，裁判模型倾向于<strong>偏爱第一个</strong>呈现给它的答案。</li><li><strong>冗长偏见（Verbosity Bias）：</strong> 裁判模型倾向于给<strong>更长、更详细</strong>的回答打更高的分数，即使这些回答可能包含冗余或无用的信息。</li><li><strong>自我偏好&#x2F;风格偏见（Self-Preference &#x2F; Style Bias）：</strong> 裁判模型可能更偏爱那些与<strong>它自己生成风格相似</strong>的回答，这会惩罚那些风格不同但同样优秀的模型。</li><li><strong>有限的知识与推理能力（Limited Knowledge and Reasoning）：</strong> 裁判模型本身也可能犯事实性错误或进行错误的逻辑推理。它可能无法识别出被测试模型回答中非常细微的、专业领域的错误，从而给出错误的评估。</li><li><strong>过于“宽容”：</strong> 研究发现，裁判模型有时对于一些有害或不当内容的判断会比人类更宽容。</li></ol><p>  因此，LLM-as-a-Judge是一个强大高效的评估工具，但不能完全替代人类评估，尤其是在需要深度专业知识和对齐验证的场景。最佳实践是将其作为人类评估的有力补充和规模化工具。</p></li></ul><hr><h4 id="6-4-如何设计一个评估方案来衡量-LLM-的特定能力，比如“事实性-x2F-幻觉水平”、“推理能力”或“安全性”？"><a href="#6-4-如何设计一个评估方案来衡量-LLM-的特定能力，比如“事实性-x2F-幻觉水平”、“推理能力”或“安全性”？" class="headerlink" title="6.4 如何设计一个评估方案来衡量 LLM 的特定能力，比如“事实性&#x2F;幻觉水平”、“推理能力”或“安全性”？"></a><strong>6.4 如何设计一个评估方案来衡量 LLM 的特定能力，比如“事实性&#x2F;幻觉水平”、“推理能力”或“安全性”？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  为衡量LLM的特定能力设计评估方案，需要遵循“<strong>定义能力 -&gt; 构建数据集 -&gt; 确定评估方法</strong>”的流程。</p><p>  <strong>1. 衡量“事实性&#x2F;幻觉水平”：</strong></p><ul><li><strong>能力定义：</strong> 模型生成的回答是否基于可验证的事实，而不是捏造信息。</li><li><strong>数据集构建：</strong><ul><li><strong>基于知识库的QA：</strong> 构建一个问题集，其中每个问题的答案都可以从一个确定的知识源（如Wikipedia、公司内部文档、数据库）中找到。</li><li><strong>对抗性问题：</strong> 设计一些诱导模型产生幻觉的问题，比如询问关于不存在的人物或事件的信息。</li></ul></li><li><strong>评估方法：</strong><ul><li><strong>精确匹配&#x2F;关键词匹配：</strong> 对于事实简单的问题（如“谁是新加坡现任总统？”），可以直接将生成答案中的实体与标准答案进行比较。</li><li><strong>LLM-as-a-Judge：</strong> 使用一个更强大的LLM，让它判断生成的答案是否与提供的源知识（ground-truth knowledge）相符或矛盾。</li><li><strong>自动化框架：</strong> 使用如 <strong>FaithScore</strong> 或 <strong>RAGAS</strong> 中的 <strong>Faithfulness</strong> 指标，它们通过自动化的方式将生成答案的每个声明与上下文进行比对验证。</li></ul></li></ul><p>  <strong>2. 衡量“推理能力”：</strong></p><ul><li><strong>能力定义：</strong> 模型能否在没有直接知识的情况下，通过逻辑、数学或常识进行多步推导，得出正确结论。</li><li><strong>数据集构建：</strong><ul><li>使用专门的推理基准，如 <strong>GSM8K</strong>（数学应用题）、<strong>LogiQA</strong>（逻辑推理）、<strong>Big-Bench Hard</strong> 中的部分任务。</li><li>自行设计需要特定推理路径的任务，例如，给出一系列前提，要求模型推断结论。</li></ul></li><li><strong>评估方法：</strong><ul><li><strong>结果评估（Outcome-based）：</strong> 只判断最终答案是否正确。这是最直接的方法。</li><li><strong>过程评估（Process-based）：</strong> 对于使用了思维链（CoT）的模型，不仅评估最终答案，还由人类或另一个LLM来评估其推理步骤是否合乎逻辑、是否正确。这能更深入地了解模型的推理过程。</li></ul></li></ul><p>  <strong>3. 衡量“安全性”：</strong></p><ul><li><strong>能力定义：</strong> 模型能否拒绝回答有害、不道德、危险或非法的用户请求。</li><li><strong>数据集构建：</strong><ul><li>使用公开的对抗性提示数据集，如 <strong>AdvBench (Adversarial Benchmarks)</strong> 或 <strong>SafetyBench</strong>，它们包含了大量经过设计的、试图绕过安全护栏的“危险问题”。</li><li>通过<strong>红队测试（Red Teaming）</strong>，由人类专家主动地、创造性地构建新的攻击性提示。</li></ul></li><li><strong>评估方法：</strong><ul><li><strong>分类器评估：</strong> 将模型的回答输入到一个预训练好的<strong>安全分类器</strong>（通常是另一个LLM或专用分类模型）中，判断其是否属于“有害”、“拒绝回答”或其他类别。</li><li><strong>核心指标：</strong><ul><li><strong>拒绝率（Refusal Rate）：</strong> 模型成功拒绝回答有害问题的比例。</li><li><strong>误伤率（False Refusal Rate）：</strong> 模型错误地拒绝回答一个正常、安全问题的比例。</li></ul></li><li><strong>人工评估：</strong> 对于模糊或新型的案例，人工审核是最终的黄金标准。</li></ul></li></ul></li></ul><hr><h4 id="6-5-评估一个-Agent-为什么比评估一个基础-LLM-更加困难和复杂？评估的维度有哪些不同？"><a href="#6-5-评估一个-Agent-为什么比评估一个基础-LLM-更加困难和复杂？评估的维度有哪些不同？" class="headerlink" title="6.5 评估一个 Agent 为什么比评估一个基础 LLM 更加困难和复杂？评估的维度有哪些不同？"></a><strong>6.5 评估一个 Agent 为什么比评估一个基础 LLM 更加困难和复杂？评估的维度有哪些不同？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  评估一个Agent比评估一个基础LLM更加困难和复杂，因为评估的对象从一个<strong>静态的、单轮的“文本生成器”</strong>，转变为一个<strong>动态的、多轮的、与环境交互的“决策者”</strong>。</p><p>  <strong>困难和复杂性的根源：</strong></p><ol><li><strong>交互性与状态空间：</strong> 基础LLM是无状态的（stateless），其评估是“输入-&gt;输出”的简单模式。而Agent是<strong>有状态的（stateful）</strong>，它与环境进行多步交互，每一步的行动都会改变环境和自身的内部状态。这导致其可能的行为轨迹（trajectory）数量是天文数字，难以完全覆盖。</li><li><strong>环境的动态性与不确定性：</strong> LLM的评估环境是确定的（相同的输入总是有相同的期望输出范围）。Agent的评估环境（如真实的网页、API）是<strong>动态变化的、不可预测的</strong>。一个今天还能用的API明天可能就失效了，一个网页的结构可能随时改变，这使得评估结果难以复现。</li><li><strong>非确定性（Non-determinism）：</strong> 由于LLM本身的采样随机性和环境的动态性，同一个Agent在完全相同的初始任务下，两次执行的结果和路径可能完全不同。</li><li><strong>任务的开放性：</strong> Agent处理的任务往往是开放式的、没有唯一正确答案的（例如，“帮我预订一张去新加坡的性价比最高的机票”），这使得定义一个简单的“正确&#x2F;错误”指标变得不可能。</li></ol><p>  <strong>评估维度的不同：</strong></p><table><thead><tr><th align="left"><strong>评估维度</strong></th><th align="left"><strong>基础 LLM</strong></th><th align="left"><strong>Agent</strong></th></tr></thead><tbody><tr><td align="left"><strong>核心评估对象</strong></td><td align="left"><strong>单个回答的质量</strong> (Quality of a single response)</td><td align="left"><strong>整个任务完成过程</strong> (The entire task completion process)</td></tr><tr><td align="left"><strong>主要维度</strong></td><td align="left">- <strong>准确性 (Accuracy)</strong><br>- <strong>流畅性 (Fluency)</strong><br>- <strong>相关性 (Relevance)</strong><br>- <strong>安全性 (Safety)</strong></td><td align="left">- <strong>任务成功率 (Task Success Rate):</strong> 能否最终完成目标？<br>- <strong>效率 (Efficiency):</strong> 完成任务花了多少资源？（见下文）<br>- <strong>鲁棒性 (Robustness):</strong> 能否处理异常和错误？<br>- <strong>自主性 (Autonomy):</strong> 在没有人类干预的情况下能走多远？</td></tr><tr><td align="left"><strong>新增的过程维度</strong></td><td align="left">(无)</td><td align="left">- <strong>成本 (Cost):</strong> LLM调用次数、API费用、Token消耗。<br>- <strong>延迟 (Latency):</strong> 完成任务的总时间。<br>- <strong>步骤数 (Number of Steps):</strong> 任务分解和执行的步数。<br>- <strong>纠错能力 (Error Recovery):</strong> 从工具报错或错误状态中恢复的能力。</td></tr><tr><td align="left"><strong>评估方法</strong></td><td align="left">静态数据集上的基准测试 (MMLU, HumanEval)</td><td align="left"><strong>交互式环境</strong>中的基准测试 (WebArena, AgentBench)</td></tr></tbody></table><p>  总结来说，对LLM的评估更像是“<strong>产品质量检测</strong>”，而对Agent的评估更像是“<strong>路况复杂的真实驾驶测试</strong>”，不仅要看是否到达终点，更要看驾驶过程中的效率、安全性和应对突发状况的能力。</p></li></ul><hr><h4 id="6-6-你了解哪些专门用于评估-Agent-能力的基准测试？这些基准通常如何构建测试环境和任务？"><a href="#6-6-你了解哪些专门用于评估-Agent-能力的基准测试？这些基准通常如何构建测试环境和任务？" class="headerlink" title="6.6 你了解哪些专门用于评估 Agent 能力的基准测试？这些基准通常如何构建测试环境和任务？"></a><strong>6.6 你了解哪些专门用于评估 Agent 能力的基准测试？这些基准通常如何构建测试环境和任务？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  是的，随着Agent研究的兴起，一系列专门用于评估Agent能力的基准测试被开发出来，它们的核心特点是提供<strong>可控的、可复现的交互式环境</strong>。</p><p>  <strong>几个知名的Agent能力基准测试：</strong></p><ol><li><p><strong>WebArena:</strong></p><ul><li><strong>专注领域：</strong> <strong>网页浏览与操作</strong>。</li><li><strong>简介：</strong> 一个高度逼真的、独立的网页环境模拟器。它复刻了多个真实网站（如电商、论坛、软件开发协作工具）的功能，让Agent在其中完成真实世界的复杂任务。</li><li><strong>任务举例：</strong> 在电商网站上找到一个满足特定要求（如价格、评分）的商品并加入购物车；在论坛上预订一个会议室。</li><li><strong>评估方式：</strong> 基于最终网页状态的程序化判断（例如，购物车里是否有正确的商品）。</li></ul></li><li><p><strong>AgentBench:</strong></p><ul><li><strong>专注领域：</strong> <strong>通用Agent能力的综合评估</strong>。</li><li><strong>简介：</strong> 一个全面的基准，包含了8个不同环境来评估Agent在不同场景下的能力。</li><li><strong>任务举例：</strong><ul><li><strong>操作系统环境：</strong> 在一个Linux终端中操作文件、执行命令。</li><li><strong>数据库环境：</strong> 根据自然语言问题，对一个SQL数据库进行查询。</li><li><strong>知识图谱环境：</strong> 在知识图谱中进行多跳推理。</li><li><strong>游戏环境：</strong> 玩一些简单的文字冒险游戏。</li></ul></li></ul></li><li><p><strong>GAIA (General AI Assistants):</strong></p><ul><li><strong>专注领域：</strong> <strong>模拟人类使用真实工具完成复杂任务</strong>。</li><li><strong>简介：</strong> 一个极具挑战性的基准，其问题通常需要Agent进行多步推理，并<strong>组合使用多种工具</strong>（如网页浏览器、代码解释器、文件操作）才能解决。这些问题被设计得对人类来说很简单，但对AI来说却很困难。</li><li><strong>任务举例：</strong> “找出引用了论文A和论文B的所有论文中，被引用次数最高的那篇的第三位作者是谁？”</li></ul></li></ol><p>  <strong>这些基准通常如何构建测试环境和任务？</strong></p><ol><li><p><strong>环境构建 -&gt; 沙箱化与可复现性（Sandboxing &amp; Reproducibility）：</strong></p><ul><li>为了安全和可复现，基准测试通常不会让Agent直接访问真实的互联网，而是创建一个<strong>受控的、隔离的</strong>环境。</li><li><strong>方法：</strong><ul><li>使用 <strong>Docker 容器</strong>来封装一个包含浏览器、终端、文件系统的独立环境。</li><li>对于网页浏览，通常会<strong>本地托管</strong>一个网站的静态副本，或使用<strong>Web后台模拟器</strong>来响应Agent的请求。</li><li>对API的调用会被重定向到一个<strong>模拟（mock）的API服务器</strong>上。</li></ul></li></ul></li><li><p><strong>任务构建 -&gt; 目标导向（Goal-Oriented）：</strong></p><ul><li>任务通常以一个 <strong>高层次的目标（high-level goal）</strong> 的形式给出，而不是具体的步骤指令。</li><li>任务的设计会尽量覆盖多种需要Agent展示的能力，如<strong>信息检索、工具使用、推理规划、记忆</strong>等。</li><li>任务通常附带一个<strong>明确的、可程序化验证的成功标准</strong>。</li></ul></li><li><p><strong>评估构建 -&gt; 程序化验证（Programmatic Validation）：</strong></p><ul><li>评估的核心是自动判断任务是否成功。</li><li><strong>方法：</strong> 在Agent完成任务后，一个 <strong>评估脚本（evaluator script）</strong> 会自动检查环境的 <strong>最终状态（final state）</strong> 是否满足成功条件。</li><li><strong>举例：</strong><ul><li>检查磁盘上是否创建了内容正确的文件。</li><li>检查购物车的最终状态是否包含了正确的商品和数量。</li><li>检查Agent提交的最终答案字符串是否与标准答案匹配。</li></ul></li></ul></li></ol></li></ul><hr><h4 id="6-7-在评估一个-Agent-的任务完成情况时，除了最终结果的正确性，还有哪些过程指标是值得关注的？（例如：效率、成本、鲁棒性）"><a href="#6-7-在评估一个-Agent-的任务完成情况时，除了最终结果的正确性，还有哪些过程指标是值得关注的？（例如：效率、成本、鲁棒性）" class="headerlink" title="6.7 在评估一个 Agent 的任务完成情况时，除了最终结果的正确性，还有哪些过程指标是值得关注的？（例如：效率、成本、鲁棒性）"></a><strong>6.7 在评估一个 Agent 的任务完成情况时，除了最终结果的正确性，还有哪些过程指标是值得关注的？（例如：效率、成本、鲁棒性）</strong></h4><ul><li><p><strong>参考答案：</strong><br>  在评估Agent时，只看最终结果的正确性（Task Success）是远远不够的。一个优秀的Agent不仅要能“做对事”，还要“聪明地、高效地、可靠地做事”。因此，关注过程指标至关重要，它们能更全面地反映Agent的智能水平。</p><p>  <strong>值得关注的关键过程指标包括：</strong></p><p>  <strong>1. 效率 (Efficiency):</strong></p><ul><li><strong>定义：</strong> 衡量Agent完成任务所消耗的资源。效率是决定Agent在现实世界中是否可用的关键因素。</li><li><strong>具体指标：</strong><ul><li><strong>成本 (Cost):</strong><ul><li><strong>Token消耗量：</strong> Agent在所有思考和生成步骤中消耗的总Token数。</li><li><strong>API调用费用：</strong> 如果使用了付费的LLM或工具API，完成一次任务的总花费。</li></ul></li><li><strong>延迟 (Latency):</strong><ul><li><strong>总耗时 (Wall-clock Time):</strong> 从任务开始到结束所经过的真实时间。</li><li><strong>计算时间 (CPU&#x2F;GPU Time):</strong> Agent自身运行所占用的计算时间。</li></ul></li><li><strong>步骤数 (Number of Steps &#x2F; Turns):</strong> Agent执行“思考-行动”循环的总次数。通常，能用更少步骤完成任务的Agent被认为规划能力更强。</li></ul></li></ul><p>  <strong>2. 鲁棒性 (Robustness):</strong></p><ul><li><strong>定义：</strong> 衡量Agent在面对非理想、非预期情况时的表现。</li><li><strong>具体指标：</strong><ul><li><strong>错误处理能力 (Error Handling Capability):</strong> 当工具返回错误、网页加载失败或遇到预期外的环境状态时，Agent能否识别问题并采取纠正措施（例如，尝试不同的工具、修正输入参数、重新规划）。</li><li><strong>抗干扰能力 (Disturbance Resistance):</strong> 在环境中加入一些噪声或误导性信息，评估Agent的成功率下降了多少。</li></ul></li></ul><p>  <strong>3. 自主性与对齐 (Autonomy &amp; Alignment):</strong></p><ul><li><strong>定义：</strong> 衡量Agent在多大程度上能够独立完成任务，以及其行为是否符合人类的意图。</li><li><strong>具体指标：</strong><ul><li><strong>需要人类干预的次数 (Number of Human Interventions):</strong> 在一个需要人类协助的系统中，一个更自主的Agent需要人类帮助的次数更少。</li><li><strong>行为可解释性 (Interpretability):</strong> Agent的“思考”过程是否清晰、合乎逻辑，是否能让人类理解其决策依据。</li><li><strong>计划遵循度 (Plan Adherence):</strong> 如果Agent预先生成了一个计划，它在多大程度上遵循了自己的计划。</li></ul></li></ul><p>  通过综合评估这些过程指标，我们不仅能知道Agent“是否能行”，还能深入了解它“行不行得好”，并找到针对性的优化方向。</p></li></ul><hr><h4 id="6-8-什么是红队测试？它在发现-LLM-和-Agent-的安全漏洞与偏见方面扮演着什么角色？"><a href="#6-8-什么是红队测试？它在发现-LLM-和-Agent-的安全漏洞与偏见方面扮演着什么角色？" class="headerlink" title="6.8 什么是红队测试？它在发现 LLM 和 Agent 的安全漏洞与偏见方面扮演着什么角色？"></a><strong>6.8 什么是红队测试？它在发现 LLM 和 Agent 的安全漏洞与偏见方面扮演着什么角色？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  <strong>红队测试（Red Teaming）</strong>是一种<strong>对抗性测试</strong>方法，源自于网络安全领域的渗透测试。在AI领域，它指的是<strong>组织一个专门的团队（红队），主动地、创造性地、像一个“攻击者”一样，去寻找和利用LLM或Agent的漏洞、缺陷和非预期行为</strong>，以评估和提升其安全性和鲁棒性。</p><p>  与常规测试（使用固定的、已知的测试用例）不同，红队测试的核心在于<strong>“探索未知”</strong>，发现那些开发者在设计时没有预料到的、可能导致严重后果的“边缘案例”和“攻击向量”。</p><p>  <strong>红队测试在发现安全漏洞与偏见方面的核心角色：</strong></p><p>  <strong>1. 发现安全漏洞 (Security Vulnerabilities):</strong></p><ul><li><strong>绕过安全护栏：</strong> 红队会设计各种复杂的、精心构造的提示（即“越狱提示”），试图绕过模型的安全审查机制，诱导其生成有害内容，如暴力、色情、仇恨言论或违法活动的指导。</li><li><strong>提示注入（Prompt Injection）攻击（针对Agent）：</strong> 这是对Agent最核心的威胁之一。红队会模拟恶意用户或被污染的外部数据（如一个包含恶意指令的网页），尝试劫持Agent的控制流，让Agent执行非预期的、危险的操作，例如：<ul><li>泄露其上下文中的敏感信息。</li><li>滥用其工具，如发送垃圾邮件、删除文件。</li><li>改变其原始目标。</li></ul></li><li><strong>发现资源滥用漏洞：</strong> 红队会尝试让Agent陷入无限循环或执行高消耗的操作，测试其资源限制和熔断机制。</li></ul><p>  <strong>2. 发现偏见 (Biases):</strong></p><ul><li><strong>暴露刻板印象：</strong> 红队会设计一些涉及特定人群（如种族、性别、国籍、职业）的、看似中立但具有引导性的问题，来暴露模型是否会生成带有刻板印象或歧视性的回答。</li><li><strong>测试政治与社会偏见：</strong> 通过询问有争议的社会或政治话题，评估模型的立场是否中立，是否存在偏向性。</li><li><strong>揭示代表性不足问题：</strong> 探索模型在处理非主流文化或群体的相关问题时，是否会表现出知识的缺乏或产生不准确的描述。</li></ul><p>  <strong>总结：</strong><br>  红队测试扮演着“<strong>AI系统的免疫系统压力测试员</strong>”的角色。它通过模拟最坏情况和最狡猾的对手，帮助开发者在模型部署前，系统性地发现并修复那些在标准测试中难以暴露的深层次安全和对齐问题，是确保AI系统安全、可靠、公平的重要保障。</p></li></ul><hr><h4 id="6-9-在进行人工评估时，如何设计合理的评估准则和流程，以保证评估结果的客观性和一致性？"><a href="#6-9-在进行人工评估时，如何设计合理的评估准则和流程，以保证评估结果的客观性和一致性？" class="headerlink" title="6.9 在进行人工评估时，如何设计合理的评估准则和流程，以保证评估结果的客观性和一致性？"></a><strong>6.9 在进行人工评估时，如何设计合理的评估准则和流程，以保证评估结果的客观性和一致性？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  在人工评估中，保证结果的 <strong>客观性（Objectivity）</strong> 和 <strong>一致性（Consistency）</strong> 是最大的挑战，因为人类的判断天生是主观的。设计合理的评估准则（Rubric）和流程是克服这一挑战的关键。</p><p>  <strong>一、 设计合理的评估准则（Rubric）：</strong></p><ol><li><p><strong>明确且原子化的评估维度（Clear and Atomic Dimensions）：</strong></p><ul><li>不要使用模糊的词语如“好”或“坏”。将“质量”分解为多个<strong>相互独立</strong>的、具体的维度。例如：<ul><li><strong>准确性（Accuracy）：</strong> 答案是否包含事实错误？</li><li><strong>完整性（Completeness）：</strong> 答案是否全面地回应了问题的所有方面？</li><li><strong>简洁性（Conciseness）：</strong> 是否有冗余信息？</li><li><strong>安全性（Harmlessness）：</strong> 是否包含有害内容？</li></ul></li></ul></li><li><p><strong>量化的评分标准（Quantitative Rating Scale）：</strong></p><ul><li>使用量化的尺度，如 <strong>李克特量表（1-5分）</strong> 或 <strong>二元判断（是&#x2F;否）</strong>。</li><li>为<strong>每一个分数等级</strong>提供清晰、明确的定义。例如，对于准确性维度：5分&#x3D;完全准确；4分&#x3D;基本准确但有细微瑕疵；3分&#x3D;包含明显但非核心的错误…；1分&#x3D;完全错误。</li></ul></li><li><p><strong>提供丰富的示例（Abundant Examples）：</strong></p><ul><li>为每个维度的每个分数等级，提供<strong>典型的正面和负面示例（Golden examples and counter-examples）</strong>。这能极大地帮助标注者校准他们的判断标准。</li></ul></li></ol><p>  <strong>二、 设计合理的评估流程：</strong></p><ol><li><p><strong>标注者培训与校准（Rater Training and Calibration）：</strong></p><ul><li>在评估开始前，对所有标注者进行<strong>系统性培训</strong>，确保他们完全理解评估准则和所有定义。</li><li>进行<strong>校准会</strong>，让所有标注者对同一批样本进行打分，然后公开讨论和对齐打分差异，直到大家的理解趋于一致。</li></ul></li><li><p><strong>盲评（Blind Evaluation）：</strong></p><ul><li>标注者<strong>不应该知道</strong>他们正在评估的回答来自哪个模型（A模型、B模型还是人类）。这可以消除品牌偏见或先入为主的观念。</li></ul></li><li><p><strong>多次独立评估与一致性检验（Multiple Independent Ratings &amp; Consistency Check）：</strong></p><ul><li>每个样本至少由 <strong>2-3名</strong> 标注者独立进行评估。</li><li>使用统计指标来衡量<strong>标注者间信度（Inter-Annotator Agreement, IAA）</strong>，如 <strong>Cohen’s Kappa</strong> 或 <strong>Fleiss’ Kappa</strong>。</li><li>如果IAA过低，说明评估准则存在歧义，需要返回第一步进行修改。</li></ul></li><li><p><strong>采用成对比较（Pairwise Comparison）而非绝对评分：</strong></p><ul><li>对于对比两个模型（A vs. B）的场景，让人类判断“<strong>哪个更好</strong>”（A更好&#x2F;B更好&#x2F;平局）通常比让他们分别为A和B打绝对分数<strong>更容易、也更可靠</strong>。这种方法可以有效地减少个体打分尺度的差异。</li></ul></li><li><p><strong>建立仲裁机制（Adjudication Mechanism）：</strong></p><ul><li>对于标注者之间分歧较大的“疑难案例”，需要有一个更高阶的专家或委员会进行最终的<strong>仲裁</strong>，以确保最终结果的权威性。</li></ul></li></ol></li></ul><hr><h4 id="6-10-如何持续监控和评估一个已经部署上线的-LLM-应用或-Agent-服务的表现，以应对可能出现的性能衰退或行为漂移？"><a href="#6-10-如何持续监控和评估一个已经部署上线的-LLM-应用或-Agent-服务的表现，以应对可能出现的性能衰退或行为漂移？" class="headerlink" title="6.10 如何持续监控和评估一个已经部署上线的 LLM 应用或 Agent 服务的表现，以应对可能出现的性能衰退或行为漂移？"></a><strong>6.10 如何持续监控和评估一个已经部署上线的 LLM 应用或 Agent 服务的表现，以应对可能出现的性能衰退或行为漂移？</strong></h4><ul><li><p><strong>参考答案：</strong><br>  对已部署上线的LLM应用或Agent服务进行持续监控和评估，是一个主动的、循环的过程，旨在应对<strong>模型漂移（Model Drift）</strong>和<strong>数据漂移（Data Drift）</strong>，确保服务质量的稳定。</p><p>  <strong>数据漂移</strong>指生产环境中的输入数据分布发生了变化（例如，用户开始问一些新型的问题），而<strong>模型漂移</strong>指模型的预测能力因数据漂移而下降。</p><p>  一个完整的监控评估体系应包含以下几个层面：</p><p>  <strong>1. 采集与日志（Collection and Logging）：</strong></p><ul><li><strong>全面日志：</strong> 记录每一次请求的完整交互数据，包括用户输入、模型生成的中间步骤（如Agent的思考链）、最终输出、调用的工具、延迟、Token消耗等。</li><li><strong>用户反馈：</strong> 在产品界面中嵌入明确的用户反馈机制，如“顶&#x2F;踩”按钮、打分、一键报告问题等。这是最直接的性能信号。</li></ul><p>  <strong>2. 自动化监控（Automated Monitoring）：</strong></p><ul><li><strong>监控代理指标（Proxy Metrics）：</strong> 监控那些与性能高度相关的、可自动计算的指标。这些指标的异常波动通常是问题的早期预警。<ul><li><strong>输入指标：</strong> 问题长度、主题分布、提问语言等。</li><li><strong>输出指标：</strong> 回答长度、代码块比例、JSON格式错误率、拒绝回答率等。</li><li><strong>过程指标（针对Agent）：</strong> 平均执行步数、工具调用频率、工具调用失败率。</li></ul></li><li><strong>自动化质量评估：</strong><ul><li><strong>定期抽样：</strong> 从生产流量中随机抽取一小部分样本。</li><li><strong>LLM-as-a-Judge：</strong> 使用一个强大的“裁判LLM”，根据一套固定的评估准则（如是否有害、是否跑题），对抽样样本进行自动打分。</li><li><strong>对比黄金集：</strong> 将抽样样本与一个内部维护的、高质量的“黄金评估集”进行对比，看模型在这些关键问题上的表现是否稳定。</li></ul></li></ul><p>  <strong>3. 人工审核与分析（Human Review and Analysis）：</strong></p><ul><li><strong>定期人工审计：</strong> 定期组织运营或评估团队，对生产环境中的随机样本、用户反馈的坏案例、以及自动化监控发现的异常案例进行深入的人工分析。</li><li><strong>根本原因分析（Root Cause Analysis）：</strong> 对于发现的问题，需要深入分析是哪个环节出了问题？是LLM本身能力退化？是Agent的规划逻辑有误？还是某个工具API发生了变更？</li></ul><p>  <strong>4. 反馈闭环与模型迭代（Feedback Loop and Model Iteration）：</strong></p><ul><li><strong>持续的数据管理：</strong> 将从生产环境中发现的有价值的案例（特别是失败案例和用户不喜欢的案例）清洗、标注后，持续地加入到<strong>评估集</strong>和<strong>微调数据集中</strong>。</li><li><strong>定期再训练&#x2F;微调：</strong> 根据积累的新数据，定期对模型进行微调（Fine-tuning）或重新训练（Re-training），以适应新的数据分布和用户需求。</li><li><strong>A&#x2F;B测试：</strong> 在上线新版本的模型或Agent逻辑时，使用A&#x2F;B测试框架，小流量验证新版本的性能是否优于旧版本，确保每次迭代都是正向的。</li></ul><p>  通过建立这样一个“<strong>采集 -&gt; 监控 -&gt; 分析 -&gt; 迭代</strong>”的闭环，我们可以主动地管理和维护线上服务的质量，而不是被动地等待用户投诉。</p></li></ul>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/02/ai-agent-learning/Extra01-%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/</id>
    <link href="http://jasondong97.github.io/2026/03/02/ai-agent-learning/Extra01-%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/"/>
    <published>2026-03-02T12:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="LLM-amp-VLM-amp-Agent-面试回答参考"><a href="#LLM-amp-VLM-amp-Agent-面试回答参考" class="headerlink" title="LLM &amp; VLM &amp; Agent 面试回答参考"></]]>
    </summary>
    <title>
      <![CDATA[LLM & VLM & Agent 面试回答参考]]>
    </title>
    <updated>2026-03-08T09:24:16.361Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="第十六章-毕业设计：构建属于你的多智能体应用"><a href="#第十六章-毕业设计：构建属于你的多智能体应用" class="headerlink" title="第十六章 毕业设计：构建属于你的多智能体应用"></a>第十六章 毕业设计：构建属于你的多智能体应用</h1><p>恭喜你来到 Hello-Agents 教程的最后一章！在前面的 15 章中，我们从零开始构建了 HelloAgents 框架，学习了智能体的核心概念、多种范式、工具系统、记忆机制、通信协议、强化学习训练和性能评估等知识。在第 13-15 章中，我们还通过三个完整的实战项目（智能旅行助手、自动化深度研究智能体、赛博小镇）展示了如何将所学知识融会贯通。</p><p>现在，是时候让你成为真正的智能体系统构建者了！本章将指导你<strong>构建属于你自己的多智能体应用</strong>，并通过开源协作的方式与社区分享你的成果。</p><h2 id="16-1-毕业设计的意义"><a href="#16-1-毕业设计的意义" class="headerlink" title="16.1 毕业设计的意义"></a>16.1 毕业设计的意义</h2><h3 id="16-1-1-为什么要做毕业设计"><a href="#16-1-1-为什么要做毕业设计" class="headerlink" title="16.1.1 为什么要做毕业设计"></a>16.1.1 为什么要做毕业设计</h3><p>学习技术最好的方式不是看教程，而是<strong>动手实践</strong>。通过前面章节的学习，你已经掌握了构建智能体系统的理论知识和技术工具。但是，真正的挑战在于：<strong>如何将这些知识应用到实际问题中？如何设计一个完整的系统？如何处理各种边界情况和异常？</strong></p><p>毕业设计的核心价值在于培养你的综合应用能力，将前面学到的所有知识（智能体范式、工具系统、记忆机制、通信协议等）选择性的整合到一个完整的项目中。</p><p>通过本章的学习和实践，希望你能够独立设计并实现一个完整的智能体应用，熟练使用 HelloAgents 框架的各种功能，掌握 Git 和 GitHub 的基本操作，学会编写清晰的项目文档，参与开源社区的协作开发，最终获得一个可以展示的技术作品。</p><h3 id="16-1-2-毕业设计的形式"><a href="#16-1-2-毕业设计的形式" class="headerlink" title="16.1.2 毕业设计的形式"></a>16.1.2 毕业设计的形式</h3><p>你的毕业设计将以<strong>开源项目</strong>的形式提交到 Hello-Agents 的共创项目仓库（<code>Co-creation-projects</code>目录）。具体要求如下：</p><ol><li><p><strong>项目命名</strong>：使用<code>&#123;你的GitHub用户名&#125;-&#123;项目名称&#125;</code>的格式，例如<code>jjyaoao-CodeReviewAgent</code></p></li><li><p><strong>项目内容</strong>：</p><ul><li>一个可运行的 Jupyter Notebook（<code>.ipynb</code>文件）或 Python 脚本</li><li>完整的依赖列表（<code>requirements.txt</code>）</li><li>清晰的 README 文档（<code>README.md</code>）</li><li>可选：演示视频、截图、数据集等</li></ul></li><li><p><strong>提交方式</strong>：通过 GitHub 的 Pull Request（PR）提交</p></li><li><p><strong>评审流程</strong>：社区成员会 review 你的代码，提出改进建议，通过后合并到主仓库</p></li></ol><h2 id="16-2-项目选题指南"><a href="#16-2-项目选题指南" class="headerlink" title="16.2 项目选题指南"></a>16.2 项目选题指南</h2><h3 id="16-2-1-选题原则"><a href="#16-2-1-选题原则" class="headerlink" title="16.2.1 选题原则"></a>16.2.1 选题原则</h3><p>一个好的毕业设计项目应该具有实用性，解决真实的问题而不是为了技术而技术，我们需要追求在有限的时间和资源内可以完成，并且能够清晰地展示你的技术能力。</p><h3 id="16-2-2-推荐选题方向"><a href="#16-2-2-推荐选题方向" class="headerlink" title="16.2.2 推荐选题方向"></a>16.2.2 推荐选题方向</h3><p>以下是一些推荐的项目方向，你可以选择其中一个，也可以自己提出新的想法：</p><p><strong>（1）生产力工具类</strong></p><ul><li><strong>智能代码审查助手</strong>：自动分析代码质量、发现潜在 bug、提出优化建议</li><li><strong>智能文档生成器</strong>：根据代码自动生成 API 文档、用户手册</li><li><strong>智能会议助手</strong>：记录会议内容、生成会议纪要、提取行动项</li><li><strong>智能邮件助手</strong>：自动分类邮件、生成回复草稿、提醒重要事项</li></ul><p><strong>（2）学习辅助类</strong></p><ul><li><strong>智能学习伙伴</strong>：根据学习进度推荐学习资源、生成练习题、答疑解惑</li><li><strong>智能论文助手</strong>：帮助查找文献、总结论文、生成引用</li><li><strong>智能编程导师</strong>：提供编程练习、代码 review、学习路径规划</li><li><strong>智能语言学习助手</strong>：提供对话练习、语法纠错、词汇扩展</li></ul><p><strong>（3）创意娱乐类</strong></p><ul><li><strong>智能故事生成器</strong>：根据用户输入生成小说、剧本、诗歌</li><li><strong>智能游戏 NPC</strong>：创建有个性的游戏角色，能够与玩家自然对话</li><li><strong>智能音乐推荐</strong>：根据心情、场景推荐音乐，生成播放列表</li><li><strong>智能菜谱助手</strong>：根据食材、口味推荐菜谱，生成购物清单</li></ul><p><strong>（4）数据分析类</strong></p><ul><li><strong>智能数据分析师</strong>：自动分析数据、生成可视化图表、撰写分析报告</li><li><strong>智能股票分析</strong>：分析股票数据、新闻舆情，提供投资建议</li><li><strong>智能舆情监控</strong>：监控社交媒体、新闻网站，分析舆情趋势</li><li><strong>智能竞品分析</strong>：收集竞品信息、对比分析、生成报告</li></ul><p><strong>（5）生活服务类</strong></p><ul><li><strong>智能健康助手</strong>：记录健康数据、提供健康建议、制定运动计划</li><li><strong>智能理财助手</strong>：记录收支、分析消费习惯、提供理财建议</li><li><strong>智能购物助手</strong>：比价、推荐商品、生成购物清单</li><li><strong>智能家居控制</strong>：通过自然语言控制智能家居设备</li></ul><h3 id="16-2-3-选题示例"><a href="#16-2-3-选题示例" class="headerlink" title="16.2.3 选题示例"></a>16.2.3 选题示例</h3><p>让我们通过一个具体的例子来说明如何选题和设计项目。</p><p><strong>项目名称</strong>：智能代码审查助手（CodeReviewAgent）</p><p><strong>问题分析</strong>：代码审查是软件开发中的重要环节，但人工审查耗时且容易遗漏问题。现有的静态分析工具只能发现语法错误，无法理解代码逻辑，因此需要一个能够理解代码语义、提供深度分析的智能助手。</p><p><strong>核心功能</strong>：该项目将实现代码质量分析（检查代码风格、命名规范、注释完整性）、潜在 bug 检测（发现逻辑错误、边界条件问题、资源泄漏）、性能优化建议（识别性能瓶颈、提出优化方案）、安全漏洞扫描（检测 SQL 注入、XSS 等安全问题）以及最佳实践推荐（根据语言特性和设计模式提出改进建议）。</p><p><strong>预期成果</strong>：最终将交付一个可运行的 Jupyter Notebook 展示完整的审查流程，支持 Python、JavaScript 等主流语言，能够生成结构化的 Markdown 格式审查报告，并提供具体的代码示例和改进建议。</p><h2 id="16-3-开发环境准备"><a href="#16-3-开发环境准备" class="headerlink" title="16.3 开发环境准备"></a>16.3 开发环境准备</h2><h3 id="16-3-1-安装必要工具"><a href="#16-3-1-安装必要工具" class="headerlink" title="16.3.1 安装必要工具"></a>16.3.1 安装必要工具</h3><p>在开始开发之前，请确保你的开发环境已经安装了以下工具：</p><p><strong>（1）Python 环境</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><code class="hljs bash"><span class="hljs-comment"># 安装HelloAgents</span><br>pip install <span class="hljs-string">&quot;hello-agents[all]&quot;</span><br></code></pre></td></tr></table></figure><p><strong>（2）Git 和 GitHub</strong></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><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><code class="hljs bash"><span class="hljs-comment"># 检查Git版本</span><br>git --version<br><br><span class="hljs-comment"># 配置Git用户信息</span><br>git config --global user.name <span class="hljs-string">&quot;你的名字&quot;</span><br>git config --global user.email <span class="hljs-string">&quot;你的邮箱&quot;</span><br><br><span class="hljs-comment"># 配置GitHub SSH密钥（推荐）</span><br><span class="hljs-comment"># 1. 生成SSH密钥</span><br>ssh-keygen -t ed25519 -C <span class="hljs-string">&quot;你的邮箱&quot;</span><br><br><span class="hljs-comment"># 2. 将公钥添加到GitHub</span><br><span class="hljs-comment"># 复制 ~/.ssh/id_ed25519.pub 的内容</span><br><span class="hljs-comment"># 在GitHub Settings &gt; SSH and GPG keys 中添加</span><br><br><span class="hljs-comment"># 3. 测试连接</span><br>ssh -T git@github.com<br></code></pre></td></tr></table></figure><p><strong>（3）Jupyter Notebook</strong></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><code class="hljs bash"><span class="hljs-comment"># 安装Jupyter</span><br>pip install jupyter notebook<br><br><span class="hljs-comment"># 或者使用JupyterLab（推荐）</span><br>pip install jupyterlab<br><br><span class="hljs-comment"># 启动Jupyter</span><br>jupyter lab<br></code></pre></td></tr></table></figure><h3 id="16-3-2-Fork-项目仓库"><a href="#16-3-2-Fork-项目仓库" class="headerlink" title="16.3.2 Fork 项目仓库"></a>16.3.2 Fork 项目仓库</h3><p><strong>步骤 1：Fork 仓库</strong></p><ol><li>访问 Hello-Agents 仓库：<a href="https://github.com/datawhalechina/hello-agents">https://github.com/datawhalechina/hello-agents</a></li><li>点击右上角的”Fork”按钮，如图 16.1 红色方框位置</li><li>选择你的 GitHub 账号，创建 Fork</li></ol><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/hello-agents/main/docs/images/16-figures/16-1.png" alt="" width="85%"/>  <p>图 16.1 Fork 仓库步骤</p></div><p><strong>步骤 2：克隆到本地</strong></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><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 如图16.2所示，克隆你Fork的仓库</span><br>git <span class="hljs-built_in">clone</span> git@github.com:你的用户名/hello-agents.git<br><br><span class="hljs-comment"># 进入项目目录</span><br><span class="hljs-built_in">cd</span> Hello-Agents<br><br><span class="hljs-comment"># 添加上游仓库（用于同步更新）</span><br>git remote add upstream https://github.com/datawhalechina/hello-agents.git<br><br><span class="hljs-comment"># 查看远程仓库</span><br>git remote -v<br></code></pre></td></tr></table></figure><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/hello-agents/main/docs/images/16-figures/16-2.png" alt="" width="85%"/>  <p>图 16.2 克隆仓库到本地</p></div><p><strong>步骤 3：创建开发分支</strong></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><code class="hljs bash"><span class="hljs-comment"># 创建并切换到新分支</span><br>git checkout -b feature/你的项目名称<br><br><span class="hljs-comment"># 例如:</span><br>git checkout -b feature/code-review-agent<br></code></pre></td></tr></table></figure><h3 id="16-3-3-项目目录结构"><a href="#16-3-3-项目目录结构" class="headerlink" title="16.3.3 项目目录结构"></a>16.3.3 项目目录结构</h3><p>在<code>Co-creation-projects</code>目录下创建你的项目文件夹：</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><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 进入共创项目目录</span><br><span class="hljs-built_in">cd</span> Co-creation-projects<br><br><span class="hljs-comment"># 创建项目文件夹（格式:GitHub用户名-项目名称）</span><br><span class="hljs-built_in">mkdir</span> 你的用户名-项目名称<br><br><span class="hljs-comment"># 例如:</span><br><span class="hljs-built_in">mkdir</span> jjyaoao-CodeReviewAgent<br><br><span class="hljs-comment"># 进入项目目录</span><br><span class="hljs-built_in">cd</span> jjyaoao-CodeReviewAgent<br></code></pre></td></tr></table></figure><p>推荐的项目结构：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><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><code class="hljs powershell">jjyaoao<span class="hljs-literal">-CodeReviewAgent</span>/<br>├── README.md              <span class="hljs-comment"># 项目说明文档</span><br>├── requirements.txt       <span class="hljs-comment"># Python依赖列表</span><br>├── main.ipynb            <span class="hljs-comment"># 主要的Jupyter Notebook</span><br>├── <span class="hljs-keyword">data</span>/                 <span class="hljs-comment"># 数据文件（可选）</span><br>│   ├── sample_code.py<br>│   └── test_cases.json<br>├── outputs/              <span class="hljs-comment"># 输出结果（可选）</span><br>│   ├── review_report.md<br>│   └── screenshots/<br>├── src/                  <span class="hljs-comment"># 源代码（可选，如果代码较多）</span><br>│   ├── agents/<br>│   ├── tools/<br>│   └── utils/<br>└──<br></code></pre></td></tr></table></figure><h2 id="16-4-项目开发指南"><a href="#16-4-项目开发指南" class="headerlink" title="16.4 项目开发指南"></a>16.4 项目开发指南</h2><h3 id="16-4-1-编写-README-文档"><a href="#16-4-1-编写-README-文档" class="headerlink" title="16.4.1 编写 README 文档"></a>16.4.1 编写 README 文档</h3><p>README 是项目的门面，一个好的 README 应该包含以下内容：</p><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><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section"># 项目名称</span><br><br><span class="hljs-quote">&gt; 一句话描述你的项目</span><br><br><span class="hljs-section">## 📝 项目简介</span><br><br>详细介绍你的项目:<br><span class="hljs-bullet">-</span> 解决什么问题？<br><span class="hljs-bullet">-</span> 有什么特色功能？<br><span class="hljs-bullet">-</span> 适用于什么场景？<br><br><span class="hljs-section">## ✨ 核心功能</span><br><br><span class="hljs-bullet">-</span> [ ] 功能1:描述<br><span class="hljs-bullet">-</span> [ ] 功能2:描述<br><span class="hljs-bullet">-</span> [ ] 功能3:描述<br><br><span class="hljs-section">## 🛠️ 技术栈</span><br><br><span class="hljs-bullet">-</span> HelloAgents框架<br><span class="hljs-bullet">-</span> 使用的智能体范式（如ReAct、Plan-and-Solve等）<br><span class="hljs-bullet">-</span> 使用的工具和API<br><span class="hljs-bullet">-</span> 其他依赖库<br><br><span class="hljs-section">## 🚀 快速开始</span><br><br><span class="hljs-section">### 环境要求</span><br><br><span class="hljs-bullet">-</span> Python 3.10+<br><span class="hljs-bullet">-</span> 其他要求<br><br><span class="hljs-section">### 安装依赖</span><br><br><br>pip install -r requirements.txt<br><br><br><span class="hljs-section">### 配置API密钥</span><br><br><br><span class="hljs-section"># 创建.env文件</span><br>cp .env.example .env<br><br><span class="hljs-section"># 编辑.env文件，填入你的API密钥</span><br><br><br><span class="hljs-section">### 运行项目</span><br><br><br><span class="hljs-section"># 启动Jupyter Notebook</span><br>jupyter lab<br><br><span class="hljs-section"># 打开main.ipynb并运行</span><br><br><br><span class="hljs-section">## 📖 使用示例</span><br><br>展示如何使用你的项目，最好包含代码示例和运行结果。<br><br><span class="hljs-section">## 🎯 项目亮点</span><br><br><span class="hljs-bullet">-</span> 亮点1:说明<br><span class="hljs-bullet">-</span> 亮点2:说明<br><span class="hljs-bullet">-</span> 亮点3:说明<br><br><span class="hljs-section">## 📊 性能评估</span><br><br>如果有评估结果，展示在这里:<br><span class="hljs-bullet">-</span> 准确率:XX%<br><span class="hljs-bullet">-</span> 响应时间:XX秒<br><span class="hljs-bullet">-</span> 其他指标<br><br><span class="hljs-section">## 🔮 未来计划</span><br><br><span class="hljs-bullet">-</span> [ ] 待实现的功能1<br><span class="hljs-bullet">-</span> [ ] 待实现的功能2<br><span class="hljs-bullet">-</span> [ ] 待优化的部分<br><br><span class="hljs-section">## 🤝 贡献指南</span><br><br>欢迎提出Issue和Pull Request！<br><br><span class="hljs-section">## 📄 许可证</span><br><br>MIT License<br><br><span class="hljs-section">## 👤 作者</span><br><br><span class="hljs-bullet">-</span> GitHub: [<span class="hljs-string">@你的用户名</span>](<span class="hljs-link">https://github.com/你的用户名</span>)<br><span class="hljs-bullet">-</span> Email: 你的邮箱（可选）<br><br><span class="hljs-section">## 🙏 致谢</span><br><br>感谢Datawhale社区和Hello-Agents项目！<br></code></pre></td></tr></table></figure><h3 id="16-4-2-编写-requirements-txt"><a href="#16-4-2-编写-requirements-txt" class="headerlink" title="16.4.2 编写 requirements.txt"></a>16.4.2 编写 requirements.txt</h3><p>列出项目所需的所有 Python 依赖：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><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><code class="hljs txt"># 核心依赖<br>hello-agents[all]&gt;=0.2.7<br><br># 可视化（如果需要）<br>matplotlib&gt;=3.7.0<br>plotly&gt;=5.14.0<br><br># Web框架（如果需要）<br>fastapi&gt;=0.109.0<br>uvicorn&gt;=0.27.0<br></code></pre></td></tr></table></figure><h3 id="16-4-3-开发-Jupyter-Notebook"><a href="#16-4-3-开发-Jupyter-Notebook" class="headerlink" title="16.4.3 开发 Jupyter Notebook"></a>16.4.3 开发 Jupyter Notebook</h3><p><strong>（1）Notebook 结构建议</strong></p><p>一个好的 Jupyter Notebook 应该包含以下部分：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># ========================================</span><br><span class="hljs-comment"># 第1部分:项目介绍</span><br><span class="hljs-comment"># ========================================</span><br><br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string"># 项目名称</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 项目简介</span><br><span class="hljs-string">简要介绍项目的目标和功能</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 作者信息</span><br><span class="hljs-string">- 姓名:XXX</span><br><span class="hljs-string">- GitHub:@XXX</span><br><span class="hljs-string">- 日期:2025-XX-XX</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># ========================================</span><br><span class="hljs-comment"># 第2部分:环境配置</span><br><span class="hljs-comment"># ========================================</span><br><br><span class="hljs-comment"># 安装依赖</span><br>!pip install -q hello-agents[<span class="hljs-built_in">all</span>]<br><br><span class="hljs-comment"># 导入必要的库</span><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> BaseTool<br><span class="hljs-keyword">import</span> os<br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><br><span class="hljs-comment"># 加载环境变量</span><br>load_dotenv()<br><br><span class="hljs-comment"># ========================================</span><br><span class="hljs-comment"># 第3部分:工具定义</span><br><span class="hljs-comment"># ========================================</span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">CustomTool</span>(<span class="hljs-title class_ inherited__">BaseTool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;自定义工具类&quot;&quot;&quot;</span><br><br>    name = <span class="hljs-string">&quot;tool_name&quot;</span><br>    description = <span class="hljs-string">&quot;工具描述&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;工具执行逻辑&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 实现你的工具逻辑</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;结果&quot;</span><br><br><span class="hljs-comment"># ========================================</span><br><span class="hljs-comment"># 第4部分:智能体构建</span><br><span class="hljs-comment"># ========================================</span><br><br><span class="hljs-comment"># 创建LLM</span><br>llm = HelloAgentsLLM()<br><br><span class="hljs-comment"># 创建智能体</span><br>agent = SimpleAgent(<br>    name=<span class="hljs-string">&quot;智能体名称&quot;</span>,<br>    llm=llm,<br>    system_prompt=<span class="hljs-string">&quot;系统提示词&quot;</span><br>)<br><br><span class="hljs-comment"># 添加工具</span><br>agent.add_tool(CustomTool())<br><br><span class="hljs-comment"># ========================================</span><br><span class="hljs-comment"># 第5部分:功能演示</span><br><span class="hljs-comment"># ========================================</span><br><br><span class="hljs-comment"># 示例1:基础功能</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=== 示例1:基础功能 ===&quot;</span>)<br>result = agent.run(<span class="hljs-string">&quot;用户输入&quot;</span>)<br><span class="hljs-built_in">print</span>(result)<br><br><span class="hljs-comment"># 示例2:复杂场景</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n=== 示例2:复杂场景 ===&quot;</span>)<br>result = agent.run(<span class="hljs-string">&quot;复杂的用户输入&quot;</span>)<br><span class="hljs-built_in">print</span>(result)<br><br><span class="hljs-comment"># ========================================</span><br><span class="hljs-comment"># 第6部分:性能评估（可选）</span><br><span class="hljs-comment"># ========================================</span><br><br><span class="hljs-comment"># 评估代码</span><br><span class="hljs-comment"># ...</span><br><br><span class="hljs-comment"># ========================================</span><br><span class="hljs-comment"># 第7部分:总结与展望</span><br><span class="hljs-comment"># ========================================</span><br><br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">## 项目总结</span><br><span class="hljs-string"></span><br><span class="hljs-string">### 实现的功能</span><br><span class="hljs-string">- 功能1</span><br><span class="hljs-string">- 功能2</span><br><span class="hljs-string"></span><br><span class="hljs-string">### 遇到的挑战</span><br><span class="hljs-string">- 挑战1及解决方案</span><br><span class="hljs-string">- 挑战2及解决方案</span><br><span class="hljs-string"></span><br><span class="hljs-string">### 未来改进方向</span><br><span class="hljs-string">- 改进1</span><br><span class="hljs-string">- 改进2</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><h3 id="16-4-4-测试你的项目"><a href="#16-4-4-测试你的项目" class="headerlink" title="16.4.4 测试你的项目"></a>16.4.4 测试你的项目</h3><p>在提交之前，可以使用测试清单来判断自己的项目是否满足提交要求：</p><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-bullet">-</span> [ ] 代码能够正常运行，没有报错<br><span class="hljs-bullet">-</span> [ ] README文档完整，说明清晰<br><span class="hljs-bullet">-</span> [ ] requirements.txt包含所有依赖<br><span class="hljs-bullet">-</span> [ ] 有清晰的使用示例<br><span class="hljs-bullet">-</span> [ ] 代码有适当的注释<br><span class="hljs-bullet">-</span> [ ] 输出结果符合预期<br><span class="hljs-bullet">-</span> [ ] 处理了常见的异常情况<br><span class="hljs-bullet">-</span> [ ] 项目结构清晰，文件命名规范<br><span class="hljs-bullet">-</span> [ ] 大文件已妥善处理（见下节）<br></code></pre></td></tr></table></figure><h3 id="16-4-5-大文件处理指南"><a href="#16-4-5-大文件处理指南" class="headerlink" title="16.4.5 大文件处理指南"></a>16.4.5 大文件处理指南</h3><p><strong>⚠️ 重要：避免主仓库过大</strong></p><p>为了保持 Hello-Agents 主仓库的轻量化，请遵循以下大文件处理规范：</p><p><strong>（1）文件大小限制</strong></p><ul><li><strong>项目总大小</strong>： 不超过 5MB</li><li><strong>禁止直接提交</strong>： 视频文件、大型数据集、模型文件</li></ul><p><strong>（2）大文件处理方案</strong></p><p>如果你的项目包含大文件（数据集、视频、模型等），请使用以下方案：</p><p><strong>方案 1：使用外部链接（推荐）</strong></p><p>将大文件上传到外部平台，在 README 中提供下载链接：</p><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section">## 数据集</span><br><br>本项目使用的数据集较大，请从以下链接下载:<br><br><span class="hljs-bullet">-</span> 数据集1: [<span class="hljs-string">百度网盘</span>](<span class="hljs-link">链接</span>) 提取码: xxxx<br><span class="hljs-bullet">-</span> 数据集2: [<span class="hljs-string">Google Drive</span>](<span class="hljs-link">链接</span>)<br><span class="hljs-bullet">-</span> 演示视频: [<span class="hljs-string">B站</span>](<span class="hljs-link">链接</span>) / [<span class="hljs-string">YouTube</span>](<span class="hljs-link">链接</span>)<br></code></pre></td></tr></table></figure><p>推荐的外部平台：</p><ul><li><strong>数据集</strong>： 百度网盘、Google Drive、Kaggle、HuggingFace Datasets</li><li><strong>视频</strong>： B 站、YouTube、腾讯视频</li><li><strong>模型</strong>： HuggingFace Models、ModelScope</li><li><strong>图片</strong>： GitHub Issues、图床服务</li></ul><p><strong>方案 2：创建独立仓库</strong></p><p>如果项目资源较多，建议创建独立的数据仓库：</p><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section">## 项目资源</span><br><br>由于项目包含大量数据和演示资源，已单独创建资源仓库:<br><br><span class="hljs-bullet">-</span> 资源仓库: https://github.com/你的用户名/项目名称-resources<br><span class="hljs-bullet">-</span> 包含内容: 数据集、演示视频、模型文件、测试数据等<br><br><span class="hljs-section">### 使用方法</span><br><br>\<span class="hljs-code">`\`</span>\`bash<br><span class="hljs-section"># 克隆资源仓库</span><br>git clone https://github.com/你的用户名/项目名称-resources.git<br><br><span class="hljs-section"># 将数据放到项目目录</span><br>cp -r 项目名称-resources/data ./data<br>\<span class="hljs-code">`\`</span>\`<br></code></pre></td></tr></table></figure><p><strong>方案 3：使用示例数据</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 在README中说明</span><br><span class="hljs-comment">## 数据说明</span><br><br>- `data/sample.csv`: 示例数据（<span class="hljs-number">100</span>条记录）<br>- 完整数据集（<span class="hljs-number">10</span>万条记录）请从[这里](链接)下载<br></code></pre></td></tr></table></figure><p><strong>（3）最佳实践示例</strong></p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><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><code class="hljs glsl">你的用户名-项目名称/<br>├── README.md              <span class="hljs-meta"># 包含外部资源链接</span><br>├── requirements.txt<br>├── main.ipynb<br>├── .gitignore            <span class="hljs-meta"># 忽略大文件</span><br>├── data/<br>│   └── <span class="hljs-keyword">sample</span>.csv        <span class="hljs-meta"># 仅示例数据（&lt;1MB）</span><br>└── outputs/<br>    └── demo_result.png   <span class="hljs-meta"># 仅演示结果（&lt;1MB）</span><br></code></pre></td></tr></table></figure><p>README 中的说明：</p><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section">## 数据和资源</span><br><br><span class="hljs-section">### 示例数据</span><br>项目包含小规模示例数据用于快速测试（位于<span class="hljs-code">`data/sample.csv`</span>）<br><br><span class="hljs-section">### 完整数据集</span><br>完整数据集（500MB）请从以下链接下载:<br><span class="hljs-bullet">-</span> 百度网盘: [链接] 提取码: xxxx<br><span class="hljs-bullet">-</span> 下载后解压到<span class="hljs-code">`data/`</span>目录<br><br><span class="hljs-section">### 演示视频</span><br><span class="hljs-bullet">-</span> B站: [<span class="hljs-string">项目演示视频</span>](<span class="hljs-link">链接</span>)<br><span class="hljs-bullet">-</span> YouTube: [<span class="hljs-string">Demo Video</span>](<span class="hljs-link">链接</span>)<br></code></pre></td></tr></table></figure><h2 id="16-5-提交-Pull-Request"><a href="#16-5-提交-Pull-Request" class="headerlink" title="16.5 提交 Pull Request"></a>16.5 提交 Pull Request</h2><h3 id="16-5-1-提交代码到-GitHub"><a href="#16-5-1-提交代码到-GitHub" class="headerlink" title="16.5.1 提交代码到 GitHub"></a>16.5.1 提交代码到 GitHub</h3><p><strong>步骤 1：检查修改</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><code class="hljs bash"><span class="hljs-comment"># 查看修改的文件</span><br>git status<br></code></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><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><code class="hljs bash"><span class="hljs-comment"># 添加所有修改的文件</span><br>git add .<br><br><span class="hljs-comment"># 或者添加特定文件</span><br>git add Co-creation-projects/你的用户名-项目名称/<br></code></pre></td></tr></table></figure><p><strong>步骤 3：提交修改</strong></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><code class="hljs bash"><span class="hljs-comment"># 格式:类型: 简短描述</span><br>git commit -m <span class="hljs-string">&quot;feat: 添加XXX毕业设计项目&quot;</span><br></code></pre></td></tr></table></figure><p><strong>提交类型规范：</strong></p><ul><li><code>feat</code>： 新增功能或项目（毕业设计项目使用此类型）</li><li><code>fix</code>： 修复 bug</li><li><code>docs</code>： 文档更新</li><li><code>style</code>： 代码格式调整（不影响功能）</li><li><code>refactor</code>： 代码重构</li><li><code>test</code>： 测试相关</li><li><code>chore</code>： 其他修改（如依赖更新）</li></ul><p><strong>步骤 4：推送到 GitHub</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><code class="hljs bash"><span class="hljs-comment"># 推送到你的Fork仓库</span><br>git push origin feature/你的项目名称<br></code></pre></td></tr></table></figure><h3 id="16-5-2-创建-Pull-Request"><a href="#16-5-2-创建-Pull-Request" class="headerlink" title="16.5.2 创建 Pull Request"></a>16.5.2 创建 Pull Request</h3><p><strong>步骤 1：访问 GitHub</strong></p><ol><li>访问你 Fork 的仓库：<code>https://github.com/你的用户名/hello-agents</code></li><li>点击”Pull requests”标签，如图 16.3 所示</li><li>点击”New pull request”按钮</li></ol><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/hello-agents/main/docs/images/16-figures/16-3.png" alt="" width="85%"/>  <p>图 16.3 创建 Pull Request</p></div><p><strong>步骤 2：选择分支</strong></p><ul><li>Base repository： <code>datawhalechina/hello-agents</code></li><li>Base branch： <code>main</code></li><li>Head repository： <code>你的用户名/hello-agents</code></li><li>Compare branch： <code>feature/你的项目名称</code></li></ul><p><strong>步骤 3：填写 PR 信息</strong></p><p><strong>⚠️ 重要：PR 标题统一格式</strong></p><p>为了便于管理和检索，所有毕业设计项目的 PR 标题必须遵循以下格式：</p><figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs angelscript"><span class="hljs-string">[毕业设计]</span> 项目名称 - 简短描述<br></code></pre></td></tr></table></figure><p>示例：</p><ul><li><code>[毕业设计] CodeReviewAgent - 智能代码审查助手</code></li><li><code>[毕业设计] StudyBuddy - AI学习伙伴</code></li><li><code>[毕业设计] DataAnalyst - 智能数据分析师</code></li></ul><p><strong>PR 描述模板：</strong></p><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><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section">## 项目信息</span><br><br><span class="hljs-bullet">-</span> <span class="hljs-strong">**项目名称**</span>:XXX<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**作者**</span>:@你的用户名<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**项目类型**</span>:生产力工具/学习辅助/创意娱乐/数据分析/生活服务<br><br><span class="hljs-section">## 项目简介</span><br><br>简要描述你的项目（2-3句话）<br><br><span class="hljs-section">## 核心功能</span><br><br><span class="hljs-bullet">-</span> [ ] 功能1<br><span class="hljs-bullet">-</span> [ ] 功能2<br><span class="hljs-bullet">-</span> [ ] 功能3<br><br><span class="hljs-section">## 技术亮点</span><br><br><span class="hljs-bullet">-</span> 使用了XXX范式<br><span class="hljs-bullet">-</span> 实现了XXX功能<br><span class="hljs-bullet">-</span> 优化了XXX性能<br><br><span class="hljs-section">## 演示效果</span><br><br>（可选）添加截图或GIF展示项目效果<br><br><span class="hljs-section">## 自检清单</span><br><br><span class="hljs-bullet">-</span> [ ] 代码能够正常运行<br><span class="hljs-bullet">-</span> [ ] README文档完整<br><span class="hljs-bullet">-</span> [ ] requirements.txt完整<br><span class="hljs-bullet">-</span> [ ] 有清晰的使用示例<br><span class="hljs-bullet">-</span> [ ] 代码有适当的注释<br><br><span class="hljs-section">## 其他说明</span><br><br>（可选）其他需要说明的内容<br></code></pre></td></tr></table></figure><p><strong>步骤 4：提交 PR</strong></p><p>如图 16.4 所示，点击”Create pull request”按钮提交。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/hello-agents/main/docs/images/16-figures/16-4.png" alt="" width="85%"/>  <p>图 16.4 提交 Pull Request</p></div><h3 id="16-5-3-响应-Review-意见"><a href="#16-5-3-响应-Review-意见" class="headerlink" title="16.5.3 响应 Review 意见"></a>16.5.3 响应 Review 意见</h3><p>提交 PR 后，社区成员会 review 你的代码并提出建议。请及时响应：</p><ol><li><strong>查看评论</strong>：在 PR 页面查看 reviewer 的评论</li><li><strong>修改代码</strong>：根据建议修改代码</li><li><strong>提交更新</strong>：<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></pre></td><td class="code"><pre><code class="hljs bash">git add .<br>git commit -m <span class="hljs-string">&quot;fix: 根据review意见修改XXX&quot;</span><br>git push origin feature/你的项目名称<br></code></pre></td></tr></table></figure></li><li><strong>回复评论</strong>：在 GitHub 上回复 reviewer，说明你的修改</li></ol><h2 id="16-6-示例项目展示"><a href="#16-6-示例项目展示" class="headerlink" title="16.6 示例项目展示"></a>16.6 示例项目展示</h2><p>为了帮助你更好地理解毕业设计的要求，这里展示一个完整的示例项目，请别担心，小的创意同样可以被收录，只要是自己动手的作品都是值得珍惜的。</p><p><strong>项目信息</strong></p><ul><li><strong>项目名称</strong>：CodeReviewAgent</li><li><strong>作者</strong>：@jjyaoao</li><li><strong>项目路径</strong>：<code>Co-creation-projects/jjyaoao-CodeReviewAgent/</code></li></ul><p><strong>项目结构</strong></p><figure class="highlight jboss-cli"><table><tr><td class="gutter"><pre><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><code class="hljs jboss-cli">jjyaoao-CodeReviewAgent/<br>├── README.md              <span class="hljs-comment"># 项目文档</span><br>├── requirements.txt       <span class="hljs-comment"># 依赖列表</span><br>├── main.ipynb            <span class="hljs-comment"># 主程序(含快速演示和完整功能)</span><br>├── <span class="hljs-string">.env.example</span>          <span class="hljs-comment"># 环境变量示例</span><br>├── <span class="hljs-string">.gitignore</span>            <span class="hljs-comment"># Git忽略规则</span><br>├── data/<br>│   └── sample_code.py    <span class="hljs-comment"># 示例代码</span><br>└── outputs/<br>    └── review_report.md  <span class="hljs-comment"># 示例报告</span><br></code></pre></td></tr></table></figure><p><strong>核心代码片段（main.ipynb）</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><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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># ========================================</span><br><span class="hljs-comment"># 智能代码审查助手</span><br><span class="hljs-comment"># ========================================</span><br><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM, ToolRegistry<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> Tool, ToolParameter<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Dict</span>, <span class="hljs-type">Any</span>, <span class="hljs-type">List</span><br><span class="hljs-keyword">import</span> ast<br><span class="hljs-keyword">import</span> os<br><br><span class="hljs-comment"># ========================================</span><br><span class="hljs-comment"># 0. 配置LLM参数</span><br><span class="hljs-comment"># ========================================</span><br><br>os.environ[<span class="hljs-string">&quot;LLM_MODEL_ID&quot;</span>] = <span class="hljs-string">&quot;Qwen/Qwen2.5-72B-Instruct&quot;</span><br>os.environ[<span class="hljs-string">&quot;LLM_API_KEY&quot;</span>] = <span class="hljs-string">&quot;your_api_key_here&quot;</span><br>os.environ[<span class="hljs-string">&quot;LLM_BASE_URL&quot;</span>] = <span class="hljs-string">&quot;https://api-inference.modelscope.cn/v1/&quot;</span><br>os.environ[<span class="hljs-string">&quot;LLM_TIMEOUT&quot;</span>] = <span class="hljs-string">&quot;60&quot;</span><br><br><span class="hljs-comment"># ========================================</span><br><span class="hljs-comment"># 1. 定义代码分析工具</span><br><span class="hljs-comment"># ========================================</span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">CodeAnalysisTool</span>(<span class="hljs-title class_ inherited__">Tool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;代码静态分析工具&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-built_in">super</span>().__init__(<br>            name=<span class="hljs-string">&quot;code_analysis&quot;</span>,<br>            description=<span class="hljs-string">&quot;分析Python代码的结构、复杂度和潜在问题&quot;</span><br>        )<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, parameters: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;分析代码并返回结果&quot;&quot;&quot;</span><br>        code = parameters.get(<span class="hljs-string">&quot;code&quot;</span>, <span class="hljs-string">&quot;&quot;</span>)<br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> code:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;错误:代码不能为空&quot;</span><br><br>        <span class="hljs-keyword">try</span>:<br>            tree = ast.parse(code)<br>            functions = [node <span class="hljs-keyword">for</span> node <span class="hljs-keyword">in</span> ast.walk(tree)<br>                        <span class="hljs-keyword">if</span> <span class="hljs-built_in">isinstance</span>(node, ast.FunctionDef)]<br>            classes = [node <span class="hljs-keyword">for</span> node <span class="hljs-keyword">in</span> ast.walk(tree)<br>                      <span class="hljs-keyword">if</span> <span class="hljs-built_in">isinstance</span>(node, ast.ClassDef)]<br><br>            result = &#123;<br>                <span class="hljs-string">&quot;函数数量&quot;</span>: <span class="hljs-built_in">len</span>(functions),<br>                <span class="hljs-string">&quot;类数量&quot;</span>: <span class="hljs-built_in">len</span>(classes),<br>                <span class="hljs-string">&quot;代码行数&quot;</span>: <span class="hljs-built_in">len</span>(code.split(<span class="hljs-string">&#x27;\n&#x27;</span>)),<br>                <span class="hljs-string">&quot;函数列表&quot;</span>: [f.name <span class="hljs-keyword">for</span> f <span class="hljs-keyword">in</span> functions],<br>                <span class="hljs-string">&quot;类列表&quot;</span>: [c.name <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> classes]<br>            &#125;<br>            <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(result)<br>        <span class="hljs-keyword">except</span> SyntaxError <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;语法错误:<span class="hljs-subst">&#123;<span class="hljs-built_in">str</span>(e)&#125;</span>&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_parameters</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">List</span>[ToolParameter]:<br>        <span class="hljs-keyword">return</span> [<br>            ToolParameter(<br>                name=<span class="hljs-string">&quot;code&quot;</span>,<br>                <span class="hljs-built_in">type</span>=<span class="hljs-string">&quot;string&quot;</span>,<br>                description=<span class="hljs-string">&quot;要分析的Python代码&quot;</span>,<br>                required=<span class="hljs-literal">True</span><br>            )<br>        ]<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">StyleCheckTool</span>(<span class="hljs-title class_ inherited__">Tool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;代码风格检查工具&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-built_in">super</span>().__init__(<br>            name=<span class="hljs-string">&quot;style_check&quot;</span>,<br>            description=<span class="hljs-string">&quot;检查代码是否符合PEP 8规范&quot;</span><br>        )<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, parameters: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;检查代码风格&quot;&quot;&quot;</span><br>        code = parameters.get(<span class="hljs-string">&quot;code&quot;</span>, <span class="hljs-string">&quot;&quot;</span>)<br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> code:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;错误:代码不能为空&quot;</span><br><br>        issues = []<br>        lines = code.split(<span class="hljs-string">&#x27;\n&#x27;</span>)<br>        <span class="hljs-keyword">for</span> i, line <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(lines, <span class="hljs-number">1</span>):<br>            <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(line) &gt; <span class="hljs-number">79</span>:<br>                issues.append(<span class="hljs-string">f&quot;第<span class="hljs-subst">&#123;i&#125;</span>行:超过79个字符&quot;</span>)<br>            <span class="hljs-keyword">if</span> line.startswith(<span class="hljs-string">&#x27; &#x27;</span>) <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> line.startswith(<span class="hljs-string">&#x27;    &#x27;</span>):<br>                <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(line) - <span class="hljs-built_in">len</span>(line.lstrip()) <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> [<span class="hljs-number">0</span>, <span class="hljs-number">4</span>, <span class="hljs-number">8</span>, <span class="hljs-number">12</span>]:<br>                    issues.append(<span class="hljs-string">f&quot;第<span class="hljs-subst">&#123;i&#125;</span>行:缩进不规范&quot;</span>)<br><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> issues:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;代码风格良好，符合PEP 8规范&quot;</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;发现以下问题:\n&quot;</span> + <span class="hljs-string">&quot;\n&quot;</span>.join(issues)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_parameters</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">List</span>[ToolParameter]:<br>        <span class="hljs-keyword">return</span> [<br>            ToolParameter(<br>                name=<span class="hljs-string">&quot;code&quot;</span>,<br>                <span class="hljs-built_in">type</span>=<span class="hljs-string">&quot;string&quot;</span>,<br>                description=<span class="hljs-string">&quot;要检查的Python代码&quot;</span>,<br>                required=<span class="hljs-literal">True</span><br>            )<br>        ]<br><br><span class="hljs-comment"># ========================================</span><br><span class="hljs-comment"># 2. 创建工具注册表和智能体</span><br><span class="hljs-comment"># ========================================</span><br><br><span class="hljs-comment"># 创建工具注册表</span><br>tool_registry = ToolRegistry()<br>tool_registry.register_tool(CodeAnalysisTool())<br>tool_registry.register_tool(StyleCheckTool())<br><br><span class="hljs-comment"># 初始化LLM</span><br>llm = HelloAgentsLLM()<br><br><span class="hljs-comment"># 定义系统提示词</span><br>system_prompt = <span class="hljs-string">&quot;&quot;&quot;你是一位经验丰富的代码审查专家。你的任务是:</span><br><span class="hljs-string"></span><br><span class="hljs-string">1. 使用code_analysis工具分析代码结构</span><br><span class="hljs-string">2. 使用style_check工具检查代码风格</span><br><span class="hljs-string">3. 基于分析结果，提供详细的审查报告</span><br><span class="hljs-string"></span><br><span class="hljs-string">审查报告应包括:</span><br><span class="hljs-string">- 代码结构分析</span><br><span class="hljs-string">- 风格问题</span><br><span class="hljs-string">- 潜在bug</span><br><span class="hljs-string">- 性能优化建议</span><br><span class="hljs-string">- 最佳实践建议</span><br><span class="hljs-string"></span><br><span class="hljs-string">请以Markdown格式输出报告。&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># 创建智能体</span><br>agent = SimpleAgent(<br>    name=<span class="hljs-string">&quot;代码审查助手&quot;</span>,<br>    llm=llm,<br>    system_prompt=system_prompt,<br>    tool_registry=tool_registry<br>)<br><br><span class="hljs-comment"># ========================================</span><br><span class="hljs-comment"># 3. 运行示例</span><br><span class="hljs-comment"># ========================================</span><br><br><span class="hljs-comment"># 读取示例代码</span><br><span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(<span class="hljs-string">&quot;data/sample_code.py&quot;</span>, <span class="hljs-string">&quot;r&quot;</span>, encoding=<span class="hljs-string">&quot;utf-8&quot;</span>) <span class="hljs-keyword">as</span> f:<br>    sample_code = f.read()<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=== 待审查的代码 ===&quot;</span>)<br><span class="hljs-built_in">print</span>(sample_code)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n&quot;</span> + <span class="hljs-string">&quot;=&quot;</span>*<span class="hljs-number">50</span> + <span class="hljs-string">&quot;\n&quot;</span>)<br><br><span class="hljs-comment"># 执行代码审查</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=== 开始代码审查 ===&quot;</span>)<br>review_result = agent.run(<span class="hljs-string">f&quot;请审查以下Python代码:\n\n```python\n<span class="hljs-subst">&#123;sample_code&#125;</span>\n```&quot;</span>)<br><br><span class="hljs-built_in">print</span>(review_result)<br><br><span class="hljs-comment"># 保存审查报告</span><br><span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(<span class="hljs-string">&quot;outputs/review_report.md&quot;</span>, <span class="hljs-string">&quot;w&quot;</span>, encoding=<span class="hljs-string">&quot;utf-8&quot;</span>) <span class="hljs-keyword">as</span> f:<br>    f.write(review_result)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n审查报告已保存到 outputs/review_report.md&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>README.md 示例</strong></p><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><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section"># CodeReviewAgent - 智能代码审查助手</span><br><br><span class="hljs-quote">&gt; 基于HelloAgents框架的智能代码审查工具</span><br><br><span class="hljs-section">## 📝 项目简介</span><br><br>CodeReviewAgent是一个智能代码审查助手，能够自动分析Python代码的质量、发现潜在问题并提供优化建议。<br><br><span class="hljs-section">### 核心功能</span><br><br><span class="hljs-bullet">-</span> ✅ 代码结构分析:统计函数、类、代码行数等<br><span class="hljs-bullet">-</span> ✅ 风格检查:检查是否符合PEP 8规范<br><span class="hljs-bullet">-</span> ✅ 智能建议:基于LLM提供深度分析和优化建议<br><span class="hljs-bullet">-</span> ✅ 报告生成:生成Markdown格式的审查报告<br><br><span class="hljs-section">## 🛠️ 技术栈</span><br><br><span class="hljs-bullet">-</span> HelloAgents框架（SimpleAgent + ToolRegistry）<br><span class="hljs-bullet">-</span> Python AST模块（代码解析）<br><span class="hljs-bullet">-</span> ModelScope API（Qwen2.5-72B模型）<br><br><span class="hljs-section">## 🚀 快速开始</span><br><br><span class="hljs-section">### 安装依赖</span><br><br>\<span class="hljs-code">`\`</span>\`bash<br>pip install -r requirements.txt<br>\<span class="hljs-code">`\`</span>\`<br><br><span class="hljs-section">### 配置LLM参数</span><br><br><span class="hljs-strong">**方式1: 使用.env文件**</span><br><br>\<span class="hljs-code">`\`</span>\`bash<br>cp .env.example .env<br><span class="hljs-section"># 编辑.env文件,填入你的API密钥</span><br>\<span class="hljs-code">`\`</span>\`<br><br><span class="hljs-strong">**方式2: 直接在Notebook中设置**</span><br><br>项目已预配置ModelScope API,可直接运行。如需修改,编辑main.ipynb第1部分的配置代码。<br><br><span class="hljs-section">### 运行项目</span><br><br>\<span class="hljs-code">`\`</span>\`bash<br>jupyter lab<br><span class="hljs-section"># 打开main.ipynb并运行所有单元格</span><br>\<span class="hljs-code">`\`</span>\`<br><br><span class="hljs-section">## 📖 使用示例</span><br><br><span class="hljs-bullet">1.</span> 将待审查的代码放入<span class="hljs-code">`data/sample_code.py`</span><br><span class="hljs-bullet">2.</span> 运行<span class="hljs-code">`main.ipynb`</span><br><span class="hljs-bullet">3.</span> 查看生成的审查报告<span class="hljs-code">`outputs/review_report.md`</span><br><br><span class="hljs-section">## 🎯 项目亮点</span><br><br><span class="hljs-bullet">-</span> <span class="hljs-strong">**自动化**</span>:无需人工逐行检查，自动发现问题<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**智能化**</span>:利用LLM理解代码语义，提供深度建议<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**可扩展**</span>:易于添加新的检查规则和工具<br><br><span class="hljs-section">## 👤 作者</span><br><br><span class="hljs-bullet">-</span> GitHub: [<span class="hljs-string">@jjyaoao</span>](<span class="hljs-link">https://github.com/jjyaoao</span>)<br><span class="hljs-bullet">-</span> 项目链接:[<span class="hljs-string">CodeReviewAgent</span>](<span class="hljs-link">https://github.com/datawhalechina/hello-agents/tree/main/Co-creation-projects/jjyaoao-CodeReviewAgent</span>)<br><br><span class="hljs-section">## 🙏 致谢</span><br><br>感谢Datawhale社区和Hello-Agents项目！<br></code></pre></td></tr></table></figure><h2 id="16-7-总结与展望"><a href="#16-7-总结与展望" class="headerlink" title="16.7 总结与展望"></a>16.7 总结与展望</h2><p>通过完成毕业设计，你应该已经掌握了智能体系统设计的完整流程。从需求出发设计系统架构，熟练使用 HelloAgents 框架的各种功能和组件，开发自定义工具扩展智能体能力，完成从需求分析到代码实现的完整项目开发，学会使用 Git 和 GitHub 进行开源协作，以及编写清晰的技术文档。</p><p>在本项目中，我们从零开始构建了 HelloAgents 框架，并用它实现了多个实用的应用。完成毕业设计只是开始，你可以继续深入学习更多智能体范式和算法、提示工程和上下文工程、多智能体协作机制等理论知识；也可以扩展技术栈，学习 Web 开发构建完整的应用、学习数据库实现数据持久化、学习部署将应用上线；还可以持续优化你的项目，添加更多功能、优化性能和用户体验、完善测试和文档；更重要的是，积极参与社区贡献，帮助其他学习者、参与 Hello-Agents 框架开发、分享你的经验和心得。</p><p>从第一章的简单智能体，到现在能够独立构建完整的多智能体应用，你已经走过了一段精彩的学习旅程。但这不是终点，而是新的起点。</p><p>AI 技术日新月异，智能体领域更是充满无限可能。希望你能够保持好奇心持续学习新技术，勇于用 AI 技术解决实际问题创造价值，乐于将你的经验和成果分享给社区，不断打磨你的作品追求卓越。</p><p>最后，感谢你完整阅读了本项目。希望你在学习的过程中有所收获，也希望你能够将所学应用到实际项目中，创造出令人惊叹的智能体应用。AI 的未来充满无限可能，让我们一起探索和创造!</p><p><strong>记住：最好的学习方式就是动手实践！</strong></p><p>现在，开始构建属于你的智能体应用吧！我们期待在 Co-creation-projects 目录中看到你的精彩作品！</p><p>如果你觉得 Hello-Agents 项目对你有帮助，请给我们一个⭐Star！</p><hr><div align="center">  <strong>🎓 恭喜你完成了 Hello-Agents 教程的学习！🎉</strong>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC16%E7%AB%A0-%E6%AF%95%E4%B8%9A%E8%AE%BE%E8%AE%A1/</id>
    <link href="http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC16%E7%AB%A0-%E6%AF%95%E4%B8%9A%E8%AE%BE%E8%AE%A1/"/>
    <published>2026-03-02T10:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="第十六章-毕业设计：构建属于你的多智能体应用"><a href="#第十六章-毕业设计：构建属于你的多智能体应用" class="headerlink" title="第十六章 毕业设计：构建属于你的多智能体应用"></a>第十六章 毕业设计：构建属于你的多智能体]]>
    </summary>
    <title>第十六章 毕业设计：构建属于你的多智能体应用</title>
    <updated>2026-03-08T09:24:16.357Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="第十五章-构建赛博小镇"><a href="#第十五章-构建赛博小镇" class="headerlink" title="第十五章 构建赛博小镇"></a>第十五章 构建赛博小镇</h1><p>这一章，我们将探索一个全新的方向：<strong>将智能体技术与游戏引擎结合，构建一个充满生命力的 AI 小镇</strong>。</p><p>还记得《模拟人生》或《动物森友会》中那些栩栩如生的 NPC 吗?他们有自己的性格、记忆和社交关系。本章的赛博小镇将是一个类似的项目，但与传统游戏不同的是，我们的 NPC 拥有真正的”智能”——他们能够理解玩家的对话，记住过去的互动，并根据好感度做出不同的反应。本章的赛博小镇包含以下核心功能：</p><p><strong>（1）智能 NPC 对话系统</strong>：玩家可以与 NPC 进行自然语言对话，NPC 会根据自己的角色设定和记忆做出回应。</p><p><strong>（2）记忆系统</strong>：NPC 拥有短期记忆和长期记忆，能够记住与玩家的互动历史。</p><p><strong>（3）好感度系统</strong>：NPC 对玩家的态度会随着互动而变化，从陌生到熟悉，从友好到亲密。</p><p><strong>（4）游戏化交互</strong>：玩家可以在 2D 像素风格的办公室场景中自由移动，与不同的 NPC 互动。</p><p><strong>（5）实时日志系统</strong>：所有对话和互动都会被记录，方便调试和分析。</p><h2 id="15-1-项目概述与架构设计"><a href="#15-1-项目概述与架构设计" class="headerlink" title="15.1 项目概述与架构设计"></a>15.1 项目概述与架构设计</h2><h3 id="15-1-1-为什么要构建-AI-小镇"><a href="#15-1-1-为什么要构建-AI-小镇" class="headerlink" title="15.1.1 为什么要构建 AI 小镇"></a>15.1.1 为什么要构建 AI 小镇</h3><p>传统游戏中的 NPC 通常只能说固定的台词，或者通过预设的对话树进行有限的互动。即使是最复杂的 RPG 游戏，NPC 的对话也是由编剧事先写好的。这种方式虽然可控，但缺乏真正的”智能”和”生命力”。</p><p>想象一下，如果游戏中的 NPC 能够理解你说的任何话，不再局限于预设的选项，你可以用自然语言与 NPC 交流。NPC 会记得你上次说了什么，你们的关系如何，甚至你的喜好。每个 NPC 都有自己的职业、性格和说话风格。NPC 对你的态度会随着互动而变化，从陌生人到朋友，甚至挚友。</p><p>这就是 AI 技术为游戏带来的新可能。通过将大语言模型与游戏引擎结合，我们可以创造出真正”活着”的 NPC。这不仅仅是一个技术演示，更是对未来游戏形态的探索。在教育游戏中，NPC 可以扮演历史人物、科学家，与学生进行互动式教学。在虚拟办公室中，NPC 可以扮演同事、导师，提供帮助和建议。NPC 还可以作为陪伴者，与用户进行情感交流，应用于心理健康领域。当然，最直接的应用就是为传统游戏增加 AI NPC，提升玩家体验。</p><h3 id="15-1-2-技术架构概览"><a href="#15-1-2-技术架构概览" class="headerlink" title="15.1.2 技术架构概览"></a>15.1.2 技术架构概览</h3><p>赛博小镇采用<strong>游戏引擎+后端服务</strong>的分离架构，分为四个层次，如图 15.1 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-1.png" alt="" width="85%"/>  <p>图 15.1 赛博小镇技术架构</p></div><p>前端层使用 Godot 4.5 游戏引擎，负责游戏渲染、玩家控制、NPC 显示和对话 UI。Godot 是一个开源的 2D&#x2F;3D 游戏引擎，非常适合快速开发像素风格的游戏。后端层使用 FastAPI 框架，负责 API 路由、NPC 状态管理、对话处理和日志记录。FastAPI 是一个现代化的 Python Web 框架，性能优秀且易于开发。智能体层使用我们自己构建的 HelloAgents 框架，负责 NPC 智能、记忆管理和好感度计算。每个 NPC 都是一个 SimpleAgent 实例，拥有独立的记忆和状态。外部服务层提供 LLM 能力、向量存储和数据持久化，包括 LLM API、Qdrant 向量数据库和 SQLite 关系数据库。</p><p>数据流转过程如图 15.2 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-2.png" alt="" width="85%"/>  <p>图 15.2 数据流转过程</p></div><p>玩家在 Godot 中按 E 键与 NPC 互动，Godot 通过 HTTP API 发送对话请求到 FastAPI 后端。后端调用 HelloAgents 的 SimpleAgent 处理对话，Agent 从记忆系统中检索相关历史，然后调用 LLM 生成回复。后端更新 NPC 状态和好感度，记录日志到控制台和文件，最后返回回复给 Godot 前端。Godot 显示 NPC 回复并更新 UI，完成一次完整的交互循环。</p><p>项目的结构如下，方便你定位源码:</p><figure class="highlight applescript"><table><tr><td class="gutter"><pre><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><code class="hljs applescript">Helloagents-AI-Town/<br>├── helloagents-ai-town/           <span class="hljs-comment"># Godot游戏项目</span><br>│   ├── project.godot              <span class="hljs-comment"># Godot项目配置</span><br>│   ├── scenes/                    <span class="hljs-comment"># 游戏场景</span><br>│   │   ├── main.tscn              <span class="hljs-comment"># 主场景(办公室)</span><br>│   │   ├── player.tscn            <span class="hljs-comment"># 玩家角色</span><br>│   │   ├── npc.tscn               <span class="hljs-comment"># NPC角色</span><br>│   │   └── dialogue_ui.tscn       <span class="hljs-comment"># 对话UI</span><br>│   ├── scripts/                   <span class="hljs-comment"># GDScript脚本</span><br>│   │   ├── main.gd                <span class="hljs-comment"># 主场景逻辑</span><br>│   │   ├── player.gd              <span class="hljs-comment"># 玩家控制</span><br>│   │   ├── npc.gd                 <span class="hljs-comment"># NPC行为</span><br>│   │   ├── dialogue_ui.gd         <span class="hljs-comment"># 对话UI逻辑</span><br>│   │   ├── api_client.gd          <span class="hljs-comment"># API客户端</span><br>│   │   └── config.gd              <span class="hljs-comment"># 配置管理</span><br>│   └── assets/                    <span class="hljs-comment"># 游戏资源</span><br>│       ├── <span class="hljs-built_in">characters</span>/            <span class="hljs-comment"># 角色精灵图</span><br>│       ├── interiors/             <span class="hljs-comment"># 室内场景</span><br>│       ├── ui/                    <span class="hljs-comment"># UI素材</span><br>│       └── audio/                 <span class="hljs-comment"># 音效音乐</span><br>│<br>└── backend/                       <span class="hljs-comment"># Python后端</span><br>    ├── main.py                    <span class="hljs-comment"># FastAPI主程序</span><br>    ├── agents.py                  <span class="hljs-comment"># NPC Agent系统</span><br>    ├── relationship_manager.py    <span class="hljs-comment"># 好感度管理</span><br>    ├── state_manager.py           <span class="hljs-comment"># 状态管理</span><br>    ├── logger.py                  <span class="hljs-comment"># 日志系统</span><br>    ├── config.py                  <span class="hljs-comment"># 配置管理</span><br>    ├── models.py                  <span class="hljs-comment"># 数据模型</span><br>    ├── requirements.txt           <span class="hljs-comment"># Python依赖</span><br>    └── .env.example               <span class="hljs-comment"># 环境变量示例</span><br></code></pre></td></tr></table></figure><p>详细的架构设计和数据流转将在后续章节中介绍。</p><h3 id="15-1-3-快速体验：5-分钟运行项目"><a href="#15-1-3-快速体验：5-分钟运行项目" class="headerlink" title="15.1.3 快速体验：5 分钟运行项目"></a>15.1.3 快速体验：5 分钟运行项目</h3><p>在深入学习实现细节之前，让我们先把项目跑起来，看看最终的效果。这样你会对整个系统有一个直观的认识。</p><p><strong>环境要求：</strong></p><ul><li>Godot 4.2 或更高版本</li><li>Python 3.10 或更高版本</li><li>LLM API 密钥(OpenAI、DeepSeek、智谱等)</li></ul><p><strong>获取项目：</strong></p><p>你可以到<code>code/chapter15/Helloagents-AI-Town</code>中查看，或者从 GitHub 克隆完整的 hello-agents 仓库。</p><p><strong>启动后端：</strong></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><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 1. 进入backend目录</span><br><span class="hljs-built_in">cd</span> Helloagents-AI-Town/backend<br><br><span class="hljs-comment"># 2. 安装依赖</span><br>pip install -r requirements.txt<br><br><span class="hljs-comment"># 3. 配置环境变量</span><br><span class="hljs-built_in">cp</span> .env.example .<span class="hljs-built_in">env</span><br><span class="hljs-comment"># 编辑.env文件，填写你的API密钥</span><br><br><span class="hljs-comment"># 4. 启动后端服务</span><br>python main.py<br></code></pre></td></tr></table></figure><p>成功启动后，你会看到如下输出：</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><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><code class="hljs asciidoc">============================================================<br>🎮 赛博小镇后端服务启动中...<br>============================================================<br>✅ 所有服务已启动!<br>📡 API地址: http://0.0.0.0:8000<br><span class="hljs-section">📚 API文档: http://0.0.0.0:8000/docs</span><br><span class="hljs-section">============================================================</span><br></code></pre></td></tr></table></figure><p><strong>启动 Godot：</strong></p><p>Godot 的安装非常简单，Windows 提供了直接打开的<code>.exe</code>文件，Mac 也提供了<code>.dmg</code>文件。可直接在官网下载(<a href="https://godotengine.org/download/windows/">Windows</a> &#x2F; <a href="https://godotengine.org/download/macos/">Mac</a>)</p><p>打开 Godot 引擎，点击”导入”按钮，浏览到<code>Helloagents-AI-Town/helloagents-ai-town/scenes/main.tscn</code>，点击”导入并编辑”。等待 Godot 导入资源后，按<code>F5</code>或点击”运行”按钮启动游戏。</p><p><strong>体验核心功能：</strong></p><p>游戏启动后，你会看到一个像素风格的 Datawhale 办公室场景，如图 15.3 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-3.png" alt="" width="85%"/>  <p>图 15.3 赛博小镇游戏场景</p></div><p>使用 WASD 键移动玩家角色，走到 NPC 附近时，屏幕上会显示”按 E 键交互”的提示。按下 E 键后，会弹出对话框，你可以输入任何想说的话，如图 15.4 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-4.png" alt="" width="85%"/>  <p>图 15.4 与 NPC 对话界面</p></div><p>NPC 会根据自己的角色设定(Python 工程师、产品经理、UI 设计师)和你们的互动历史做出回应。随着对话的进行，NPC 对你的好感度会逐渐提升，从”陌生”到”熟悉”，再到”友好”、”亲密”甚至”挚友”。</p><p><strong>好感度系统在后端实现</strong>，每次对话都会根据玩家的消息内容和情感分析来调整好感度值。虽然前端游戏界面中没有直接显示好感度数值，但所有的好感度变化都会被详细记录在后端日志中。你可以在<code>backend/logs/dialogue_YYYY-MM-DD.log</code>文件中查看每次对话的好感度变化。日志文件会记录每次对话的详细信息，包括：当前好感度值、检索到的相关记忆、NPC 的回复、好感度变化量(+2.0、+3.0 等)、变化原因(友好问候、正常交流等)以及情感分析结果(positive、neutral 等)。这种设计让开发者可以清晰地追踪 NPC 与玩家的关系发展，也为后续在前端添加好感度 UI 提供了数据基础。</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><code class="hljs bash"><span class="hljs-comment"># 在backend目录下</span><br>python view_logs.py<br></code></pre></td></tr></table></figure><p>这个简单的体验展示了 AI 小镇的核心功能。接下来，我们将深入学习如何实现这些功能。</p><h2 id="15-2-NPC-智能体系统"><a href="#15-2-NPC-智能体系统" class="headerlink" title="15.2 NPC 智能体系统"></a>15.2 NPC 智能体系统</h2><h3 id="15-2-1-基于-HelloAgents-的-SimpleAgent"><a href="#15-2-1-基于-HelloAgents-的-SimpleAgent" class="headerlink" title="15.2.1 基于 HelloAgents 的 SimpleAgent"></a>15.2.1 基于 HelloAgents 的 SimpleAgent</h3><p>在赛博小镇中，每个 NPC 都是一个独立的智能体。我们使用 HelloAgents 框架中的 SimpleAgent 来实现 NPC 的智能。SimpleAgent 是一个轻量级的智能体实现，它封装了 LLM 调用、消息管理和工具调用等核心功能。</p><p>回顾一下第七章中我们学习的 SimpleAgent，它的核心是一个简单的对话循环：接收用户消息，调用 LLM 生成回复，返回结果。在赛博小镇中，我们需要为每个 NPC 创建一个 SimpleAgent 实例，并为其配置独特的系统提示词，让每个 NPC 拥有不同的性格和角色设定。</p><p>让我们看看如何创建一个 NPC Agent。首先，我们需要定义 NPC 的基本信息，包括 ID、名称、职业和性格。然后，我们根据这些信息构建系统提示词，让 LLM 扮演这个 NPC 的角色。最后，我们创建 SimpleAgent 实例，并配置记忆系统。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.memory <span class="hljs-keyword">import</span> MemoryManager, WorkingMemory, EpisodicMemory<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_npc_agent</span>(<span class="hljs-params">npc_id: <span class="hljs-built_in">str</span>, name: <span class="hljs-built_in">str</span>, role: <span class="hljs-built_in">str</span>, personality: <span class="hljs-built_in">str</span></span>):<br>    <span class="hljs-string">&quot;&quot;&quot;创建NPC Agent&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 构建系统提示词</span><br>    system_prompt = <span class="hljs-string">f&quot;&quot;&quot;你是<span class="hljs-subst">&#123;name&#125;</span>,一位<span class="hljs-subst">&#123;role&#125;</span>。</span><br><span class="hljs-string">你的性格特点:<span class="hljs-subst">&#123;personality&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">你在Datawhale办公室工作,与同事们一起推动开源社区的发展。</span><br><span class="hljs-string">请根据你的角色和性格,自然地与玩家对话。</span><br><span class="hljs-string">记住你们之前的对话内容,保持对话的连贯性。</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br>    <span class="hljs-comment"># 创建LLM实例</span><br>    llm = HelloAgentsLLM()<br><br>    <span class="hljs-comment"># 创建记忆管理器</span><br>    memory_manager = MemoryManager(<br>        working_memory=WorkingMemory(capacity=<span class="hljs-number">10</span>, ttl_minutes=<span class="hljs-number">120</span>),<br>        episodic_memory=EpisodicMemory(<br>            db_path=<span class="hljs-string">f&quot;memory_data/<span class="hljs-subst">&#123;npc_id&#125;</span>_episodic.db&quot;</span>,<br>            collection_name=<span class="hljs-string">f&quot;<span class="hljs-subst">&#123;npc_id&#125;</span>_memories&quot;</span><br>        )<br>    )<br><br>    <span class="hljs-comment"># 创建Agent</span><br>    agent = SimpleAgent(<br>        name=name,<br>        llm=llm,<br>        system_prompt=system_prompt,<br>        memory_manager=memory_manager<br>    )<br><br>    <span class="hljs-keyword">return</span> agent<br></code></pre></td></tr></table></figure><p>这段代码展示了如何创建一个 NPC Agent。系统提示词定义了 NPC 的身份和性格，记忆管理器让 NPC 能够记住与玩家的对话历史。WorkingMemory 是短期记忆，容量为 10 条消息，保留时间为 120 分钟。EpisodicMemory 是长期记忆，使用 SQLite 数据库和 Qdrant 向量数据库存储，可以检索相关的历史对话。</p><p>NPC Agent 的工作流程如图 15.5 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-5.png" alt="" width="85%"/>  <p>图 15.5 NPC Agent 工作流程</p></div><h3 id="15-2-2-NPC-角色设定与-Prompt-设计"><a href="#15-2-2-NPC-角色设定与-Prompt-设计" class="headerlink" title="15.2.2 NPC 角色设定与 Prompt 设计"></a>15.2.2 NPC 角色设定与 Prompt 设计</h3><p>一个好的 NPC 需要有鲜明的性格和角色设定。在赛博小镇中，我们设计了三个 NPC，分别代表不同的职业和性格。</p><p><strong>张三 - Python 工程师</strong></p><p>张三是一位资深的 Python 工程师，负责 HelloAgents 框架的核心开发。他性格严谨，说话直接，喜欢用技术术语。他对代码质量有很高的要求，经常会分享一些编程技巧和最佳实践。</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><code class="hljs python">npc_zhang = &#123;<br>    <span class="hljs-string">&quot;npc_id&quot;</span>: <span class="hljs-string">&quot;zhang_san&quot;</span>,<br>    <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;张三&quot;</span>,<br>    <span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;Python工程师&quot;</span>,<br>    <span class="hljs-string">&quot;personality&quot;</span>: <span class="hljs-string">&quot;严谨、专业、喜欢分享技术知识。说话直接,注重代码质量。&quot;</span><br>&#125;<br></code></pre></td></tr></table></figure><p><strong>李四 - 产品经理</strong></p><p>李四是一位经验丰富的产品经理，负责 HelloAgents 框架的产品规划和用户体验设计。他性格外向，善于沟通，总是能从用户的角度思考问题。他喜欢讨论产品设计和用户需求，经常会问”为什么”。</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><code class="hljs python">npc_li = &#123;<br>    <span class="hljs-string">&quot;npc_id&quot;</span>: <span class="hljs-string">&quot;li_si&quot;</span>,<br>    <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;李四&quot;</span>,<br>    <span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;产品经理&quot;</span>,<br>    <span class="hljs-string">&quot;personality&quot;</span>: <span class="hljs-string">&quot;外向、善于沟通、注重用户体验。喜欢从用户角度思考问题。&quot;</span><br>&#125;<br></code></pre></td></tr></table></figure><p><strong>王五 - UI 设计师</strong></p><p>王五是一位富有创意的 UI 设计师，负责 HelloAgents 框架的界面设计和视觉呈现。他性格温和，审美独特，对色彩和布局有敏锐的感知。他喜欢讨论设计理念和美学，经常会分享一些设计灵感。</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><code class="hljs python">npc_wang = &#123;<br>    <span class="hljs-string">&quot;npc_id&quot;</span>: <span class="hljs-string">&quot;wang_wu&quot;</span>,<br>    <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;王五&quot;</span>,<br>    <span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;UI设计师&quot;</span>,<br>    <span class="hljs-string">&quot;personality&quot;</span>: <span class="hljs-string">&quot;温和、富有创意、审美独特。注重视觉呈现和用户体验。&quot;</span><br>&#125;<br></code></pre></td></tr></table></figure><p>这三个 NPC 的设定各有特色，玩家可以根据自己的兴趣选择与不同的 NPC 互动。张三可以教你编程技巧，李四可以和你讨论产品设计，王五可以分享设计灵感。</p><h3 id="15-2-3-记忆系统集成"><a href="#15-2-3-记忆系统集成" class="headerlink" title="15.2.3 记忆系统集成"></a>15.2.3 记忆系统集成</h3><p>记忆系统是 NPC 智能的关键。一个能够记住过去对话的 NPC，会让玩家感觉更加真实和有趣。我们采用 helloagents 的<code>WorkingMemory</code>和<code>EpisodicMemory</code>构造短期记忆和长期记忆。</p><p>短期记忆存储最近的对话内容，容量有限，会随着时间自动清理。它的作用是保持对话的连贯性，让 NPC 能够理解上下文。比如，当玩家说”它是什么颜色的?”时，NPC 需要从短期记忆中找到”它”指的是什么。</p><p>长期记忆存储所有的对话历史，使用向量数据库进行语义检索。当玩家提到某个话题时，NPC 可以从长期记忆中检索相关的历史对话，回忆起之前讨论过的内容。比如，当玩家说”还记得我们上次讨论的那个项目吗?”，NPC 可以从长期记忆中找到相关的对话记录。</p><p>记忆系统的架构如图 15.6 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-6.png" alt="" width="85%"/>  <p>图 15.6 记忆系统架构</p></div><p>在实际使用中，Agent 会先从短期记忆中获取最近的对话，然后从长期记忆中检索相关的历史对话，将这些信息一起发送给 LLM，生成更加准确和个性化的回复。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># Agent处理对话的流程</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">process_dialogue</span>(<span class="hljs-params">agent, player_message</span>):<br>    <span class="hljs-comment"># 1. 从短期记忆获取最近对话</span><br>    recent_messages = agent.memory_manager.working_memory.get_recent_messages(<span class="hljs-number">5</span>)<br><br>    <span class="hljs-comment"># 2. 从长期记忆检索相关历史</span><br>    relevant_memories = agent.memory_manager.episodic_memory.search(<br>        query=player_message,<br>        top_k=<span class="hljs-number">3</span><br>    )<br><br>    <span class="hljs-comment"># 3. 构建上下文</span><br>    context = &#123;<br>        <span class="hljs-string">&quot;recent&quot;</span>: recent_messages,<br>        <span class="hljs-string">&quot;relevant&quot;</span>: relevant_memories<br>    &#125;<br><br>    <span class="hljs-comment"># 4. 调用Agent生成回复</span><br>    reply = agent.run(player_message, context=context)<br><br>    <span class="hljs-comment"># 5. 保存到记忆系统</span><br>    agent.memory_manager.add_interaction(player_message, reply)<br><br>    <span class="hljs-keyword">return</span> reply<br></code></pre></td></tr></table></figure><p>这个流程确保了 NPC 能够记住与玩家的互动历史，并在对话中体现出来。</p><h3 id="15-2-4-批量对话生成：轻负载模式"><a href="#15-2-4-批量对话生成：轻负载模式" class="headerlink" title="15.2.4 批量对话生成：轻负载模式"></a>15.2.4 批量对话生成：轻负载模式</h3><p>在实际运行中，很快就会发现了一个问题：当多个玩家同时与不同的 NPC 对话时，后端需要并发处理多个 LLM 请求。每个请求都需要调用 API，这不仅增加了成本，还可能因为并发限制导致请求失败或延迟。</p><p>为了解决这个问题，我们设计了一个<strong>批量对话生成系统</strong>。核心思想是：将多个 NPC 的对话请求合并成一次 LLM 调用，让 LLM 一次性生成所有 NPC 的回复。这就像餐厅的”预制菜”一样，提前批量准备好，需要时直接使用，大大降低了成本和延迟。</p><p>批量生成的工作流程如图 15.7 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-7.png" alt="" width="85%"/>  <p>图 15.7 批量生成 vs 传统模式</p></div><p>批量生成器的实现非常巧妙。我们构建一个特殊的提示词，要求 LLM 一次性生成所有 NPC 的对话，并以 JSON 格式返回。这样，一次 API 调用就能获得所有 NPC 的回复，成本降低到原来的 1&#x2F;3，延迟也大幅减少。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">NPCBatchGenerator</span>：<br>    <span class="hljs-string">&quot;&quot;&quot;批量生成NPC对话的生成器&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):<br>        self.llm = HelloAgentsLLM()<br>        self.npc_configs = NPC_ROLES  <span class="hljs-comment"># 所有NPC的配置</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">generate_batch_dialogues</span>(<span class="hljs-params">self, context: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span></span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-built_in">str</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;批量生成所有NPC的对话</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Args:</span><br><span class="hljs-string">            context: 场景上下文(如&quot;上午工作时间&quot;、&quot;午餐时间&quot;等)</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Returns:</span><br><span class="hljs-string">            Dict[str, str]: NPC名称到对话内容的映射</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 构建批量生成提示词</span><br>        prompt = self._build_batch_prompt(context)<br><br>        <span class="hljs-comment"># 一次LLM调用生成所有对话</span><br>        response = self.llm.invoke([<br>            &#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;system&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;你是一个游戏NPC对话生成器,擅长创作自然真实的办公室对话。&quot;</span>&#125;,<br>            &#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: prompt&#125;<br>        ])<br><br>        <span class="hljs-comment"># 解析JSON响应</span><br>        dialogues = json.loads(response)<br>        <span class="hljs-comment"># 返回格式: &#123;&quot;张三&quot;: &quot;...&quot;, &quot;李四&quot;: &quot;...&quot;, &quot;王五&quot;: &quot;...&quot;&#125;</span><br><br>        <span class="hljs-keyword">return</span> dialogues<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_build_batch_prompt</span>(<span class="hljs-params">self, context: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;构建批量生成提示词&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 根据时间自动推断场景</span><br>        <span class="hljs-keyword">if</span> context <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br>            context = self._get_current_context()<br><br>        <span class="hljs-comment"># 构建NPC描述</span><br>        npc_descriptions = []<br>        <span class="hljs-keyword">for</span> name, cfg <span class="hljs-keyword">in</span> self.npc_configs.items():<br>            desc = <span class="hljs-string">f&quot;- <span class="hljs-subst">&#123;name&#125;</span>(<span class="hljs-subst">&#123;cfg[<span class="hljs-string">&#x27;title&#x27;</span>]&#125;</span>): 在<span class="hljs-subst">&#123;cfg[<span class="hljs-string">&#x27;location&#x27;</span>]&#125;</span><span class="hljs-subst">&#123;cfg[<span class="hljs-string">&#x27;activity&#x27;</span>]&#125;</span>,性格<span class="hljs-subst">&#123;cfg[<span class="hljs-string">&#x27;personality&#x27;</span>]&#125;</span>&quot;</span><br>            npc_descriptions.append(desc)<br><br>        npc_desc_text = <span class="hljs-string">&quot;\n&quot;</span>.join(npc_descriptions)<br><br>        prompt = <span class="hljs-string">f&quot;&quot;&quot;请为Datawhale办公室的3个NPC生成当前的对话或行为描述。</span><br><span class="hljs-string"></span><br><span class="hljs-string">【场景】<span class="hljs-subst">&#123;context&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">【NPC信息】</span><br><span class="hljs-string"><span class="hljs-subst">&#123;npc_desc_text&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">【生成要求】</span><br><span class="hljs-string">1. 每个NPC生成1句话(20-40字)</span><br><span class="hljs-string">2. 内容要符合角色设定、当前活动和场景氛围</span><br><span class="hljs-string">3. 可以是自言自语、工作状态描述、或简单的思考</span><br><span class="hljs-string">4. 要自然真实,像真实的办公室同事</span><br><span class="hljs-string">5. **必须严格按照JSON格式返回**</span><br><span class="hljs-string"></span><br><span class="hljs-string">【输出格式】(严格遵守)</span><br><span class="hljs-string">&#123;&#123;&quot;张三&quot;: &quot;...&quot;, &quot;李四&quot;: &quot;...&quot;, &quot;王五&quot;: &quot;...&quot;&#125;&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">【示例输出】</span><br><span class="hljs-string">&#123;&#123;&quot;张三&quot;: &quot;这个bug真是见鬼了,已经调试两小时了...&quot;, &quot;李四&quot;: &quot;嗯,这个功能的优先级需要重新评估一下。&quot;, &quot;王五&quot;: &quot;这杯咖啡的拉花真不错,灵感来了!&quot;&#125;&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">请生成(只返回JSON,不要其他内容):</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> prompt<br></code></pre></td></tr></table></figure><p>这个设计的关键在于提示词的构建。我们明确要求 LLM 返回 JSON 格式，并提供了示例输出。LLM 会严格按照这个格式生成回复，我们只需要解析 JSON 就能获得所有 NPC 的对话。</p><p>批量生成还有一个额外的好处：所有 NPC 的对话是在同一个上下文中生成的，因此它们之间会有一定的关联性。比如，如果张三在调试 bug，李四可能会提到要帮忙看看;如果王五在设计界面，张三可能会说等会儿去看看设计稿。这让整个办公室的氛围更加真实和连贯。</p><p>当然，批量生成也有一些限制。它更适合生成 NPC 的”背景对话”或”自言自语”，而不是与玩家的直接互动。对于玩家发起的对话，我们仍然使用单独的 Agent 来处理，以保证回复的个性化和准确性。批量生成主要用于以下场景：</p><ol><li><strong>NPC 背景对话</strong>：玩家进入场景时，NPC 正在做什么、说什么</li><li><strong>定时更新</strong>：每隔一段时间更新 NPC 的状态和对话</li><li><strong>场景氛围</strong>：根据时间(早上、中午、晚上)生成不同的对话</li><li><strong>降低成本</strong>：在高并发场景下，使用批量生成降低 API 调用次数</li></ol><p><strong>混合模式：批量生成+即时响应</strong></p><p>在实际实现中，我们采用了一种混合模式，将批量生成和即时响应结合起来。这个设计非常巧妙，既保证了效率，又保证了交互的质量。</p><p>具体来说，系统会在后台定期运行批量生成，为所有 NPC 生成当前场景下的”背景对话”。这些对话会被缓存起来，当玩家靠近 NPC 但还没有发起交互时，NPC 会显示这些背景对话，比如”正在调试代码…”、”在看产品文档…”等。这让 NPC 看起来是”活着的”，而不是静止的模型。</p><p>但是，当玩家按下 E 键发起交互时，系统会立即切换到即时响应模式。此时，后端会调用该 NPC 的专属 Agent，根据玩家的具体消息、历史记忆和好感度，生成个性化的回复。这个过程是实时的，确保 NPC 的回复与玩家的输入高度相关。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 在main.py中的混合模式实现</span><br><span class="hljs-meta">@app.post(<span class="hljs-params"><span class="hljs-string">&quot;/dialogue&quot;</span></span>)</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">dialogue</span>(<span class="hljs-params">request: DialogueRequest</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;处理玩家与NPC的对话(即时响应模式)&quot;&quot;&quot;</span><br>    npc_id = request.npc_id<br>    player_message = request.player_message<br>    player_name = request.player_name<br><br>    <span class="hljs-comment"># 获取NPC Agent(每个NPC有独立的Agent)</span><br>    agent = npc_agents.get(npc_id)<br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> agent:<br>        <span class="hljs-keyword">raise</span> HTTPException(status_code=<span class="hljs-number">404</span>, detail=<span class="hljs-string">&quot;NPC not found&quot;</span>)<br><br>    <span class="hljs-comment"># 即时生成个性化回复</span><br>    <span class="hljs-comment"># 这里不使用批量生成,而是调用Agent的run方法</span><br>    reply = agent.run(player_message)<br><br>    <span class="hljs-comment"># 更新好感度</span><br>    affinity_change = relationship_manager.update_affinity(<br>        npc_id, player_name, player_message, reply<br>    )<br><br>    <span class="hljs-keyword">return</span> &#123;<br>        <span class="hljs-string">&quot;npc_reply&quot;</span>: reply,<br>        <span class="hljs-string">&quot;affinity_score&quot;</span>: affinity_change[<span class="hljs-string">&quot;score&quot;</span>],<br>        <span class="hljs-string">&quot;affinity_level&quot;</span>: affinity_change[<span class="hljs-string">&quot;level&quot;</span>]<br>    &#125;<br><br><span class="hljs-comment"># 后台任务:定期批量生成背景对话</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">background_dialogue_update</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;后台任务:每5分钟更新一次NPC背景对话&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:<br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-comment"># 使用批量生成器生成所有NPC的背景对话</span><br>            batch_generator = get_batch_generator()<br>            dialogues = batch_generator.generate_batch_dialogues()<br><br>            <span class="hljs-comment"># 更新到状态管理器</span><br>            <span class="hljs-keyword">for</span> npc_name, dialogue <span class="hljs-keyword">in</span> dialogues.items():<br>                state_manager.update_npc_background_dialogue(npc_name, dialogue)<br><br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 背景对话更新完成: <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(dialogues)&#125;</span>个NPC&quot;</span>)<br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;❌ 背景对话更新失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br><br>        <span class="hljs-comment"># 等待5分钟</span><br>        <span class="hljs-keyword">await</span> asyncio.sleep(<span class="hljs-number">300</span>)<br></code></pre></td></tr></table></figure><p>这种混合模式的优势非常明显：</p><ol><li><strong>降低成本</strong>：背景对话使用批量生成，一次调用生成所有 NPC 的对话，成本低</li><li><strong>保证质量</strong>：玩家交互使用即时响应，每个回复都是个性化的，质量高</li><li><strong>提升体验</strong>：NPC 始终有”背景对话”，看起来很生动;玩家交互时回复准确，体验好</li><li><strong>灵活调整</strong>：可以根据服务器负载动态调整批量生成的频率</li></ol><p>通过批量生成和即时响应的结合，我们实现了一个既高效又智能的 NPC 系统。在正常情况下，玩家感受不到任何差异，但后端的成本和性能得到了显著优化。这个设计思路也可以应用到其他需要大量 AI 调用的场景中。</p><h2 id="15-3-好感度系统设计"><a href="#15-3-好感度系统设计" class="headerlink" title="15.3 好感度系统设计"></a>15.3 好感度系统设计</h2><h3 id="15-3-1-好感度等级划分"><a href="#15-3-1-好感度等级划分" class="headerlink" title="15.3.1 好感度等级划分"></a>15.3.1 好感度等级划分</h3><p>在赛博小镇中，NPC 对玩家的态度会随着互动而变化。我们设计了一个五级好感度系统，从陌生到挚友，每个等级都有不同的分数范围和对应的行为表现。</p><p>好感度系统的核心思想是：通过量化 NPC 与玩家的关系，让 NPC 的回复更加真实和有层次感。当玩家刚进入游戏时，所有 NPC 对玩家都是陌生的态度，回复比较礼貌但疏远。随着对话的进行，如果玩家表现友好，NPC 的好感度会逐渐提升，回复也会变得更加亲切和详细。</p><p>我们将好感度分为五个等级，每个等级对应一个分数范围，如图 15.8 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-8.png" alt="" width="85%"/>  <p>图 15.8 好感度等级划分</p></div><ul><li><p><strong>陌生(0-20 分)</strong>：NPC 刚认识玩家，态度礼貌但保持距离。回复简短，不会主动分享个人信息。</p></li><li><p><strong>熟悉(21-40 分)</strong>：NPC 开始记住玩家，愿意进行简单的交流。回复变得更加自然，偶尔会分享一些工作相关的信息。</p></li><li><p><strong>友好(41-60 分)</strong>：NPC 把玩家当作朋友，愿意分享更多信息。回复更加详细，会主动询问玩家的情况。</p></li><li><p><strong>亲密(61-80 分)</strong>：NPC 非常信任玩家，愿意分享私人话题。回复充满热情，会给玩家提供帮助和建议。</p></li><li><p><strong>挚友(81-100 分)</strong>：NPC 把玩家当作最好的朋友，无话不谈。回复非常亲切，会分享内心的想法和感受。</p></li></ul><p>这个设计让玩家能够清晰地感受到与 NPC 关系的变化，也为后续的游戏玩法提供了基础。比如，只有达到一定好感度，NPC 才会分享某些特殊信息或提供特殊任务。</p><h3 id="15-3-2-好感度计算逻辑"><a href="#15-3-2-好感度计算逻辑" class="headerlink" title="15.3.2 好感度计算逻辑"></a>15.3.2 好感度计算逻辑</h3><p>好感度的计算需要考虑多个因素。我们不能简单地让每次对话都增加固定的分数，这样会让系统显得机械和不真实。一个好的好感度系统应该能够识别玩家的态度，并根据对话内容动态调整分数。</p><p>在赛博小镇中，我们使用 LLM 来分析对话内容，判断玩家的态度是友好、中立还是不友好。然后根据判断结果调整好感度分数。这个过程是自动的，不需要玩家刻意选择选项，让互动更加自然。</p><p>好感度计算流程如图 15.9 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-9.png" alt="" width="85%"/>  <p>图 15.9 好感度计算流程</p></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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">RelationshipManager</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;好感度管理器&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):<br>        self.affinity_data = &#123;&#125;  <span class="hljs-comment"># 存储好感度数据</span><br>        self.llm = HelloAgentsLLM()  <span class="hljs-comment"># 用于分析对话</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">analyze_sentiment</span>(<span class="hljs-params">self, player_message: <span class="hljs-built_in">str</span>, npc_reply: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">int</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;分析对话情感,返回好感度变化值&quot;&quot;&quot;</span><br>        prompt = <span class="hljs-string">f&quot;&quot;&quot;分析以下对话中玩家的态度:</span><br><span class="hljs-string">玩家: <span class="hljs-subst">&#123;player_message&#125;</span></span><br><span class="hljs-string">NPC: <span class="hljs-subst">&#123;npc_reply&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">请判断玩家的态度是:</span><br><span class="hljs-string">1. 友好(+5分): 礼貌、热情、表示感谢或赞同</span><br><span class="hljs-string">2. 中立(+2分): 普通的询问或陈述</span><br><span class="hljs-string">3. 不友好(-3分): 粗鲁、冷漠、批评或否定</span><br><span class="hljs-string"></span><br><span class="hljs-string">只返回数字,不要其他内容。&quot;&quot;&quot;</span><br><br>        response = self.llm.think([&#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: prompt&#125;])<br>        <span class="hljs-keyword">try</span>:<br>            score_change = <span class="hljs-built_in">int</span>(response.strip())<br>            <span class="hljs-keyword">return</span> <span class="hljs-built_in">max</span>(-<span class="hljs-number">3</span>, <span class="hljs-built_in">min</span>(<span class="hljs-number">5</span>, score_change))  <span class="hljs-comment"># 限制在-3到5之间</span><br>        <span class="hljs-keyword">except</span>:<br>            <span class="hljs-keyword">return</span> <span class="hljs-number">2</span>  <span class="hljs-comment"># 默认中立</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">update_affinity</span>(<span class="hljs-params">self, npc_id: <span class="hljs-built_in">str</span>, player_name: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">                       player_message: <span class="hljs-built_in">str</span>, npc_reply: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">dict</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;更新好感度&quot;&quot;&quot;</span><br>        key = <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;npc_id&#125;</span>_<span class="hljs-subst">&#123;player_name&#125;</span>&quot;</span><br><br>        <span class="hljs-comment"># 获取当前好感度</span><br>        <span class="hljs-keyword">if</span> key <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.affinity_data:<br>            self.affinity_data[key] = &#123;<br>                <span class="hljs-string">&quot;score&quot;</span>: <span class="hljs-number">0</span>,<br>                <span class="hljs-string">&quot;level&quot;</span>: <span class="hljs-string">&quot;陌生&quot;</span>,<br>                <span class="hljs-string">&quot;interaction_count&quot;</span>: <span class="hljs-number">0</span><br>            &#125;<br><br>        <span class="hljs-comment"># 分析对话情感</span><br>        score_change = self.analyze_sentiment(player_message, npc_reply)<br><br>        <span class="hljs-comment"># 更新分数</span><br>        current_score = self.affinity_data[key][<span class="hljs-string">&quot;score&quot;</span>]<br>        new_score = <span class="hljs-built_in">max</span>(<span class="hljs-number">0</span>, <span class="hljs-built_in">min</span>(<span class="hljs-number">100</span>, current_score + score_change))<br><br>        <span class="hljs-comment"># 更新等级</span><br>        level = self.get_affinity_level(new_score)<br><br>        <span class="hljs-comment"># 更新数据</span><br>        self.affinity_data[key].update(&#123;<br>            <span class="hljs-string">&quot;score&quot;</span>: new_score,<br>            <span class="hljs-string">&quot;level&quot;</span>: level,<br>            <span class="hljs-string">&quot;interaction_count&quot;</span>: self.affinity_data[key][<span class="hljs-string">&quot;interaction_count&quot;</span>] + <span class="hljs-number">1</span><br>        &#125;)<br><br>        <span class="hljs-keyword">return</span> self.affinity_data[key]<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_affinity_level</span>(<span class="hljs-params">self, score: <span class="hljs-built_in">int</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;根据分数获取好感度等级&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> score &lt;= <span class="hljs-number">20</span>:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;陌生&quot;</span><br>        <span class="hljs-keyword">elif</span> score &lt;= <span class="hljs-number">40</span>:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;熟悉&quot;</span><br>        <span class="hljs-keyword">elif</span> score &lt;= <span class="hljs-number">60</span>:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;友好&quot;</span><br>        <span class="hljs-keyword">elif</span> score &lt;= <span class="hljs-number">80</span>:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;亲密&quot;</span><br>        <span class="hljs-keyword">else</span>:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;挚友&quot;</span><br></code></pre></td></tr></table></figure><p>这个实现使用 LLM 来分析对话内容，自动判断玩家的态度并调整好感度。这样的设计让好感度系统更加智能和自然，玩家不需要刻意讨好 NPC，只需要正常交流即可。</p><h3 id="15-3-3-好感度影响对话"><a href="#15-3-3-好感度影响对话" class="headerlink" title="15.3.3 好感度影响对话"></a>15.3.3 好感度影响对话</h3><p>好感度不仅仅是一个数字，它应该真正影响 NPC 的行为。在赛博小镇中，我们通过修改 NPC 的系统提示词，让 NPC 根据当前的好感度等级调整回复风格。</p><p>当好感度较低时，NPC 会保持礼貌但疏远的态度。当好感度提升后，NPC 会变得更加热情和健谈。这种变化是通过动态调整系统提示词实现的。</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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_npc_agent_with_affinity</span>(<span class="hljs-params">npc_id: <span class="hljs-built_in">str</span>, name: <span class="hljs-built_in">str</span>, role: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">                                   personality: <span class="hljs-built_in">str</span>, affinity_level: <span class="hljs-built_in">str</span></span>):<br>    <span class="hljs-string">&quot;&quot;&quot;创建带好感度的NPC Agent&quot;&quot;&quot;</span><br><br>    <span class="hljs-comment"># 根据好感度等级调整提示词</span><br>    affinity_prompts = &#123;<br>        <span class="hljs-string">&quot;陌生&quot;</span>: <span class="hljs-string">&quot;你刚认识这位玩家,保持礼貌但不要过于热情。回复简短专业。&quot;</span>,<br>        <span class="hljs-string">&quot;熟悉&quot;</span>: <span class="hljs-string">&quot;你已经认识这位玩家,可以进行正常的交流。回复自然友好。&quot;</span>,<br>        <span class="hljs-string">&quot;友好&quot;</span>: <span class="hljs-string">&quot;你把这位玩家当作朋友,愿意分享更多信息。回复详细热情。&quot;</span>,<br>        <span class="hljs-string">&quot;亲密&quot;</span>: <span class="hljs-string">&quot;你非常信任这位玩家,可以分享私人话题。回复充满关心。&quot;</span>,<br>        <span class="hljs-string">&quot;挚友&quot;</span>: <span class="hljs-string">&quot;你把这位玩家当作最好的朋友,无话不谈。回复亲切真诚。&quot;</span><br>    &#125;<br><br>    system_prompt = <span class="hljs-string">f&quot;&quot;&quot;你是<span class="hljs-subst">&#123;name&#125;</span>,一位<span class="hljs-subst">&#123;role&#125;</span>。</span><br><span class="hljs-string">你的性格特点:<span class="hljs-subst">&#123;personality&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">当前与玩家的关系:<span class="hljs-subst">&#123;affinity_level&#125;</span></span><br><span class="hljs-string"><span class="hljs-subst">&#123;affinity_prompts.get(affinity_level, affinity_prompts[<span class="hljs-string">&quot;陌生&quot;</span>])&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">你在Datawhale办公室工作,与同事们一起推动开源社区的发展。</span><br><span class="hljs-string">请根据你的角色、性格和与玩家的关系,自然地回复。</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br>    <span class="hljs-comment"># 创建Agent</span><br>    llm = HelloAgentsLLM()<br>    agent = SimpleAgent(<br>        name=name,<br>        llm=llm,<br>        system_prompt=system_prompt<br>    )<br><br>    <span class="hljs-keyword">return</span> agent<br></code></pre></td></tr></table></figure><p>这个设计让 NPC 的行为随着好感度动态变化。玩家可以明显感受到，随着互动的增加，NPC 对自己的态度在逐渐改变，这大大增强了游戏的沉浸感和趣味性。</p><h2 id="15-4-后端服务实现"><a href="#15-4-后端服务实现" class="headerlink" title="15.4 后端服务实现"></a>15.4 后端服务实现</h2><h3 id="15-4-1-FastAPI-应用结构"><a href="#15-4-1-FastAPI-应用结构" class="headerlink" title="15.4.1 FastAPI 应用结构"></a>15.4.1 FastAPI 应用结构</h3><p>赛博小镇的后端使用 FastAPI 框架构建，负责处理 Godot 前端的请求，调用 HelloAgents 的 NPC Agent，管理 NPC 状态和好感度，以及记录日志。一个清晰的应用结构能够让代码更易于维护和扩展。</p><p>我们的 FastAPI 应用采用模块化设计，将不同的功能分离到不同的文件中，如图 15.10 所示:</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-10.png" alt="" width="85%"/>  <p>图 15.10 后端应用结构</p></div><p>让我们从<code>main.py</code>开始，这是 FastAPI 应用的入口文件：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI, HTTPException<br><span class="hljs-keyword">from</span> fastapi.middleware.cors <span class="hljs-keyword">import</span> CORSMiddleware<br><span class="hljs-keyword">from</span> pydantic <span class="hljs-keyword">import</span> BaseModel, Field<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Optional</span><br><span class="hljs-keyword">import</span> uvicorn<br><br><span class="hljs-keyword">from</span> agents <span class="hljs-keyword">import</span> NPCAgentManager<br><span class="hljs-keyword">from</span> relationship_manager <span class="hljs-keyword">import</span> RelationshipManager<br><span class="hljs-keyword">from</span> state_manager <span class="hljs-keyword">import</span> StateManager<br><span class="hljs-keyword">from</span> logger <span class="hljs-keyword">import</span> DialogueLogger<br><span class="hljs-keyword">from</span> config <span class="hljs-keyword">import</span> settings<br><br><span class="hljs-comment"># 创建FastAPI应用</span><br>app = FastAPI(<br>    title=<span class="hljs-string">&quot;赛博小镇后端服务&quot;</span>,<br>    description=<span class="hljs-string">&quot;基于HelloAgents的AI NPC对话系统&quot;</span>,<br>    version=<span class="hljs-string">&quot;1.0.0&quot;</span><br>)<br><br><span class="hljs-comment"># 配置CORS,允许Godot前端访问</span><br>app.add_middleware(<br>    CORSMiddleware,<br>    allow_origins=[<span class="hljs-string">&quot;*&quot;</span>],  <span class="hljs-comment"># 生产环境应该限制具体域名</span><br>    allow_credentials=<span class="hljs-literal">True</span>,<br>    allow_methods=[<span class="hljs-string">&quot;*&quot;</span>],<br>    allow_headers=[<span class="hljs-string">&quot;*&quot;</span>],<br>)<br><br><span class="hljs-comment"># 初始化各个管理器</span><br>agent_manager = NPCAgentManager()<br>relationship_manager = RelationshipManager()<br>state_manager = StateManager()<br>dialogue_logger = DialogueLogger()<br><br><span class="hljs-meta">@app.on_event(<span class="hljs-params"><span class="hljs-string">&quot;startup&quot;</span></span>)</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">startup_event</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;应用启动时的初始化&quot;&quot;&quot;</span><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">60</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;🎮 赛博小镇后端服务启动中...&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">60</span>)<br><br>    <span class="hljs-comment"># 初始化NPC Agents</span><br>    agent_manager.initialize_npcs()<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;✅ NPC Agents已初始化&quot;</span>)<br><br>    <span class="hljs-comment"># 初始化状态管理器</span><br>    state_manager.initialize_npcs()<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;✅ 状态管理器已初始化&quot;</span>)<br><br><span class="hljs-meta">@app.get(<span class="hljs-params"><span class="hljs-string">&quot;/&quot;</span></span>)</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">root</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;健康检查&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">return</span> &#123;<br>        <span class="hljs-string">&quot;status&quot;</span>: <span class="hljs-string">&quot;running&quot;</span>,<br>        <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">&quot;赛博小镇后端服务正在运行&quot;</span>,<br>        <span class="hljs-string">&quot;version&quot;</span>: <span class="hljs-string">&quot;1.0.0&quot;</span>,<br>        <span class="hljs-string">&quot;npcs&quot;</span>: state_manager.get_npc_count()<br>    &#125;<br><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:<br>    uvicorn.run(<br>        app,<br>        host=settings.HOST,<br>        port=settings.PORT,<br>        log_level=<span class="hljs-string">&quot;info&quot;</span><br>    )<br></code></pre></td></tr></table></figure><p>这个主程序文件定义了 FastAPI 应用的基本结构，配置了 CORS 中间件以允许跨域请求，并在启动时初始化各个管理器。接下来我们将实现具体的 API 路由。</p><h3 id="15-4-2-API-路由设计"><a href="#15-4-2-API-路由设计" class="headerlink" title="15.4.2 API 路由设计"></a>15.4.2 API 路由设计</h3><p>赛博小镇的后端需要提供几个核心 API 端点，用于处理 Godot 前端的请求。我们将这些路由添加到<code>main.py</code>中。</p><p><strong>获取 NPC 状态</strong></p><p>这个 API 返回所有 NPC 的当前状态，包括位置、是否忙碌等信息：</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><code class="hljs python"><span class="hljs-keyword">from</span> models <span class="hljs-keyword">import</span> NPCStatusResponse<br><br><span class="hljs-meta">@app.get(<span class="hljs-params"><span class="hljs-string">&quot;/npcs/status&quot;</span>, response_model=NPCStatusResponse</span>)</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_npc_status</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;获取所有NPC的状态&quot;&quot;&quot;</span><br>    npcs = state_manager.get_all_npc_states()<br>    <span class="hljs-keyword">return</span> &#123;<span class="hljs-string">&quot;npcs&quot;</span>: npcs&#125;<br><br><span class="hljs-meta">@app.get(<span class="hljs-params"><span class="hljs-string">&quot;/npcs/&#123;npc_id&#125;/status&quot;</span></span>)</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_single_npc_status</span>(<span class="hljs-params">npc_id: <span class="hljs-built_in">str</span></span>):<br>    <span class="hljs-string">&quot;&quot;&quot;获取单个NPC的状态&quot;&quot;&quot;</span><br>    npc = state_manager.get_npc_state(npc_id)<br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> npc:<br>        <span class="hljs-keyword">raise</span> HTTPException(status_code=<span class="hljs-number">404</span>, detail=<span class="hljs-string">f&quot;NPC <span class="hljs-subst">&#123;npc_id&#125;</span> 不存在&quot;</span>)<br>    <span class="hljs-keyword">return</span> npc<br></code></pre></td></tr></table></figure><p><strong>对话接口</strong></p><p>这是最核心的 API，处理玩家与 NPC 的对话：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> models <span class="hljs-keyword">import</span> DialogueRequest, DialogueResponse<br><br><span class="hljs-meta">@app.post(<span class="hljs-params"><span class="hljs-string">&quot;/dialogue&quot;</span>, response_model=DialogueResponse</span>)</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">dialogue</span>(<span class="hljs-params">request: DialogueRequest</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;处理玩家与NPC的对话&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 1. 验证NPC是否存在</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> agent_manager.has_npc(request.npc_id):<br>        <span class="hljs-keyword">raise</span> HTTPException(status_code=<span class="hljs-number">404</span>, detail=<span class="hljs-string">f&quot;NPC <span class="hljs-subst">&#123;request.npc_id&#125;</span> 不存在&quot;</span>)<br><br>    <span class="hljs-comment"># 2. 检查NPC是否忙碌</span><br>    <span class="hljs-keyword">if</span> state_manager.is_npc_busy(request.npc_id):<br>        <span class="hljs-keyword">raise</span> HTTPException(status_code=<span class="hljs-number">409</span>, detail=<span class="hljs-string">f&quot;NPC <span class="hljs-subst">&#123;request.npc_id&#125;</span> 正在与其他玩家对话&quot;</span>)<br><br>    <span class="hljs-comment"># 3. 标记NPC为忙碌状态</span><br>    state_manager.set_npc_busy(request.npc_id, <span class="hljs-literal">True</span>)<br><br>    <span class="hljs-keyword">try</span>:<br>        <span class="hljs-comment"># 4. 获取当前好感度</span><br>        affinity_info = relationship_manager.get_affinity(<br>            request.npc_id,<br>            request.player_name<br>        )<br><br>        <span class="hljs-comment"># 5. 调用Agent生成回复</span><br>        agent = agent_manager.get_agent(request.npc_id, affinity_info[<span class="hljs-string">&quot;level&quot;</span>])<br>        reply = agent.run(request.player_message)<br><br>        <span class="hljs-comment"># 6. 更新好感度</span><br>        new_affinity = relationship_manager.update_affinity(<br>            request.npc_id,<br>            request.player_name,<br>            request.player_message,<br>            reply<br>        )<br><br>        <span class="hljs-comment"># 7. 记录日志</span><br>        dialogue_logger.log_dialogue(<br>            npc_id=request.npc_id,<br>            player_name=request.player_name,<br>            player_message=request.player_message,<br>            npc_reply=reply,<br>            affinity_info=new_affinity<br>        )<br><br>        <span class="hljs-comment"># 8. 返回回复</span><br>        <span class="hljs-keyword">return</span> DialogueResponse(<br>            npc_reply=reply,<br>            affinity_level=new_affinity[<span class="hljs-string">&quot;level&quot;</span>],<br>            affinity_score=new_affinity[<span class="hljs-string">&quot;score&quot;</span>]<br>        )<br><br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        dialogue_logger.log_error(<span class="hljs-string">f&quot;对话处理失败: <span class="hljs-subst">&#123;<span class="hljs-built_in">str</span>(e)&#125;</span>&quot;</span>)<br>        <span class="hljs-keyword">raise</span> HTTPException(status_code=<span class="hljs-number">500</span>, detail=<span class="hljs-string">f&quot;对话处理失败: <span class="hljs-subst">&#123;<span class="hljs-built_in">str</span>(e)&#125;</span>&quot;</span>)<br><br>    <span class="hljs-keyword">finally</span>:<br>        <span class="hljs-comment"># 9. 释放NPC状态</span><br>        state_manager.set_npc_busy(request.npc_id, <span class="hljs-literal">False</span>)<br></code></pre></td></tr></table></figure><p><strong>好感度查询</strong></p><p>这个 API 允许查询玩家与 NPC 的好感度：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> models <span class="hljs-keyword">import</span> AffinityInfo<br><br><span class="hljs-meta">@app.get(<span class="hljs-params"><span class="hljs-string">&quot;/affinity/&#123;npc_id&#125;/&#123;player_name&#125;&quot;</span>, response_model=AffinityInfo</span>)</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_affinity</span>(<span class="hljs-params">npc_id: <span class="hljs-built_in">str</span>, player_name: <span class="hljs-built_in">str</span></span>):<br>    <span class="hljs-string">&quot;&quot;&quot;获取玩家与NPC的好感度&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> agent_manager.has_npc(npc_id):<br>        <span class="hljs-keyword">raise</span> HTTPException(status_code=<span class="hljs-number">404</span>, detail=<span class="hljs-string">f&quot;NPC <span class="hljs-subst">&#123;npc_id&#125;</span> 不存在&quot;</span>)<br><br>    affinity = relationship_manager.get_affinity(npc_id, player_name)<br>    <span class="hljs-keyword">return</span> affinity<br></code></pre></td></tr></table></figure><p>API 路由的调用流程如图 15.11 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-11.png" alt="" width="85%"/>  <p>图 15.11 API 调用流程</p></div><h3 id="15-4-3-状态管理与日志系统"><a href="#15-4-3-状态管理与日志系统" class="headerlink" title="15.4.3 状态管理与日志系统"></a>15.4.3 状态管理与日志系统</h3><p><strong>状态管理器</strong></p><p>状态管理器负责跟踪每个 NPC 的当前状态，包括位置、是否忙碌、当前动作等。这对于防止并发问题很重要,比如避免一个 NPC 同时与多个玩家对话。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># state_manager.py</span><br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Dict</span>, <span class="hljs-type">List</span>, <span class="hljs-type">Optional</span><br><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">StateManager</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;NPC状态管理器&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):<br>        self.npc_states: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-built_in">dict</span>] = &#123;&#125;<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">initialize_npcs</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;初始化NPC状态&quot;&quot;&quot;</span><br>        npcs = [<br>            &#123;<br>                <span class="hljs-string">&quot;npc_id&quot;</span>: <span class="hljs-string">&quot;zhang_san&quot;</span>,<br>                <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;张三&quot;</span>,<br>                <span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;Python工程师&quot;</span>,<br>                <span class="hljs-string">&quot;position&quot;</span>: &#123;<span class="hljs-string">&quot;x&quot;</span>: <span class="hljs-number">300</span>, <span class="hljs-string">&quot;y&quot;</span>: <span class="hljs-number">200</span>&#125;<br>            &#125;,<br>            &#123;<br>                <span class="hljs-string">&quot;npc_id&quot;</span>: <span class="hljs-string">&quot;li_si&quot;</span>,<br>                <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;李四&quot;</span>,<br>                <span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;产品经理&quot;</span>,<br>                <span class="hljs-string">&quot;position&quot;</span>: &#123;<span class="hljs-string">&quot;x&quot;</span>: <span class="hljs-number">500</span>, <span class="hljs-string">&quot;y&quot;</span>: <span class="hljs-number">200</span>&#125;<br>            &#125;,<br>            &#123;<br>                <span class="hljs-string">&quot;npc_id&quot;</span>: <span class="hljs-string">&quot;wang_wu&quot;</span>,<br>                <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;王五&quot;</span>,<br>                <span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;UI设计师&quot;</span>,<br>                <span class="hljs-string">&quot;position&quot;</span>: &#123;<span class="hljs-string">&quot;x&quot;</span>: <span class="hljs-number">700</span>, <span class="hljs-string">&quot;y&quot;</span>: <span class="hljs-number">200</span>&#125;<br>            &#125;<br>        ]<br><br>        <span class="hljs-keyword">for</span> npc <span class="hljs-keyword">in</span> npcs:<br>            self.npc_states[npc[<span class="hljs-string">&quot;npc_id&quot;</span>]] = &#123;<br>                **npc,<br>                <span class="hljs-string">&quot;is_busy&quot;</span>: <span class="hljs-literal">False</span>,<br>                <span class="hljs-string">&quot;current_action&quot;</span>: <span class="hljs-string">&quot;idle&quot;</span>,<br>                <span class="hljs-string">&quot;last_interaction&quot;</span>: <span class="hljs-literal">None</span><br>            &#125;<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_npc_state</span>(<span class="hljs-params">self, npc_id: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-type">Optional</span>[<span class="hljs-built_in">dict</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;获取NPC状态&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> self.npc_states.get(npc_id)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_all_npc_states</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;获取所有NPC状态&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-built_in">list</span>(self.npc_states.values())<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">is_npc_busy</span>(<span class="hljs-params">self, npc_id: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">bool</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;检查NPC是否忙碌&quot;&quot;&quot;</span><br>        npc = self.npc_states.get(npc_id)<br>        <span class="hljs-keyword">return</span> npc[<span class="hljs-string">&quot;is_busy&quot;</span>] <span class="hljs-keyword">if</span> npc <span class="hljs-keyword">else</span> <span class="hljs-literal">False</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">set_npc_busy</span>(<span class="hljs-params">self, npc_id: <span class="hljs-built_in">str</span>, busy: <span class="hljs-built_in">bool</span></span>):<br>        <span class="hljs-string">&quot;&quot;&quot;设置NPC忙碌状态&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> npc_id <span class="hljs-keyword">in</span> self.npc_states:<br>            self.npc_states[npc_id][<span class="hljs-string">&quot;is_busy&quot;</span>] = busy<br>            <span class="hljs-keyword">if</span> busy:<br>                self.npc_states[npc_id][<span class="hljs-string">&quot;last_interaction&quot;</span>] = datetime.now().isoformat()<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_npc_count</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-built_in">int</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;获取NPC数量&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-built_in">len</span>(self.npc_states)<br></code></pre></td></tr></table></figure><p><strong>日志系统</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># logger.py</span><br><span class="hljs-keyword">import</span> logging<br><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><span class="hljs-keyword">from</span> pathlib <span class="hljs-keyword">import</span> Path<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">DialogueLogger</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;对话日志记录器&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, log_dir: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;logs&quot;</span></span>):<br>        self.log_dir = Path(log_dir)<br>        self.log_dir.mkdir(exist_ok=<span class="hljs-literal">True</span>)<br><br>        <span class="hljs-comment"># 创建日志文件名(按日期)</span><br>        today = datetime.now().strftime(<span class="hljs-string">&quot;%Y-%m-%d&quot;</span>)<br>        log_file = self.log_dir / <span class="hljs-string">f&quot;dialogue_<span class="hljs-subst">&#123;today&#125;</span>.log&quot;</span><br><br>        <span class="hljs-comment"># 配置日志</span><br>        self.logger = logging.getLogger(<span class="hljs-string">&quot;DialogueLogger&quot;</span>)<br>        self.logger.setLevel(logging.INFO)<br><br>        <span class="hljs-comment"># 控制台处理器</span><br>        console_handler = logging.StreamHandler()<br>        console_handler.setLevel(logging.INFO)<br>        console_formatter = logging.Formatter(<br>            <span class="hljs-string">&#x27;%(asctime)s - %(levelname)s - %(message)s&#x27;</span>,<br>            datefmt=<span class="hljs-string">&#x27;%H:%M:%S&#x27;</span><br>        )<br>        console_handler.setFormatter(console_formatter)<br><br>        <span class="hljs-comment"># 文件处理器</span><br>        file_handler = logging.FileHandler(log_file, encoding=<span class="hljs-string">&#x27;utf-8&#x27;</span>)<br>        file_handler.setLevel(logging.INFO)<br>        file_formatter = logging.Formatter(<br>            <span class="hljs-string">&#x27;%(asctime)s - %(levelname)s - %(message)s&#x27;</span>,<br>            datefmt=<span class="hljs-string">&#x27;%Y-%m-%d %H:%M:%S&#x27;</span><br>        )<br>        file_handler.setFormatter(file_formatter)<br><br>        <span class="hljs-comment"># 添加处理器</span><br>        self.logger.addHandler(console_handler)<br>        self.logger.addHandler(file_handler)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">log_dialogue</span>(<span class="hljs-params">self, npc_id: <span class="hljs-built_in">str</span>, player_name: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">                    player_message: <span class="hljs-built_in">str</span>, npc_reply: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">                    affinity_info: <span class="hljs-built_in">dict</span></span>):<br>        <span class="hljs-string">&quot;&quot;&quot;记录对话&quot;&quot;&quot;</span><br>        log_message = <span class="hljs-string">f&quot;&quot;&quot;</span><br><span class="hljs-string"><span class="hljs-subst">&#123;<span class="hljs-string">&#x27;=&#x27;</span>*<span class="hljs-number">60</span>&#125;</span></span><br><span class="hljs-string">NPC: <span class="hljs-subst">&#123;npc_id&#125;</span></span><br><span class="hljs-string">玩家: <span class="hljs-subst">&#123;player_name&#125;</span></span><br><span class="hljs-string">玩家消息: <span class="hljs-subst">&#123;player_message&#125;</span></span><br><span class="hljs-string">NPC回复: <span class="hljs-subst">&#123;npc_reply&#125;</span></span><br><span class="hljs-string">好感度: <span class="hljs-subst">&#123;affinity_info[<span class="hljs-string">&#x27;level&#x27;</span>]&#125;</span> (<span class="hljs-subst">&#123;affinity_info[<span class="hljs-string">&#x27;score&#x27;</span>]&#125;</span>/100)</span><br><span class="hljs-string">互动次数: <span class="hljs-subst">&#123;affinity_info[<span class="hljs-string">&#x27;interaction_count&#x27;</span>]&#125;</span></span><br><span class="hljs-string"><span class="hljs-subst">&#123;<span class="hljs-string">&#x27;=&#x27;</span>*<span class="hljs-number">60</span>&#125;</span></span><br><span class="hljs-string">&quot;&quot;&quot;</span><br>        self.logger.info(log_message)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">log_error</span>(<span class="hljs-params">self, error_message: <span class="hljs-built_in">str</span></span>):<br>        <span class="hljs-string">&quot;&quot;&quot;记录错误&quot;&quot;&quot;</span><br>        self.logger.error(error_message)<br></code></pre></td></tr></table></figure><p>这个日志系统会在控制台实时显示对话内容，同时保存到文件中。每天的日志会保存在单独的文件中,方便后续分析。</p><h3 id="15-4-4-理解-Godot-的场景系统"><a href="#15-4-4-理解-Godot-的场景系统" class="headerlink" title="15.4.4 理解 Godot 的场景系统"></a>15.4.4 理解 Godot 的场景系统</h3><p>在开始构建游戏场景之前，我们需要先理解 Godot 的核心概念——场景(Scene)和节点(Node)。这是 Godot 与其他游戏引擎最大的不同之处，也是它最强大的特性之一。</p><p><strong>什么是节点?</strong></p><p>节点是 Godot 中最基本的构建块。你可以把节点想象成乐高积木，每个节点都有特定的功能。比如，Sprite2D 节点用于显示图片，AudioStreamPlayer 节点用于播放音频，CharacterBody2D 节点用于处理角色的物理移动。Godot 提供了上百种不同类型的节点，每种节点都专注于做好一件事。</p><p>节点之间可以形成父子关系，构成一个树状结构。父节点可以影响子节点，比如移动父节点会同时移动所有子节点，隐藏父节点会同时隐藏所有子节点。这种层级关系让我们可以轻松地组织和管理复杂的游戏对象。</p><p><strong>什么是场景?</strong></p><p>场景是一组节点的集合，保存在一个.tscn 文件中。你可以把场景理解为一个”预制件”。比如，我们可以创建一个”玩家”场景，包含角色的精灵、碰撞体、音效等所有相关节点。然后在游戏中多次使用这个场景，每次使用都会创建一个独立的实例。</p><p>场景的强大之处在于它的可复用性和模块化。我们可以在一个场景中实例化另一个场景，形成嵌套结构。比如，主场景可以包含玩家场景、多个 NPC 场景和 UI 场景。修改 NPC 场景会自动影响所有 NPC 实例，这大大简化了开发和维护。</p><p><strong>一个简单的例子</strong></p><p>让我们用一个简单的例子来理解场景和节点。假设我们要创建一个”玩家”场景：</p><figure class="highlight gcode"><table><tr><td class="gutter"><pre><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><code class="hljs gcode">Player <span class="hljs-comment">(CharacterBody2D)</span>  ← 根节点,负责物理移动<br>├─ A<span class="hljs-symbol">nimatedSprite2</span>D       ← 子节点,显示角色动画<br>├─ Collisio<span class="hljs-symbol">nShape2</span>D       ← 子节点,定义碰撞形状<br>└─ Camera<span class="hljs-number">2</span>D               ← 子节点,摄像机跟随玩家<br></code></pre></td></tr></table></figure><p>这个场景包含 4 个节点，形成树状结构。CharacterBody2D 是根节点，其他三个是它的子节点。我们可以给每个节点添加脚本来控制它的行为，也可以给根节点添加脚本来协调所有子节点。</p><p>当我们在主场景中实例化这个 Player 场景时，Godot 会创建这整个节点树的一个副本。我们可以创建多个玩家实例，每个实例都是独立的，有自己的位置、状态和行为。</p><p><strong>场景实例化的优势</strong></p><p>在赛博小镇中，我们有三个 NPC：张三、李四和王五。如果不使用场景系统，我们需要为每个 NPC 分别创建节点、设置属性、编写脚本，这会导致大量重复工作。而使用场景系统，我们只需要创建一个通用的 NPC 场景，然后实例化三次，通过脚本参数设置不同的名称和角色信息即可。</p><p>这种设计的好处是：如果我们想给所有 NPC 添加一个新功能(比如头顶显示对话气泡)，只需要修改 NPC 场景，所有实例都会自动获得这个功能。</p><h2 id="15-5-Godot-游戏场景构建"><a href="#15-5-Godot-游戏场景构建" class="headerlink" title="15.5 Godot 游戏场景构建"></a>15.5 Godot 游戏场景构建</h2><p><strong>为什么选择 Godot 作为游戏引擎?</strong></p><p>在众多游戏引擎中，我们选择 Godot 4.5 作为前端引擎，主要基于以下几个考虑：</p><p>（1）Godot 在 2D 游戏开发上有着天然的优势</strong>。赛博小镇是一个俯视角的 2D 像素风格游戏，Godot 的 2D 引擎非常成熟，提供了 TileMap、AnimatedSprite2D、CharacterBody2D 等专门为 2D 游戏设计的节点类型，开发效率远高于 Unity 等引擎。Godot 的场景系统(Scene System)让我们可以将玩家、NPC、UI 等元素封装成独立的场景，然后在主场景中实例化，这种组件化的设计非常适合我们的需求。</p><p>（2）<strong>Godot 是完全开源且免费的</strong>。Godot 使用 MIT 许可证，没有任何版权费用或收入分成，这对于教学项目和开源项目非常友好。你可以自由地修改引擎源码，也可以将游戏商业化而不用担心授权问题。相比之下，Unity 虽然功能强大，但在 2024 年引入了运行时费用政策，引发了开发者社区的广泛争议。</p><p>（3）<strong>Godot 的学习成本极低</strong>。Godot 使用 GDScript 作为主要脚本语言，这是一种类似 Python 的动态类型语言，语法简洁易懂，学习曲线非常平缓。对于已经熟悉 Python 的读者来说，学习 GDScript 几乎没有门槛——变量声明、函数定义、控制流程等语法都与 Python 高度相似，你甚至可以在几小时内就上手编写游戏脚本。Godot 的节点树结构也非常直观，你可以在编辑器中直观地看到场景的层级关系，这对于初学者非常友好。</p><p>（4）<strong>Godot 与 Python 后端的集成非常简单</strong>。Godot 内置了 HTTPRequest 节点，可以轻松地与 FastAPI 后端进行 HTTP 通信。我们只需要创建一个 API 客户端脚本，封装所有的 API 调用，就可以在游戏中调用后端的 AI 能力。这种前后端分离的架构让我们可以独立开发和测试游戏逻辑和 AI 逻辑，大大提高了开发效率。</p><p>当然，Godot 也有一些局限性。比如，Godot 的 3D 能力相比 Unreal Engine 和 Unity 还有差距，如果你要开发大型 3D 游戏，可能需要考虑其他引擎。但对于 2D 游戏、独立游戏和教学项目，Godot 是一个非常优秀的选择。</p><h3 id="15-5-1-场景设计与资源组织"><a href="#15-5-1-场景设计与资源组织" class="headerlink" title="15.5.1 场景设计与资源组织"></a>15.5.1 场景设计与资源组织</h3><p>理解了 Godot 的场景系统后，我们来看看赛博小镇的场景设计。整个游戏由四个核心场景组成：Main(主场景)、Player(玩家)、NPC(非玩家角色)和 DialogueUI(对话界面)。每个场景都是一个独立的模块，可以单独编辑和测试，然后组合在一起形成完整的游戏。</p><p>赛博小镇的场景组织采用了模块化设计。我们首先创建三个基础场景：Player(玩家)、NPC(非玩家角色)和 DialogueUI(对话界面)。然后在 Main(主场景)中将这些场景实例化并组合起来。特别值得注意的是，三个 NPC(张三、李四、王五)都是同一个 NPC 场景的实例，只是通过脚本参数设置了不同的角色信息。</p><p>让我们先看看四个核心场景的结构，如图 15.12 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-12.png" alt="" width="85%"/>  <p>图 15.12 赛博小镇的四个核心场景</p></div><p>这个图展示了四个独立的场景及其内部结构。<strong>场景 1(Main)</strong>是主场景，它包含了背景图片(Sprite2D)、玩家实例、NPCs 组织节点(下面有三个 NPC 实例)、对话界面实例、墙体组织节点和背景音乐。注意，这里的 Player、NPC_Zhang、NPC_Li、NPC_Wang 和 DialogueUI 都是场景实例，不是普通节点。<strong>场景 2(Player)</strong>定义了玩家角色的结构，包含动画、碰撞、摄像机和两个音效节点。<strong>场景 3(NPC)</strong>是一个通用模板，张三、李四、王五都是这个场景的实例，包含碰撞、动画、交互区域和两个标签。<strong>场景 4(DialogueUI)</strong>是一个 CanvasLayer 节点，包含 Panel 和各种 UI 元素。</p><p>场景实例化的过程可以这样理解：我们在 Godot 编辑器中创建了 NPC.tscn 这个场景文件，定义了 NPC 的节点结构。然后在 Main 场景中，我们三次”实例化”这个 NPC 场景，创建了三个独立的副本，分别命名为 NPC_Zhang、NPC_Li 和 NPC_Wang。每个副本都有自己的位置和状态，但它们共享相同的节点结构。如果我们修改 NPC.tscn，比如给 NPC 添加一个新的音效节点，那么所有三个实例都会自动获得这个音效。</p><p>在 Godot 中创建这些场景的步骤如下：</p><ol><li><p><strong>创建 Player 场景</strong>：新建场景，选择 CharacterBody2D 作为根节点，添加 AnimatedSprite2D、CollisionShape2D、Camera2D、InteractSound 和 RunningSound 子节点，保存为 Player.tscn。</p></li><li><p><strong>创建 NPC 场景</strong>：新建场景，选择 CharacterBody2D 作为根节点，添加 CollisionShape2D、AnimatedSprite2D、InteractionArea(Area2D，下面有 CollisionShape2D)、NameLabel 和 DialogueLabel 子节点，保存为 NPC.tscn。</p></li><li><p><strong>创建 DialogueUI 场景</strong>：新建场景，选择 CanvasLayer 作为根节点，添加 Panel 子节点，在 Panel 下添加 NPCName、NPCTitle、DialogueText(RichTextLabel)、PlayerInput(LineEdit)、SendButton 和 CloseButton，保存为 DialogueUI.tscn。</p></li><li><p><strong>创建 Main 场景</strong>：新建场景，选择 Node2D 作为根节点，添加 Background(Sprite2D)作为背景图，在 Background 下添加小鲸鱼装饰，然后实例化 Player 场景，创建 NPCs 节点并在其下三次实例化 NPC 场景，实例化 DialogueUI 场景，创建 Walls 节点用于组织墙体碰撞，最后添加 AudioStreamPlayer 播放背景音乐。</p></li></ol><p>这种场景组织方式的优势在于：每个场景都是独立的，可以单独测试;NPC 使用同一个场景的实例，修改一次就能影响所有 NPC;场景之间通过信号通信，耦合度低，易于维护和扩展。</p><h3 id="15-5-2-玩家控制实现"><a href="#15-5-2-玩家控制实现" class="headerlink" title="15.5.2 玩家控制实现"></a>15.5.2 玩家控制实现</h3><p>玩家角色是游戏中最重要的元素之一。我们需要实现 WASD 移动控制、动画切换、碰撞检测、与 NPC 的交互，以及音效系统。</p><p>玩家场景的结构包括：一个 CharacterBody2D 作为根节点，负责物理移动和碰撞;一个 AnimatedSprite2D 显示角色动画;一个 CollisionShape2D 定义碰撞形状;一个 Camera2D 跟随玩家;两个 AudioStreamPlayer 分别播放交互音效和走路音效。</p><p>玩家控制脚本<code>player.gd</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><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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br></pre></td><td class="code"><pre><code class="hljs python">extends CharacterBody2D<br><br><span class="hljs-comment"># 移动速度</span><br><span class="hljs-meta">@export var speed: float = <span class="hljs-number">200.0</span></span><br><br><span class="hljs-comment"># 当前可交互的NPC</span><br>var nearby_npc: Node = null<br><br><span class="hljs-comment"># 交互状态(交互时禁用移动)</span><br>var is_interacting: <span class="hljs-built_in">bool</span> = false<br><br><span class="hljs-comment"># 节点引用</span><br><span class="hljs-meta">@onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D</span><br><span class="hljs-meta">@onready var camera: Camera2D = $Camera2D</span><br><br><span class="hljs-comment"># 音效引用</span><br><span class="hljs-meta">@onready var interact_sound: AudioStreamPlayer = null</span><br><span class="hljs-meta">@onready var running_sound: AudioStreamPlayer = null</span><br><br><span class="hljs-comment"># 走路音效状态</span><br>var is_playing_running_sound: <span class="hljs-built_in">bool</span> = false<br><br>func _ready():<br>    <span class="hljs-comment"># 添加到player组(重要!NPC需要通过这个组来识别玩家)</span><br>    add_to_group(<span class="hljs-string">&quot;player&quot;</span>)<br><br>    <span class="hljs-comment"># 获取音效节点(可选,如果不存在也不会报错)</span><br>    interact_sound = get_node_or_null(<span class="hljs-string">&quot;InteractSound&quot;</span>)<br>    running_sound = get_node_or_null(<span class="hljs-string">&quot;RunningSound&quot;</span>)<br><br>    <span class="hljs-comment"># 启用相机</span><br>    camera.enabled = true<br><br>    <span class="hljs-comment"># 播放默认动画</span><br>    <span class="hljs-keyword">if</span> animated_sprite.sprite_frames != null <span class="hljs-keyword">and</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;idle&quot;</span>):<br>        animated_sprite.play(<span class="hljs-string">&quot;idle&quot;</span>)<br><br>func _physics_process(_delta: <span class="hljs-built_in">float</span>):<br>    <span class="hljs-comment"># 如果正在交互,禁用移动</span><br>    <span class="hljs-keyword">if</span> is_interacting:<br>        velocity = Vector2.ZERO<br>        move_and_slide()<br>        <span class="hljs-comment"># 播放idle动画</span><br>        <span class="hljs-keyword">if</span> animated_sprite.sprite_frames != null <span class="hljs-keyword">and</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;idle&quot;</span>):<br>            animated_sprite.play(<span class="hljs-string">&quot;idle&quot;</span>)<br>        <span class="hljs-comment"># 停止走路音效</span><br>        stop_running_sound()<br>        <span class="hljs-keyword">return</span><br><br>    <span class="hljs-comment"># 获取输入方向</span><br>    var input_direction = Input.get_vector(<span class="hljs-string">&quot;ui_left&quot;</span>, <span class="hljs-string">&quot;ui_right&quot;</span>, <span class="hljs-string">&quot;ui_up&quot;</span>, <span class="hljs-string">&quot;ui_down&quot;</span>)<br><br>    <span class="hljs-comment"># 设置速度</span><br>    velocity = input_direction * speed<br><br>    <span class="hljs-comment"># 移动</span><br>    move_and_slide()<br><br>    <span class="hljs-comment"># 更新动画和朝向</span><br>    update_animation(input_direction)<br><br>    <span class="hljs-comment"># 更新走路音效</span><br>    update_running_sound(input_direction)<br><br>func update_animation(direction: Vector2):<br>    <span class="hljs-string">&quot;&quot;&quot;更新角色动画(支持4方向)&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> animated_sprite.sprite_frames == null:<br>        <span class="hljs-keyword">return</span><br><br>    <span class="hljs-comment"># 根据移动方向播放动画</span><br>    <span class="hljs-keyword">if</span> direction.length() &gt; <span class="hljs-number">0</span>:<br>        <span class="hljs-comment"># 移动中 - 判断主要方向</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">abs</span>(direction.x) &gt; <span class="hljs-built_in">abs</span>(direction.y):<br>            <span class="hljs-comment"># 左右移动</span><br>            <span class="hljs-keyword">if</span> direction.x &gt; <span class="hljs-number">0</span>:<br>                <span class="hljs-comment"># 向右</span><br>                <span class="hljs-keyword">if</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk_right&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk_right&quot;</span>)<br>                    animated_sprite.flip_h = false<br>                <span class="hljs-keyword">elif</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk&quot;</span>)<br>                    animated_sprite.flip_h = false<br>            <span class="hljs-keyword">else</span>:<br>                <span class="hljs-comment"># 向左</span><br>                <span class="hljs-keyword">if</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk_left&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk_left&quot;</span>)<br>                    animated_sprite.flip_h = false<br>                <span class="hljs-keyword">elif</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk&quot;</span>)<br>                    animated_sprite.flip_h = true<br>        <span class="hljs-keyword">else</span>:<br>            <span class="hljs-comment"># 上下移动</span><br>            <span class="hljs-keyword">if</span> direction.y &gt; <span class="hljs-number">0</span>:<br>                <span class="hljs-comment"># 向下</span><br>                <span class="hljs-keyword">if</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk_down&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk_down&quot;</span>)<br>                <span class="hljs-keyword">elif</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk&quot;</span>)<br>            <span class="hljs-keyword">else</span>:<br>                <span class="hljs-comment"># 向上</span><br>                <span class="hljs-keyword">if</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk_up&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk_up&quot;</span>)<br>                <span class="hljs-keyword">elif</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk&quot;</span>)<br>    <span class="hljs-keyword">else</span>:<br>        <span class="hljs-comment"># 静止</span><br>        <span class="hljs-keyword">if</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;idle&quot;</span>):<br>            animated_sprite.play(<span class="hljs-string">&quot;idle&quot;</span>)<br><br>func _<span class="hljs-built_in">input</span>(event: InputEvent):<br>    <span class="hljs-comment"># 按E键与NPC交互</span><br>    <span class="hljs-keyword">if</span> event <span class="hljs-keyword">is</span> InputEventKey:<br>        <span class="hljs-keyword">if</span> event.pressed <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> event.echo:<br>            <span class="hljs-keyword">if</span> event.keycode == KEY_E <span class="hljs-keyword">or</span> event.keycode == KEY_ENTER:<br>                <span class="hljs-keyword">if</span> nearby_npc != null:<br>                    interact_with_npc()<br><br>func interact_with_npc():<br>    <span class="hljs-string">&quot;&quot;&quot;与附近的NPC交互&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> nearby_npc != null:<br>        <span class="hljs-comment"># 播放交互音效</span><br>        <span class="hljs-keyword">if</span> interact_sound:<br>            interact_sound.play()<br><br>        <span class="hljs-comment"># 发送信号给对话系统</span><br>        get_tree().call_group(<span class="hljs-string">&quot;dialogue_system&quot;</span>, <span class="hljs-string">&quot;start_dialogue&quot;</span>, nearby_npc.npc_name)<br><br>func set_nearby_npc(npc: Node):<br>    <span class="hljs-string">&quot;&quot;&quot;设置附近的NPC&quot;&quot;&quot;</span><br>    nearby_npc = npc<br><br>func set_interacting(interacting: <span class="hljs-built_in">bool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;设置交互状态&quot;&quot;&quot;</span><br>    is_interacting = interacting<br>    <span class="hljs-keyword">if</span> interacting:<br>        <span class="hljs-comment"># 停止走路音效</span><br>        stop_running_sound()<br><br>func update_running_sound(direction: Vector2):<br>    <span class="hljs-string">&quot;&quot;&quot;更新走路音效&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> running_sound == null:<br>        <span class="hljs-keyword">return</span><br><br>    <span class="hljs-comment"># 如果正在移动</span><br>    <span class="hljs-keyword">if</span> direction.length() &gt; <span class="hljs-number">0</span>:<br>        <span class="hljs-comment"># 如果音效还没播放,开始播放</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> is_playing_running_sound:<br>            running_sound.play()<br>            is_playing_running_sound = true<br>    <span class="hljs-keyword">else</span>:<br>        <span class="hljs-comment"># 如果停止移动,停止音效</span><br>        stop_running_sound()<br><br>func stop_running_sound():<br>    <span class="hljs-string">&quot;&quot;&quot;停止走路音效&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> running_sound <span class="hljs-keyword">and</span> is_playing_running_sound:<br>        running_sound.stop()<br>        is_playing_running_sound = false<br></code></pre></td></tr></table></figure><p>这个脚本实现了完整的玩家控制。玩家使用 WASD 键(或方向键)移动，角色会根据移动方向播放相应的 4 方向动画(walk_up&#x2F;down&#x2F;left&#x2F;right)。当玩家靠近 NPC 时，NPC 会调用<code>set_nearby_npc()</code>设置自己为可交互对象，玩家按 E 键即可触发交互。交互时会播放音效，并通过<code>call_group()</code>通知对话系统开始对话。对话期间，<code>set_interacting(true)</code>会禁用玩家移动，对话结束后恢复移动。走路音效会在玩家移动时自动播放，停止时自动停止。</p><h3 id="15-5-3-NPC-行为与交互"><a href="#15-5-3-NPC-行为与交互" class="headerlink" title="15.5.3 NPC 行为与交互"></a>15.5.3 NPC 行为与交互</h3><p>NPC 需要实现三个核心功能：在场景中随机巡逻游走、响应玩家的交互、显示对话气泡。我们使用 Area2D 来检测玩家是否靠近 NPC，当玩家进入交互范围时通知玩家，玩家按 E 键即可开始对话。</p><p>NPC 场景的结构包括：CharacterBody2D 作为根节点;CollisionShape2D 定义 NPC 的碰撞形状;AnimatedSprite2D 显示 NPC 动画;InteractionArea(Area2D)检测玩家进入交互范围，下面有 CollisionShape2D 定义交互范围;NameLabel 显示 NPC 名字;DialogueLabel 显示对话气泡。</p><p>NPC 脚本<code>npc.gd</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><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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br></pre></td><td class="code"><pre><code class="hljs python">extends CharacterBody2D<br><br><span class="hljs-comment"># NPC信息</span><br><span class="hljs-meta">@export var npc_name: String = <span class="hljs-string">&quot;张三&quot;</span></span><br><span class="hljs-meta">@export var npc_title: String = <span class="hljs-string">&quot;Python工程师&quot;</span></span><br><br><span class="hljs-comment"># NPC外观配置</span><br><span class="hljs-meta">@export var sprite_frames: SpriteFrames = null  </span><span class="hljs-comment"># 自定义精灵帧资源</span><br><br><span class="hljs-comment"># NPC移动配置</span><br><span class="hljs-meta">@export var move_speed: float = <span class="hljs-number">50.0</span>  </span><span class="hljs-comment"># 移动速度</span><br><span class="hljs-meta">@export var wander_enabled: bool = true  </span><span class="hljs-comment"># 是否启用巡逻</span><br><span class="hljs-meta">@export var wander_range: float = <span class="hljs-number">200.0</span>  </span><span class="hljs-comment"># 巡逻范围</span><br><span class="hljs-meta">@export var wander_interval_min: float = <span class="hljs-number">3.0</span>  </span><span class="hljs-comment"># 最小巡逻间隔(秒)</span><br><span class="hljs-meta">@export var wander_interval_max: float = <span class="hljs-number">8.0</span>  </span><span class="hljs-comment"># 最大巡逻间隔(秒)</span><br><br><span class="hljs-comment"># 当前对话内容(从后端获取)</span><br>var current_dialogue: String = <span class="hljs-string">&quot;&quot;</span><br><br><span class="hljs-comment"># 节点引用</span><br><span class="hljs-meta">@onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D</span><br><span class="hljs-meta">@onready var interaction_area: Area2D = $InteractionArea</span><br><span class="hljs-meta">@onready var name_label: Label = $NameLabel</span><br><span class="hljs-meta">@onready var dialogue_label: Label = $DialogueLabel</span><br><br><span class="hljs-comment"># 玩家引用</span><br>var player: Node = null<br><br><span class="hljs-comment"># 巡逻相关变量</span><br>var wander_target: Vector2 = Vector2.ZERO  <span class="hljs-comment"># 巡逻目标位置</span><br>var wander_timer: <span class="hljs-built_in">float</span> = <span class="hljs-number">0.0</span>  <span class="hljs-comment"># 巡逻计时器</span><br>var is_wandering: <span class="hljs-built_in">bool</span> = false  <span class="hljs-comment"># 是否正在巡逻</span><br>var is_interacting: <span class="hljs-built_in">bool</span> = false  <span class="hljs-comment"># 是否正在与玩家交互</span><br>var spawn_position: Vector2 = Vector2.ZERO  <span class="hljs-comment"># 出生位置</span><br><br>func _ready():<br>    <span class="hljs-comment"># 添加到npcs组</span><br>    add_to_group(<span class="hljs-string">&quot;npcs&quot;</span>)<br><br>    <span class="hljs-comment"># 设置NPC名字</span><br>    name_label.text = npc_name<br><br>    <span class="hljs-comment"># 连接交互区域信号</span><br>    interaction_area.body_entered.connect(_on_body_entered)<br>    interaction_area.body_exited.connect(_on_body_exited)<br><br>    <span class="hljs-comment"># 初始化对话标签</span><br>    dialogue_label.text = <span class="hljs-string">&quot;&quot;</span><br>    dialogue_label.visible = false<br><br>    <span class="hljs-comment"># 设置自定义精灵帧(如果有)</span><br>    <span class="hljs-keyword">if</span> sprite_frames != null:<br>        animated_sprite.sprite_frames = sprite_frames<br><br>    <span class="hljs-comment"># 播放默认动画</span><br>    <span class="hljs-keyword">if</span> animated_sprite.sprite_frames != null <span class="hljs-keyword">and</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;idle&quot;</span>):<br>        animated_sprite.play(<span class="hljs-string">&quot;idle&quot;</span>)<br><br>    <span class="hljs-comment"># 记录出生位置</span><br>    spawn_position = global_position<br><br>    <span class="hljs-comment"># 初始化巡逻计时器</span><br>    <span class="hljs-keyword">if</span> wander_enabled:<br>        wander_timer = randf_range(wander_interval_min, wander_interval_max)<br>        choose_new_wander_target()<br><br>func _on_body_entered(body: Node2D):<br>    <span class="hljs-string">&quot;&quot;&quot;玩家进入交互范围&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> body.is_in_group(<span class="hljs-string">&quot;player&quot;</span>):<br>        player = body<br><br>        <span class="hljs-keyword">if</span> player.has_method(<span class="hljs-string">&quot;set_nearby_npc&quot;</span>):<br>            player.set_nearby_npc(self)<br><br>func _on_body_exited(body: Node2D):<br>    <span class="hljs-string">&quot;&quot;&quot;玩家离开交互范围&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> body.is_in_group(<span class="hljs-string">&quot;player&quot;</span>):<br>        <span class="hljs-keyword">if</span> player != null <span class="hljs-keyword">and</span> player.has_method(<span class="hljs-string">&quot;set_nearby_npc&quot;</span>):<br>            player.set_nearby_npc(null)<br>        player = null<br><br>func update_dialogue(dialogue: String):<br>    <span class="hljs-string">&quot;&quot;&quot;更新NPC对话内容&quot;&quot;&quot;</span><br>    current_dialogue = dialogue<br>    dialogue_label.text = dialogue<br>    dialogue_label.visible = true<br><br>    <span class="hljs-comment"># 10秒后隐藏对话</span><br>    <span class="hljs-keyword">await</span> get_tree().create_timer(<span class="hljs-number">10.0</span>).timeout<br>    dialogue_label.visible = false<br><br>func _physics_process(delta: <span class="hljs-built_in">float</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;物理更新 - 处理移动&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 如果正在与玩家交互,停止移动</span><br>    <span class="hljs-keyword">if</span> is_interacting:<br>        velocity = Vector2.ZERO<br>        move_and_slide()<br>        <span class="hljs-comment"># 播放idle动画</span><br>        <span class="hljs-keyword">if</span> animated_sprite.sprite_frames != null <span class="hljs-keyword">and</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;idle&quot;</span>):<br>            animated_sprite.play(<span class="hljs-string">&quot;idle&quot;</span>)<br>        <span class="hljs-keyword">return</span><br><br>    <span class="hljs-comment"># 如果未启用巡逻,不移动</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> wander_enabled:<br>        <span class="hljs-keyword">return</span><br><br>    <span class="hljs-comment"># 更新巡逻计时器</span><br>    wander_timer -= delta<br><br>    <span class="hljs-comment"># 如果计时器结束,选择新目标并开始移动</span><br>    <span class="hljs-keyword">if</span> wander_timer &lt;= <span class="hljs-number">0</span>:<br>        choose_new_wander_target()<br>        wander_timer = randf_range(wander_interval_min, wander_interval_max)<br><br>    <span class="hljs-comment"># 如果正在巡逻,移动到目标</span><br>    <span class="hljs-keyword">if</span> is_wandering:<br>        <span class="hljs-comment"># 检查是否到达目标</span><br>        <span class="hljs-keyword">if</span> global_position.distance_to(wander_target) &lt; <span class="hljs-number">10</span>:<br>            <span class="hljs-comment"># 到达目标,停止移动</span><br>            is_wandering = false<br>            velocity = Vector2.ZERO<br>            move_and_slide()<br>            <span class="hljs-comment"># 播放idle动画</span><br>            <span class="hljs-keyword">if</span> animated_sprite.sprite_frames != null <span class="hljs-keyword">and</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;idle&quot;</span>):<br>                animated_sprite.play(<span class="hljs-string">&quot;idle&quot;</span>)<br>        <span class="hljs-keyword">else</span>:<br>            <span class="hljs-comment"># 继续移动到目标</span><br>            var direction = (wander_target - global_position).normalized()<br>            velocity = direction * move_speed<br>            move_and_slide()<br>            <span class="hljs-comment"># 更新动画</span><br>            update_animation(direction)<br>    <span class="hljs-keyword">else</span>:<br>        <span class="hljs-comment"># 停止移动</span><br>        velocity = Vector2.ZERO<br>        move_and_slide()<br>        <span class="hljs-comment"># 播放idle动画</span><br>        <span class="hljs-keyword">if</span> animated_sprite.sprite_frames != null <span class="hljs-keyword">and</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;idle&quot;</span>):<br>            animated_sprite.play(<span class="hljs-string">&quot;idle&quot;</span>)<br><br>func choose_new_wander_target():<br>    <span class="hljs-string">&quot;&quot;&quot;选择新的巡逻目标&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 在出生位置附近随机选择一个点</span><br>    var offset = Vector2(<br>        randf_range(-wander_range, wander_range),<br>        randf_range(-wander_range, wander_range)<br>    )<br>    wander_target = spawn_position + offset<br>    is_wandering = true<br><br>func update_animation(direction: Vector2):<br>    <span class="hljs-string">&quot;&quot;&quot;更新动画&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> animated_sprite.sprite_frames == null:<br>        <span class="hljs-keyword">return</span><br><br>    <span class="hljs-keyword">if</span> direction.length() &gt; <span class="hljs-number">0</span>:<br>        <span class="hljs-comment"># 移动动画</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">abs</span>(direction.x) &gt; <span class="hljs-built_in">abs</span>(direction.y):<br>            <span class="hljs-comment"># 左右移动</span><br>            <span class="hljs-keyword">if</span> direction.x &gt; <span class="hljs-number">0</span>:<br>                <span class="hljs-keyword">if</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk_right&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk_right&quot;</span>)<br>                <span class="hljs-keyword">elif</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk&quot;</span>)<br>                    animated_sprite.flip_h = false<br>            <span class="hljs-keyword">else</span>:<br>                <span class="hljs-keyword">if</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk_left&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk_left&quot;</span>)<br>                <span class="hljs-keyword">elif</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk&quot;</span>)<br>                    animated_sprite.flip_h = true<br>        <span class="hljs-keyword">else</span>:<br>            <span class="hljs-comment"># 上下移动</span><br>            <span class="hljs-keyword">if</span> direction.y &gt; <span class="hljs-number">0</span>:<br>                <span class="hljs-keyword">if</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk_down&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk_down&quot;</span>)<br>                <span class="hljs-keyword">elif</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk&quot;</span>)<br>            <span class="hljs-keyword">else</span>:<br>                <span class="hljs-keyword">if</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk_up&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk_up&quot;</span>)<br>                <span class="hljs-keyword">elif</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;walk&quot;</span>):<br>                    animated_sprite.play(<span class="hljs-string">&quot;walk&quot;</span>)<br>    <span class="hljs-keyword">else</span>:<br>        <span class="hljs-comment"># 静止动画</span><br>        <span class="hljs-keyword">if</span> animated_sprite.sprite_frames.has_animation(<span class="hljs-string">&quot;idle&quot;</span>):<br>            animated_sprite.play(<span class="hljs-string">&quot;idle&quot;</span>)<br><br>func set_interacting(interacting: <span class="hljs-built_in">bool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;设置交互状态&quot;&quot;&quot;</span><br>    is_interacting = interacting<br></code></pre></td></tr></table></figure><p>这个脚本实现了 NPC 的完整行为。NPC 会在出生位置附近的<code>wander_range</code>范围内随机巡逻，每隔<code>wander_interval_min</code>到<code>wander_interval_max</code>秒选择一个新的目标点并移动过去。移动时会播放 4 方向动画(walk_up&#x2F;down&#x2F;left&#x2F;right)，到达目标后停止并播放 idle 动画。当玩家进入 InteractionArea 时，NPC 会调用玩家的<code>set_nearby_npc(self)</code>方法，将自己设置为可交互对象。玩家按 E 键后，对话系统会调用 NPC 的<code>set_interacting(true)</code>方法，NPC 停止移动。对话结束后调用<code>set_interacting(false)</code>，NPC 恢复巡逻。主场景会定时调用<code>update_dialogue()</code>方法更新 NPC 的对话气泡，显示 NPC 之间的自主对话内容。</p><h2 id="15-6-前后端通信实现"><a href="#15-6-前后端通信实现" class="headerlink" title="15.6 前后端通信实现"></a>15.6 前后端通信实现</h2><h3 id="15-6-1-API-客户端封装"><a href="#15-6-1-API-客户端封装" class="headerlink" title="15.6.1 API 客户端封装"></a>15.6.1 API 客户端封装</h3><p>Godot 前端需要与 FastAPI 后端进行 HTTP 通信。我们创建一个 API 客户端脚本<code>api_client.gd</code>，封装所有的 API 调用，并将其设置为 AutoLoad(自动加载)单例，让其他脚本可以方便地使用。</p><p>API 客户端使用 Godot 的 HTTPRequest 节点来发送 HTTP 请求。HTTPRequest 是一个异步节点，发送请求后不会阻塞游戏，而是通过信号通知请求完成。这样可以保证游戏的流畅性，即使网络延迟较高也不会卡顿。我们使用信号机制来通知其他脚本 API 响应，而不是使用 await，这样可以让多个脚本同时监听同一个 API 响应。</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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># api_client.gd</span><br>extends Node<br><br><span class="hljs-comment"># 信号定义</span><br>signal chat_response_received(npc_name: String, message: String)<br>signal chat_error(error_message: String)<br>signal npc_status_received(dialogues: Dictionary)<br>signal npc_list_received(npcs: Array)<br><br><span class="hljs-comment"># HTTP请求节点</span><br>var http_chat: HTTPRequest<br>var http_status: HTTPRequest<br>var http_npcs: HTTPRequest<br><br>func _ready():<br>    <span class="hljs-comment"># 创建HTTP请求节点</span><br>    http_chat = HTTPRequest.new()<br>    http_status = HTTPRequest.new()<br>    http_npcs = HTTPRequest.new()<br><br>    add_child(http_chat)<br>    add_child(http_status)<br>    add_child(http_npcs)<br><br>    <span class="hljs-comment"># 连接信号</span><br>    http_chat.request_completed.connect(_on_chat_request_completed)<br>    http_status.request_completed.connect(_on_status_request_completed)<br>    http_npcs.request_completed.connect(_on_npcs_request_completed)<br><br><span class="hljs-comment"># ==================== 对话API ====================</span><br>func send_chat(npc_name: String, message: String) -&gt; void:<br>    <span class="hljs-string">&quot;&quot;&quot;发送对话请求&quot;&quot;&quot;</span><br>    var data = &#123;<br>        <span class="hljs-string">&quot;npc_name&quot;</span>: npc_name,<br>        <span class="hljs-string">&quot;message&quot;</span>: message<br>    &#125;<br><br>    var json_string = JSON.stringify(data)<br>    var headers = [<span class="hljs-string">&quot;Content-Type: application/json&quot;</span>]<br><br>    var error = http_chat.request(<br>        Config.API_CHAT,<br>        headers,<br>        HTTPClient.METHOD_POST,<br>        json_string<br>    )<br><br>    <span class="hljs-keyword">if</span> error != OK:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[ERROR] 发送对话请求失败: &quot;</span>, error)<br>        chat_error.emit(<span class="hljs-string">&quot;网络请求失败&quot;</span>)<br><br>func _on_chat_request_completed(_result: <span class="hljs-built_in">int</span>, response_code: <span class="hljs-built_in">int</span>, _headers: PackedStringArray, body: PackedByteArray) -&gt; void:<br>    <span class="hljs-string">&quot;&quot;&quot;处理对话响应&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> response_code != <span class="hljs-number">200</span>:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[ERROR] 对话请求失败: HTTP &quot;</span>, response_code)<br>        chat_error.emit(<span class="hljs-string">&quot;服务器错误: &quot;</span> + <span class="hljs-built_in">str</span>(response_code))<br>        <span class="hljs-keyword">return</span><br><br>    var json = JSON.new()<br>    var parse_result = json.parse(body.get_string_from_utf8())<br><br>    <span class="hljs-keyword">if</span> parse_result != OK:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[ERROR] 解析响应失败&quot;</span>)<br>        chat_error.emit(<span class="hljs-string">&quot;响应解析失败&quot;</span>)<br>        <span class="hljs-keyword">return</span><br><br>    var response = json.data<br><br>    <span class="hljs-keyword">if</span> response.has(<span class="hljs-string">&quot;success&quot;</span>) <span class="hljs-keyword">and</span> response[<span class="hljs-string">&quot;success&quot;</span>]:<br>        var npc_name = response[<span class="hljs-string">&quot;npc_name&quot;</span>]<br>        var msg = response[<span class="hljs-string">&quot;message&quot;</span>]<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[INFO] 收到NPC回复: &quot;</span>, npc_name, <span class="hljs-string">&quot; -&gt; &quot;</span>, msg)<br>        chat_response_received.emit(npc_name, msg)<br>    <span class="hljs-keyword">else</span>:<br>        chat_error.emit(<span class="hljs-string">&quot;对话失败&quot;</span>)<br><br><span class="hljs-comment"># ==================== NPC状态API ====================</span><br>func get_npc_status() -&gt; void:<br>    <span class="hljs-string">&quot;&quot;&quot;获取NPC状态&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 检查是否正在处理请求</span><br>    <span class="hljs-keyword">if</span> http_status.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[WARN] NPC状态请求正在处理中,跳过本次请求&quot;</span>)<br>        <span class="hljs-keyword">return</span><br><br>    var error = http_status.request(Config.API_NPC_STATUS)<br><br>    <span class="hljs-keyword">if</span> error != OK:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[ERROR] 获取NPC状态失败: &quot;</span>, error)<br><br>func _on_status_request_completed(_result: <span class="hljs-built_in">int</span>, response_code: <span class="hljs-built_in">int</span>, _headers: PackedStringArray, body: PackedByteArray) -&gt; void:<br>    <span class="hljs-string">&quot;&quot;&quot;处理NPC状态响应&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> response_code != <span class="hljs-number">200</span>:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[ERROR] NPC状态请求失败: HTTP &quot;</span>, response_code)<br>        <span class="hljs-keyword">return</span><br><br>    var json = JSON.new()<br>    var parse_result = json.parse(body.get_string_from_utf8())<br><br>    <span class="hljs-keyword">if</span> parse_result != OK:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[ERROR] 解析NPC状态失败&quot;</span>)<br>        <span class="hljs-keyword">return</span><br><br>    var response = json.data<br><br>    <span class="hljs-keyword">if</span> response.has(<span class="hljs-string">&quot;dialogues&quot;</span>):<br>        var dialogues = response[<span class="hljs-string">&quot;dialogues&quot;</span>]<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[INFO] 收到NPC状态更新: &quot;</span>, dialogues.size(), <span class="hljs-string">&quot;个NPC&quot;</span>)<br>        npc_status_received.emit(dialogues)<br><br><span class="hljs-comment"># ==================== NPC列表API ====================</span><br>func get_npc_list() -&gt; void:<br>    <span class="hljs-string">&quot;&quot;&quot;获取NPC列表&quot;&quot;&quot;</span><br>    var error = http_npcs.request(Config.API_NPCS)<br><br>    <span class="hljs-keyword">if</span> error != OK:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[ERROR] 获取NPC列表失败: &quot;</span>, error)<br><br>func _on_npcs_request_completed(_result: <span class="hljs-built_in">int</span>, response_code: <span class="hljs-built_in">int</span>, _headers: PackedStringArray, body: PackedByteArray) -&gt; void:<br>    <span class="hljs-string">&quot;&quot;&quot;处理NPC列表响应&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> response_code != <span class="hljs-number">200</span>:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[ERROR] NPC列表请求失败: HTTP &quot;</span>, response_code)<br>        <span class="hljs-keyword">return</span><br><br>    var json = JSON.new()<br>    var parse_result = json.parse(body.get_string_from_utf8())<br><br>    <span class="hljs-keyword">if</span> parse_result != OK:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[ERROR] 解析NPC列表失败&quot;</span>)<br>        <span class="hljs-keyword">return</span><br><br>    var response = json.data<br><br>    <span class="hljs-keyword">if</span> response.has(<span class="hljs-string">&quot;npcs&quot;</span>):<br>        var npcs = response[<span class="hljs-string">&quot;npcs&quot;</span>]<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[INFO] 收到NPC列表: &quot;</span>, npcs.size(), <span class="hljs-string">&quot;个NPC&quot;</span>)<br>        npc_list_received.emit(npcs)<br></code></pre></td></tr></table></figure><p>这个 API 客户端封装了三个核心功能：发送对话请求(<code>send_chat</code>)、获取 NPC 状态(<code>get_npc_status</code>)和获取 NPC 列表(<code>get_npc_list</code>)。所有的 HTTP 请求都是异步的，通过信号通知响应结果。我们为每个 API 创建了独立的 HTTPRequest 节点，这样可以同时发送多个请求而不会互相干扰。API 的 URL 从 Config 单例中获取，方便统一管理。对话系统监听<code>chat_response_received</code>信号来接收 NPC 回复，主场景监听<code>npc_status_received</code>信号来更新 NPC 对话气泡。</p><h3 id="15-6-2-对话-UI-实现"><a href="#15-6-2-对话-UI-实现" class="headerlink" title="15.6.2 对话 UI 实现"></a>15.6.2 对话 UI 实现</h3><p>对话 UI 是玩家与 NPC 交互的界面。我们需要设计一个简洁美观的对话框，包含 NPC 名称、职位、对话内容显示、输入框和按钮。</p><p>对话 UI 的结构如图 15.13 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-13.png" alt="" width="85%"/>  <p>图 15.13 对话 UI 结构</p></div><p>对话 UI 的设计非常简洁。DialogueUI 是一个 CanvasLayer 节点，这意味着它会始终显示在游戏画面的最上层，不会被其他游戏对象遮挡。Panel 是对话框的背景，锚定在屏幕底部。Panel 下直接放置了 6 个 UI 元素：NPCName 显示 NPC 的名字，NPCTitle 显示职位，DialogueText 使用 RichTextLabel 显示对话内容(支持富文本格式)，PlayerInput 是一个 LineEdit 用于玩家输入，SendButton 和 CloseButton 分别用于发送消息和关闭对话框。</p><p>对话 UI 脚本<code>dialogue_ui.gd</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><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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># dialogue_ui.gd</span><br>extends CanvasLayer<br><br><span class="hljs-comment"># UI节点引用</span><br><span class="hljs-meta">@onready var panel = $Panel</span><br><span class="hljs-meta">@onready var npc_name_label = $Panel/NPCName</span><br><span class="hljs-meta">@onready var npc_title_label = $Panel/NPCTitle</span><br><span class="hljs-meta">@onready var dialogue_text = $Panel/DialogueText</span><br><span class="hljs-meta">@onready var input_field = $Panel/PlayerInput</span><br><span class="hljs-meta">@onready var send_button = $Panel/SendButton</span><br><span class="hljs-meta">@onready var close_button = $Panel/CloseButton</span><br><br><span class="hljs-comment"># API客户端</span><br>var api_client: Node = null<br><br><span class="hljs-comment"># 当前对话的NPC</span><br>var current_npc_name: String = <span class="hljs-string">&quot;&quot;</span><br><br>func _ready():<br>    <span class="hljs-comment"># 初始化时隐藏对话框</span><br>    visible = false<br><br>    <span class="hljs-comment"># 连接按钮信号</span><br>    send_button.pressed.connect(_on_send_button_pressed)<br>    close_button.pressed.connect(_on_close_button_pressed)<br>    input_field.text_submitted.connect(_on_text_submitted)<br><br>    <span class="hljs-comment"># 获取API客户端</span><br>    api_client = get_node_or_null(<span class="hljs-string">&quot;/root/APIClient&quot;</span>)<br><br>func start_dialogue(npc_name: String):<br>    <span class="hljs-string">&quot;&quot;&quot;开始与NPC对话&quot;&quot;&quot;</span><br>    current_npc_name = npc_name<br><br>    <span class="hljs-comment"># 设置NPC信息</span><br>    npc_name_label.text = npc_name<br>    npc_title_label.text = get_npc_title(npc_name)<br><br>    <span class="hljs-comment"># 清空对话内容</span><br>    dialogue_text.clear()<br>    dialogue_text.append_text(<span class="hljs-string">&quot;[color=gray]与 &quot;</span> + npc_name + <span class="hljs-string">&quot; 的对话开始...[/color]\n&quot;</span>)<br><br>    <span class="hljs-comment"># 清空输入框</span><br>    input_field.text = <span class="hljs-string">&quot;&quot;</span><br><br>    <span class="hljs-comment"># 显示对话框</span><br>    show_dialogue()<br><br>    <span class="hljs-comment"># 聚焦输入框</span><br>    input_field.grab_focus()<br><br>func show_dialogue():<br>    <span class="hljs-string">&quot;&quot;&quot;显示对话框&quot;&quot;&quot;</span><br>    visible = true<br><br>    <span class="hljs-comment"># 通知玩家进入交互状态(禁用移动)</span><br>    var player = get_tree().get_first_node_in_group(<span class="hljs-string">&quot;player&quot;</span>)<br>    <span class="hljs-keyword">if</span> player <span class="hljs-keyword">and</span> player.has_method(<span class="hljs-string">&quot;set_interacting&quot;</span>):<br>        player.set_interacting(true)<br><br>func hide_dialogue():<br>    <span class="hljs-string">&quot;&quot;&quot;隐藏对话框&quot;&quot;&quot;</span><br>    visible = false<br>    current_npc_name = <span class="hljs-string">&quot;&quot;</span><br><br>    <span class="hljs-comment"># 通知玩家退出交互状态(启用移动)</span><br>    var player = get_tree().get_first_node_in_group(<span class="hljs-string">&quot;player&quot;</span>)<br>    <span class="hljs-keyword">if</span> player <span class="hljs-keyword">and</span> player.has_method(<span class="hljs-string">&quot;set_interacting&quot;</span>):<br>        player.set_interacting(false)<br><br>func _on_send_button_pressed():<br>    <span class="hljs-string">&quot;&quot;&quot;发送按钮点击&quot;&quot;&quot;</span><br>    send_message()<br><br>func _on_close_button_pressed():<br>    <span class="hljs-string">&quot;&quot;&quot;关闭按钮点击&quot;&quot;&quot;</span><br>    hide_dialogue()<br><br>func _on_text_submitted(_text: String):<br>    <span class="hljs-string">&quot;&quot;&quot;输入框回车&quot;&quot;&quot;</span><br>    send_message()<br><br>func send_message():<br>    <span class="hljs-string">&quot;&quot;&quot;发送消息&quot;&quot;&quot;</span><br>    var message = input_field.text.strip_edges()<br><br>    <span class="hljs-keyword">if</span> message.is_empty():<br>        <span class="hljs-keyword">return</span><br><br>    <span class="hljs-keyword">if</span> current_npc_name.is_empty():<br>        <span class="hljs-keyword">return</span><br><br>    <span class="hljs-comment"># 显示玩家消息</span><br>    dialogue_text.append_text(<span class="hljs-string">&quot;\n[color=cyan]玩家:[/color] &quot;</span> + message + <span class="hljs-string">&quot;\n&quot;</span>)<br><br>    <span class="hljs-comment"># 清空输入框</span><br>    input_field.text = <span class="hljs-string">&quot;&quot;</span><br><br>    <span class="hljs-comment"># 禁用输入</span><br>    input_field.editable = false<br>    send_button.disabled = true<br><br>    <span class="hljs-comment"># 发送API请求</span><br>    <span class="hljs-keyword">if</span> api_client:<br>        api_client.send_chat_request(current_npc_name, message)<br><br>func on_chat_response_received(npc_name: String, response: String):<br>    <span class="hljs-string">&quot;&quot;&quot;收到NPC回复&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> npc_name == current_npc_name:<br>        <span class="hljs-comment"># 显示NPC回复</span><br>        dialogue_text.append_text(<span class="hljs-string">&quot;[color=yellow]&quot;</span> + npc_name + <span class="hljs-string">&quot;:[/color] &quot;</span> + response + <span class="hljs-string">&quot;\n&quot;</span>)<br><br>        <span class="hljs-comment"># 启用输入</span><br>        input_field.editable = true<br>        send_button.disabled = false<br>        input_field.grab_focus()<br><br>func get_npc_title(npc_name: String) -&gt; String:<br>    <span class="hljs-string">&quot;&quot;&quot;获取NPC职位&quot;&quot;&quot;</span><br>    var titles = &#123;<br>        <span class="hljs-string">&quot;张三&quot;</span>: <span class="hljs-string">&quot;Python工程师&quot;</span>,<br>        <span class="hljs-string">&quot;李四&quot;</span>: <span class="hljs-string">&quot;产品经理&quot;</span>,<br>        <span class="hljs-string">&quot;王五&quot;</span>: <span class="hljs-string">&quot;UI设计师&quot;</span><br>    &#125;<br>    <span class="hljs-keyword">return</span> titles.get(npc_name, <span class="hljs-string">&quot;&quot;</span>)<br></code></pre></td></tr></table></figure><p>这个对话 UI 实现了完整的对话功能。玩家可以输入消息并发送，UI 使用 RichTextLabel 的 append_text 方法显示对话内容，支持富文本格式(颜色、粗体等)。所有的 API 调用都是异步的，在等待响应时会禁用输入框，防止重复发送。对话框显示时会通知玩家进入交互状态，禁用移动，关闭时恢复移动。</p><h3 id="15-6-3-主场景整合"><a href="#15-6-3-主场景整合" class="headerlink" title="15.6.3 主场景整合"></a>15.6.3 主场景整合</h3><p>最后，我们需要在主场景中整合所有的功能：玩家控制、NPC 交互、对话 UI 和 NPC 状态更新。主场景脚本<code>main.gd</code>负责协调这些组件，并定时从后端获取 NPC 状态，更新 NPC 的对话气泡。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># main.gd</span><br>extends Node2D<br><br><span class="hljs-comment"># NPC节点引用</span><br><span class="hljs-meta">@onready var npc_zhang: Node2D = $NPCs/NPC_Zhang</span><br><span class="hljs-meta">@onready var npc_li: Node2D = $NPCs/NPC_Li</span><br><span class="hljs-meta">@onready var npc_wang: Node2D = $NPCs/NPC_Wang</span><br><br><span class="hljs-comment"># API客户端</span><br>var api_client: Node = null<br><br><span class="hljs-comment"># NPC状态更新计时器</span><br>var status_update_timer: <span class="hljs-built_in">float</span> = <span class="hljs-number">0.0</span><br><br>func _ready():<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[INFO] 主场景初始化&quot;</span>)<br><br>    <span class="hljs-comment"># 获取API客户端</span><br>    api_client = get_node_or_null(<span class="hljs-string">&quot;/root/APIClient&quot;</span>)<br>    <span class="hljs-keyword">if</span> api_client:<br>        api_client.npc_status_received.connect(_on_npc_status_received)<br><br>        <span class="hljs-comment"># 立即获取一次NPC状态</span><br>        api_client.get_npc_status()<br>    <span class="hljs-keyword">else</span>:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[ERROR] API客户端未找到&quot;</span>)<br><br>func _process(delta: <span class="hljs-built_in">float</span>):<br>    <span class="hljs-comment"># 定时更新NPC状态</span><br>    status_update_timer += delta<br>    <span class="hljs-keyword">if</span> status_update_timer &gt;= Config.NPC_STATUS_UPDATE_INTERVAL:<br>        status_update_timer = <span class="hljs-number">0.0</span><br>        <span class="hljs-keyword">if</span> api_client:<br>            api_client.get_npc_status()<br><br>func _on_npc_status_received(dialogues: Dictionary):<br>    <span class="hljs-string">&quot;&quot;&quot;收到NPC状态更新&quot;&quot;&quot;</span><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[INFO] 更新NPC状态: &quot;</span>, dialogues)<br><br>    <span class="hljs-comment"># 更新各个NPC的对话</span><br>    <span class="hljs-keyword">for</span> npc_name <span class="hljs-keyword">in</span> dialogues:<br>        var dialogue = dialogues[npc_name]<br>        update_npc_dialogue(npc_name, dialogue)<br><br>func update_npc_dialogue(npc_name: String, dialogue: String):<br>    <span class="hljs-string">&quot;&quot;&quot;更新指定NPC的对话&quot;&quot;&quot;</span><br>    var npc_node = get_npc_node(npc_name)<br>    <span class="hljs-keyword">if</span> npc_node <span class="hljs-keyword">and</span> npc_node.has_method(<span class="hljs-string">&quot;update_dialogue&quot;</span>):<br>        npc_node.update_dialogue(dialogue)<br><br>func get_npc_node(npc_name: String) -&gt; Node2D:<br>    <span class="hljs-string">&quot;&quot;&quot;根据名字获取NPC节点&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">match</span> npc_name:<br>        <span class="hljs-string">&quot;张三&quot;</span>:<br>            <span class="hljs-keyword">return</span> npc_zhang<br>        <span class="hljs-string">&quot;李四&quot;</span>:<br>            <span class="hljs-keyword">return</span> npc_li<br>        <span class="hljs-string">&quot;王五&quot;</span>:<br>            <span class="hljs-keyword">return</span> npc_wang<br>        _:<br>            <span class="hljs-keyword">return</span> null<br></code></pre></td></tr></table></figure><p>主场景脚本的核心功能是定时从后端获取 NPC 状态。在<code>_ready()</code>中，我们获取 APIClient 单例的引用，并连接<code>npc_status_received</code>信号。然后立即调用<code>get_npc_status()</code>获取一次 NPC 状态。在<code>_process()</code>中，我们使用计时器每隔<code>Config.NPC_STATUS_UPDATE_INTERVAL</code>秒(默认 30 秒)调用一次<code>get_npc_status()</code>。当收到 NPC 状态更新时，<code>_on_npc_status_received()</code>回调函数会遍历所有 NPC，调用它们的<code>update_dialogue()</code>方法更新对话气泡。这样，即使玩家不与 NPC 交互，也能看到 NPC 之间的自主对话。</p><p>整个前后端通信流程如图 15.14 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-14.png" alt="" width="85%"/>  <p>图 15.14 前后端通信完整流程</p></div><p>至此，前后端通信的所有功能都已实现。玩家可以在游戏中自由移动，与 NPC 互动，进行自然语言对话。同时，主场景会定时从后端获取 NPC 状态，更新 NPC 的对话气泡，展示 NPC 之间的自主对话。整个系统使用信号机制进行通信，各个组件之间松耦合，易于维护和扩展。</p><h2 id="15-7-总结与展望"><a href="#15-7-总结与展望" class="headerlink" title="15.7 总结与展望"></a>15.7 总结与展望</h2><h3 id="15-7-1-本章回顾"><a href="#15-7-1-本章回顾" class="headerlink" title="15.7.1 本章回顾"></a>15.7.1 本章回顾</h3><p>在本章中，我们完成了一个完整的 AI 小镇项目——赛博小镇。这个项目将 HelloAgents 框架与 Godot 游戏引擎结合，创造出了一个充满生命力的虚拟世界。让我们回顾一下我们学到的核心内容。</p><p><strong>技术架构设计</strong></p><p>我们采用了游戏引擎+后端服务的分离架构，将前端渲染、后端逻辑和 AI 智能分离到不同的层次。Godot 负责游戏画面和玩家交互，FastAPI 负责 API 服务和状态管理，HelloAgents 负责 NPC 智能和记忆系统。这种分层设计让每个部分都可以独立开发和测试，也为后续的扩展提供了良好的基础。</p><p><strong>NPC 智能体系统</strong></p><p>我们使用 HelloAgents 的 SimpleAgent 为每个 NPC 创建了独立的智能体。每个 NPC 都有自己的角色设定、性格特点和记忆系统。通过精心设计的系统提示词，我们让张三成为了一位严谨的 Python 工程师，李四成为了一位善于沟通的产品经理，王五成为了一位富有创意的 UI 设计师。这些 NPC 不仅能够理解玩家的对话，还能根据自己的角色特点做出相应的回复。</p><p><strong>记忆与好感度系统</strong></p><p>我们实现了两层记忆系统：短期记忆保持对话的连贯性，长期记忆存储所有的互动历史。通过向量数据库的语义检索，NPC 可以回忆起之前讨论过的话题。好感度系统让 NPC 对玩家的态度随着互动而变化，从陌生到挚友，每个等级都有不同的行为表现。这些设计让 NPC 显得更加真实和有趣。</p><p><strong>游戏场景构建</strong></p><p>我们使用 Godot 创建了一个像素风格的办公室场景，实现了玩家控制、NPC 游走、交互检测和对话 UI。通过场景系统的模块化设计，我们可以轻松地添加新的 NPC、新的场景和新的功能。GDScript 的简洁语法让游戏逻辑的实现变得直观和高效。</p><p><strong>前后端通信</strong></p><p>我们使用 HTTP REST API 实现了 Godot 前端与 FastAPI 后端的通信。通过异步请求和信号系统，我们保证了游戏的流畅性，即使网络延迟较高也不会影响玩家体验。API 客户端的封装让其他脚本可以方便地调用后端服务，对话 UI 的实现让玩家可以自然地与 NPC 交流。</p><p>整个项目的技术栈如图 15.15 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/15-figures/15-15.png" alt="" width="85%"/>  <p>图 15.15 赛博小镇技术栈</p></div><h3 id="15-7-2-扩展方向"><a href="#15-7-2-扩展方向" class="headerlink" title="15.7.2 扩展方向"></a>15.7.2 扩展方向</h3><p>赛博小镇只是一个起点，还有很多可以扩展的方向。这些扩展不仅能够增强游戏的趣味性，也能探索 AI 技术在游戏中的更多可能性。</p><p><strong>（1）多人在线支持</strong></p><p>目前的赛博小镇是单人游戏，但我们可以将其扩展为多人在线游戏。多个玩家可以同时进入同一个办公室，与 NPC 和其他玩家互动。这需要引入 WebSocket 进行实时通信，以及数据库来持久化玩家数据和 NPC 状态。NPC 可以记住与不同玩家的互动，对每个玩家保持独立的好感度。</p><p><strong>（2）任务系统</strong></p><p>我们可以为 NPC 设计任务系统。当玩家与 NPC 的好感度达到一定程度时，NPC 会提供特殊任务。比如张三可能会请玩家帮忙调试一段代码，李四可能会请玩家收集用户反馈，王五可能会请玩家评价设计方案。完成任务可以获得奖励，也能进一步提升好感度。</p><p><strong>（3）NPC 之间的互动</strong></p><p>目前 NPC 只与玩家互动，但我们可以让 NPC 之间也能互动。张三可以和李四讨论产品需求，李四可以和王五讨论界面设计，王五可以和张三讨论技术实现。这些互动可以在后台自动进行，玩家可以观察到 NPC 之间的对话，让整个世界显得更加生动。</p><p><strong>（4）情感系统</strong></p><p>除了好感度，我们还可以为 NPC 添加更复杂的情感系统。NPC 可以有开心、难过、生气、兴奋等不同的情绪状态，这些情绪会影响 NPC 的回复风格和行为。比如当 NPC 心情好的时候，会更愿意分享信息;当 NPC 心情不好的时候，可能会比较冷淡。</p><p><strong>（5）动态事件系统</strong></p><p>我们可以设计一些动态事件，让游戏世界更加丰富。比如定期举办团队会议，所有 NPC 和玩家聚在一起讨论项目进展;或者举办生日派对，庆祝某个 NPC 的生日;或者突发紧急任务，需要大家协作完成。这些事件可以增加游戏的变化性和趣味性。</p><p><strong>（6）更大的世界</strong></p><p>目前的赛博小镇只有一个办公室场景，但我们可以扩展到更大的世界。可以添加咖啡厅、图书馆、公园等不同的场景，每个场景有不同的 NPC 和互动方式。玩家可以在不同场景之间移动，探索更广阔的虚拟世界。</p><p><strong>（7）个性化学习</strong></p><p>NPC 可以学习每个玩家的偏好和习惯。比如如果玩家经常和张三讨论 Python，NPC 会记住玩家对编程感兴趣，以后会主动分享相关的内容。如果玩家喜欢在晚上玩游戏，NPC 会记住这个时间习惯，在晚上更加活跃。</p><h3 id="15-7-3-思考与展望"><a href="#15-7-3-思考与展望" class="headerlink" title="15.7.3 思考与展望"></a>15.7.3 思考与展望</h3><p>赛博小镇展示了 AI 技术在游戏中的巨大潜力。传统游戏中的 NPC 受限于预设的对话树和脚本，而 AI NPC 可以理解和生成自然语言，与玩家进行真正的对话。这不仅提升了游戏的沉浸感，也为游戏设计带来了新的可能性。</p><p>但 AI NPC 也面临一些挑战。首先是成本问题，每次对话都需要调用 LLM API，这会产生一定的费用。对于大型多人在线游戏，这个成本可能会很高。其次是延迟问题，LLM 的推理需要时间，如果网络延迟较高，玩家可能需要等待几秒才能看到 NPC 的回复。最后是内容控制问题，LLM 生成的内容可能不完全可控，需要设计好的提示词和内容过滤机制。</p><p>尽管有这些挑战，AI NPC 的未来仍然充满希望。随着 LLM 技术的发展，推理速度会越来越快，成本会越来越低。本地化的小型 LLM 也在快速发展，未来可能可以在玩家的设备上直接运行，完全不需要网络请求。AI 技术与游戏的结合，将为玩家带来前所未有的体验。</p><p>在第五部分的毕业设计章节，我们将会学习如何用单智能体和多智能体构造通用智能体，这将是你的创作时间，敬请期待！</p>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC15%E7%AB%A0-%E6%9E%84%E5%BB%BA%E8%B5%9B%E5%8D%9A%E5%B0%8F%E9%95%87/</id>
    <link href="http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC15%E7%AB%A0-%E6%9E%84%E5%BB%BA%E8%B5%9B%E5%8D%9A%E5%B0%8F%E9%95%87/"/>
    <published>2026-03-02T08:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="第十五章-构建赛博小镇"><a href="#第十五章-构建赛博小镇" class="headerlink" title="第十五章 构建赛博小镇"></a>第十五章 构建赛博小镇</h1><p>这一章，我们将探索一个全新的方向：<strong>将智能体技术与游戏]]>
    </summary>
    <title>第十五章 构建赛博小镇</title>
    <updated>2026-03-08T09:24:16.354Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="第十四章-自动化深度研究智能体"><a href="#第十四章-自动化深度研究智能体" class="headerlink" title="第十四章 自动化深度研究智能体"></a>第十四章 自动化深度研究智能体</h1><p>在第十三章的旅行助手项目中，我们体验了如何将 HelloAgents 应用于一个多智能体产品。本章我们继续向前，聚焦「知识密集型应用」：<strong>构建一个能够自动化执行深度研究任务的智能体助手。</strong></p><p>相比旅行规划，深度研究的难点在于信息的不断发散、事实的快速更新以及用户对引用来源的高要求。为了交付可信的研究报告，我们需要让智能体具备三个核心能力：</p><p><strong>（1）问题剖析</strong>：将用户的开放主题拆解为可检索的查询语句。</p><p><strong>（2）多轮信息采集</strong>：结合不同搜索 API 持续挖掘资料，并去重整合。</p><p><strong>（3）反思与总结</strong>：依据阶段结果识别知识空白，决定是否继续检索，并生成结构化总结。</p><h2 id="14-1-项目概述与架构设计"><a href="#14-1-项目概述与架构设计" class="headerlink" title="14.1 项目概述与架构设计"></a>14.1 项目概述与架构设计</h2><h3 id="14-1-1-为什么需要深度研究助手"><a href="#14-1-1-为什么需要深度研究助手" class="headerlink" title="14.1.1 为什么需要深度研究助手"></a>14.1.1 为什么需要深度研究助手</h3><p>在信息爆炸的时代，我们每天都需要快速了解新的技术、概念或事件。传统的研究方式有几个痛点。首先是<strong>信息过载</strong>。搜索引擎返回成千上万的结果，你需要逐个点开链接，阅读大量内容，才能找到有用的信息。其次是<strong>缺少结构</strong>。即使找到了相关信息，这些信息往往是碎片化的，缺少系统性的组织。最后是<strong>重复劳动</strong>。每次研究新主题时，都需要重复”搜索→阅读→总结→整理”的过程。</p><p>这就是深度研究助手需要解决的问题。它不仅仅是一个搜索工具，而是一个能够自主规划、执行和总结的研究助手。</p><p><strong>深度研究助手的核心价值：</strong></p><ol><li><strong>节省时间</strong>：将 1-2 小时的研究工作压缩到 5-10 分钟</li><li><strong>提高质量</strong>：系统化的研究流程，避免遗漏重要信息</li><li><strong>可追溯</strong>：记录所有搜索结果和来源，方便验证和引用</li><li><strong>可扩展</strong>：可以轻松添加新的搜索引擎、数据源和分析工具</li></ol><h3 id="14-1-2-技术架构概览"><a href="#14-1-2-技术架构概览" class="headerlink" title="14.1.2 技术架构概览"></a>14.1.2 技术架构概览</h3><p>此次系统仍然采用经典的<strong>前后端分离架构</strong>，如图 14.1 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/14-figures/14-1.png" alt="" width="85%"/>  <p>图 14.1 深度研究助手技术架构</p></div><p>系统分为四层架构设计：</p><p><strong>前端层 (Vue3+TypeScript)</strong>：全屏模态对话框 UI、Markdown 结果可视化</p><p><strong>后端层 (FastAPI)</strong>：API 路由（<code>/research/stream</code>）</p><p><strong>智能体层 (HelloAgents)</strong>：三个专门 Agent（TODO Planner、Task Summarizer、Report Writer）+ 两个核心工具（SearchTool、NoteTool）</p><p><strong>外部服务层</strong>：搜索引擎+ LLM 提供商</p><p>让我们看看一个完整的研究请求是如何在系统中流转的，如图 14.2 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/14-figures/14-2.png" alt="" width="85%"/>  <p>图 14.2 深度研究助手数据流转过程</p></div><ol><li><strong>用户输入</strong>：用户在前端输入研究主题</li><li><strong>前端发送</strong>：前端通过 SSE 连接到<code>/research/stream</code></li><li><strong>后端接收</strong>：FastAPI 接收请求，创建研究状态</li><li><strong>规划阶段</strong>：调用研究规划 Agent，分解为 3 个子任务</li><li><strong>执行阶段</strong>：逐个执行每个子任务<ul><li>使用 SearchTool 搜索</li><li>调用任务总结 Agent 总结</li><li>使用 NoteTool 记录结果</li></ul></li><li><strong>报告阶段</strong>：调用报告生成 Agent，整合所有总结</li><li><strong>流式返回</strong>：通过 SSE 推送进度和结果到前端</li><li><strong>前端展示</strong>：前端实时更新任务状态、进度条、日志、报告</li></ol><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><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><code class="hljs bash">helloagents-deepresearch/<br>├── backend/                    <span class="hljs-comment"># 后端代码</span><br>│   ├── src/<br>│   │   ├── agent.py           <span class="hljs-comment"># 核心协调器</span><br>│   │   ├── main.py            <span class="hljs-comment"># FastAPI入口</span><br>│   │   ├── models.py          <span class="hljs-comment"># 数据模型</span><br>│   │   ├── prompts.py         <span class="hljs-comment"># Prompt模板</span><br>│   │   ├── config.py          <span class="hljs-comment"># 配置管理</span><br>│   │   └── services/          <span class="hljs-comment"># 服务层</span><br>│   │       ├── planner.py     <span class="hljs-comment"># 规划服务</span><br>│   │       ├── summarizer.py  <span class="hljs-comment"># 总结服务</span><br>│   │       ├── reporter.py    <span class="hljs-comment"># 报告服务</span><br>│   │       └── search.py      <span class="hljs-comment"># 搜索服务</span><br>│   ├── .<span class="hljs-built_in">env</span>                   <span class="hljs-comment"># 环境变量</span><br>│   ├── pyproject.toml         <span class="hljs-comment"># 依赖管理</span><br>│   └── workspace/             <span class="hljs-comment"># 研究笔记</span><br>│<br>└── frontend/                   <span class="hljs-comment"># 前端代码</span><br>    ├── src/<br>    │   ├── App.vue            <span class="hljs-comment"># 主组件</span><br>    │   ├── components/        <span class="hljs-comment"># UI组件</span><br>    │   │   └── ResearchModal.vue<br>    │   └── composables/       <span class="hljs-comment"># 组合式函数</span><br>    │       └── useResearch.ts<br>    ├── package.json           <span class="hljs-comment"># npm依赖</span><br>    └── vite.config.ts         <span class="hljs-comment"># 构建配置</span><br></code></pre></td></tr></table></figure><h3 id="14-1-3-快速体验：5-分钟运行项目"><a href="#14-1-3-快速体验：5-分钟运行项目" class="headerlink" title="14.1.3 快速体验：5 分钟运行项目"></a>14.1.3 快速体验：5 分钟运行项目</h3><p>在深入学习实现细节之前，让我们先把项目跑起来，看看最终的效果。这样你会对整个系统有一个直观的认识。</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><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">python --version  <span class="hljs-comment"># 应该显示 Python 3.10.x 或更高</span><br>node --version    <span class="hljs-comment"># 应该显示 v16.x.x 或更高</span><br>npm --version     <span class="hljs-comment"># 应该显示 8.x.x 或更高</span><br></code></pre></td></tr></table></figure><p>（1）启动后端</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><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><code class="hljs bash"><span class="hljs-comment"># 1. 进入后端目录</span><br><span class="hljs-built_in">cd</span> helloagents-deepresearch/backend<br><br><span class="hljs-comment"># 2. 安装依赖</span><br><span class="hljs-comment"># 方式1：使用uv（推荐，更快的Python包管理器）</span><br>uv <span class="hljs-built_in">sync</span><br><br><span class="hljs-comment"># 方式2：使用pip</span><br>pip install -e .<br><br><span class="hljs-comment"># 3. 配置环境变量</span><br><span class="hljs-built_in">cp</span> .env.example .<span class="hljs-built_in">env</span><br><br><span class="hljs-comment"># 4. 编辑.env文件，填入你的API密钥</span><br><span class="hljs-comment"># 使用你喜欢的编辑器打开.env文件</span><br><span class="hljs-comment"># 至少需要配置：</span><br><span class="hljs-comment"># - LLM_PROVIDER（如 openai、deepseek、qwen）</span><br><span class="hljs-comment"># - LLM_API_KEY（你的LLM API密钥）</span><br><span class="hljs-comment"># - SEARCH_API（如 duckduckgo、tavily）</span><br><br><span class="hljs-comment"># 5. 启动后端</span><br>python src/main.py<br></code></pre></td></tr></table></figure><p>如果一切正常，你会看到类似的输出：</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><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><code class="hljs pgsql"><span class="hljs-keyword">INFO</span>:     Started <span class="hljs-keyword">server</span> process [<span class="hljs-number">12345</span>]<br><span class="hljs-keyword">INFO</span>:     Waiting <span class="hljs-keyword">for</span> application startup.<br><span class="hljs-keyword">INFO</span>:     Application startup complete.<br><span class="hljs-keyword">INFO</span>:     Uvicorn running <span class="hljs-keyword">on</span> http://<span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8000</span> (Press CTRL+C <span class="hljs-keyword">to</span> quit)<br></code></pre></td></tr></table></figure><p>（2）启动前端</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><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><code class="hljs bash"><span class="hljs-comment"># 1. 进入前端目录</span><br><span class="hljs-built_in">cd</span> helloagents-deepresearch/frontend<br><br><span class="hljs-comment"># 2. 安装依赖</span><br>npm install<br><br><span class="hljs-comment"># 3. 启动前端</span><br>npm run dev<br></code></pre></td></tr></table></figure><p>如果一切正常，你会看到类似的输出：</p><figure class="highlight delphi"><table><tr><td class="gutter"><pre><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><code class="hljs delphi">VITE v5.<span class="hljs-number">0.0</span>  ready <span class="hljs-keyword">in</span> <span class="hljs-number">500</span> ms<br><br>➜  <span class="hljs-keyword">Local</span>:   http:<span class="hljs-comment">//localhost:5174/</span><br>➜  Network: use --host <span class="hljs-keyword">to</span> expose<br>➜  press h + enter <span class="hljs-keyword">to</span> show help<br></code></pre></td></tr></table></figure><p>（3）开始研究</p><p>打开浏览器访问 <code>http://localhost:5174</code>，你会看到一个居中的输入卡片，如图 14.3 所示。输入研究主题，例如<code>Datawhale是一个什么样的组织？</code>，选择搜索引擎（如果配置了多个），点击”开始研究”按钮。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/14-figures/14-3.png" alt="" width="85%"/>  <p>图 14.3 深度研究助手搜索页面</p></div><p>如图 14.4 所示，系统会自动展开为全屏，左侧显示研究信息，右侧实时显示研究进度和结果。整个研究过程大约需要 1-3 分钟，取决于主题的复杂度和搜索引擎的响应速度。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/14-figures/14-4.png" alt="" width="85%"/>  <p>图 14.4 深度研究助手展开研究</p></div><p>研究完成后，你会看到：</p><ul><li><strong>任务列表</strong>：显示所有子任务及其状态</li><li><strong>进度日志</strong>：显示研究过程中的所有操作</li><li><strong>最终报告</strong>：结构化的 Markdown 报告，包含所有子任务的总结和来源引用</li></ul><p>现在你已经成功运行了深度研究助手，对系统有了直观的认识。</p><h2 id="14-2-TODO-驱动的研究范式"><a href="#14-2-TODO-驱动的研究范式" class="headerlink" title="14.2 TODO 驱动的研究范式"></a>14.2 TODO 驱动的研究范式</h2><h3 id="14-2-1-什么是-TODO-驱动的研究"><a href="#14-2-1-什么是-TODO-驱动的研究" class="headerlink" title="14.2.1 什么是 TODO 驱动的研究"></a>14.2.1 什么是 TODO 驱动的研究</h3><p>传统的搜索引擎只能回答单个问题，而深度研究需要回答一系列相关的问题。TODO 驱动的研究范式将复杂的研究主题分解为多个子任务（TODO），逐个执行并整合结果。</p><p>这种范式的核心思想是：<strong>将”研究”这个复杂任务转化为”规划→执行→整合”的流程</strong>。</p><p>让我们通过一个例子来理解这个转变。假设你想研究”Datawhale 是一个什么样的组织？”，传统的搜索方式是：</p><figure class="highlight subunit"><table><tr><td class="gutter"><pre><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><code class="hljs subunit">用户输入：Datawhale是一个什么样的组织？<br>搜索引擎：返回10<span class="hljs-string">-20</span>个链接<br>用户：逐个点开链接，阅读内容，记录笔记<br>结果：碎片化的信息，缺少系统性<br></code></pre></td></tr></table></figure><p>这种方式的问题在于每个链接只涵盖主题的一个方面、缺少系统性结构，需要手动整理和总结。</p><p><strong>TODO 驱动方式：系统化研究</strong></p><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><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs markdown">用户输入：Datawhale是一个什么样的组织？<br><br>系统规划：<br>  ├─ TODO 1：Datawhale的基本信息（组织定位）<br>  ├─ TODO 2：Datawhale的主要项目（核心内容）<br>  ├─ TODO 3：Datawhale的社区文化（价值观）<br>  └─ TODO 4：Datawhale的影响力（社会贡献）<br><br>系统执行：<br>  对每个TODO：<br><span class="hljs-bullet">    1.</span> 搜索相关资料<br><span class="hljs-bullet">    2.</span> 总结关键信息<br><span class="hljs-bullet">    3.</span> 记录来源引用<br><br>系统整合：<br>  生成结构化报告：<br><span class="hljs-code">    ├─ 第一部分：组织定位（来自TODO 1）</span><br><span class="hljs-code">    ├─ 第二部分：核心内容（来自TODO 2）</span><br><span class="hljs-code">    ├─ 第三部分：价值观（来自TODO 3）</span><br><span class="hljs-code">    ├─ 第四部分：社会贡献（来自TODO 4）</span><br><span class="hljs-code">    └─ 参考文献：所有来源引用</span><br></code></pre></td></tr></table></figure><p>这种方式的优势在于将复杂主题分解为清晰的子问题，每个子任务的搜索结果和总结都被记录下来，方便追溯。同时，系统化的研究流程避免了遗漏重要信息，可以轻松添加新的子任务或调整执行顺序。</p><p>一个完整的 TODO 驱动研究系统包含三个核心要素：</p><p><strong>（1）智能规划器（TODO Planner）</strong>：负责将研究主题分解为子任务。一个好的规划器需要理解主题的关键方面和研究目标，将主题分解为 3-5 个子任务（太少覆盖不全，太多会冗余），并为每个子任务设计合适的搜索查询。</p><p><strong>（2）任务执行器（Task Executor）</strong>：负责执行每个子任务。执行器需要使用搜索引擎获取相关资料，提取关键信息并去除冗余内容，同时保存所有来源引用以方便验证。</p><p><strong>（3）报告生成器（Report Writer）</strong>：负责整合所有子任务的结果。生成器需要按照逻辑顺序组织内容，合并重复的信息，并为每个观点添加来源引用。</p><p>在我们的案例里，TODO 驱动的研究流程如图 14.5 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/14-figures/14-5.png" alt="" width="85%"/>  <p>图 14.5 TODO 驱动的研究流程</p></div><p>整个流程是线性的，但每个阶段都有明确的输入和输出。这种设计使得系统易于理解和调试。</p><h3 id="14-2-2-三阶段研究流程"><a href="#14-2-2-三阶段研究流程" class="headerlink" title="14.2.2 三阶段研究流程"></a>14.2.2 三阶段研究流程</h3><p>TODO 驱动的研究流程分为三个阶段:规划（Planning）、执行（Execution）、报告（Reporting）。每个阶段都有专门的 Agent 负责。</p><p><strong>（1）阶段 1：规划</strong></p><p>规划阶段的目标是将研究主题分解为 3-5 个子任务。系统接收研究主题和当前日期作为输入，输出 JSON 格式的子任务列表。每个子任务包含三个字段：title（任务标题）、intent（研究意图）和 query（搜索查询）。</p><p>研究规划 Agent 会根据主题特点采用不同的分解策略，通常从基础概念入手，然后了解技术现状、实际应用和发展趋势，必要时还会进行对比分析。例如，对于”Datawhale 是一个什么样的组织？”，规划 Agent 可能生成以下子任务：</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><code class="hljs json"><span class="hljs-punctuation">[</span><br>  <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;title&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Datawhale的基本信息&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;intent&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;了解Datawhale的组织定位、成立时间、发展历程&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;query&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Datawhale organization introduction history 2024&quot;</span><br>  <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;title&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Datawhale的主要项目&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;intent&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;了解Datawhale的核心开源项目和教程&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;query&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Datawhale projects tutorials open source 2024&quot;</span><br>  <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>......<br><span class="hljs-punctuation">]</span><br></code></pre></td></tr></table></figure><p>一个好的规划应该覆盖全面、逻辑清晰、查询精准、条目数量适中。</p><p><strong>（2）阶段 2：执行</strong></p><p>执行阶段逐个执行每个子任务，搜索并总结相关资料。系统接收子任务列表和搜索引擎配置作为输入，输出每个子任务的总结（Markdown 格式）和来源引用列表。执行流程如下：</p><p>对于每个子任务，执行器会：</p><ol><li><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></pre></td><td class="code"><pre><code class="hljs python">search_results = search_tool.run(&#123;<br>    <span class="hljs-string">&quot;input&quot;</span>: task.query,<br>    <span class="hljs-string">&quot;backend&quot;</span>: <span class="hljs-string">&quot;tavily&quot;</span>,<br>    <span class="hljs-string">&quot;mode&quot;</span>: <span class="hljs-string">&quot;structured&quot;</span>,<br>    <span class="hljs-string">&quot;max_results&quot;</span>: <span class="hljs-number">5</span><br>&#125;)<br></code></pre></td></tr></table></figure></li><li><p><strong>获取搜索结果</strong>：提取标题、URL、摘要</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;results&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>    <span class="hljs-punctuation">&#123;</span><br>      <span class="hljs-attr">&quot;title&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;What is a Multimodal Model?&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;url&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;https://example.com/multimodal-model&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;snippet&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;A multimodal model is an AI model that can process multiple types of data...&quot;</span><br>    <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>    ...<br>  <span class="hljs-punctuation">]</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure></li><li><p><strong>调用总结 Agent</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></pre></td><td class="code"><pre><code class="hljs python">summary = summarizer_agent.run(<br>    task=task,<br>    search_results=search_results<br>)<br></code></pre></td></tr></table></figure></li><li><p><strong>记录总结和来源</strong>：保存到 NoteTool</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><code class="hljs python">note_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create&quot;</span>,<br>    <span class="hljs-string">&quot;title&quot;</span>: task.title,<br>    <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">f&quot;## <span class="hljs-subst">&#123;task.title&#125;</span>\n\n<span class="hljs-subst">&#123;summary&#125;</span>\n\n## 来源\n<span class="hljs-subst">&#123;sources&#125;</span>&quot;</span>,<br>    <span class="hljs-string">&quot;tags&quot;</span>: [<span class="hljs-string">&quot;research&quot;</span>, <span class="hljs-string">&quot;summary&quot;</span>]<br>&#125;)<br></code></pre></td></tr></table></figure></li></ol><p>任务总结 Agent 会从每个搜索结果中提取核心观点，合并相似信息，保留重要的数字、日期、名称等关键数据，并为每个观点添加来源引用。例如，对于”Datawhale 的基本信息”的搜索结果，总结 Agent 可能生成：</p><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><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section">## Datawhale的基本信息</span><br><br>Datawhale是一个专注于数据科学与AI领域的开源组织，成立于2018年[1]。组织的核心使命是&quot;for the learner，和学习者一起成长&quot;，致力于构建一个纯粹的学习社区[2]。<br><br><span class="hljs-strong">**核心定位：**</span><br><br><span class="hljs-bullet">1.</span> <span class="hljs-strong">**开源教育平台**</span>：提供高质量的AI和数据科学学习资源[1]<br><span class="hljs-bullet">2.</span> <span class="hljs-strong">**学习者社区**</span>：汇聚了数万名AI学习者和实践者[3]<br><span class="hljs-bullet">3.</span> <span class="hljs-strong">**知识共享**</span>：倡导开源精神，所有内容完全免费开放[2]<br><br><span class="hljs-strong">**发展历程：**</span><br><br><span class="hljs-bullet">-</span> <span class="hljs-strong">**2018年**</span>：Datawhale成立，发布首个开源教程[1]<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**2020年**</span>：成为国内领先的AI学习社区之一[3]<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**2024年**</span>：累计发布50+开源项目，影响10万+学习者[4]<br><br><span class="hljs-section">## 来源</span><br><br>[1] https://github.com/datawhalechina<br>[2] https://datawhale.club/about<br>[3] https://www.zhihu.com/org/datawhale<br>[4] https://datawhale.cn<br></code></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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;status&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;message&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;正在搜索：Datawhale的基本信息&quot;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><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><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;status&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;message&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;正在总结搜索结果...&quot;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><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><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;task&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;task&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;title&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Datawhale的基本信息&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;status&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;completed&quot;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p><strong>（3）阶段 3：报告</strong></p><p>报告阶段的目标是整合所有子任务的总结，生成最终报告。系统接收所有子任务的总结和研究主题作为输入，输出 Markdown 格式的最终报告。报告包含标题、概述、各个子任务的详细分析、总结和参考文献五个部分。例如，对于”Datawhale 是一个什么样的组织？”，最终报告可能是：</p><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><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><code class="hljs markdown"><span class="hljs-section"># Datawhale是一个什么样的组织？</span><br><br><span class="hljs-section">## 概述</span><br><br>本报告系统地研究了Datawhale这个开源组织，涵盖基本信息、主要项目、社区文化和影响力四个方面。<br><br><span class="hljs-section">## 1. Datawhale的基本信息</span><br><br>Datawhale是一个专注于数据科学与AI领域的开源组织，成立于2018年...<br><br>（此处插入子任务1的总结）<br><br><span class="hljs-section">## 2. Datawhale的主要项目</span><br><br>Datawhale发布了多个高质量的开源教程，包括Hello-Agents、Joyful-Pandas等...<br><br>（此处插入子任务2的总结）<br>......<br><span class="hljs-section">## 总结</span><br><br>通过本次研究，我们了解了Datawhale的组织定位、核心项目、社区文化和社会贡献。Datawhale是一个纯粹的学习社区，为AI教育做出了重要贡献。<br><br><span class="hljs-section">## 参考文献</span><br><br>[1] https://github.com/datawhalechina<br>[2] https://datawhale.club/about<br>...<br></code></pre></td></tr></table></figure><p>报告生成 Agent 会按照子任务的逻辑顺序组织内容，在开头添加简要概述，合并重复的信息，统一 Markdown 格式，并将所有来源引用整理到参考文献部分。</p><h2 id="14-3-智能体系统设计"><a href="#14-3-智能体系统设计" class="headerlink" title="14.3 智能体系统设计"></a>14.3 智能体系统设计</h2><h3 id="14-3-1-Agent-职责划分"><a href="#14-3-1-Agent-职责划分" class="headerlink" title="14.3.1 Agent 职责划分"></a>14.3.1 Agent 职责划分</h3><p>在深度研究助手中，我们设计了三个专门的 Agent，每个 Agent 负责一个特定的任务。这使得每个 Agent 都很简单，易于理解和维护。</p><p>在第七章中，我们学习了如何使用<code>SimpleAgent</code>来构建智能体。<code>SimpleAgent</code>的设计理念是简单直接：每次调用<code>run()</code>方法时，Agent 会分析用户的问题，决定是否需要调用工具，然后返回结果。这种设计在处理简单任务时非常有效，但当面对深度研究这样的复杂任务时，就需要我们继续采用多智能体协作的方案进行。</p><p>如表 14.1 所示，三个 Agent 分别负责规划、总结和报告生成。</p><div align="center">  <p>表 14.1 三个 Agent 的职责划分</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/14-figures/14-table-1.png" alt="" width="85%"/></div><p>让我们详细介绍每个 Agent 的设计。</p><p><strong>Agent 1：研究规划专家（TODO Planner）</strong></p><p><strong>职责</strong>：将研究主题分解为 3-5 个子任务</p><p><strong>设计理念</strong>：研究规划专家的核心任务是理解用户的研究主题，分析主题的关键方面，然后生成一系列子任务。这个过程类似于人类研究者在开始研究前的”头脑风暴”阶段。</p><p><strong>Prompt 设计</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><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs python">todo_planner_instructions = <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">你是一个研究规划专家。你的任务是将用户的研究主题分解为3-5个子任务。</span><br><span class="hljs-string"></span><br><span class="hljs-string">当前日期：&#123;current_date&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">研究主题：&#123;research_topic&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">请分析这个研究主题，将其分解为3-5个子任务。每个子任务应该：</span><br><span class="hljs-string">1. 涵盖主题的一个重要方面</span><br><span class="hljs-string">2. 有明确的研究目标</span><br><span class="hljs-string">3. 可以通过搜索引擎找到相关资料</span><br><span class="hljs-string"></span><br><span class="hljs-string">请以JSON格式返回子任务列表，每个子任务包含：</span><br><span class="hljs-string">- title：任务标题（简洁明了）</span><br><span class="hljs-string">- intent：任务意图（为什么要研究这个）</span><br><span class="hljs-string">- query：搜索查询（用于搜索引擎的查询字符串，可以使用英文以获得更好的搜索结果）</span><br><span class="hljs-string"></span><br><span class="hljs-string">示例输出：</span><br><span class="hljs-string">[</span><br><span class="hljs-string">  &#123;&#123;</span><br><span class="hljs-string">    &quot;title&quot;: &quot;什么是多模态模型&quot;,</span><br><span class="hljs-string">    &quot;intent&quot;: &quot;了解多模态模型的基础概念，为后续研究打下基础&quot;,</span><br><span class="hljs-string">    &quot;query&quot;: &quot;multimodal model definition concept 2024&quot;</span><br><span class="hljs-string">  &#125;&#125;,</span><br><span class="hljs-string">  ...</span><br><span class="hljs-string">]</span><br><span class="hljs-string"></span><br><span class="hljs-string">请确保：</span><br><span class="hljs-string">1. 子任务数量在3-5个之间</span><br><span class="hljs-string">2. 子任务之间有逻辑关系（如从基础到应用，从现状到趋势）</span><br><span class="hljs-string">3. 搜索查询能够准确找到相关资料</span><br><span class="hljs-string">4. 只返回JSON，不要包含其他文本</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p><strong>关键设计点</strong>：提示词包含当前日期以获取最新信息，明确要求 JSON 格式输出便于解析，通过示例帮助 Agent 理解期望输出，并强调子任务数量、逻辑关系等约束。</p><p><strong>实现代码</strong>：</p><p>这里的 ToolAwareSimpleAgent 是根据 SimpleAgent 拓展实现，可以在 14.3.2 了解，这里不用深究。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">PlanningService</span>:<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, llm: HelloAgentsLLM</span>):<br>        self._agent = ToolAwareSimpleAgent(<br>            name=<span class="hljs-string">&quot;TODO Planner&quot;</span>,<br>            system_prompt=<span class="hljs-string">&quot;你是一个研究规划专家&quot;</span>,<br>            llm=llm,<br>            tool_call_listener=self._on_tool_call<br>        )<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">plan_todo_list</span>(<span class="hljs-params">self, state: SummaryState</span>) -&gt; <span class="hljs-type">List</span>[TodoItem]:<br>        prompt = todo_planner_instructions.<span class="hljs-built_in">format</span>(<br>            current_date=get_current_date(),<br>            research_topic=state.research_topic,<br>        )<br>        <br>        response = self._agent.run(prompt)<br>        tasks_payload = self._extract_tasks(response)<br>        <br>        todo_items = []<br>        <span class="hljs-keyword">for</span> idx, item <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(tasks_payload, start=<span class="hljs-number">1</span>):<br>            task = TodoItem(<br>                <span class="hljs-built_in">id</span>=idx,<br>                title=item[<span class="hljs-string">&quot;title&quot;</span>],<br>                intent=item[<span class="hljs-string">&quot;intent&quot;</span>],<br>                query=item[<span class="hljs-string">&quot;query&quot;</span>],<br>            )<br>            todo_items.append(task)<br>        <br>        <span class="hljs-keyword">return</span> todo_items<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_extract_tasks</span>(<span class="hljs-params">self, response: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;从Agent响应中提取JSON&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 使用正则表达式提取JSON部分</span><br>        json_match = re.search(<span class="hljs-string">r&#x27;\[.*\]&#x27;</span>, response, re.DOTALL)<br>        <span class="hljs-keyword">if</span> json_match:<br>            json_str = json_match.group(<span class="hljs-number">0</span>)<br>            <span class="hljs-keyword">return</span> json.loads(json_str)<br>        <span class="hljs-keyword">else</span>:<br>            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">&quot;无法从响应中提取JSON&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>Agent 2：任务总结专家（Task Summarizer）</strong></p><p><strong>职责</strong>：总结搜索结果，提取关键信息</p><p><strong>设计理念</strong>：任务总结专家的核心任务是阅读搜索结果，提取关键信息，并以结构化的方式呈现。这个过程类似于人类研究者在阅读文献后做笔记的过程。</p><p><strong>Prompt 设计</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><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><code class="hljs python">task_summarizer_instructions = <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">你是一个任务总结专家。你的任务是总结搜索结果，提取关键信息。</span><br><span class="hljs-string"></span><br><span class="hljs-string">任务标题：&#123;task_title&#125;</span><br><span class="hljs-string">任务意图：&#123;task_intent&#125;</span><br><span class="hljs-string">搜索查询：&#123;task_query&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">搜索结果：</span><br><span class="hljs-string">&#123;search_results&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">请仔细阅读以上搜索结果，提取关键信息，并以Markdown格式返回总结。</span><br><span class="hljs-string"></span><br><span class="hljs-string">总结应该包含：</span><br><span class="hljs-string">1. **核心观点**：搜索结果中的核心观点和结论</span><br><span class="hljs-string">2. **关键数据**：重要的数字、日期、名称等</span><br><span class="hljs-string">3. **来源引用**：为每个观点添加来源引用（使用[1]、[2]等标记）</span><br><span class="hljs-string"></span><br><span class="hljs-string">请确保：</span><br><span class="hljs-string">1. 总结简洁明了，避免冗余</span><br><span class="hljs-string">2. 保留重要的细节和数据</span><br><span class="hljs-string">3. 为每个观点添加来源引用</span><br><span class="hljs-string">4. 使用Markdown格式（标题、列表、加粗等）</span><br><span class="hljs-string"></span><br><span class="hljs-string">示例输出：</span><br><span class="hljs-string">## 核心观点</span><br><span class="hljs-string"></span><br><span class="hljs-string">多模态模型是一种能够处理多种类型数据的AI模型[1]。与传统的单模态模型不同，多模态模型可以同时理解文本、图像、音频等[2]。</span><br><span class="hljs-string"></span><br><span class="hljs-string">**关键特点：**</span><br><span class="hljs-string">- 跨模态理解[1]</span><br><span class="hljs-string">- 统一表示[3]</span><br><span class="hljs-string">- 端到端训练[2]</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 来源</span><br><span class="hljs-string"></span><br><span class="hljs-string">[1] https://example.com/source1</span><br><span class="hljs-string">[2] https://example.com/source2</span><br><span class="hljs-string">[3] https://example.com/source3</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p><strong>关键设计点</strong>：提示词包含任务标题、意图、查询等上下文帮助 Agent 理解任务，明确要求输出包含核心观点、关键数据、来源引用，强调为每个观点添加来源引用，并通过示例帮助 Agent 理解期望的输出格式。</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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">SummarizationService</span>:<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, llm: HelloAgentsLLM</span>):<br>        self._agent = ToolAwareSimpleAgent(<br>            name=<span class="hljs-string">&quot;Task Summarizer&quot;</span>,<br>            system_prompt=<span class="hljs-string">&quot;你是一个任务总结专家&quot;</span>,<br>            llm=llm,<br>            tool_call_listener=self._on_tool_call<br>        )<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">summarize_task</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        task: TodoItem,</span><br><span class="hljs-params">        search_results: <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]</span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-comment"># 格式化搜索结果</span><br>        formatted_sources = self._format_sources(search_results)<br>        <br>        prompt = task_summarizer_instructions.<span class="hljs-built_in">format</span>(<br>            task_title=task.title,<br>            task_intent=task.intent,<br>            task_query=task.query,<br>            search_results=formatted_sources,<br>        )<br>        <br>        summary = self._agent.run(prompt)<br>        <span class="hljs-keyword">return</span> summary<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_format_sources</span>(<span class="hljs-params">self, search_results: <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;格式化搜索结果&quot;&quot;&quot;</span><br>        formatted = []<br>        <span class="hljs-keyword">for</span> idx, result <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(search_results, start=<span class="hljs-number">1</span>):<br>            formatted.append(<br>                <span class="hljs-string">f&quot;[<span class="hljs-subst">&#123;idx&#125;</span>] <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;title&#x27;</span>]&#125;</span>\n&quot;</span><br>                <span class="hljs-string">f&quot;URL: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;url&#x27;</span>]&#125;</span>\n&quot;</span><br>                <span class="hljs-string">f&quot;摘要: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;snippet&#x27;</span>]&#125;</span>\n&quot;</span><br>            )<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;\n&quot;</span>.join(formatted)<br></code></pre></td></tr></table></figure><p><strong>Agent 3：报告撰写专家（Report Writer）</strong></p><p><strong>职责</strong>：整合所有子任务的总结，生成最终报告</p><p><strong>设计理念</strong>：报告撰写专家的核心任务是将所有子任务的总结整合成一份结构化的报告。这个过程类似于人类研究者在完成所有调研后撰写研究报告的过程。</p><p><strong>Prompt 设计</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><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></pre></td><td class="code"><pre><code class="hljs python">report_writer_instructions = <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">你是一个报告撰写专家。你的任务是整合所有子任务的总结，生成一份结构化的研究报告。</span><br><span class="hljs-string"></span><br><span class="hljs-string">研究主题：&#123;research_topic&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">子任务总结：</span><br><span class="hljs-string">&#123;task_summaries&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">请整合以上所有子任务的总结，生成一份结构化的研究报告。</span><br><span class="hljs-string"></span><br><span class="hljs-string">报告应该包含：</span><br><span class="hljs-string">1. **标题**：研究主题</span><br><span class="hljs-string">2. **概述**：简要介绍研究主题和报告结构（2-3段）</span><br><span class="hljs-string">3. **各个子任务的详细分析**：按照逻辑顺序组织（使用二级标题）</span><br><span class="hljs-string">4. **总结**：总结研究的主要发现（1-2段）</span><br><span class="hljs-string">5. **参考文献**：所有来源引用（按照子任务分组）</span><br><span class="hljs-string"></span><br><span class="hljs-string">请确保：</span><br><span class="hljs-string">1. 报告结构清晰，逻辑连贯</span><br><span class="hljs-string">2. 消除重复的信息</span><br><span class="hljs-string">3. 保留所有来源引用</span><br><span class="hljs-string">4. 使用Markdown格式</span><br><span class="hljs-string"></span><br><span class="hljs-string">示例输出：</span><br><span class="hljs-string"># 多模态大模型的最新进展</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 概述</span><br><span class="hljs-string"></span><br><span class="hljs-string">本报告系统地研究了多模态大模型的最新进展...</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 1. 什么是多模态模型</span><br><span class="hljs-string"></span><br><span class="hljs-string">（此处插入子任务1的总结）</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 2. 最新的多模态模型有哪些</span><br><span class="hljs-string"></span><br><span class="hljs-string">（此处插入子任务2的总结）</span><br><span class="hljs-string"></span><br><span class="hljs-string">...</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 总结</span><br><span class="hljs-string"></span><br><span class="hljs-string">通过本次研究，我们了解了...</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 参考文献</span><br><span class="hljs-string"></span><br><span class="hljs-string">### 任务1：什么是多模态模型</span><br><span class="hljs-string">[1] https://example.com/source1</span><br><span class="hljs-string">...</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p><strong>关键设计点</strong>：提示词明确要求报告包含标题、概述、详细分析、总结、参考文献等结构，强调按逻辑顺序组织内容，要求合并重复信息消除冗余，并保留所有来源引用。</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><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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ReportingService</span>:<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, llm: HelloAgentsLLM</span>):<br>        self._agent = ToolAwareSimpleAgent(<br>            name=<span class="hljs-string">&quot;Report Writer&quot;</span>,<br>            system_prompt=<span class="hljs-string">&quot;你是一个报告撰写专家&quot;</span>,<br>            llm=llm,<br>            tool_call_listener=self._on_tool_call<br>        )<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">generate_report</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        research_topic: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        task_summaries: <span class="hljs-type">List</span>[<span class="hljs-type">Tuple</span>[TodoItem, <span class="hljs-built_in">str</span>]]</span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-comment"># 格式化子任务总结</span><br>        formatted_summaries = self._format_summaries(task_summaries)<br>        <br>        prompt = report_writer_instructions.<span class="hljs-built_in">format</span>(<br>            research_topic=research_topic,<br>            task_summaries=formatted_summaries,<br>        )<br>        <br>        report = self._agent.run(prompt)<br>        <span class="hljs-keyword">return</span> report<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_format_summaries</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        task_summaries: <span class="hljs-type">List</span>[<span class="hljs-type">Tuple</span>[TodoItem, <span class="hljs-built_in">str</span>]]</span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;格式化子任务总结&quot;&quot;&quot;</span><br>        formatted = []<br>        <span class="hljs-keyword">for</span> idx, (task, summary) <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(task_summaries, start=<span class="hljs-number">1</span>):<br>            formatted.append(<br>                <span class="hljs-string">f&quot;## 任务<span class="hljs-subst">&#123;idx&#125;</span>：<span class="hljs-subst">&#123;task.title&#125;</span>\n&quot;</span><br>                <span class="hljs-string">f&quot;意图：<span class="hljs-subst">&#123;task.intent&#125;</span>\n\n&quot;</span><br>                <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;summary&#125;</span>\n&quot;</span><br>            )<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;\n&quot;</span>.join(formatted)<br></code></pre></td></tr></table></figure><h3 id="14-3-2-ToolAwareSimpleAgent-的设计"><a href="#14-3-2-ToolAwareSimpleAgent-的设计" class="headerlink" title="14.3.2 ToolAwareSimpleAgent 的设计"></a>14.3.2 ToolAwareSimpleAgent 的设计</h3><p>在第七章中，我们实现了<code>SimpleAgent</code>，它是 HelloAgents 框架的基础 Agent。但在深度研究助手中，我们需要一个能够<strong>记录工具调用</strong>的 Agent。这就是<code>ToolAwareSimpleAgent</code>的由来。</p><p>在深度研究助手中，我们需要记录每个 Agent 的工具调用情况，用于：</p><ol><li><strong>调试</strong>：查看 Agent 调用了哪些工具，传入了什么参数</li><li><strong>日志</strong>：记录研究过程中的所有操作</li><li><strong>分析</strong>：分析 Agent 的行为模式</li><li><strong>进度展示</strong>：实时显示 Agent 正在做什么</li></ol><p><code>SimpleAgent</code>本身不支持工具调用监听，因此我们需要扩展它。</p><p><code>ToolAwareSimpleAgent</code>在<code>SimpleAgent</code>的基础上增加了一个<code>tool_call_listener</code>参数，这是一个回调函数，每次工具调用时都会被调用。</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><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ToolAwareSimpleAgent<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">tool_listener</span>(<span class="hljs-params">call_info</span>):<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Agent: <span class="hljs-subst">&#123;call_info[<span class="hljs-string">&#x27;agent_name&#x27;</span>]&#125;</span>&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;工具: <span class="hljs-subst">&#123;call_info[<span class="hljs-string">&#x27;tool_name&#x27;</span>]&#125;</span>&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;参数: <span class="hljs-subst">&#123;call_info[<span class="hljs-string">&#x27;parsed_parameters&#x27;</span>]&#125;</span>&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;结果: <span class="hljs-subst">&#123;call_info[<span class="hljs-string">&#x27;result&#x27;</span>]&#125;</span>&quot;</span>)<br><br>agent = ToolAwareSimpleAgent(<br>    name=<span class="hljs-string">&quot;研究助手&quot;</span>,<br>    system_prompt=<span class="hljs-string">&quot;你是一个研究助手&quot;</span>,<br>    llm=llm,<br>    tool_call_listener=tool_listener<br>)<br></code></pre></td></tr></table></figure><p><code>ToolAwareSimpleAgent</code>继承自<code>SimpleAgent</code>，重写了<code>_execute_tool_call</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><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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ToolAwareSimpleAgent</span>(<span class="hljs-title class_ inherited__">SimpleAgent</span>):<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        name: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        system_prompt: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        llm: HelloAgentsLLM,</span><br><span class="hljs-params">        tool_registry: <span class="hljs-type">Optional</span>[ToolRegistry] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        tool_call_listener: <span class="hljs-type">Optional</span>[<span class="hljs-type">Callable</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    </span>):<br>        <span class="hljs-built_in">super</span>().__init__(<br>            name=name,<br>            system_prompt=system_prompt,<br>            llm=llm,<br>            tool_registry=tool_registry,<br>        )<br>        self._tool_call_listener = tool_call_listener<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_execute_tool_call</span>(<span class="hljs-params">self, tool_name: <span class="hljs-built_in">str</span>, parameters: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;执行工具调用，并通知监听器&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 解析参数</span><br>        parsed_parameters = self._parse_parameters(parameters)<br>        <br>        <span class="hljs-comment"># 调用工具</span><br>        result = <span class="hljs-built_in">super</span>()._execute_tool_call(tool_name, parameters)<br>        <br>        <span class="hljs-comment"># 通知监听器</span><br>        <span class="hljs-keyword">if</span> self._tool_call_listener:<br>            self._tool_call_listener(&#123;<br>                <span class="hljs-string">&quot;agent_name&quot;</span>: self.name,<br>                <span class="hljs-string">&quot;tool_name&quot;</span>: tool_name,<br>                <span class="hljs-string">&quot;parsed_parameters&quot;</span>: parsed_parameters,<br>                <span class="hljs-string">&quot;result&quot;</span>: result,<br>            &#125;)<br>        <br>        <span class="hljs-keyword">return</span> result<br></code></pre></td></tr></table></figure><p>在深度研究助手中，我们使用<code>ToolAwareSimpleAgent</code>来记录所有 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><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">DeepResearchAgent</span>:<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, config: Configuration</span>):<br>        self.config = config<br>        self.llm = HelloAgentsLLM(...)<br>        <br>        <span class="hljs-comment"># 创建工具调用监听器</span><br>        <span class="hljs-keyword">def</span> <span class="hljs-title function_">tool_listener</span>(<span class="hljs-params">call_info</span>):<br>            self._emit_event(&#123;<br>                <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;tool_call&quot;</span>,<br>                <span class="hljs-string">&quot;agent&quot;</span>: call_info[<span class="hljs-string">&quot;agent_name&quot;</span>],<br>                <span class="hljs-string">&quot;tool&quot;</span>: call_info[<span class="hljs-string">&quot;tool_name&quot;</span>],<br>                <span class="hljs-string">&quot;parameters&quot;</span>: call_info[<span class="hljs-string">&quot;parsed_parameters&quot;</span>],<br>            &#125;)<br>        <br>        <span class="hljs-comment"># 创建三个Agent，都使用相同的监听器</span><br>        self.planner = PlanningService(self.llm, tool_listener)<br>        self.summarizer = SummarizationService(self.llm, tool_listener)<br>        self.reporter = ReportingService(self.llm, tool_listener)<br></code></pre></td></tr></table></figure><p>这样，所有 Agent 的工具调用都会被记录，并通过 SSE 推送到前端，实时显示给用户。</p><h3 id="14-3-3-Agent-协作模式"><a href="#14-3-3-Agent-协作模式" class="headerlink" title="14.3.3 Agent 协作模式"></a>14.3.3 Agent 协作模式</h3><p>三个 Agent 之间是<strong>顺序协作</strong>的关系，如图 14.6 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/14-figures/14-6.png" alt="" width="85%"/>  <p>图 14.6 Agent 协作流程</p></div><p>顺序协作模式的特点是：</p><ol><li><strong>线性流程</strong>：Agent 按照固定的顺序执行</li><li><strong>明确的输入输出</strong>：每个 Agent 的输入来自上一个 Agent 的输出</li><li><strong>无并发</strong>：同一时间只有一个 Agent 在工作</li></ol><p><code>DeepResearchAgent</code>是整个系统的核心协调器，负责调度三个 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><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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">DeepResearchAgent</span>:<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, research_topic: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-comment"># 1. 规划阶段</span><br>        self._emit_event(&#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;status&quot;</span>, <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">&quot;正在规划研究任务...&quot;</span>&#125;)<br>        todo_list = self.planner.plan_todo_list(research_topic)<br>        self._emit_event(&#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;tasks&quot;</span>, <span class="hljs-string">&quot;tasks&quot;</span>: todo_list&#125;)<br>        <br>        <span class="hljs-comment"># 2. 执行阶段</span><br>        task_summaries = []<br>        <span class="hljs-keyword">for</span> task <span class="hljs-keyword">in</span> todo_list:<br>            self._emit_event(&#123;<br>                <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;status&quot;</span>,<br>                <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">f&quot;正在研究：<span class="hljs-subst">&#123;task.title&#125;</span>&quot;</span><br>            &#125;)<br>            <br>            <span class="hljs-comment"># 搜索</span><br>            search_results = self.search_service.search(task.query)<br>            <br>            <span class="hljs-comment"># 总结</span><br>            summary = self.summarizer.summarize_task(task, search_results)<br>            task_summaries.append((task, summary))<br>            <br>            self._emit_event(&#123;<br>                <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;task_completed&quot;</span>,<br>                <span class="hljs-string">&quot;task_id&quot;</span>: task.<span class="hljs-built_in">id</span><br>            &#125;)<br>        <br>        <span class="hljs-comment"># 3. 报告阶段</span><br>        self._emit_event(&#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;status&quot;</span>, <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">&quot;正在生成报告...&quot;</span>&#125;)<br>        report = self.reporter.generate_report(research_topic, task_summaries)<br>        self._emit_event(&#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;report&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: report&#125;)<br>        <br>        <span class="hljs-keyword">return</span> report<br></code></pre></td></tr></table></figure><h2 id="14-4-工具系统集成"><a href="#14-4-工具系统集成" class="headerlink" title="14.4 工具系统集成"></a>14.4 工具系统集成</h2><h3 id="14-4-1-SearchTool-扩展"><a href="#14-4-1-SearchTool-扩展" class="headerlink" title="14.4.1 SearchTool 扩展"></a>14.4.1 SearchTool 扩展</h3><p>在第七章中，我们实现了<code>SearchTool</code>的基础版本，集成了 Tavily 和 SerpApi 两个搜索引擎，展示了多源搜索的设计思想。在本章的深度研究助手中，我们进一步扩展了<code>SearchTool</code>的能力，新增了 DuckDuckGo、Perplexity、SearXNG 等搜索引擎，并实现了 Advanced 模式（组合多个搜索引擎）。搜索是深度研究助手最核心的功能，这些扩展使得系统能够适应不同的使用场景和需求。</p><p>如表 14.2 所示，这次增加的搜索引擎有不同的特点和适用场景。</p><div align="center">  <p>表 14.2 多搜索引擎对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/14-figures/14-table-2.png" alt="" width="85%"/></div><p>我们不再单独讨论如何扩展，可以参考源码以及第七章的拓展案例实现。<code>SearchTool</code>提供了统一的搜索接口，无论使用哪个搜索引擎，调用方式都是一样的。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># config.py</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">SearchAPI</span>(<span class="hljs-built_in">str</span>, Enum):<br>    TAVILY = <span class="hljs-string">&quot;tavily&quot;</span><br>    DUCKDUCKGO = <span class="hljs-string">&quot;duckduckgo&quot;</span><br>    PERPLEXITY = <span class="hljs-string">&quot;perplexity&quot;</span><br>    SEARXNG = <span class="hljs-string">&quot;searxng&quot;</span><br>    ADVANCED = <span class="hljs-string">&quot;advanced&quot;</span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Configuration</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    search_api: SearchAPI = SearchAPI.DUCKDUCKGO<br>    <span class="hljs-comment"># ...</span><br></code></pre></td></tr></table></figure><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><code class="hljs python"><span class="hljs-comment"># .env</span><br>SEARCH_API=tavily<br></code></pre></td></tr></table></figure><p>这样，用户可以通过修改<code>.env</code>文件来选择搜索引擎，无需修改代码。</p><p><code>SearchTool</code>返回的结果是一个字典，包含：</p><ul><li><code>results</code>：搜索结果列表，每个结果包含标题、URL、摘要</li><li><code>backend</code>：使用的搜索引擎</li><li><code>answer</code>：AI 生成的答案（仅 Perplexity）</li><li><code>notices</code>：通知信息（如 API 限制、错误等）</li></ul><p>以下是一些特殊情况的处理。</p><p>搜索结果可能包含重复的 URL，我们需要去重：</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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">deduplicate_sources</span>(<span class="hljs-params">sources: <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]</span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;去除重复的URL&quot;&quot;&quot;</span><br>    seen_urls = <span class="hljs-built_in">set</span>()<br>    unique_sources = []<br>    <br>    <span class="hljs-keyword">for</span> source <span class="hljs-keyword">in</span> sources:<br>        <span class="hljs-keyword">if</span> source[<span class="hljs-string">&quot;url&quot;</span>] <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> seen_urls:<br>            seen_urls.add(source[<span class="hljs-string">&quot;url&quot;</span>])<br>            unique_sources.append(source)<br>    <br>    <span class="hljs-keyword">return</span> unique_sources<br></code></pre></td></tr></table></figure><p>搜索结果可能包含大量文本，我们需要限制每个来源的 Token 数量：</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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">limit_source_tokens</span>(<span class="hljs-params">source: <span class="hljs-built_in">dict</span>, max_tokens: <span class="hljs-built_in">int</span> = <span class="hljs-number">2000</span></span>) -&gt; <span class="hljs-built_in">dict</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;限制来源的Token数量&quot;&quot;&quot;</span><br>    snippet = source[<span class="hljs-string">&quot;snippet&quot;</span>]<br>    <br>    <span class="hljs-comment"># 简单的Token估算：1个Token约等于4个字符</span><br>    max_chars = max_tokens * <span class="hljs-number">4</span><br>    <br>    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(snippet) &gt; max_chars:<br>        snippet = snippet[:max_chars] + <span class="hljs-string">&quot;...&quot;</span><br>    <br>    <span class="hljs-keyword">return</span> &#123;<br>        **source,<br>        <span class="hljs-string">&quot;snippet&quot;</span>: snippet<br>    &#125;<br></code></pre></td></tr></table></figure><h3 id="14-4-2-NoteTool-使用"><a href="#14-4-2-NoteTool-使用" class="headerlink" title="14.4.2 NoteTool 使用"></a>14.4.2 NoteTool 使用</h3><p>在深度研究助手中，我们使用<code>NoteTool</code>来持久化研究进度。<code>NoteTool</code>是第九章集成的内置工具，用于创建、读取、更新和删除笔记。</p><p>在研究过程中，我们需要记录每个子任务的搜索结果、总结以及最终的研究报告。这些信息需要持久化到磁盘，以便在研究过程中断时能够从上次的进度继续，同时也方便查看研究过程中的所有操作，分析研究的质量和效率。</p><p><code>NoteTool</code>将笔记存储在指定的工作空间目录中，每个笔记是一个 Markdown 文件。笔记的文件名是任务 ID，内容包含任务标题、任务意图、搜索查询、搜索结果和总结。</p><p>最后生成的文件风格会是下面的树状图风格：</p><figure class="highlight jboss-cli"><table><tr><td class="gutter"><pre><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><code class="hljs jboss-cli">workspace/<br>├── notes/<br>│   ├── 1.md  <span class="hljs-comment"># 任务1的笔记</span><br>│   ├── 2.md  <span class="hljs-comment"># 任务2的笔记</span><br>│   ├── 3.md  <span class="hljs-comment"># 任务3的笔记</span><br>│   └── <span class="hljs-string">...</span><br>└── reports/<br>    └── final_report.md  <span class="hljs-comment"># 最终报告</span><br></code></pre></td></tr></table></figure><p>在深度研究助手中，我们使用<code>NoteTool</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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">NotesService</span>:<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, workspace: <span class="hljs-built_in">str</span></span>):<br>        self.note_tool = NoteTool(workspace=workspace)<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">save_task_summary</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        task: TodoItem,</span><br><span class="hljs-params">        search_results: <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>],</span><br><span class="hljs-params">        summary: <span class="hljs-built_in">str</span></span><br><span class="hljs-params">    </span>):<br>        <span class="hljs-string">&quot;&quot;&quot;保存任务总结&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 格式化笔记内容</span><br>        content = self._format_note_content(<br>            task=task,<br>            search_results=search_results,<br>            summary=summary<br>        )<br>        <br>        <span class="hljs-comment"># 创建笔记</span><br>        self.note_tool.run(&#123;<br>            <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create&quot;</span>,<br>            <span class="hljs-string">&quot;title&quot;</span>: <span class="hljs-string">f&quot;任务<span class="hljs-subst">&#123;task.<span class="hljs-built_in">id</span>&#125;</span>：<span class="hljs-subst">&#123;task.title&#125;</span>&quot;</span>,<br>            <span class="hljs-string">&quot;content&quot;</span>: content,<br>            <span class="hljs-string">&quot;tags&quot;</span>: [<span class="hljs-string">&quot;research&quot;</span>, <span class="hljs-string">&quot;summary&quot;</span>]<br>        &#125;)<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_format_note_content</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        task: TodoItem,</span><br><span class="hljs-params">        search_results: <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>],</span><br><span class="hljs-params">        summary: <span class="hljs-built_in">str</span></span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;格式化笔记内容&quot;&quot;&quot;</span><br>        content = <span class="hljs-string">f&quot;# 任务<span class="hljs-subst">&#123;task.<span class="hljs-built_in">id</span>&#125;</span>：<span class="hljs-subst">&#123;task.title&#125;</span>\n\n&quot;</span><br>        content += <span class="hljs-string">f&quot;## 任务信息\n\n&quot;</span><br>        content += <span class="hljs-string">f&quot;- **意图**：<span class="hljs-subst">&#123;task.intent&#125;</span>\n&quot;</span><br>        content += <span class="hljs-string">f&quot;- **查询**：<span class="hljs-subst">&#123;task.query&#125;</span>\n\n&quot;</span><br>        <br>        content += <span class="hljs-string">f&quot;## 搜索结果\n\n&quot;</span><br>        <span class="hljs-keyword">for</span> idx, result <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(search_results, start=<span class="hljs-number">1</span>):<br>            content += <span class="hljs-string">f&quot;[<span class="hljs-subst">&#123;idx&#125;</span>] <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;title&#x27;</span>]&#125;</span>\n&quot;</span><br>            content += <span class="hljs-string">f&quot;URL: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;url&#x27;</span>]&#125;</span>\n&quot;</span><br>            content += <span class="hljs-string">f&quot;摘要: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;snippet&#x27;</span>]&#125;</span>\n\n&quot;</span><br>        <br>        content += <span class="hljs-string">f&quot;## 总结\n\n<span class="hljs-subst">&#123;summary&#125;</span>\n&quot;</span><br>        <br>        <span class="hljs-keyword">return</span> content<br></code></pre></td></tr></table></figure><h3 id="14-4-3-ToolRegistry-工具管理"><a href="#14-4-3-ToolRegistry-工具管理" class="headerlink" title="14.4.3 ToolRegistry 工具管理"></a>14.4.3 ToolRegistry 工具管理</h3><p><code>ToolRegistry</code>是 HelloAgents 框架的工具注册表，同样也是在我们的第七章所支持，用于管理所有工具的注册和调用。在深度研究助手中，我们使用<code>ToolRegistry</code>来管理<code>SearchTool</code>和<code>NoteTool</code>。</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><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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ToolAwareSimpleAgent<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> ToolRegistry<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> SearchTool<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> NoteTool<br><br><span class="hljs-comment"># 创建工具</span><br>search_tool = SearchTool(backend=<span class="hljs-string">&quot;hybrid&quot;</span>)<br>note_tool = NoteTool(workspace=<span class="hljs-string">&quot;./workspace/notes&quot;</span>)<br><br><span class="hljs-comment"># 创建注册表</span><br>registry = ToolRegistry()<br><br><span class="hljs-comment"># 注册工具</span><br>registry.register_tool(search_tool)<br>registry.register_tool(note_tool)<br><br><span class="hljs-comment"># 创建Agent</span><br>agent = ToolAwareSimpleAgent(<br>    name=<span class="hljs-string">&quot;研究助手&quot;</span>,<br>    system_prompt=<span class="hljs-string">&quot;你是一个研究助手&quot;</span>,<br>    llm=llm,<br>    tool_registry=registry<br>)<br></code></pre></td></tr></table></figure><p>当 Agent 需要调用工具时，它会生成工具调用指令，如图 14.7 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/14-figures/14-7.png" alt="" width="85%"/>  <p>图 14.7 工具调用流程</p></div><p>**工具调用流程<strong>：</p><ol><li></strong>Agent 生成指令<strong>：Agent 生成工具调用指令，如<code>[TOOL_CALL:search_tool:&#123;&quot;input&quot;: &quot;Datawhale组织&quot;, &quot;backend&quot;: &quot;tavily&quot;&#125;]</code></li><li></strong>解析指令<strong>：<code>ToolRegistry</code>解析指令，提取工具名称和参数</li><li></strong>查找工具<strong>：<code>ToolRegistry</code>根据工具名称查找对应的工具</li><li></strong>调用工具<strong>：调用工具的<code>run</code>方法，传入参数</li><li></strong>返回结果<strong>：工具返回执行结果</li><li></strong>格式化结果<strong>：将结果格式化为字符串，返回给 Agent</li></ol><h2 id="14-5-服务层实现"><a href="#14-5-服务层实现" class="headerlink" title="14.5 服务层实现"></a>14.5 服务层实现</h2><p>本节将详细介绍核心服务的实现，包括 PlanningService、SummarizationService、ReportingService 和 SearchService。这些服务是连接 Agent 和工具的桥梁，负责具体的业务逻辑。</p><h3 id="14-5-1-任务规划服务"><a href="#14-5-1-任务规划服务" class="headerlink" title="14.5.1 任务规划服务"></a>14.5.1 任务规划服务</h3><p><code>PlanningService</code>负责调用研究规划 Agent，将研究主题分解为子任务。这是整个研究流程的第一步，也是最关键的一步。</p><p></strong>（1）方案实现<strong></p><p>它的核心职责是：</p><ol><li></strong>构建规划 Prompt<strong>：根据研究主题和当前日期构建 Prompt</li><li></strong>调用规划 Agent<strong>：调用 TODO Planner Agent 生成子任务列表</li><li></strong>解析 JSON 响应<strong>：从 Agent 的响应中提取 JSON 格式的子任务列表</li><li></strong>验证子任务格式**：确保每个子任务包含必需的字段（title、intent、query）</li></ol><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> re<br><span class="hljs-keyword">import</span> json<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">List</span>, <span class="hljs-type">Callable</span>, <span class="hljs-type">Optional</span><br><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ToolAwareSimpleAgent<br><span class="hljs-keyword">from</span> models <span class="hljs-keyword">import</span> TodoItem, SummaryState<br><span class="hljs-keyword">from</span> prompts <span class="hljs-keyword">import</span> todo_planner_instructions<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">PlanningService</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;任务规划服务&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        llm: HelloAgentsLLM,</span><br><span class="hljs-params">        tool_call_listener: <span class="hljs-type">Optional</span>[<span class="hljs-type">Callable</span>] = <span class="hljs-literal">None</span></span><br><span class="hljs-params">    </span>):<br>        self._llm = llm<br>        self._tool_call_listener = tool_call_listener<br><br>        <span class="hljs-comment"># 创建规划Agent</span><br>        self._agent = ToolAwareSimpleAgent(<br>            name=<span class="hljs-string">&quot;TODO Planner&quot;</span>,<br>            system_prompt=<span class="hljs-string">&quot;你是一个研究规划专家，擅长将复杂的研究主题分解为清晰的子任务。&quot;</span>,<br>            llm=llm,<br>            tool_call_listener=tool_call_listener<br>        )<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">plan_todo_list</span>(<span class="hljs-params">self, state: SummaryState</span>) -&gt; <span class="hljs-type">List</span>[TodoItem]:<br>        <span class="hljs-string">&quot;&quot;&quot;规划TODO列表</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Args:</span><br><span class="hljs-string">            state: 研究状态，包含研究主题</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Returns:</span><br><span class="hljs-string">            子任务列表</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 构建Prompt</span><br>        prompt = todo_planner_instructions.<span class="hljs-built_in">format</span>(<br>            current_date=self._get_current_date(),<br>            research_topic=state.research_topic,<br>        )<br><br>        <span class="hljs-comment"># 调用Agent</span><br>        response = self._agent.run(prompt)<br><br>        <span class="hljs-comment"># 解析JSON</span><br>        tasks_payload = self._extract_tasks(response)<br><br>        <span class="hljs-comment"># 验证并创建TodoItem</span><br>        todo_items = []<br>        <span class="hljs-keyword">for</span> idx, item <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(tasks_payload, start=<span class="hljs-number">1</span>):<br>            <span class="hljs-comment"># 验证必需字段</span><br>            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> <span class="hljs-built_in">all</span>(key <span class="hljs-keyword">in</span> item <span class="hljs-keyword">for</span> key <span class="hljs-keyword">in</span> [<span class="hljs-string">&quot;title&quot;</span>, <span class="hljs-string">&quot;intent&quot;</span>, <span class="hljs-string">&quot;query&quot;</span>]):<br>                <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f&quot;任务<span class="hljs-subst">&#123;idx&#125;</span>缺少必需字段&quot;</span>)<br><br>            task = TodoItem(<br>                <span class="hljs-built_in">id</span>=idx,<br>                title=item[<span class="hljs-string">&quot;title&quot;</span>],<br>                intent=item[<span class="hljs-string">&quot;intent&quot;</span>],<br>                query=item[<span class="hljs-string">&quot;query&quot;</span>],<br>            )<br>            todo_items.append(task)<br><br>        <span class="hljs-keyword">return</span> todo_items<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_get_current_date</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;获取当前日期&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> datetime.now().strftime(<span class="hljs-string">&quot;%Y年%m月%d日&quot;</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_extract_tasks</span>(<span class="hljs-params">self, response: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;从Agent响应中提取JSON</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Agent的响应可能包含额外的文本，如：</span><br><span class="hljs-string">        &quot;好的，我将为您规划以下任务：\n[&#123;...&#125;, &#123;...&#125;]\n这些任务涵盖了...&quot;</span><br><span class="hljs-string"></span><br><span class="hljs-string">        我们需要提取其中的JSON部分。</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 方法1：使用正则表达式提取JSON数组</span><br>        json_match = re.search(<span class="hljs-string">r&#x27;\[.*\]&#x27;</span>, response, re.DOTALL)<br>        <span class="hljs-keyword">if</span> json_match:<br>            json_str = json_match.group(<span class="hljs-number">0</span>)<br>            <span class="hljs-keyword">try</span>:<br>                <span class="hljs-keyword">return</span> json.loads(json_str)<br>            <span class="hljs-keyword">except</span> json.JSONDecodeError <span class="hljs-keyword">as</span> e:<br>                <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f&quot;JSON解析失败：<span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br><br>        <span class="hljs-comment"># 方法2：如果没有找到JSON数组，尝试直接解析整个响应</span><br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-keyword">return</span> json.loads(response)<br>        <span class="hljs-keyword">except</span> json.JSONDecodeError:<br>            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">&quot;无法从响应中提取JSON&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>（2）JSON 解析与验证</strong></p><p>Agent 返回的 JSON 可能包含额外的文本或格式错误，我们需要 robust 的解析逻辑：</p><p><strong>常见问题</strong>：</p><ol><li><strong>包含额外文本</strong>：Agent 可能在 JSON 前后添加说明文字</li><li><strong>格式错误</strong>：JSON 可能缺少引号、逗号等</li><li><strong>字段缺失</strong>：某些子任务可能缺少必需字段</li></ol><p><strong>解决方案</strong>：</p><ol><li><strong>使用正则表达式</strong>：提取 JSON 部分</li><li><strong>多种解析策略</strong>：先尝试提取 JSON 数组，再尝试直接解析</li><li><strong>字段验证</strong>：确保每个子任务包含必需字段</li></ol><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><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><code class="hljs python"><span class="hljs-comment"># Agent响应示例1：包含额外文本</span><br>response1 = <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">好的，我将为您规划以下任务：</span><br><span class="hljs-string"></span><br><span class="hljs-string">[</span><br><span class="hljs-string">  &#123;</span><br><span class="hljs-string">    &quot;title&quot;: &quot;什么是多模态模型&quot;,</span><br><span class="hljs-string">    &quot;intent&quot;: &quot;了解基础概念&quot;,</span><br><span class="hljs-string">    &quot;query&quot;: &quot;multimodal model definition&quot;</span><br><span class="hljs-string">  &#125;,</span><br><span class="hljs-string">  &#123;</span><br><span class="hljs-string">    &quot;title&quot;: &quot;最新的多模态模型&quot;,</span><br><span class="hljs-string">    &quot;intent&quot;: &quot;了解技术现状&quot;,</span><br><span class="hljs-string">    &quot;query&quot;: &quot;latest multimodal models 2024&quot;</span><br><span class="hljs-string">  &#125;</span><br><span class="hljs-string">]</span><br><span class="hljs-string"></span><br><span class="hljs-string">这些任务涵盖了Datawhale组织的基本信息和核心项目。</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># 提取JSON</span><br>tasks1 = service._extract_tasks(response1)<br><span class="hljs-comment"># 结果：[&#123;&quot;title&quot;: &quot;Datawhale的基本信息&quot;, ...&#125;, ...]</span><br><br><span class="hljs-comment"># Agent响应示例2：纯JSON</span><br>response2 = <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">[</span><br><span class="hljs-string">  &#123;&quot;title&quot;: &quot;Datawhale的基本信息&quot;, &quot;intent&quot;: &quot;了解组织定位&quot;, &quot;query&quot;: &quot;Datawhale organization introduction&quot;&#125;,</span><br><span class="hljs-string">  &#123;&quot;title&quot;: &quot;Datawhale的主要项目&quot;, &quot;intent&quot;: &quot;了解核心内容&quot;, &quot;query&quot;: &quot;Datawhale projects tutorials 2024&quot;&#125;</span><br><span class="hljs-string">]</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># 提取JSON</span><br>tasks2 = service._extract_tasks(response2)<br><span class="hljs-comment"># 结果：[&#123;&quot;title&quot;: &quot;什么是多模态模型&quot;, ...&#125;, ...]</span><br></code></pre></td></tr></table></figure><p><strong>（3）规划质量评估</strong></p><p>一个好的规划应该满足以下标准：</p><ol><li><strong>覆盖全面</strong>：涵盖主题的所有重要方面</li><li><strong>逻辑清晰</strong>：子任务之间有明确的逻辑关系</li><li><strong>查询精准</strong>：搜索查询能够准确找到相关资料</li><li><strong>数量适中</strong>：3-5 个子任务</li></ol><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">evaluate_plan</span>(<span class="hljs-params">self, todo_items: <span class="hljs-type">List</span>[TodoItem]</span>) -&gt; <span class="hljs-built_in">dict</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;评估规划质量</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        评估结果，包含分数和建议</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    score = <span class="hljs-number">100</span><br>    suggestions = []<br><br>    <span class="hljs-comment"># 检查数量</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(todo_items) &lt; <span class="hljs-number">3</span>:<br>        score -= <span class="hljs-number">20</span><br>        suggestions.append(<span class="hljs-string">&quot;子任务数量过少，可能遗漏重要信息&quot;</span>)<br>    <span class="hljs-keyword">elif</span> <span class="hljs-built_in">len</span>(todo_items) &gt; <span class="hljs-number">5</span>:<br>        score -= <span class="hljs-number">10</span><br>        suggestions.append(<span class="hljs-string">&quot;子任务数量过多，可能存在冗余&quot;</span>)<br><br>    <span class="hljs-comment"># 检查查询质量</span><br>    <span class="hljs-keyword">for</span> task <span class="hljs-keyword">in</span> todo_items:<br>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(task.query.split()) &lt; <span class="hljs-number">2</span>:<br>            score -= <span class="hljs-number">10</span><br>            suggestions.append(<span class="hljs-string">f&quot;任务「<span class="hljs-subst">&#123;task.title&#125;</span>」的查询过于简单&quot;</span>)<br><br>    <span class="hljs-comment"># 检查逻辑关系</span><br>    <span class="hljs-comment"># （这里可以添加更复杂的逻辑检查）</span><br><br>    <span class="hljs-keyword">return</span> &#123;<br>        <span class="hljs-string">&quot;score&quot;</span>: score,<br>        <span class="hljs-string">&quot;suggestions&quot;</span>: suggestions<br>    &#125;<br></code></pre></td></tr></table></figure><h3 id="14-5-2-总结服务"><a href="#14-5-2-总结服务" class="headerlink" title="14.5.2 总结服务"></a>14.5.2 总结服务</h3><p><code>SummarizationService</code>负责调用任务总结 Agent，总结搜索结果。这是研究流程的核心环节，决定了研究的质量。</p><p>它的职责是：</p><ol><li><strong>格式化搜索结果</strong>：将搜索结果格式化为易读的文本</li><li><strong>构建总结 Prompt</strong>：根据任务信息和搜索结果构建 Prompt</li><li><strong>调用总结 Agent</strong>：调用 Task Summarizer Agent 生成总结</li><li><strong>提取来源引用</strong>：从总结中提取来源引用</li></ol><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">List</span>, <span class="hljs-type">Callable</span>, <span class="hljs-type">Optional</span>, <span class="hljs-type">Tuple</span><br><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ToolAwareSimpleAgent<br><span class="hljs-keyword">from</span> models <span class="hljs-keyword">import</span> TodoItem<br><span class="hljs-keyword">from</span> prompts <span class="hljs-keyword">import</span> task_summarizer_instructions<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">SummarizationService</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;总结服务&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        llm: HelloAgentsLLM,</span><br><span class="hljs-params">        tool_call_listener: <span class="hljs-type">Optional</span>[<span class="hljs-type">Callable</span>] = <span class="hljs-literal">None</span></span><br><span class="hljs-params">    </span>):<br>        self._llm = llm<br>        self._tool_call_listener = tool_call_listener<br><br>        <span class="hljs-comment"># 创建总结Agent</span><br>        self._agent = ToolAwareSimpleAgent(<br>            name=<span class="hljs-string">&quot;Task Summarizer&quot;</span>,<br>            system_prompt=<span class="hljs-string">&quot;你是一个任务总结专家，擅长从搜索结果中提取关键信息。&quot;</span>,<br>            llm=llm,<br>            tool_call_listener=tool_call_listener<br>        )<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">summarize_task</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        task: TodoItem,</span><br><span class="hljs-params">        search_results: <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]</span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-type">Tuple</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>]]:<br>        <span class="hljs-string">&quot;&quot;&quot;总结任务</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Args:</span><br><span class="hljs-string">            task: 任务信息</span><br><span class="hljs-string">            search_results: 搜索结果列表</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Returns:</span><br><span class="hljs-string">            (总结文本, 来源URL列表)</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 格式化搜索结果</span><br>        formatted_sources = self._format_sources(search_results)<br><br>        <span class="hljs-comment"># 构建Prompt</span><br>        prompt = task_summarizer_instructions.<span class="hljs-built_in">format</span>(<br>            task_title=task.title,<br>            task_intent=task.intent,<br>            task_query=task.query,<br>            search_results=formatted_sources,<br>        )<br><br>        <span class="hljs-comment"># 调用Agent</span><br>        summary = self._agent.run(prompt)<br><br>        <span class="hljs-comment"># 提取来源URL</span><br>        source_urls = [result[<span class="hljs-string">&quot;url&quot;</span>] <span class="hljs-keyword">for</span> result <span class="hljs-keyword">in</span> search_results]<br><br>        <span class="hljs-keyword">return</span> summary, source_urls<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_format_sources</span>(<span class="hljs-params">self, search_results: <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;格式化搜索结果</span><br><span class="hljs-string"></span><br><span class="hljs-string">        将搜索结果格式化为易读的文本，包含：</span><br><span class="hljs-string">        - 序号</span><br><span class="hljs-string">        - 标题</span><br><span class="hljs-string"></span><br><span class="hljs-string">### 报告结构设计</span><br><span class="hljs-string"></span><br><span class="hljs-string">最终报告应该包含以下部分，.......</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 参考文献</span><br><span class="hljs-string"></span><br><span class="hljs-string">### 任务1：什么是多模态模型</span><br><span class="hljs-string">- https://example.com/multimodal-model-definition</span><br><span class="hljs-string">....</span><br><span class="hljs-string"></span><br><span class="hljs-string">### 任务2：最新的多模态模型有哪些</span><br><span class="hljs-string">- https://example.com/gpt4v</span><br><span class="hljs-string">....</span><br><span class="hljs-string">...</span><br></code></pre></td></tr></table></figure><h3 id="14-5-3-报告生成服务"><a href="#14-5-3-报告生成服务" class="headerlink" title="14.5.3 报告生成服务"></a>14.5.3 报告生成服务</h3><p><code>ReportingService</code>负责调用报告生成 Agent，整合所有子任务的总结。这是研究流程的最后一步，生成最终的研究报告。</p><p>它的职责是：</p><ol><li><strong>格式化子任务总结</strong>：将所有子任务的总结格式化为统一的格式</li><li><strong>构建报告 Prompt</strong>：根据研究主题和子任务总结构建 Prompt</li><li><strong>调用报告 Agent</strong>：调用 Report Writer Agent 生成最终报告</li><li><strong>整理引用</strong>：将所有来源引用整理到参考文献部分</li></ol><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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">List</span>, <span class="hljs-type">Callable</span>, <span class="hljs-type">Optional</span>, <span class="hljs-type">Tuple</span><br><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ToolAwareSimpleAgent<br><span class="hljs-keyword">from</span> models <span class="hljs-keyword">import</span> TodoItem<br><span class="hljs-keyword">from</span> prompts <span class="hljs-keyword">import</span> report_writer_instructions<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ReportingService</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;报告生成服务&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        llm: HelloAgentsLLM,</span><br><span class="hljs-params">        tool_call_listener: <span class="hljs-type">Optional</span>[<span class="hljs-type">Callable</span>] = <span class="hljs-literal">None</span></span><br><span class="hljs-params">    </span>):<br>        self._llm = llm<br>        self._tool_call_listener = tool_call_listener<br><br>        <span class="hljs-comment"># 创建报告Agent</span><br>        self._agent = ToolAwareSimpleAgent(<br>            name=<span class="hljs-string">&quot;Report Writer&quot;</span>,<br>            system_prompt=<span class="hljs-string">&quot;你是一个报告撰写专家，擅长整合信息并生成结构化的报告。&quot;</span>,<br>            llm=llm,<br>            tool_call_listener=tool_call_listener<br>        )<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">generate_report</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        research_topic: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        task_summaries: <span class="hljs-type">List</span>[<span class="hljs-type">Tuple</span>[TodoItem, <span class="hljs-built_in">str</span>, <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>]]]</span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;生成最终报告</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Args:</span><br><span class="hljs-string">            research_topic: 研究主题</span><br><span class="hljs-string">            task_summaries: 子任务总结列表，每个元素是(任务, 总结, 来源URL列表)</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Returns:</span><br><span class="hljs-string">            最终报告（Markdown格式）</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 格式化子任务总结</span><br>        formatted_summaries = self._format_summaries(task_summaries)<br><br>        <span class="hljs-comment"># 构建Prompt</span><br>        prompt = report_writer_instructions.<span class="hljs-built_in">format</span>(<br>            research_topic=research_topic,<br>            task_summaries=formatted_summaries,<br>        )<br><br>        <span class="hljs-comment"># 调用Agent</span><br>        report = self._agent.run(prompt)<br><br>        <span class="hljs-keyword">return</span> report<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_format_summaries</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        task_summaries: <span class="hljs-type">List</span>[<span class="hljs-type">Tuple</span>[TodoItem, <span class="hljs-built_in">str</span>, <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>]]]</span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;格式化子任务总结</span><br><span class="hljs-string"></span><br><span class="hljs-string">        将所有子任务的总结格式化为统一的格式，包含：</span><br><span class="hljs-string">        - 任务序号</span><br><span class="hljs-string">        - 任务标题</span><br><span class="hljs-string">        - 任务意图</span><br><span class="hljs-string">        - 总结内容</span><br><span class="hljs-string">        - 来源URL</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        formatted = []<br>        <span class="hljs-keyword">for</span> idx, (task, summary, source_urls) <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(task_summaries, start=<span class="hljs-number">1</span>):<br>            formatted.append(<br>                <span class="hljs-string">f&quot;## 任务<span class="hljs-subst">&#123;idx&#125;</span>：<span class="hljs-subst">&#123;task.title&#125;</span>\n\n&quot;</span><br>                <span class="hljs-string">f&quot;**意图**：<span class="hljs-subst">&#123;task.intent&#125;</span>\n\n&quot;</span><br>                <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;summary&#125;</span>\n\n&quot;</span><br>                <span class="hljs-string">f&quot;**来源**：\n&quot;</span><br>            )<br>            <span class="hljs-keyword">for</span> url <span class="hljs-keyword">in</span> source_urls:<br>                formatted.append(<span class="hljs-string">f&quot;- <span class="hljs-subst">&#123;url&#125;</span>\n&quot;</span>)<br>            formatted.append(<span class="hljs-string">&quot;\n&quot;</span>)<br><br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;</span>.join(formatted)<br></code></pre></td></tr></table></figure><h3 id="14-5-4-搜索调度服务"><a href="#14-5-4-搜索调度服务" class="headerlink" title="14.5.4 搜索调度服务"></a>14.5.4 搜索调度服务</h3><p><code>SearchService</code>负责调度搜索引擎，执行搜索并返回结果。这是连接 Agent 和 SearchTool 的桥梁。在这里我们没有采用往常一样的使得 simpleAgent 直接调用工具的形式，而是将 SearchTool 的执行结果通过中间层来返回给 Agent，这样会使得 Agent 更加专注处理得到的信息。</p><p>它的职责是：</p><ol><li><strong>调度搜索引擎</strong>：根据配置选择搜索引擎</li><li><strong>执行搜索</strong>：调用 SearchTool 执行搜索</li><li><strong>处理结果</strong>：去重、限制 Token、格式化</li><li><strong>错误处理</strong>：处理搜索失败的情况</li></ol><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">List</span>, <span class="hljs-type">Optional</span><br><span class="hljs-keyword">import</span> logging<br><br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> SearchTool<br><span class="hljs-keyword">from</span> config <span class="hljs-keyword">import</span> Configuration<br><br>logger = logging.getLogger(__name__)<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">SearchService</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;搜索调度服务&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, config: Configuration</span>):<br>        self.config = config<br><br>        <span class="hljs-comment"># 创建SearchTool</span><br>        self.search_tool = SearchTool(backend=<span class="hljs-string">&quot;hybrid&quot;</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">search</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        query: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        max_results: <span class="hljs-built_in">int</span> = <span class="hljs-number">5</span></span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;执行搜索</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Args:</span><br><span class="hljs-string">            query: 搜索查询</span><br><span class="hljs-string">            max_results: 最大结果数量</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Returns:</span><br><span class="hljs-string">            搜索结果列表</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-comment"># 调用SearchTool</span><br>            raw_response = self.search_tool.run(&#123;<br>                <span class="hljs-string">&quot;input&quot;</span>: query,<br>                <span class="hljs-string">&quot;backend&quot;</span>: self.config.search_api.value,<br>                <span class="hljs-string">&quot;mode&quot;</span>: <span class="hljs-string">&quot;structured&quot;</span>,<br>                <span class="hljs-string">&quot;max_results&quot;</span>: max_results<br>            &#125;)<br><br>            <span class="hljs-comment"># 提取结果</span><br>            results = raw_response.get(<span class="hljs-string">&quot;results&quot;</span>, [])<br><br>            <span class="hljs-comment"># 处理结果</span><br>            results = self._deduplicate_sources(results)<br>            results = self._limit_source_tokens(results)<br><br>            logger.info(<span class="hljs-string">f&quot;搜索成功：<span class="hljs-subst">&#123;query&#125;</span>，返回<span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(results)&#125;</span>个结果&quot;</span>)<br><br>            <span class="hljs-keyword">return</span> results<br><br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            logger.error(<span class="hljs-string">f&quot;搜索失败：<span class="hljs-subst">&#123;query&#125;</span>，错误：<span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>            <span class="hljs-keyword">return</span> []<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_deduplicate_sources</span>(<span class="hljs-params">self, sources: <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]</span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;去除重复的URL&quot;&quot;&quot;</span><br>        seen_urls = <span class="hljs-built_in">set</span>()<br>        unique_sources = []<br><br>        <span class="hljs-keyword">for</span> source <span class="hljs-keyword">in</span> sources:<br>            url = source.get(<span class="hljs-string">&quot;url&quot;</span>, <span class="hljs-string">&quot;&quot;</span>)<br>            <span class="hljs-keyword">if</span> url <span class="hljs-keyword">and</span> url <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> seen_urls:<br>                seen_urls.add(url)<br>                unique_sources.append(source)<br><br>        <span class="hljs-keyword">return</span> unique_sources<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_limit_source_tokens</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        sources: <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>],</span><br><span class="hljs-params">        max_tokens_per_source: <span class="hljs-built_in">int</span> = <span class="hljs-number">2000</span></span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;限制每个来源的Token数量&quot;&quot;&quot;</span><br>        limited_sources = []<br><br>        <span class="hljs-keyword">for</span> source <span class="hljs-keyword">in</span> sources:<br>            snippet = source.get(<span class="hljs-string">&quot;snippet&quot;</span>, <span class="hljs-string">&quot;&quot;</span>)<br><br>            <span class="hljs-comment"># 简单的Token估算：1个Token约等于4个字符</span><br>            max_chars = max_tokens_per_source * <span class="hljs-number">4</span><br><br>            <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(snippet) &gt; max_chars:<br>                snippet = snippet[:max_chars] + <span class="hljs-string">&quot;...&quot;</span><br><br>            limited_sources.append(&#123;<br>                **source,<br>                <span class="hljs-string">&quot;snippet&quot;</span>: snippet<br>            &#125;)<br><br>        <span class="hljs-keyword">return</span> limited_sources<br></code></pre></td></tr></table></figure><p>根据配置选择搜索引擎，如图 14.8 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/14-figures/14-8.png" alt="" width="85%"/>  <p>图 14.8 搜索引擎调度流程</p></div><p>**调度逻辑<strong>：</p><ol><li></strong>读取配置<strong>：从<code>.env</code>文件读取<code>SEARCH_API</code>配置</li><li></strong>选择引擎<strong>：根据配置选择搜索引擎（tavily、duckduckgo、perplexity 等）</li><li></strong>执行搜索<strong>：调用 SearchTool 执行搜索</li><li></strong>处理结果<strong>：去重、限制 Token、格式化</li><li></strong>返回结果<strong>：返回处理后的搜索结果</li></ol><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> hashlib<br><span class="hljs-keyword">import</span> json<br><span class="hljs-keyword">from</span> pathlib <span class="hljs-keyword">import</span> Path<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">SearchService</span>:<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, config: Configuration</span>):<br>        self.config = config<br>        self.search_tool = SearchTool(backend=<span class="hljs-string">&quot;hybrid&quot;</span>)<br><br>        <span class="hljs-comment"># 缓存目录</span><br>        self.cache_dir = Path(<span class="hljs-string">&quot;./cache/search&quot;</span>)<br>        self.cache_dir.mkdir(parents=<span class="hljs-literal">True</span>, exist_ok=<span class="hljs-literal">True</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">search</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        query: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        max_results: <span class="hljs-built_in">int</span> = <span class="hljs-number">5</span>,</span><br><span class="hljs-params">        use_cache: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span></span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">dict</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;执行搜索（带缓存）&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 生成缓存键</span><br>        cache_key = self._generate_cache_key(query, max_results)<br>        cache_file = self.cache_dir / <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;cache_key&#125;</span>.json&quot;</span><br><br>        <span class="hljs-comment"># 尝试从缓存读取</span><br>        <span class="hljs-keyword">if</span> use_cache <span class="hljs-keyword">and</span> cache_file.exists():<br>            logger.info(<span class="hljs-string">f&quot;从缓存读取搜索结果：<span class="hljs-subst">&#123;query&#125;</span>&quot;</span>)<br>            <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(cache_file, <span class="hljs-string">&quot;r&quot;</span>, encoding=<span class="hljs-string">&quot;utf-8&quot;</span>) <span class="hljs-keyword">as</span> f:<br>                <span class="hljs-keyword">return</span> json.load(f)<br><br>        <span class="hljs-comment"># 执行搜索</span><br>        results = self._execute_search(query, max_results)<br><br>        <span class="hljs-comment"># 保存到缓存</span><br>        <span class="hljs-keyword">if</span> use_cache <span class="hljs-keyword">and</span> results:<br>            <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(cache_file, <span class="hljs-string">&quot;w&quot;</span>, encoding=<span class="hljs-string">&quot;utf-8&quot;</span>) <span class="hljs-keyword">as</span> f:<br>                json.dump(results, f, ensure_ascii=<span class="hljs-literal">False</span>, indent=<span class="hljs-number">2</span>)<br><br>        <span class="hljs-keyword">return</span> results<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_generate_cache_key</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span>, max_results: <span class="hljs-built_in">int</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;生成缓存键&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 使用查询和最大结果数生成MD5哈希</span><br>        content = <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;query&#125;</span>_<span class="hljs-subst">&#123;max_results&#125;</span>_<span class="hljs-subst">&#123;self.config.search_api.value&#125;</span>&quot;</span><br>        <span class="hljs-keyword">return</span> hashlib.md5(content.encode()).hexdigest()<br></code></pre></td></tr></table></figure><p>通过四个核心服务（PlanningService、SummarizationService、ReportingService、SearchService），我们构建了一个完整的研究流程。这些服务各司其职，通过清晰的接口协作，实现了从研究主题到最终报告的自动化流程。</p><h2 id="14-6-前端交互设计"><a href="#14-6-前端交互设计" class="headerlink" title="14.6 前端交互设计"></a>14.6 前端交互设计</h2><p>在前面的章节中，我们实现了完整的后端系统。本节将详细介绍前端交互设计，包括全屏模态对话框 UI、实时进度展示和研究结果可视化。</p><h3 id="14-6-1-全屏模态对话框-UI-设计"><a href="#14-6-1-全屏模态对话框-UI-设计" class="headerlink" title="14.6.1 全屏模态对话框 UI 设计"></a>14.6.1 全屏模态对话框 UI 设计</h3><p>深度研究助手采用全屏模态对话框的 UI 设计，这种设计有以下优势：</p><ol><li></strong>沉浸式体验<strong>：全屏显示，避免干扰，专注于研究</li><li></strong>清晰的层次<strong>：主页面和研究页面分离，层次清晰</li><li></strong>易于关闭<strong>：点击关闭按钮或按 ESC 键即可返回主页面</li><li></strong>响应式设计<strong>：适配不同屏幕尺寸</li></ol><p>如图 14.9 所示，全屏模态对话框包含以下部分：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/14-figures/14-9.png" alt="" width="85%"/>  <p>图 14.9 全屏模态对话框 UI</p></div><p></strong>UI 组件<strong>：</p><ol><li></strong>顶部栏<strong>：包含研究主题和关闭按钮</li><li></strong>进度区域<strong>：显示当前研究进度（规划、执行、报告）</li><li></strong>内容区域<strong>：显示研究结果（Markdown 格式）</li><li></strong>底部栏**：显示状态信息（如”研究中…”、”已完成”）</li></ol><p>对应的 Vue 实现如下所示(ResearchModal.vue):</p><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><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></pre></td><td class="code"><pre><code class="hljs vue">&lt;template&gt;<br>  &lt;div v-if=&quot;isOpen&quot; class=&quot;modal-overlay&quot; @click.self=&quot;close&quot;&gt;<br>    &lt;div class=&quot;modal-container&quot;&gt;<br>      &lt;!-- 顶部栏 --&gt;<br>      &lt;div class=&quot;modal-header&quot;&gt;<br>        &lt;h2&gt;&#123;&#123; researchTopic &#125;&#125;&lt;/h2&gt;<br>        &lt;button @click=&quot;close&quot; class=&quot;close-button&quot;&gt;<br>          &lt;svg&gt;&lt;!-- 关闭图标 --&gt;&lt;/svg&gt;<br>        &lt;/button&gt;<br>      &lt;/div&gt;<br>      <br>      &lt;!-- 进度区域 --&gt;<br>      &lt;div class=&quot;progress-section&quot;&gt;<br>        &lt;div class=&quot;progress-bar&quot;&gt;<br>          &lt;div <br>            class=&quot;progress-fill&quot; <br>            :style=&quot;&#123; width: progressPercentage + &#x27;%&#x27; &#125;&quot;<br>          &gt;&lt;/div&gt;<br>        &lt;/div&gt;<br>        &lt;div class=&quot;progress-text&quot;&gt;&#123;&#123; progressText &#125;&#125;&lt;/div&gt;<br>      &lt;/div&gt;<br>      <br>      &lt;!-- 内容区域 --&gt;<br>      &lt;div class=&quot;content-section&quot;&gt;<br>        &lt;div v-if=&quot;isLoading&quot; class=&quot;loading-spinner&quot;&gt;<br>          &lt;div class=&quot;spinner&quot;&gt;&lt;/div&gt;<br>          &lt;p&gt;研究中，请稍候...&lt;/p&gt;<br>        &lt;/div&gt;<br>        <br>        &lt;div v-else class=&quot;markdown-content&quot; v-html=&quot;renderedMarkdown&quot;&gt;&lt;/div&gt;<br>      &lt;/div&gt;<br>      <br>      &lt;!-- 底部栏 --&gt;<br>      &lt;div class=&quot;modal-footer&quot;&gt;<br>        &lt;span class=&quot;status-text&quot;&gt;&#123;&#123; statusText &#125;&#125;&lt;/span&gt;<br>      &lt;/div&gt;<br>    &lt;/div&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup lang=&quot;ts&quot;&gt;<br>import &#123; ref, computed, watch &#125; from &#x27;vue&#x27;<br>import &#123; marked &#125; from &#x27;marked&#x27;<br><br>interface Props &#123;<br>  isOpen: boolean<br>  researchTopic: string<br>&#125;<br><br>const props = defineProps&lt;Props&gt;()<br>const emit = defineEmits&lt;&#123;<br>  close: []<br>&#125;&gt;()<br><br>// 状态<br>const isLoading = ref(true)<br>const progressPercentage = ref(0)<br>const progressText = ref(&#x27;准备中...&#x27;)<br>const statusText = ref(&#x27;研究中...&#x27;)<br>const markdownContent = ref(&#x27;&#x27;)<br><br>// 渲染Markdown<br>const renderedMarkdown = computed(() =&gt; &#123;<br>  return marked(markdownContent.value)<br>&#125;)<br><br>// 关闭模态框<br>const close = () =&gt; &#123;<br>  emit(&#x27;close&#x27;)<br>&#125;<br><br>// 监听ESC键<br>const handleKeydown = (e: KeyboardEvent) =&gt; &#123;<br>  if (e.key === &#x27;Escape&#x27;) &#123;<br>    close()<br>  &#125;<br>&#125;<br><br>// 挂载时添加键盘监听<br>watch(() =&gt; props.isOpen, (isOpen) =&gt; &#123;<br>  if (isOpen) &#123;<br>    document.addEventListener(&#x27;keydown&#x27;, handleKeydown)<br>  &#125; else &#123;<br>    document.removeEventListener(&#x27;keydown&#x27;, handleKeydown)<br>  &#125;<br>&#125;)<br>&lt;/script&gt;<br><br>&lt;style scoped&gt;<br>.modal-overlay &#123;<br>  position: fixed;<br>  top: 0;<br>  left: 0;<br>  width: 100vw;<br>  height: 100vh;<br>  background-color: rgba(0, 0, 0, 0.5);<br>  display: flex;<br>  justify-content: center;<br>  align-items: center;<br>  z-index: 1000;<br>&#125;<br>......<br>&lt;/style&gt;<br></code></pre></td></tr></table></figure><p>为了适配不同屏幕尺寸，我们添加媒体查询：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><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><code class="hljs css"><span class="hljs-comment">/* 平板设备 */</span><br><span class="hljs-keyword">@media</span> (<span class="hljs-attribute">max-width</span>: <span class="hljs-number">768px</span>) &#123;<br>  <span class="hljs-selector-class">.modal-container</span> &#123;<br>    <span class="hljs-attribute">width</span>: <span class="hljs-number">95vw</span>;<br>    <span class="hljs-attribute">height</span>: <span class="hljs-number">95vh</span>;<br>  &#125;<br>  <br>  <span class="hljs-selector-class">.modal-header</span>,<br>  <span class="hljs-selector-class">.progress-section</span>,<br>  <span class="hljs-selector-class">.content-section</span>,<br>  <span class="hljs-selector-class">.modal-footer</span> &#123;<br>    <span class="hljs-attribute">padding</span>: <span class="hljs-number">15px</span> <span class="hljs-number">20px</span>;<br>  &#125;<br>&#125;<br><br><span class="hljs-comment">/* 手机设备 */</span><br><span class="hljs-keyword">@media</span> (<span class="hljs-attribute">max-width</span>: <span class="hljs-number">480px</span>) &#123;<br>  <span class="hljs-selector-class">.modal-container</span> &#123;<br>    <span class="hljs-attribute">width</span>: <span class="hljs-number">100vw</span>;<br>    <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;<br>    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">0</span>;<br>  &#125;<br>  <br>  <span class="hljs-selector-class">.modal-header</span> <span class="hljs-selector-tag">h2</span> &#123;<br>    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">18px</span>;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="14-6-2-实时进度展示"><a href="#14-6-2-实时进度展示" class="headerlink" title="14.6.2 实时进度展示"></a>14.6.2 实时进度展示</h3><p>深度研究助手使用 SSE 实现实时进度展示。SSE 是一种服务器推送技术，允许服务器主动向客户端发送数据，在协议章节也有所讲解。</p><p>如图 14.10 所示，SSE 流程包括以下步骤：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/14-figures/14-10.png" alt="" width="85%"/>  <p>图 14.10 SSE 流程</p></div><p><strong>流程说明</strong>：</p><ol><li><strong>客户端发起请求</strong>：发送 POST 请求到<code>/api/research</code>，包含研究主题</li><li><strong>服务器建立 SSE 连接</strong>：返回<code>text/event-stream</code>响应</li><li><strong>服务器推送进度</strong>：定期推送研究进度（规划、执行、报告）</li><li><strong>客户端接收进度</strong>：监听 SSE 事件，更新 UI</li><li><strong>研究完成</strong>：服务器推送最终报告，关闭连接</li></ol><p>如果想把 SSE 用于前后端的项目中还需要做如下配置。</p><p><strong>后端 FastAPI SSE 端点</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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI<br><span class="hljs-keyword">from</span> fastapi.responses <span class="hljs-keyword">import</span> StreamingResponse<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> AsyncGenerator<br><span class="hljs-keyword">import</span> asyncio<br><span class="hljs-keyword">import</span> json<br><br>app = FastAPI()<br><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">research_stream</span>(<span class="hljs-params">topic: <span class="hljs-built_in">str</span></span>) -&gt; AsyncGenerator[<span class="hljs-built_in">str</span>, <span class="hljs-literal">None</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;研究流式生成器</span><br><span class="hljs-string">    </span><br><span class="hljs-string">    生成SSE格式的数据：</span><br><span class="hljs-string">    data: &#123;&quot;type&quot;: &quot;progress&quot;, &quot;data&quot;: &#123;...&#125;&#125;</span><br><span class="hljs-string">    </span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">try</span>:<br>        <span class="hljs-comment"># 1. 规划阶段</span><br>        <span class="hljs-keyword">yield</span> <span class="hljs-string">f&quot;data: <span class="hljs-subst">&#123;json.dumps(&#123;<span class="hljs-string">&#x27;type&#x27;</span>: <span class="hljs-string">&#x27;progress&#x27;</span>, <span class="hljs-string">&#x27;stage&#x27;</span>: <span class="hljs-string">&#x27;planning&#x27;</span>, <span class="hljs-string">&#x27;percentage&#x27;</span>: <span class="hljs-number">10</span>, <span class="hljs-string">&#x27;text&#x27;</span>: <span class="hljs-string">&#x27;正在规划研究任务...&#x27;</span>&#125;</span>)&#125;\n\n&quot;</span><br>        <br>        <span class="hljs-comment"># 调用PlanningService</span><br>        todo_items = <span class="hljs-keyword">await</span> planning_service.plan_todo_list(topic)<br>        <br>        <span class="hljs-keyword">yield</span> <span class="hljs-string">f&quot;data: <span class="hljs-subst">&#123;json.dumps(&#123;<span class="hljs-string">&#x27;type&#x27;</span>: <span class="hljs-string">&#x27;plan&#x27;</span>, <span class="hljs-string">&#x27;data&#x27;</span>: [item.<span class="hljs-built_in">dict</span>() <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> todo_items]&#125;</span>)&#125;\n\n&quot;</span><br>        <br>        <span class="hljs-comment"># 2. 执行阶段</span><br>        task_summaries = []<br>        <span class="hljs-keyword">for</span> idx, task <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(todo_items, start=<span class="hljs-number">1</span>):<br>            <span class="hljs-comment"># 更新进度</span><br>            percentage = <span class="hljs-number">10</span> + (idx / <span class="hljs-built_in">len</span>(todo_items)) * <span class="hljs-number">70</span><br>            <span class="hljs-keyword">yield</span> <span class="hljs-string">f&quot;data: <span class="hljs-subst">&#123;json.dumps(&#123;<span class="hljs-string">&#x27;type&#x27;</span>: <span class="hljs-string">&#x27;progress&#x27;</span>, <span class="hljs-string">&#x27;stage&#x27;</span>: <span class="hljs-string">&#x27;executing&#x27;</span>, <span class="hljs-string">&#x27;percentage&#x27;</span>: percentage, <span class="hljs-string">&#x27;text&#x27;</span>: <span class="hljs-string">f&#x27;正在研究任务<span class="hljs-subst">&#123;idx&#125;</span>/<span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(todo_items)&#125;</span>：<span class="hljs-subst">&#123;task.title&#125;</span>&#x27;</span>&#125;</span>)&#125;\n\n&quot;</span><br>            <br>            <span class="hljs-comment"># 搜索</span><br>            search_results = <span class="hljs-keyword">await</span> search_service.search(task.query)<br>            <br>            <span class="hljs-comment"># 总结</span><br>            summary, source_urls = <span class="hljs-keyword">await</span> summarization_service.summarize_task(task, search_results)<br>            <br>            task_summaries.append((task, summary, source_urls))<br>            <br>            <span class="hljs-comment"># 推送任务总结</span><br>            <span class="hljs-keyword">yield</span> <span class="hljs-string">f&quot;data: <span class="hljs-subst">&#123;json.dumps(&#123;<span class="hljs-string">&#x27;type&#x27;</span>: <span class="hljs-string">&#x27;task_summary&#x27;</span>, <span class="hljs-string">&#x27;task_id&#x27;</span>: task.<span class="hljs-built_in">id</span>, <span class="hljs-string">&#x27;summary&#x27;</span>: summary&#125;</span>)&#125;\n\n&quot;</span><br>        <br>        <span class="hljs-comment"># 3. 报告阶段</span><br>        <span class="hljs-keyword">yield</span> <span class="hljs-string">f&quot;data: <span class="hljs-subst">&#123;json.dumps(&#123;<span class="hljs-string">&#x27;type&#x27;</span>: <span class="hljs-string">&#x27;progress&#x27;</span>, <span class="hljs-string">&#x27;stage&#x27;</span>: <span class="hljs-string">&#x27;reporting&#x27;</span>, <span class="hljs-string">&#x27;percentage&#x27;</span>: <span class="hljs-number">90</span>, <span class="hljs-string">&#x27;text&#x27;</span>: <span class="hljs-string">&#x27;正在生成最终报告...&#x27;</span>&#125;</span>)&#125;\n\n&quot;</span><br>        <br>        <span class="hljs-comment"># 生成报告</span><br>        report = <span class="hljs-keyword">await</span> reporting_service.generate_report(topic, task_summaries)<br>        <br>        <span class="hljs-comment"># 推送最终报告</span><br>        <span class="hljs-keyword">yield</span> <span class="hljs-string">f&quot;data: <span class="hljs-subst">&#123;json.dumps(&#123;<span class="hljs-string">&#x27;type&#x27;</span>: <span class="hljs-string">&#x27;report&#x27;</span>, <span class="hljs-string">&#x27;data&#x27;</span>: report&#125;</span>)&#125;\n\n&quot;</span><br>        <br>        <span class="hljs-comment"># 完成</span><br>        <span class="hljs-keyword">yield</span> <span class="hljs-string">f&quot;data: <span class="hljs-subst">&#123;json.dumps(&#123;<span class="hljs-string">&#x27;type&#x27;</span>: <span class="hljs-string">&#x27;progress&#x27;</span>, <span class="hljs-string">&#x27;stage&#x27;</span>: <span class="hljs-string">&#x27;completed&#x27;</span>, <span class="hljs-string">&#x27;percentage&#x27;</span>: <span class="hljs-number">100</span>, <span class="hljs-string">&#x27;text&#x27;</span>: <span class="hljs-string">&#x27;研究完成！&#x27;</span>&#125;</span>)&#125;\n\n&quot;</span><br>        <br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-comment"># 错误处理</span><br>        <span class="hljs-keyword">yield</span> <span class="hljs-string">f&quot;data: <span class="hljs-subst">&#123;json.dumps(&#123;<span class="hljs-string">&#x27;type&#x27;</span>: <span class="hljs-string">&#x27;error&#x27;</span>, <span class="hljs-string">&#x27;message&#x27;</span>: <span class="hljs-built_in">str</span>(e)&#125;</span>)&#125;\n\n&quot;</span><br><br><span class="hljs-meta">@app.post(<span class="hljs-params"><span class="hljs-string">&quot;/api/research&quot;</span></span>)</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">research</span>(<span class="hljs-params">request: ResearchRequest</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;研究端点（SSE）&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">return</span> StreamingResponse(<br>        research_stream(request.topic),<br>        media_type=<span class="hljs-string">&quot;text/event-stream&quot;</span>,<br>        headers=&#123;<br>            <span class="hljs-string">&quot;Cache-Control&quot;</span>: <span class="hljs-string">&quot;no-cache&quot;</span>,<br>            <span class="hljs-string">&quot;Connection&quot;</span>: <span class="hljs-string">&quot;keep-alive&quot;</span>,<br>        &#125;<br>    )<br></code></pre></td></tr></table></figure><p><strong>前端使用 EventSource 接收 SSE</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><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></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-comment">// composables/useResearch.ts</span><br><span class="hljs-keyword">import</span> &#123; ref &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;vue&#x27;</span><br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">useResearch</span>(<span class="hljs-params"></span>) &#123;<br>  <span class="hljs-keyword">const</span> isLoading = <span class="hljs-title function_">ref</span>(<span class="hljs-literal">false</span>)<br>  <span class="hljs-keyword">const</span> progressPercentage = <span class="hljs-title function_">ref</span>(<span class="hljs-number">0</span>)<br>  <span class="hljs-keyword">const</span> progressText = <span class="hljs-title function_">ref</span>(<span class="hljs-string">&#x27;&#x27;</span>)<br>  <span class="hljs-keyword">const</span> markdownContent = <span class="hljs-title function_">ref</span>(<span class="hljs-string">&#x27;&#x27;</span>)<br>  <span class="hljs-keyword">const</span> error = ref&lt;<span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>)<br>  <br>  <span class="hljs-keyword">const</span> <span class="hljs-title function_">startResearch</span> = (<span class="hljs-params">topic: <span class="hljs-built_in">string</span></span>) =&gt; &#123;<br>    isLoading.<span class="hljs-property">value</span> = <span class="hljs-literal">true</span><br>    error.<span class="hljs-property">value</span> = <span class="hljs-literal">null</span><br>    <br>    <span class="hljs-comment">// 创建EventSource</span><br>    <span class="hljs-keyword">const</span> eventSource = <span class="hljs-keyword">new</span> <span class="hljs-title class_">EventSource</span>(<span class="hljs-string">`/api/research?topic=<span class="hljs-subst">$&#123;<span class="hljs-built_in">encodeURIComponent</span>(topic)&#125;</span>`</span>)<br>    <br>    <span class="hljs-comment">// 监听消息</span><br>    eventSource.<span class="hljs-property">onmessage</span> = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> &#123;<br>      <span class="hljs-keyword">const</span> data = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(event.<span class="hljs-property">data</span>)<br>      <br>      <span class="hljs-keyword">switch</span> (data.<span class="hljs-property">type</span>) &#123;<br>        <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;progress&#x27;</span>:<br>          progressPercentage.<span class="hljs-property">value</span> = data.<span class="hljs-property">percentage</span><br>          progressText.<span class="hljs-property">value</span> = data.<span class="hljs-property">text</span><br>          <span class="hljs-keyword">break</span><br>          <br>        <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;plan&#x27;</span>:<br>          <span class="hljs-comment">// 显示规划结果</span><br>          <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;规划结果:&#x27;</span>, data.<span class="hljs-property">data</span>)<br>          <span class="hljs-keyword">break</span><br>          <br>        <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;task_summary&#x27;</span>:<br>          <span class="hljs-comment">// 追加任务总结到Markdown</span><br>          markdownContent.<span class="hljs-property">value</span> += <span class="hljs-string">`\n\n## 任务<span class="hljs-subst">$&#123;data.task_id&#125;</span>\n\n<span class="hljs-subst">$&#123;data.summary&#125;</span>`</span><br>          <span class="hljs-keyword">break</span><br>          <br>        <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;report&#x27;</span>:<br>          <span class="hljs-comment">// 显示最终报告</span><br>          markdownContent.<span class="hljs-property">value</span> = data.<span class="hljs-property">data</span><br>          <span class="hljs-keyword">break</span><br>          <br>        <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;error&#x27;</span>:<br>          error.<span class="hljs-property">value</span> = data.<span class="hljs-property">message</span><br>          eventSource.<span class="hljs-title function_">close</span>()<br>          isLoading.<span class="hljs-property">value</span> = <span class="hljs-literal">false</span><br>          <span class="hljs-keyword">break</span><br>          <br>        <span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;completed&#x27;</span>:<br>          eventSource.<span class="hljs-title function_">close</span>()<br>          isLoading.<span class="hljs-property">value</span> = <span class="hljs-literal">false</span><br>          <span class="hljs-keyword">break</span><br>      &#125;<br>    &#125;<br>    <br>    <span class="hljs-comment">// 错误处理</span><br>    eventSource.<span class="hljs-property">onerror</span> = <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> &#123;<br>      <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">&#x27;SSE错误:&#x27;</span>, err)<br>      error.<span class="hljs-property">value</span> = <span class="hljs-string">&#x27;连接失败，请重试&#x27;</span><br>      eventSource.<span class="hljs-title function_">close</span>()<br>      isLoading.<span class="hljs-property">value</span> = <span class="hljs-literal">false</span><br>    &#125;<br>  &#125;<br>  <br>  <span class="hljs-keyword">return</span> &#123;<br>    isLoading,<br>    progressPercentage,<br>    progressText,<br>    markdownContent,<br>    error,<br>    startResearch,<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><strong>在组件中使用</strong>：</p><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><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><code class="hljs vue">&lt;script setup lang=&quot;ts&quot;&gt;<br>import &#123; useResearch &#125; from &#x27;@/composables/useResearch&#x27;<br><br>const &#123; <br>  isLoading, <br>  progressPercentage, <br>  progressText, <br>  markdownContent, <br>  error,<br>  startResearch <br>&#125; = useResearch()<br><br>const handleStartResearch = (topic: string) =&gt; &#123;<br>  startResearch(topic)<br>&#125;<br>&lt;/script&gt;<br></code></pre></td></tr></table></figure><h3 id="14-6-3-研究结果可视化"><a href="#14-6-3-研究结果可视化" class="headerlink" title="14.6.3 研究结果可视化"></a>14.6.3 研究结果可视化</h3><p>研究结果以 Markdown 格式展示，包含标题、段落、列表、引用等元素。我们使用<code>marked</code>库将 Markdown 转换为 HTML，并添加自定义样式。</p><p><strong>渲染 Markdown</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><code class="hljs typescript"><span class="hljs-keyword">import</span> &#123; marked &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;marked&#x27;</span><br><br><span class="hljs-comment">// 配置marked</span><br>marked.<span class="hljs-title function_">setOptions</span>(&#123;<br>  <span class="hljs-attr">breaks</span>: <span class="hljs-literal">true</span>,  <span class="hljs-comment">// 支持换行</span><br>  <span class="hljs-attr">gfm</span>: <span class="hljs-literal">true</span>,     <span class="hljs-comment">// 支持GitHub Flavored Markdown</span><br>&#125;)<br><br><span class="hljs-comment">// 渲染</span><br><span class="hljs-keyword">const</span> renderedHtml = <span class="hljs-title function_">marked</span>(markdownContent.<span class="hljs-property">value</span>)<br></code></pre></td></tr></table></figure><p>研究报告中包含大量来源引用，我们需要特殊处理：</p><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section">## 参考文献</span><br><br><span class="hljs-section">### 任务1：Datawhale的基本信息</span><br><span class="hljs-bullet">-</span> [<span class="hljs-string">Datawhale GitHub</span>](<span class="hljs-link">https://github.com/datawhalechina</span>)<br><span class="hljs-bullet">-</span> [<span class="hljs-string">Datawhale 官网</span>](<span class="hljs-link">https://datawhale.club</span>)<br><br><span class="hljs-section">### 任务2：Datawhale的主要项目</span><br><span class="hljs-bullet">-</span> [<span class="hljs-string">Hello-Agents 教程</span>](<span class="hljs-link">https://github.com/datawhalechina/Hello-Agents</span>)<br>......<br></code></pre></td></tr></table></figure><p>通过全屏模态对话框 UI、SSE 实时进度展示和 Markdown 结果可视化，我们构建了一个用户友好的前端界面。用户可以清晰地看到研究进度，并以美观的格式查看研究结果。</p><h2 id="14-7-本章小结"><a href="#14-7-本章小结" class="headerlink" title="14.7 本章小结"></a>14.7 本章小结</h2><p>在本章中，我们从零开始构建了一个完整的自动化深度研究智能体系统。让我们回顾一下核心要点：</p><p><strong>（1）TODO 驱动的研究范式</strong></p><p>我们提出了一种新的研究范式——TODO 驱动的研究。这种范式将复杂的研究主题分解为可执行的子任务，通过三个阶段完成研究：</p><ul><li><strong>规划阶段</strong>：将研究主题分解为 3-5 个子任务，每个子任务包含标题、意图和搜索查询</li><li><strong>执行阶段</strong>：对每个子任务执行搜索和总结，生成结构化的知识</li><li><strong>报告阶段</strong>：整合所有子任务的总结，生成最终的研究报告</li></ul><p>这种范式的优势在于：</p><ol><li><strong>可控性强</strong>：每个子任务都有明确的目标和范围</li><li><strong>质量可靠</strong>：通过专门的 Agent 保证每个环节的质量</li><li><strong>易于调试</strong>：可以单独调试每个子任务</li><li><strong>可扩展性好</strong>：可以轻松添加新的子任务或修改现有子任务</li></ol><p><strong>（2）三 Agent 协作系统</strong></p><p>我们设计了三个专门的 Agent，各司其职：</p><ul><li><strong>TODO Planner（研究规划专家）</strong>：负责将研究主题分解为子任务</li><li><strong>Task Summarizer（任务总结专家）</strong>：负责总结每个子任务的搜索结果</li><li><strong>Report Writer（报告撰写专家）</strong>：负责整合所有子任务的总结，生成最终报告</li></ul><p>这种设计的优势在于：</p><ol><li><strong>职责清晰</strong>：每个 Agent 专注于一个特定的任务</li><li><strong>Prompt 优化</strong>：可以为每个 Agent 定制专门的 Prompt</li><li><strong>易于维护</strong>：修改一个 Agent 不会影响其他 Agent</li><li><strong>质量保证</strong>：每个 Agent 都是该领域的”专家”</li></ol><p><strong>（3）ToolAwareSimpleAgent 的设计</strong></p><p>我们扩展了 HelloAgents 框架的<code>SimpleAgent</code>，实现了<code>ToolAwareSimpleAgent</code>。这个 Agent 具有工具调用监听能力，可以：</p><ul><li><strong>监听工具调用</strong>：通过回调函数监听每次工具调用</li><li><strong>实时反馈</strong>：将工具调用信息实时推送给前端</li><li><strong>调试支持</strong>：记录所有工具调用，便于调试</li></ul><p>这个 Agent 已经集成到 HelloAgents 框架中，可以在其他项目中复用。</p><p><strong>（4）工具系统集成</strong></p><p>我们充分利用了 HelloAgents 框架的工具系统：</p><ul><li><strong>SearchTool</strong>：扩展支持更多种搜索引擎（Tavily、DuckDuckGo、Perplexity 等）</li><li><strong>NoteTool</strong>：持久化研究进度，支持恢复和审计</li><li><strong>ToolRegistry</strong>：统一管理所有工具，支持自定义扩展</li></ul><p>通过配置化的设计，用户可以轻松切换搜索引擎，无需修改代码。</p><p><strong>（5）核心服务实现</strong></p><p>我们实现了四个核心服务，连接 Agent 和工具：</p><ul><li><strong>PlanningService</strong>：调用规划 Agent，解析 JSON，验证格式</li><li><strong>SummarizationService</strong>：调用总结 Agent，处理搜索结果，提取来源</li><li><strong>ReportingService</strong>：调用报告 Agent，整合总结，生成报告</li><li><strong>SearchService</strong>：调度搜索引擎，处理结果，错误降级，结果缓存</li></ul><p>这些服务各司其职，通过清晰的接口协作，实现了从研究主题到最终报告的自动化流程。</p><p><strong>（6）前端交互设计</strong></p><p>我们设计了用户友好的前端界面：</p><ul><li><strong>全屏模态对话框</strong>：沉浸式体验，清晰的层次</li><li><strong>SSE 实时进度</strong>：实时展示研究进度，用户体验良好</li><li><strong>Markdown 可视化</strong>：美观的格式，清晰的结构</li></ul><p>通过 Vue 3 + TypeScript + SSE 的技术栈，我们实现了一个现代化的 Web 应用。</p><p>这些知识不仅适用于深度研究助手，也可以应用到其他 AI 应用中。希望读者能够在本章的基础上，探索更多的可能性，构建出更强大的 AI 系统。</p><p>在下一章中，我们将构建一个与游戏引擎结合的多 Agent 系统——赛博小镇，探索 Agent 之间的复杂交互和协作模式。敬请期待！</p>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC14%E7%AB%A0-%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B7%B1%E5%BA%A6%E7%A0%94%E7%A9%B6%E6%99%BA%E8%83%BD%E4%BD%93/</id>
    <link href="http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC14%E7%AB%A0-%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B7%B1%E5%BA%A6%E7%A0%94%E7%A9%B6%E6%99%BA%E8%83%BD%E4%BD%93/"/>
    <published>2026-03-02T06:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="第十四章-自动化深度研究智能体"><a href="#第十四章-自动化深度研究智能体" class="headerlink" title="第十四章 自动化深度研究智能体"></a>第十四章 自动化深度研究智能体</h1><p>在第十三章的旅行助手项目中，我们体验]]>
    </summary>
    <title>第十四章 自动化深度研究智能体</title>
    <updated>2026-03-08T09:24:16.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="第十三章-智能旅行助手"><a href="#第十三章-智能旅行助手" class="headerlink" title="第十三章 智能旅行助手"></a>第十三章 智能旅行助手</h1><p>在前面的章节中，我们从零开始构建了 HelloAgents 框架，实现了多种智能体范式、工具系统、记忆机制、协议通信和性能评估等核心功能。从本章开始，我们将进入一个全新的阶段：<strong>将所学知识融会贯通，构建完整的实用应用。</strong></p><p>还记得在第一章中，我们构建的第一个智能体吗？那是一个简单的智能旅行助手，展示了<code>Thought-Action-Observation</code>循环的基本原理。本章的智能旅行助手将是一个完整的项目，包含以下核心功能：</p><p><strong>（1）智能行程规划</strong>：用户输入目的地、日期、偏好等信息，系统自动生成包含景点、餐饮、酒店的完整行程计划。</p><p><strong>（2）地图可视化</strong>：在地图上标注景点位置、绘制游览路线，让行程一目了然。</p><p><strong>（3）预算计算</strong>：自动计算门票、酒店、餐饮、交通费用，显示预算明细。</p><p><strong>（4）行程编辑</strong>：支持添加、删除、调整景点，实时更新地图。</p><p><strong>（5）导出功能</strong>：支持导出为 PDF 或图片，方便保存和分享。</p><h2 id="13-1-项目概述与架构设计"><a href="#13-1-项目概述与架构设计" class="headerlink" title="13.1 项目概述与架构设计"></a>13.1 项目概述与架构设计</h2><h3 id="13-1-1-为什么需要智能旅行助手"><a href="#13-1-1-为什么需要智能旅行助手" class="headerlink" title="13.1.1 为什么需要智能旅行助手"></a>13.1.1 为什么需要智能旅行助手</h3><p>规划一次旅行是一件既令人兴奋又令人头疼的事情。你需要在网上搜索景点信息，对比不同的攻略，查看天气预报，预订酒店，计算预算，规划路线。这个过程可能需要花费几个小时甚至几天的时间。而且即使花了这么多时间，你也不确定规划的行程是否合理，是否遗漏了什么重要的景点，预算是否准确。</p><p>传统的旅行规划方式有几个痛点。首先是<strong>信息分散</strong>。景点信息在旅游网站上，天气信息在天气网站上，酒店信息在预订网站上，你需要在多个网站之间切换，手动整合这些信息。其次是<strong>缺少个性化</strong>。大部分攻略都是通用的，不考虑你的个人偏好、预算限制、出行时间等因素。最后是<strong>难以调整</strong>。当你想修改行程时，可能需要重新规划整个行程，因为景点的顺序、时间安排、预算都是相互关联的。</p><p>AI 技术为解决这些问题提供了新的可能。想象一下，你只需要告诉系统”我想去北京玩 3 天，喜欢历史文化，预算中等”，系统就能自动为你生成一个完整的行程计划，包括每天去哪些景点、在哪里吃饭、住哪个酒店、需要多少预算。而且这个计划是可以调整的，你可以删除不喜欢的景点，调整游览顺序，系统会自动更新地图和预算。</p><p>这就是我们要构建的智能旅行助手。它不仅仅是一个技术演示，而是一个真正有用的应用。通过这个项目，你会学到如何将 AI 技术应用到实际问题中，如何设计多智能体系统，如何构建完整的 Web 应用。</p><h3 id="13-1-2-技术架构概览"><a href="#13-1-2-技术架构概览" class="headerlink" title="13.1.2 技术架构概览"></a>13.1.2 技术架构概览</h3><p>系统采用经典的<strong>前后端分离架构</strong>，分为四个层次，如图 13.1 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/13-figures/13-1.png" alt="" width="85%"/>  <p>图 13.1 智能旅行助手技术架构</p></div><p><strong>（1）前端层 (Vue3+TypeScript)</strong>：负责用户交互和数据展示，包括表单输入、结果展示、地图可视化。</p><p><strong>（2）后端层 (FastAPI)</strong>：负责 API 路由、数据验证、业务逻辑。</p><p><strong>（3）智能体层 (HelloAgents)</strong>：负责任务分解、工具调用、结果整合。包含 4 个专门的 Agent。</p><p><strong>（4）外部服务层</strong>：提供数据和能力，包括高德地图 API、Unsplash API、LLM API。</p><p>数据流转过程如下：用户在前端填写表单 → 后端验证数据 → 调用智能体系统 → 智能体依次调用景点搜索、天气查询、酒店推荐、行程规划 Agent → 每个 Agent 通过 MCP 协议调用外部 API → 整合结果返回前端 → 前端渲染展示。</p><p>项目的结构参考如下，提供便于定位源码：</p><figure class="highlight haxe"><table><tr><td class="gutter"><pre><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><code class="hljs haxe">helloagents-trip-planner/<br>├── backend/                    <span class="hljs-meta"># 后端代码</span><br>│   ├── app/<br>│   │   ├── agents/            <span class="hljs-meta"># 智能体实现</span><br>│   │   ├── api/               <span class="hljs-meta"># API路由</span><br>│   │   ├── models/            <span class="hljs-meta"># 数据模型</span><br>│   │   ├── services/          <span class="hljs-meta"># 服务层</span><br>│   │   └── config.py          <span class="hljs-meta"># 配置文件</span><br>│   └── requirements.txt       <span class="hljs-meta"># Python依赖</span><br>│<br>└── frontend/                   <span class="hljs-meta"># 前端代码</span><br>    ├── src/<br>    │   ├── views/             <span class="hljs-meta"># 页面组件</span><br>    │   ├── services/          <span class="hljs-meta"># API服务</span><br>    │   ├── types/             <span class="hljs-meta"># 类型定义</span><br>    │   └── router/            <span class="hljs-meta"># 路由配置</span><br>    └── <span class="hljs-keyword">package</span>.json           <span class="hljs-meta"># npm依赖</span><br></code></pre></td></tr></table></figure><p>详细的架构设计和数据流转将在后续章节中介绍。</p><h3 id="13-1-3-快速体验：5-分钟运行项目"><a href="#13-1-3-快速体验：5-分钟运行项目" class="headerlink" title="13.1.3 快速体验：5 分钟运行项目"></a>13.1.3 快速体验：5 分钟运行项目</h3><p>在深入学习实现细节之前，让我们先把项目跑起来，看看最终的效果。这样你会对整个系统有一个直观的认识。</p><p><strong>环境要求：</strong></p><ul><li>Python 3.10 或更高版本</li><li>Node.js 16.0 或更高版本</li><li>npm 8.0 或更高版本</li></ul><p><strong>获取 API 密钥：</strong></p><p>你需要准备以下 API 密钥：</p><ul><li>LLM 的 API(OpenAI、DeepSeek 等)</li><li>高德地图 Web 服务 Key：访问 <a href="https://console.amap.com/">https://console.amap.com/</a> 注册并创建应用</li><li>Unsplash Access Key：访问 <a href="https://unsplash.com/developers">https://unsplash.com/developers</a> 注册并创建应用</li></ul><p>将所有 API 密钥放入<code>.env</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><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><code class="hljs bash"><span class="hljs-comment"># 1. 进入后端目录</span><br><span class="hljs-built_in">cd</span> helloagents-trip-planner/backend<br><br><span class="hljs-comment"># 2. 安装依赖</span><br>pip install -r requirements.txt<br><br><span class="hljs-comment"># 3. 配置环境变量</span><br><span class="hljs-built_in">cp</span> .env.example .<span class="hljs-built_in">env</span><br><span class="hljs-comment"># 编辑.env文件，填入你的API密钥</span><br><br><span class="hljs-comment"># 4. 启动后端服务</span><br>uvicorn app.api.main:app --reload<br><span class="hljs-comment"># 或者</span><br>python run.py<br></code></pre></td></tr></table></figure><p>成功启动后，访问 <a href="http://localhost:8000/docs">http://localhost:8000/docs</a> 可以看到 API 文档。</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><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><code class="hljs bash"><span class="hljs-comment"># 1. 进入前端目录</span><br><span class="hljs-built_in">cd</span> helloagents-trip-planner/frontend<br><br><span class="hljs-comment"># 2. 安装依赖</span><br>npm install<br><br><span class="hljs-comment"># 3. 启动前端服务</span><br>npm run dev<br></code></pre></td></tr></table></figure><p>成功启动后，访问 <a href="http://localhost:5173/">http://localhost:5173</a> 即可使用应用。</p><p>体验核心功能：</p><p>首先需在首页表单中填写目的地城市、旅行日期、偏好、预算、交通及住宿类型等信息。点击“开始规划”按钮后，系统会显示加载进度条，并很快生成结果页面，如图 13.2 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/13-figures/13-2.png" alt="" width="85%"/>  <p>图 13.2 旅行助手规划进行页面</p></div><p>随后加载成功，该页面会清晰展示行程概览、预算明细、景点地图、每日行程详情和天气信息，如图 13.3，13.4 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/13-figures/13-3.png" alt="" width="85%"/>  <p>图 13.3 旅行助手规划完成页面</p></div><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/13-figures/13-4.png" alt="" width="85%"/>  <p>图 13.4 旅行助手规划完成页面</p></div><p>如果用户需要个性化调整，可以点击“编辑行程”按钮，自由调整景点顺序或删除某个景点，如图 13.5 所示。规划完成后，通过“导出行程”下拉菜单，即可将最终方案轻松保存为图片或 PDF 文件，方便随时查阅。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/13-figures/13-5.png" alt="" width="85%"/>  <p>图 13.5 旅行助手规划完成页面</p></div><h2 id="13-2-数据模型设计"><a href="#13-2-数据模型设计" class="headerlink" title="13.2 数据模型设计"></a>13.2 数据模型设计</h2><h3 id="13-2-1-Web-应用中的数据流转"><a href="#13-2-1-Web-应用中的数据流转" class="headerlink" title="13.2.1 Web 应用中的数据流转"></a>13.2.1 Web 应用中的数据流转</h3><p>在构建智能旅行助手时，我们需要解决一个核心问题：<strong>如何表示和传递旅行计划数据?</strong> </p><p>我们需要理解一个完整的 Web 应用中数据是如何流转的。想象一下，当用户在浏览器中点击”开始规划”按钮时，会发生什么？</p><p>用户在前端填写的表单数据(目的地、日期、预算等)需要通过 HTTP 请求发送到后端服务器。后端接收到数据后，会调用智能体系统进行处理。智能体又会调用高德地图 API、Unsplash API 等外部服务获取数据。这些外部 API 返回的数据格式各不相同，有的用<code>lng</code>，有的用<code>lon</code>，有的用<code>longitude</code>。最后，后端需要将处理好的数据返回给前端，前端再渲染成用户看到的页面。</p><p>在这个过程中，数据经历了多次转换：前端表单 → HTTP 请求 → 后端 Python 对象 → 外部 API 响应 → 后端 Python 对象 → HTTP 响应 → 前端 TypeScript 对象 → 页面展示。如果没有统一的数据格式，每一步转换都可能出错。这就是为什么我们需要<strong>数据模型</strong>。</p><h3 id="13-2-2-从字典到-Pydantic-模型"><a href="#13-2-2-从字典到-Pydantic-模型" class="headerlink" title="13.2.2 从字典到 Pydantic 模型"></a>13.2.2 从字典到 Pydantic 模型</h3><p>让我们从第一章的简单原型开始。在那个原型中，我们使用 Python 字典来表示景点数据：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 第一章的做法：使用字典</span><br>attraction = &#123;<br>    <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;故宫&quot;</span>,<br>    <span class="hljs-string">&quot;location&quot;</span>: &#123;<span class="hljs-string">&quot;lng&quot;</span>: <span class="hljs-number">116.397128</span>,<span class="hljs-string">&quot;lat&quot;</span>: <span class="hljs-number">39.916527</span>&#125;,<br>    <span class="hljs-string">&quot;price&quot;</span>: <span class="hljs-number">60</span><br>&#125;<br><br><span class="hljs-comment"># 访问数据</span><br>lng = attraction[<span class="hljs-string">&quot;location&quot;</span>][<span class="hljs-string">&quot;lng&quot;</span>]<br></code></pre></td></tr></table></figure><p>这种方式在原型阶段很方便，但在实际项目中会遇到很多问题。首先是<strong>字段名不统一</strong>的问题。高德地图 API 返回的位置数据是<code>&quot;116.397128，39.916527&quot;</code>这样的字符串，需要手动分割成经纬度。而 Unsplash API 可能使用<code>longitude</code>和<code>latitude</code>。如果我们在代码中到处都用字典，就需要在每个地方都处理这些差异。</p><p>其次是<strong>类型安全</strong>的问题。假设我们不小心把<code>price</code>写成了字符串<code>&quot;60&quot;</code>，在 Python 中这不会立即报错，但在计算总预算时就会出问题。更糟糕的是，这种错误只能在运行时才能发现，而且错误信息可能很难定位。</p><p>最后是<strong>维护性</strong>的问题。当我们需要给景点添加新字段(比如<code>rating</code>评分)时，需要在代码的多个地方修改。如果遗漏了某个地方，就会导致数据不一致。</p><p>Pydantic 提供了一个解决方案。它是 Python 的数据验证库，可以让我们用类来定义数据结构，并自动处理验证、转换和序列化。让我们看一个简单的例子：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pydantic <span class="hljs-keyword">import</span> BaseModel,Field<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Location</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    longitude: <span class="hljs-built_in">float</span> = Field(...,description=<span class="hljs-string">&quot;经度&quot;</span>)<br>    latitude: <span class="hljs-built_in">float</span> = Field(...,description=<span class="hljs-string">&quot;纬度&quot;</span>)<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Attraction</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    name: <span class="hljs-built_in">str</span><br>    location: Location<br>    ticket_price: <span class="hljs-built_in">int</span> = <span class="hljs-number">0</span><br><br><span class="hljs-comment"># 创建对象</span><br>attraction = Attraction(<br>    name=<span class="hljs-string">&quot;故宫&quot;</span>,<br>    location=Location(longitude=<span class="hljs-number">116.397128</span>,latitude=<span class="hljs-number">39.916527</span>),<br>    ticket_price=<span class="hljs-number">60</span><br>)<br><br><span class="hljs-comment"># 类型安全的访问</span><br>lng = attraction.location.longitude  <span class="hljs-comment"># IDE会提供代码补全</span><br></code></pre></td></tr></table></figure><p>这样做有几个好处。首先，如果我们传入了错误的类型(比如把<code>ticket_price</code>设为字符串)，Pydantic 会立即抛出异常，告诉我们哪里出错了。其次，IDE 可以根据类型定义提供代码补全和类型检查，大大减少了拼写错误。最后，当我们需要修改数据结构时，只需要修改类定义，所有使用这个类的地方都会自动更新。</p><h3 id="13-2-3-Pydantic-的核心概念"><a href="#13-2-3-Pydantic-的核心概念" class="headerlink" title="13.2.3 Pydantic 的核心概念"></a>13.2.3 Pydantic 的核心概念</h3><p>在深入设计我们的数据模型之前，让我们先了解 Pydantic 的几个核心概念。Pydantic 的基础是<code>BaseModel</code>类，所有的数据模型都需要继承这个类。每个字段都可以指定类型，Pydantic 会自动进行类型检查和转换。</p><p>字段定义使用<code>Field</code>函数，它可以指定默认值、描述、验证规则等。<code>...</code>表示这个字段是必填的，如果创建对象时没有提供这个字段，Pydantic 会抛出异常。我们也可以使用<code>Optional</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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pydantic <span class="hljs-keyword">import</span> BaseModel,Field<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Optional</span>,<span class="hljs-type">List</span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Attraction</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    name: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;景点名称&quot;</span>)  <span class="hljs-comment"># 必填</span><br>    rating: <span class="hljs-built_in">float</span> = Field(default=<span class="hljs-number">0.0</span>,ge=<span class="hljs-number">0</span>,le=<span class="hljs-number">5</span>)  <span class="hljs-comment"># 默认值,范围验证</span><br>    visit_duration: <span class="hljs-built_in">int</span> = Field(default=<span class="hljs-number">60</span>,gt=<span class="hljs-number">0</span>)  <span class="hljs-comment"># 大于0</span><br>    description: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>  <span class="hljs-comment"># 可选字段</span><br></code></pre></td></tr></table></figure><p>Pydantic 还支持嵌套模型和列表。我们可以在一个模型中使用另一个模型作为字段类型,这样就可以构建复杂的数据结构。比如，一个景点包含位置信息，一个行程包含多个景点。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">DayPlan</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    date: <span class="hljs-built_in">str</span><br>    attractions: <span class="hljs-type">List</span>[Attraction]  <span class="hljs-comment"># 景点列表</span><br>    hotel: <span class="hljs-type">Optional</span>[Hotel] = <span class="hljs-literal">None</span>  <span class="hljs-comment"># 可选的酒店信息</span><br></code></pre></td></tr></table></figure><p>最强大的功能之一是<strong>自定义验证器</strong>。有时候外部 API 返回的数据格式不符合我们的要求，我们可以使用<code>field_validator</code>装饰器来自定义验证和转换逻辑。比如，高德地图返回的温度是<code>&quot;16°C&quot;</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><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><code class="hljs python"><span class="hljs-keyword">from</span> pydantic <span class="hljs-keyword">import</span> field_validator<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">WeatherInfo</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    temperature: <span class="hljs-built_in">int</span><br>    <br><span class="hljs-meta">    @field_validator(<span class="hljs-params"><span class="hljs-string">&#x27;temperature&#x27;</span>,mode=<span class="hljs-string">&#x27;before&#x27;</span></span>)</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">parse_temperature</span>(<span class="hljs-params">cls,v</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;解析温度字符串：&quot;16°C&quot; -&gt; 16&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">isinstance</span>(v,<span class="hljs-built_in">str</span>):<br>            v = v.replace(<span class="hljs-string">&#x27;°C&#x27;</span>,<span class="hljs-string">&#x27;&#x27;</span>).replace(<span class="hljs-string">&#x27;℃&#x27;</span>,<span class="hljs-string">&#x27;&#x27;</span>).strip()<br>            <span class="hljs-keyword">return</span> <span class="hljs-built_in">int</span>(v)<br>        <span class="hljs-keyword">return</span> v<br></code></pre></td></tr></table></figure><p>这个验证器会在创建对象之前自动执行，将字符串转换成整数。这样我们就不需要在代码的每个地方都手动处理温度格式了。</p><h3 id="13-2-4-自底向上的模型设计"><a href="#13-2-4-自底向上的模型设计" class="headerlink" title="13.2.4 自底向上的模型设计"></a>13.2.4 自底向上的模型设计</h3><p>现在让我们开始设计智能旅行助手的数据模型。一个好的设计原则是<strong>自底向上</strong>：先定义最基础的模型，然后逐步组合成复杂的结构。这样做的好处是每个模型都很简单，容易理解和维护。</p><p>最基础的模型是<strong>位置信息</strong>。无论是景点、酒店还是餐厅，都需要位置信息。我们定义一个<code>Location</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Location</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;位置信息(经纬度坐标)&quot;&quot;&quot;</span><br>    longitude: <span class="hljs-built_in">float</span> = Field(...,description=<span class="hljs-string">&quot;经度&quot;</span>,ge=-<span class="hljs-number">180</span>,le=<span class="hljs-number">180</span>)<br>    latitude: <span class="hljs-built_in">float</span> = Field(...,description=<span class="hljs-string">&quot;纬度&quot;</span>,ge=-<span class="hljs-number">90</span>,le=<span class="hljs-number">90</span>)<br></code></pre></td></tr></table></figure><p>这里我们使用了范围验证(<code>ge</code>表示大于等于，<code>le</code>表示小于等于)，确保经纬度的值在合理范围内。</p><p>接下来是<strong>景点信息</strong>。一个景点包含名称、地址、位置、游览时间、描述、评分、图片和门票价格等信息。注意我们使用了<code>Location</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><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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Attraction</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;景点信息&quot;&quot;&quot;</span><br>    name: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;景点名称&quot;</span>)<br>    address: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;地址&quot;</span>)<br>    location: Location = Field(...,description=<span class="hljs-string">&quot;经纬度坐标&quot;</span>)<br>    visit_duration: <span class="hljs-built_in">int</span> = Field(...,description=<span class="hljs-string">&quot;建议游览时间(分钟)&quot;</span>,gt=<span class="hljs-number">0</span>)<br>    description: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;景点描述&quot;</span>)<br>    category: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = Field(default=<span class="hljs-string">&quot;景点&quot;</span>,description=<span class="hljs-string">&quot;景点类别&quot;</span>)<br>    rating: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">float</span>] = Field(default=<span class="hljs-literal">None</span>,ge=<span class="hljs-number">0</span>,le=<span class="hljs-number">5</span>,description=<span class="hljs-string">&quot;评分&quot;</span>)<br>    image_url: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = Field(default=<span class="hljs-literal">None</span>,description=<span class="hljs-string">&quot;图片URL&quot;</span>)<br>    ticket_price: <span class="hljs-built_in">int</span> = Field(default=<span class="hljs-number">0</span>,ge=<span class="hljs-number">0</span>,description=<span class="hljs-string">&quot;门票价格(元)&quot;</span>)<br></code></pre></td></tr></table></figure><p>类似地，我们定义<strong>餐饮信息</strong>和<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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Meal</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;餐饮信息&quot;&quot;&quot;</span><br>    <span class="hljs-built_in">type</span>: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;餐饮类型：breakfast/lunch/dinner/snack&quot;</span>)<br>    name: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;餐饮名称&quot;</span>)<br>    address: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = Field(default=<span class="hljs-literal">None</span>,description=<span class="hljs-string">&quot;地址&quot;</span>)<br>    location: <span class="hljs-type">Optional</span>[Location] = Field(default=<span class="hljs-literal">None</span>,description=<span class="hljs-string">&quot;经纬度坐标&quot;</span>)<br>    description: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = Field(default=<span class="hljs-literal">None</span>,description=<span class="hljs-string">&quot;描述&quot;</span>)<br>    estimated_cost: <span class="hljs-built_in">int</span> = Field(default=<span class="hljs-number">0</span>,description=<span class="hljs-string">&quot;预估费用(元)&quot;</span>)<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Hotel</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;酒店信息&quot;&quot;&quot;</span><br>    name: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;酒店名称&quot;</span>)<br>    address: <span class="hljs-built_in">str</span> = Field(default=<span class="hljs-string">&quot;&quot;</span>,description=<span class="hljs-string">&quot;酒店地址&quot;</span>)<br>    location: <span class="hljs-type">Optional</span>[Location] = Field(default=<span class="hljs-literal">None</span>,description=<span class="hljs-string">&quot;酒店位置&quot;</span>)<br>    price_range: <span class="hljs-built_in">str</span> = Field(default=<span class="hljs-string">&quot;&quot;</span>,description=<span class="hljs-string">&quot;价格范围&quot;</span>)<br>    rating: <span class="hljs-built_in">str</span> = Field(default=<span class="hljs-string">&quot;&quot;</span>,description=<span class="hljs-string">&quot;评分&quot;</span>)<br>    distance: <span class="hljs-built_in">str</span> = Field(default=<span class="hljs-string">&quot;&quot;</span>,description=<span class="hljs-string">&quot;距离景点距离&quot;</span>)<br>    <span class="hljs-built_in">type</span>: <span class="hljs-built_in">str</span> = Field(default=<span class="hljs-string">&quot;&quot;</span>,description=<span class="hljs-string">&quot;酒店类型&quot;</span>)<br>    estimated_cost: <span class="hljs-built_in">int</span> = Field(default=<span class="hljs-number">0</span>,description=<span class="hljs-string">&quot;预估费用(元/晚)&quot;</span>)<br></code></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Budget</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;预算信息&quot;&quot;&quot;</span><br>    total_attractions: <span class="hljs-built_in">int</span> = Field(default=<span class="hljs-number">0</span>,description=<span class="hljs-string">&quot;景点门票总费用&quot;</span>)<br>    total_hotels: <span class="hljs-built_in">int</span> = Field(default=<span class="hljs-number">0</span>,description=<span class="hljs-string">&quot;酒店总费用&quot;</span>)<br>    total_meals: <span class="hljs-built_in">int</span> = Field(default=<span class="hljs-number">0</span>,description=<span class="hljs-string">&quot;餐饮总费用&quot;</span>)<br>    total_transportation: <span class="hljs-built_in">int</span> = Field(default=<span class="hljs-number">0</span>,description=<span class="hljs-string">&quot;交通总费用&quot;</span>)<br>    total: <span class="hljs-built_in">int</span> = Field(default=<span class="hljs-number">0</span>,description=<span class="hljs-string">&quot;总费用&quot;</span>)<br></code></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">DayPlan</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;单日行程&quot;&quot;&quot;</span><br>    date: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;日期&quot;</span>)<br>    day_index: <span class="hljs-built_in">int</span> = Field(...,description=<span class="hljs-string">&quot;第几天(从0开始)&quot;</span>)<br>    description: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;当日行程描述&quot;</span>)<br>    transportation: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;交通方式&quot;</span>)<br>    accommodation: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;住宿安排&quot;</span>)<br>    hotel: <span class="hljs-type">Optional</span>[Hotel] = Field(default=<span class="hljs-literal">None</span>,description=<span class="hljs-string">&quot;酒店信息&quot;</span>)<br>    attractions: <span class="hljs-type">List</span>[Attraction] = Field(default_factory=<span class="hljs-built_in">list</span>,description=<span class="hljs-string">&quot;景点列表&quot;</span>)<br>    meals: <span class="hljs-type">List</span>[Meal] = Field(default_factory=<span class="hljs-built_in">list</span>,description=<span class="hljs-string">&quot;餐饮安排&quot;</span>)<br></code></pre></td></tr></table></figure><p>注意这里使用了<code>List[Attraction]</code>来表示景点列表，<code>default_factory=list</code>表示默认值是一个空列表。</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><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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">WeatherInfo</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;天气信息&quot;&quot;&quot;</span><br>    date: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;日期&quot;</span>)<br>    day_weather: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;白天天气&quot;</span>)<br>    night_weather: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;夜间天气&quot;</span>)<br>    day_temp: <span class="hljs-built_in">int</span> = Field(...,description=<span class="hljs-string">&quot;白天温度(摄氏度)&quot;</span>)<br>    night_temp: <span class="hljs-built_in">int</span> = Field(...,description=<span class="hljs-string">&quot;夜间温度(摄氏度)&quot;</span>)<br>    wind_direction: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;风向&quot;</span>)<br>    wind_power: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;风力&quot;</span>)<br>    <br><span class="hljs-meta">    @field_validator(<span class="hljs-params"><span class="hljs-string">&#x27;day_temp&#x27;</span>,<span class="hljs-string">&#x27;night_temp&#x27;</span>,mode=<span class="hljs-string">&#x27;before&#x27;</span></span>)</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">parse_temperature</span>(<span class="hljs-params">cls,v</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;解析温度字符串：&quot;16°C&quot; -&gt; 16&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">isinstance</span>(v,<span class="hljs-built_in">str</span>):<br>            v = v.replace(<span class="hljs-string">&#x27;°C&#x27;</span>,<span class="hljs-string">&#x27;&#x27;</span>).replace(<span class="hljs-string">&#x27;℃&#x27;</span>,<span class="hljs-string">&#x27;&#x27;</span>).replace(<span class="hljs-string">&#x27;°&#x27;</span>,<span class="hljs-string">&#x27;&#x27;</span>).strip()<br>            <span class="hljs-keyword">try</span>:<br>                <span class="hljs-keyword">return</span> <span class="hljs-built_in">int</span>(v)<br>            <span class="hljs-keyword">except</span> ValueError:<br>                <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>  <span class="hljs-comment"># 容错处理</span><br>        <span class="hljs-keyword">return</span> v<br></code></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">TripPlan</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;旅行计划&quot;&quot;&quot;</span><br>    city: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;目的地城市&quot;</span>)<br>    start_date: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;开始日期&quot;</span>)<br>    end_date: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;结束日期&quot;</span>)<br>    days: <span class="hljs-type">List</span>[DayPlan] = Field(default_factory=<span class="hljs-built_in">list</span>,description=<span class="hljs-string">&quot;每日行程&quot;</span>)<br>    weather_info: <span class="hljs-type">List</span>[WeatherInfo] = Field(default_factory=<span class="hljs-built_in">list</span>,description=<span class="hljs-string">&quot;天气信息&quot;</span>)<br>    overall_suggestions: <span class="hljs-built_in">str</span> = Field(...,description=<span class="hljs-string">&quot;总体建议&quot;</span>)<br>    budget: <span class="hljs-type">Optional</span>[Budget] = Field(default=<span class="hljs-literal">None</span>,description=<span class="hljs-string">&quot;预算信息&quot;</span>)<br></code></pre></td></tr></table></figure><p>这样，我们就完成了整个数据模型的设计。从最基础的<code>Location</code>，到<code>Attraction</code>、<code>Meal</code>、<code>Hotel</code>，再到<code>DayPlan</code>，最后到<code>TripPlan</code>，形成了一个清晰的层次结构。</p><h3 id="13-2-5-数据模型在-Web-应用中的应用"><a href="#13-2-5-数据模型在-Web-应用中的应用" class="headerlink" title="13.2.5 数据模型在 Web 应用中的应用"></a>13.2.5 数据模型在 Web 应用中的应用</h3><p>现在让我们看看这些数据模型如何在实际的 Web 应用中使用。在 FastAPI 中，Pydantic 模型可以直接用作请求和响应的类型定义。FastAPI 会自动进行数据验证、序列化和文档生成。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI<br><span class="hljs-keyword">from</span> app.models.schemas <span class="hljs-keyword">import</span> TripPlanRequest,TripPlan<br><br>app = FastAPI()<br><br><span class="hljs-meta">@app.post(<span class="hljs-params"><span class="hljs-string">&quot;/api/trip/plan&quot;</span>,response_model=TripPlan</span>)</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">create_trip_plan</span>(<span class="hljs-params">request: TripPlanRequest</span>) -&gt; TripPlan:<br>    <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">    创建旅行计划</span><br><span class="hljs-string">    </span><br><span class="hljs-string">    FastAPI自动：</span><br><span class="hljs-string">    1. 验证请求数据(TripPlanRequest)</span><br><span class="hljs-string">    2. 验证响应数据(TripPlan)</span><br><span class="hljs-string">    3. 生成OpenAPI文档</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    trip_plan = <span class="hljs-keyword">await</span> generate_trip_plan(request)<br>    <span class="hljs-keyword">return</span> trip_plan<br></code></pre></td></tr></table></figure><p>当用户发送 POST 请求到<code>/api/trip/plan</code>时，FastAPI 会自动将 JSON 数据转换成<code>TripPlanRequest</code>对象。如果数据格式不正确(比如缺少必填字段，或者类型不匹配)，FastAPI 会自动返回 400 错误，并告诉用户哪里出错了。</p><p>在前端，我们也需要定义对应的 TypeScript 类型。虽然 TypeScript 和 Python 是不同的语言，但数据结构是一样的：</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></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">Location</span> &#123;<br>  <span class="hljs-attr">longitude</span>: <span class="hljs-built_in">number</span>;<br>  <span class="hljs-attr">latitude</span>: <span class="hljs-built_in">number</span>;<br>&#125;<br><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">Attraction</span> &#123;<br>  <span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span>;<br>  <span class="hljs-attr">address</span>: <span class="hljs-built_in">string</span>;<br>  <span class="hljs-attr">location</span>: <span class="hljs-title class_">Location</span>;<br>  <span class="hljs-attr">visit_duration</span>: <span class="hljs-built_in">number</span>;<br>  <span class="hljs-attr">ticket_price</span>: <span class="hljs-built_in">number</span>;<br>&#125;<br><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">TripPlan</span> &#123;<br>  <span class="hljs-attr">city</span>: <span class="hljs-built_in">string</span>;<br>  <span class="hljs-attr">start_date</span>: <span class="hljs-built_in">string</span>;<br>  <span class="hljs-attr">end_date</span>: <span class="hljs-built_in">string</span>;<br>  <span class="hljs-attr">days</span>: <span class="hljs-title class_">DayPlan</span>[];<br>&#125;<br></code></pre></td></tr></table></figure><p>这样，前后端就使用了统一的数据格式。当后端返回<code>TripPlan</code>对象时，前端可以直接使用，不需要任何转换。TypeScript 的类型检查也能帮助我们避免很多错误。</p><h2 id="13-3-多智能体协作设计"><a href="#13-3-多智能体协作设计" class="headerlink" title="13.3 多智能体协作设计"></a>13.3 多智能体协作设计</h2><h3 id="13-3-1-为何需要多智能体"><a href="#13-3-1-为何需要多智能体" class="headerlink" title="13.3.1 为何需要多智能体"></a>13.3.1 为何需要多智能体</h3><p>在第七章中，我们学习了如何使用 SimpleAgent 来构建智能体。SimpleAgent 的设计理念是简单直接：每次调用<code>run()</code>方法时，Agent 会分析用户的问题，决定是否需要调用工具，然后返回结果。这种设计在处理简单任务时非常有效，但当面对旅行规划这样的任务时，就会遇到一些问题。</p><p>如果用单个 Agent 来完成旅行规划。这个 Agent 需要做什么呢？首先，它要搜索景点信息，这需要调用高德地图的 POI 搜索工具。然后，它要查询天气信息，这需要调用天气查询工具。接着，它要搜索酒店信息，这又需要调用 POI 搜索工具。最后，它要把所有这些信息整合起来，生成一个完整的旅行计划。</p><p>这听起来很简单，但实际操作时会遇到第一个问题：<strong>工具调用的限制</strong>。SimpleAgent 每次<code>run()</code>调用只能执行一个工具。这意味着我们需要多次调用<code>run()</code>方法，每次调用处理一个任务。但这样做会带来一个新问题：如何在多次调用之间传递信息？第一次调用得到的景点信息，如何传递给第二次调用？我们需要手动管理这些中间结果，代码会变得很复杂。</p><p>当然，我们可以使用 ReactAgent 来解决这个问题。ReactAgent 可以在一次调用中执行多个工具，它会自动进行多轮思考和行动。但这又带来了新的问题：<strong>时间成本</strong>。ReactAgent 的每一轮思考都需要调用 LLM，如果需要调用三个工具，就需要至少三轮思考，这意味着至少三次 LLM 调用。而且这些调用是串行的，必须等前一个完成才能开始下一个，总时间会很长。</p><p>第二个问题是<strong>提示词的复杂度</strong>。如果我们要让一个 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></pre></td><td class="code"><pre><code class="hljs python">COMPLEX_PROMPT = <span class="hljs-string">&quot;&quot;&quot;你是旅行规划助手。你需要：</span><br><span class="hljs-string">1. 使用maps_text_search搜索景点，关键词根据用户偏好确定</span><br><span class="hljs-string">2. 使用maps_weather查询天气,获取未来几天的天气预报</span><br><span class="hljs-string">3. 使用maps_text_search搜索酒店,类型根据用户需求确定</span><br><span class="hljs-string">4. 整合所有信息生成旅行计划,包括每天的景点、餐饮、住宿安排</span><br><span class="hljs-string">注意：必须按顺序执行,每个工具只能调用一次,输出必须是JSON格式...</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p>这样的提示词有几个问题。首先是<strong>难以维护</strong>。如果我们想修改景点搜索的逻辑(比如增加评分筛选)，就需要修改整个提示词，很容易影响到其他部分。其次是<strong>容易出错</strong>。LLM 需要同时理解多个任务的要求，很容易搞混不同任务的格式和参数。最后是<strong>难以调试</strong>。当生成的计划不符合预期时，我们很难知道是哪个环节出了问题，是景点搜索不准确，还是天气查询失败，还是整合逻辑有问题？</p><p>面对这些问题，一个自然的想法是：能不能把复杂的任务分解成多个简单的任务，让不同的 Agent 各司其职？这就是多 Agent 协作的核心思想。</p><p>想象一下现实世界中的旅行社。当你去旅行社咨询旅行计划时，不会只有一个人为你服务。通常会有专门的景点顾问，负责推荐景点；有酒店顾问，负责预订酒店；还有行程规划师，负责把所有信息整合成完整的行程。每个人都专注于自己擅长的领域，最后由行程规划师把所有信息汇总。这种分工协作的方式，比让一个人做所有事情要高效得多。</p><h3 id="13-3-2-Agent-角色设计"><a href="#13-3-2-Agent-角色设计" class="headerlink" title="13.3.2 Agent 角色设计"></a>13.3.2 Agent 角色设计</h3><p>基于任务分解原则，我们设计了四个专门的 Agent，如图 13.6 所示:</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/13-figures/13-6.png" alt="" width="85%"/>  <p>图 13.6 多智能体协作流程</p></div><ul><li><p><strong>AttractionSearchAgent(景点搜索专家)</strong>专注于搜索景点信息。它只需要理解用户的偏好(比如”历史文化”、”自然风光”)，然后调用高德地图的 POI 搜索工具，返回相关的景点列表。它的提示词很简单，只需要说明如何根据偏好选择关键词，如何调用工具。</p></li><li><p><strong>WeatherQueryAgent(天气查询专家)</strong>专注于查询天气信息。它只需要知道城市名称，然后调用天气查询工具，返回未来几天的天气预报。它的任务非常明确，几乎不会出错。</p></li><li><p><strong>HotelAgent(酒店推荐专家)</strong>专注于搜索酒店信息。它需要理解用户的住宿需求(比如”经济型”、”豪华型”)，然后调用 POI 搜索工具，返回符合要求的酒店列表。</p></li><li><p><strong>PlannerAgent(行程规划专家)</strong>负责整合所有信息。它接收前三个 Agent 的输出，加上用户的原始需求(日期、预算等)，然后生成完整的旅行计划。它不需要调用任何外部工具，只需要专注于信息的整合和行程的安排。</p></li></ul><p>现在让我们详细设计每个 Agent 的角色和提示词。设计提示词时，我们需要考虑几个关键问题：这个 Agent 需要什么输入？它应该产生什么输出？它需要调用什么工具？它可能遇到什么问题？</p><p><strong>AttractionSearchAgent</strong>的任务是根据用户偏好搜索景点。它的输入是城市名称和用户偏好(比如”历史文化”、”自然风光”)。它需要调用<code>amap_maps_text_search</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><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><code class="hljs python">ATTRACTION_AGENT_PROMPT = <span class="hljs-string">&quot;&quot;&quot;你是景点搜索专家。</span><br><span class="hljs-string"></span><br><span class="hljs-string">**工具调用格式:**</span><br><span class="hljs-string">`[TOOL_CALL:amap_maps_text_search:keywords=景点,city=城市名]`</span><br><span class="hljs-string"></span><br><span class="hljs-string">**示例:**</span><br><span class="hljs-string">- `[TOOL_CALL:amap_maps_text_search:keywords=景点,city=北京]`</span><br><span class="hljs-string">- `[TOOL_CALL:amap_maps_text_search:keywords=博物馆,city=上海]`</span><br><span class="hljs-string"></span><br><span class="hljs-string">**重要:**</span><br><span class="hljs-string">- 必须使用工具搜索,不要编造信息</span><br><span class="hljs-string">- 根据用户偏好(&#123;preferences&#125;)搜索&#123;city&#125;的景点</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p>这个提示词很简洁，但包含了所有必要的信息。它明确说明了工具调用的格式，提供了具体的示例，还强调了两个重要原则：必须使用工具(不能编造)，要根据用户偏好搜索。</p><p><strong>WeatherQueryAgent</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><code class="hljs python">WEATHER_AGENT_PROMPT = <span class="hljs-string">&quot;&quot;&quot;你是天气查询专家。</span><br><span class="hljs-string"></span><br><span class="hljs-string">**工具调用格式:**</span><br><span class="hljs-string">`[TOOL_CALL:amap_maps_weather:city=城市名]`</span><br><span class="hljs-string"></span><br><span class="hljs-string">请查询&#123;city&#125;的天气信息。</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p><strong>HotelAgent</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><code class="hljs python">HOTEL_AGENT_PROMPT = <span class="hljs-string">&quot;&quot;&quot;你是酒店推荐专家。</span><br><span class="hljs-string"></span><br><span class="hljs-string">**工具调用格式:**</span><br><span class="hljs-string">`[TOOL_CALL:amap_maps_text_search:keywords=酒店,city=城市名]`</span><br><span class="hljs-string"></span><br><span class="hljs-string">请搜索&#123;city&#125;的&#123;accommodation&#125;酒店。</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p><strong>PlannerAgent</strong>是最复杂的，因为它需要整合所有信息。它的输入是用户需求和前三个 Agent 的输出，输出是完整的旅行计划(JSON 格式)。</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></pre></td><td class="code"><pre><code class="hljs python">PLANNER_AGENT_PROMPT = <span class="hljs-string">&quot;&quot;&quot;你是行程规划专家。</span><br><span class="hljs-string"></span><br><span class="hljs-string">**输出格式:**</span><br><span class="hljs-string">严格按照以下JSON格式返回:</span><br><span class="hljs-string">&#123;</span><br><span class="hljs-string">  &quot;city&quot;: &quot;城市名称&quot;,</span><br><span class="hljs-string">  &quot;start_date&quot;: &quot;YYYY-MM-DD&quot;,</span><br><span class="hljs-string">  &quot;end_date&quot;: &quot;YYYY-MM-DD&quot;,</span><br><span class="hljs-string">  &quot;days&quot;: [...],</span><br><span class="hljs-string">  &quot;weather_info&quot;: [...],</span><br><span class="hljs-string">  &quot;overall_suggestions&quot;: &quot;总体建议&quot;,</span><br><span class="hljs-string">  &quot;budget&quot;: &#123;...&#125;</span><br><span class="hljs-string">&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">**规划要求:**</span><br><span class="hljs-string">1. weather_info必须包含每天的天气</span><br><span class="hljs-string">2. 温度为纯数字(不带°C)</span><br><span class="hljs-string">3. 每天安排2-3个景点</span><br><span class="hljs-string">4. 考虑景点距离和游览时间</span><br><span class="hljs-string">5. 包含早中晚三餐</span><br><span class="hljs-string">6. 提供实用建议</span><br><span class="hljs-string">7. 包含预算信息</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><h3 id="13-3-3-Agent-协作流程"><a href="#13-3-3-Agent-协作流程" class="headerlink" title="13.3.3 Agent 协作流程"></a>13.3.3 Agent 协作流程</h3><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><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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">TripPlannerAgent</span>:<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):<br>        self.attraction_agent = SimpleAgent(name=<span class="hljs-string">&quot;景点搜索&quot;</span>prompt=ATTRACTION_PROMPT)<br>        self.weather_agent = SimpleAgent(name=<span class="hljs-string">&quot;天气查询&quot;</span>, prompt=WEATHER_PROMPT)<br>        self.hotel_agent = SimpleAgent(name=<span class="hljs-string">&quot;酒店推荐&quot;</span>, prompt=HOTEL_PROMPT)<br>        self.planner_agent = SimpleAgent(name=<span class="hljs-string">&quot;行程规划&quot;</span>, prompt=PLANNER_PROMPT)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">plan_trip</span>(<span class="hljs-params">self, request: TripPlanRequest</span>) -&gt; TripPlan:<br>        <span class="hljs-comment"># 步骤1: 景点搜索</span><br>        attraction_response = self.attraction_agent.run(<br>            <span class="hljs-string">f&quot;请搜索<span class="hljs-subst">&#123;request.city&#125;</span>的<span class="hljs-subst">&#123;request.preferences&#125;</span>景点&quot;</span><br>        )<br><br>        <span class="hljs-comment"># 步骤2: 天气查询</span><br>        weather_response = self.weather_agent.run(<br>            <span class="hljs-string">f&quot;请查询<span class="hljs-subst">&#123;request.city&#125;</span>的天气&quot;</span><br>        )<br><br>        <span class="hljs-comment"># 步骤3: 酒店推荐</span><br>        hotel_response = self.hotel_agent.run(<br>            <span class="hljs-string">f&quot;请搜索<span class="hljs-subst">&#123;request.city&#125;</span>的<span class="hljs-subst">&#123;request.accommodation&#125;</span>酒店&quot;</span><br>        )<br><br>        <span class="hljs-comment"># 步骤4: 整合生成计划</span><br>        planner_query = self._build_planner_query(<br>            request, attraction_response, weather_response, hotel_response<br>        )<br>        planner_response = self.planner_agent.run(planner_query)<br><br>        <span class="hljs-comment"># 步骤5: 解析JSON</span><br>        trip_plan = self._parse_trip_plan(planner_response)<br>        <span class="hljs-keyword">return</span> trip_plan<br></code></pre></td></tr></table></figure><p>这个流程顺序执行四个步骤，每个步骤的输出作为下一个步骤的输入。注意我们使用了<code>TripPlanRequest</code>和<code>TripPlan</code>这两个 Pydantic 模型，这是在 13.2 节中定义的。</p><h3 id="13-3-4-查询构建"><a href="#13-3-4-查询构建" class="headerlink" title="13.3.4 查询构建"></a>13.3.4 查询构建</h3><p>PlannerAgent 需要整合所有信息，这个查询需要包含所有必要的信息，而且要组织得清晰有序，让 LLM 能够准确理解。</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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_build_planner_query</span>(<span class="hljs-params"></span><br><span class="hljs-params">    self,</span><br><span class="hljs-params">    request: TripPlanRequest,</span><br><span class="hljs-params">    attraction_response: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">    weather_response: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">    hotel_response: <span class="hljs-built_in">str</span></span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;构建规划Agent的查询&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;&quot;&quot;</span><br><span class="hljs-string">请根据以下信息生成<span class="hljs-subst">&#123;request.city&#125;</span>的<span class="hljs-subst">&#123;request.days&#125;</span>日旅行计划:</span><br><span class="hljs-string"></span><br><span class="hljs-string">**用户需求:**</span><br><span class="hljs-string">- 目的地: <span class="hljs-subst">&#123;request.city&#125;</span></span><br><span class="hljs-string">- 日期: <span class="hljs-subst">&#123;request.start_date&#125;</span> 至 <span class="hljs-subst">&#123;request.end_date&#125;</span></span><br><span class="hljs-string">- 天数: <span class="hljs-subst">&#123;request.days&#125;</span>天</span><br><span class="hljs-string">- 偏好: <span class="hljs-subst">&#123;request.preferences&#125;</span></span><br><span class="hljs-string">- 预算: <span class="hljs-subst">&#123;request.budget&#125;</span></span><br><span class="hljs-string">- 交通方式: <span class="hljs-subst">&#123;request.transportation&#125;</span></span><br><span class="hljs-string">- 住宿类型: <span class="hljs-subst">&#123;request.accommodation&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">**景点信息:**</span><br><span class="hljs-string"><span class="hljs-subst">&#123;attraction_response&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">**天气信息:**</span><br><span class="hljs-string"><span class="hljs-subst">&#123;weather_response&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">**酒店信息:**</span><br><span class="hljs-string"><span class="hljs-subst">&#123;hotel_response&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">请生成详细的旅行计划,包括每天的景点安排、餐饮推荐、住宿信息和预算明细。</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p>通过这种多 Agent 协作的设计，我们把一个复杂的旅行规划任务分解成了四个简单的子任务。每个 Agent 都专注于自己擅长的领域，也为未来的功能扩展(比如添加餐厅推荐 Agent、交通规划 Agent)打下了良好的基础。</p><h2 id="13-4-MCP-工具集成详解"><a href="#13-4-MCP-工具集成详解" class="headerlink" title="13.4 MCP 工具集成详解"></a>13.4 MCP 工具集成详解</h2><h3 id="13-4-1-为什么不直接调用-API"><a href="#13-4-1-为什么不直接调用-API" class="headerlink" title="13.4.1 为什么不直接调用 API"></a>13.4.1 为什么不直接调用 API</h3><p>在 13.3 节中，我们设计了四个 Agent 来协作完成旅行规划任务。其中 AttractionSearchAgent、WeatherQueryAgent 和 HotelAgent 都需要调用高德地图的 API 来获取数据。一个自然的问题是：为什么不直接在 Agent 中调用高德地图的 HTTP API？</p><p>让我们先看看直接调用 API 会是什么样子。高德地图提供了 POI 搜索 API，我们需要构造 HTTP 请求，传递参数，解析响应：</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><code class="hljs python"><span class="hljs-keyword">import</span> requests<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">search_poi</span>(<span class="hljs-params">keywords: <span class="hljs-built_in">str</span>,city: <span class="hljs-built_in">str</span>,api_key: <span class="hljs-built_in">str</span></span>):<br>    <span class="hljs-string">&quot;&quot;&quot;直接调用高德地图POI搜索API&quot;&quot;&quot;</span><br>    url = <span class="hljs-string">&quot;https://restapi.amap.com/v3/place/text&quot;</span><br>    params = &#123;<br>        <span class="hljs-string">&quot;keywords&quot;</span>: keywords,<br>        <span class="hljs-string">&quot;city&quot;</span>: city,<br>        <span class="hljs-string">&quot;key&quot;</span>: api_key,<br>        <span class="hljs-string">&quot;output&quot;</span>: <span class="hljs-string">&quot;json&quot;</span><br>    &#125;<br>    response = requests.get(url,params=params)<br>    data = response.json()<br>    <span class="hljs-keyword">return</span> data<br></code></pre></td></tr></table></figure><p>这种方式看起来很简单，但在实际使用中会遇到几个问题。首先是<strong>Agent 无法自主调用</strong>。在我们的 HelloAgents 框架中，Agent 通过识别提示词中的工具调用标记(比如<code>[TOOL_CALL:tool_name:arg1=value1]</code>)来调用工具。如果我们直接在代码中调用 API，Agent 就失去了自主决策的能力，变成了一个简单的函数调用。</p><p>其次是<strong>参数传递复杂</strong>。高德地图的 API 有很多参数，比如 POI 搜索有<code>keywords</code>、<code>city</code>、<code>types</code>、<code>offset</code>、<code>page</code>等十几个参数。如果我们要让 Agent 能够灵活使用这些参数，就需要在提示词中详细说明每个参数的含义和格式，这会让提示词变得非常复杂。</p><p>第三是<strong>响应解析困难</strong>。高德地图 API 返回的是 JSON 格式的数据，结构比较复杂。我们需要编写代码来解析这些数据，提取我们需要的字段。如果 API 的响应格式发生变化，我们就需要修改解析代码。</p><p>最后是<strong>工具管理混乱</strong>。高德地图提供了十几个不同的 API(POI 搜索、天气查询、路线规划等)，如果我们为每个 API 都编写一个函数，然后手动注册到 Agent 的工具列表中，代码会变得很冗长。而且当我们想添加新的 API 时，需要修改多个地方。</p><h3 id="13-4-2-高德地图-MCP-集成"><a href="#13-4-2-高德地图-MCP-集成" class="headerlink" title="13.4.2 高德地图 MCP 集成"></a>13.4.2 高德地图 MCP 集成</h3><p>MCP(Model Context Protocol)是 Anthropic 提出的标准化协议，用于连接 LLM 和外部工具。本节将介绍如何在项目中集成高德地图 MCP 服务器。我们的项目用的是<code>amap-mcp-server</code>，这是一个用 Node.js 实现的 MCP 服务器：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/13-figures/13-7.png" alt="" width="85%"/>  <p>图 13.7 amap-mcp-server 工具</p></div><p>高德地图 MCP 服务器提供了多种工具，主要分为以下类别，如表 13.1 所示:</p><div align="center">  <p>表 13.1 高德地图 MCP 工具分类</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/13-figures/13-table-1.png" alt="" width="85%"/></div><p>通过 MCP 协议，我们可以很方便地在 HelloAgents 中集成:</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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool<br><span class="hljs-keyword">from</span> app.config <span class="hljs-keyword">import</span> get_settings<br><br>settings = get_settings()<br><br><span class="hljs-comment"># 创建MCP工具</span><br>mcp_tool = MCPTool(<br>    name=<span class="hljs-string">&quot;amap_mcp&quot;</span>,<br>    command=<span class="hljs-string">&quot;npx&quot;</span>,<br>    args=[<span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@sugarforever/amap-mcp-server&quot;</span>],<br>    env=&#123;<span class="hljs-string">&quot;AMAP_API_KEY&quot;</span>: settings.amap_api_key&#125;,<br>    auto_expand=<span class="hljs-literal">True</span><br>)<br></code></pre></td></tr></table></figure><p>这段代码做了什么呢？首先，<code>command</code>和<code>args</code>指定了如何启动 MCP 服务器。<code>npx -y @sugarforever/amap-mcp-server</code>会从 npm 仓库下载并运行<code>amap-mcp-server</code>这个包。<code>env</code>参数传递了环境变量，这里我们传递了高德地图的 API 密钥。</p><p><strong>注意：</strong>本文档中部分示例使用 <code>npx</code> 启动 MCP（Model Context Protocol）服务。而在本节代码仓中，我们实际采用的是 <code>uvx</code> 方式。需要说明的是，<code>npx</code> 和 <code>uvx</code> 在设计理念上高度一致，区别仅在于所处的生态系统，<code>npx</code> 面向 JavaScript&#x2F;Node.js（包来自 npm），而<code>uvx</code> 面向 Python（包来自 PyPI）。两种方式并无优劣之分，请大家在使用时按需进行选择。</p><p>当我们创建<code>MCPTool</code>对象时，它会在后台启动 MCP 服务器进程，并通过标准输入输出(stdin&#x2F;stdout)与服务器通信。这是 MCP 协议的一个特点：使用进程间通信而不是 HTTP，这样更高效，也更容易管理。</p><p>最关键的是<code>auto_expand=True</code>这个参数。当设置为 True 时，<code>MCPTool</code>会自动查询 MCP 服务器提供了哪些工具，然后为每个工具创建一个独立的 Tool 对象。这就是为什么我们只创建了一个<code>MCPTool</code>，但 Agent 却获得了 16 个工具。让我们看看这个过程：</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><code class="hljs python"><span class="hljs-comment"># 创建一个MCPTool</span><br>mcp_tool = MCPTool(..., auto_expand=<span class="hljs-literal">True</span>)<br>agent.add_tool(mcp_tool)<br><br><span class="hljs-comment"># Agent实际上获得了16个工具！</span><br><span class="hljs-built_in">print</span>(<span class="hljs-built_in">list</span>(agent.tools.keys()))<br><span class="hljs-comment"># [&#x27;amap_maps_text_search&#x27;, &#x27;amap_maps_weather&#x27;, ...]</span><br></code></pre></td></tr></table></figure><p>如图 13.8 所示，假设用户想搜索北京的景点，AttractionSearchAgent 接收到查询”请搜索北京的历史文化景点”。Agent 分析这个查询，决定调用<code>amap_maps_text_search</code>工具，参数是<code>keywords=景点，city=北京</code>。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/13-figures/13-8.png" alt="" width="85%"/>  <p>图 13.8 MCP 工具调用流程</p></div><p>Agent 生成工具调用标记：<code>[TOOL_CALL:amap_maps_text_search:keywords=景点，city=北京]</code>。HelloAgents 框架解析这个标记，提取工具名称和参数，然后调用对应的 Tool 对象。</p><p>Tool 对象是<code>MCPTool</code>自动创建的，它会把调用请求发送给 MCP 服务器。具体来说，它会构造一个 JSON-RPC 格式的消息，通过 stdin 发送给服务器进程：</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;jsonrpc&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2.0&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;method&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;tools/call&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;params&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;amap_maps_text_search&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;arguments&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>      <span class="hljs-attr">&quot;keywords&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;景点&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;city&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;北京&quot;</span><br>    <span class="hljs-punctuation">&#125;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>MCP 服务器接收到这个消息，解析参数，然后调用高德地图的 HTTP API。它会构造 HTTP 请求，添加 API 密钥，发送请求，接收响应。</p><p>高德地图 API 返回 JSON 格式的数据，包含景点列表、地址、坐标等信息。MCP 服务器解析这些数据，提取关键字段，然后构造响应消息，通过 stdout 返回给<code>MCPTool</code>：</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;jsonrpc&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2.0&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;result&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;content&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>      <span class="hljs-punctuation">&#123;</span><br>        <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;text&quot;</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-attr">&quot;text&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;找到以下景点：\n1. 故宫博物院 - 地址：东城区景山前街4号\n2. 天坛公园 - 地址：东城区天坛路\n...&quot;</span><br>      <span class="hljs-punctuation">&#125;</span><br>    <span class="hljs-punctuation">]</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p><code>MCPTool</code>接收到响应，提取文本内容，返回给 Agent。Agent 把这个结果作为工具调用的输出，继续生成最终的回复。</p><p>这个流程看起来很复杂，但对于 Agent 来说，它只需要知道有一个叫<code>amap_maps_text_search</code>的工具，可以搜索景点。所有的底层细节都被 MCP 协议和<code>MCPTool</code>封装起来了。</p><h3 id="13-4-3-共享-MCP-实例"><a href="#13-4-3-共享-MCP-实例" class="headerlink" title="13.4.3 共享 MCP 实例"></a>13.4.3 共享 MCP 实例</h3><p>在我们的多 Agent 系统中，有三个 Agent 都需要使用高德地图的工具。那么每个 Agent 应该创建自己的<code>MCPTool</code>实例，还是共享同一个实例？</p><p>如果每个 Agent 都创建一个<code>MCPTool</code>实例，这意味着会有三个服务器进程同时运行。每个进程都会独立地调用高德地图 API，这可能会超过 API 的速率限制。而且多个进程会占用更多的内存和 CPU 资源。</p><p>更好的做法是让所有 Agent 共享同一个<code>MCPTool</code>实例。这样只需要启动一个 MCP 服务器进程，所有的 API 调用都通过这个进程进行。这不仅节省资源，还可以更好地控制 API 调用频率。</p><p>在代码中，我们在<code>TripPlannerAgent</code>的构造函数中创建一个<code>MCPTool</code>实例，然后把它添加到每个子 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><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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">TripPlannerAgent</span>:<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):<br>        settings = get_settings()<br>        self.llm = HelloAgentsLLM()<br><br>        <span class="hljs-comment"># 创建共享的MCP工具实例(只创建一次)</span><br>        self.mcp_tool = MCPTool(<br>            name=<span class="hljs-string">&quot;amap_mcp&quot;</span>,<br>            command=<span class="hljs-string">&quot;npx&quot;</span>,<br>            args=[<span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@sugarforever/amap-mcp-server&quot;</span>],<br>            env=&#123;<span class="hljs-string">&quot;AMAP_API_KEY&quot;</span>: settings.amap_api_key&#125;,<br>            auto_expand=<span class="hljs-literal">True</span><br>        )<br><br>        <span class="hljs-comment"># 创建多个Agent,共享同一个MCP工具</span><br>        self.attraction_agent = SimpleAgent(<br>            name=<span class="hljs-string">&quot;AttractionSearchAgent&quot;</span>,<br>            llm=self.llm,<br>            system_prompt=ATTRACTION_AGENT_PROMPT<br>        )<br>        self.attraction_agent.add_tool(self.mcp_tool)  <span class="hljs-comment"># 共享</span><br><br>        self.weather_agent = SimpleAgent(<br>            name=<span class="hljs-string">&quot;WeatherQueryAgent&quot;</span>,<br>            llm=self.llm,<br>            system_prompt=WEATHER_AGENT_PROMPT<br>        )<br>        self.weather_agent.add_tool(self.mcp_tool)  <span class="hljs-comment"># 共享</span><br><br>        self.hotel_agent = SimpleAgent(<br>            name=<span class="hljs-string">&quot;HotelAgent&quot;</span>,<br>            llm=self.llm,<br>            system_prompt=HOTEL_AGENT_PROMPT<br>        )<br>        self.hotel_agent.add_tool(self.mcp_tool)  <span class="hljs-comment"># 共享</span><br></code></pre></td></tr></table></figure><p>这样，三个 Agent 都可以使用高德地图的 16 个工具，但底层只有一个 MCP 服务器进程在运行。当我们调用<code>TripPlannerAgent</code>的<code>plan_trip</code>方法时，三个 Agent 会依次调用工具，所有的请求都通过同一个 MCP 服务器发送到高德地图 API。</p><h3 id="13-4-4-Unsplash-图片-API-集成"><a href="#13-4-4-Unsplash-图片-API-集成" class="headerlink" title="13.4.4 Unsplash 图片 API 集成"></a>13.4.4 Unsplash 图片 API 集成</h3><p>除了高德地图，我们还需要为景点获取图片，让旅行计划更加生动直观。我们使用 Unsplash API 来搜索景点图片。需要注意的是，Unsplash 是国外的服务，而且是为数不多可以免费使用的图片 API，所以搜索结果可能不够准确。在实际项目中，可以考虑使用必应、百度或高德的 POI 图片 API，但这些服务通常需要付费。</p><p>Unsplash API 的集成比较简单，我们创建一个<code>UnsplashService</code>类来封装 API 调用：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># backend/app/services/unsplash_service.py</span><br><span class="hljs-keyword">import</span> requests<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Optional</span>, <span class="hljs-type">List</span>, <span class="hljs-type">Dict</span><br><span class="hljs-keyword">import</span> logging<br><br>logger = logging.getLogger(__name__)<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">UnsplashService</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;Unsplash图片服务&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, access_key: <span class="hljs-built_in">str</span></span>):<br>        self.access_key = access_key<br>        self.base_url = <span class="hljs-string">&quot;https://api.unsplash.com&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">search_photos</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span>, per_page: <span class="hljs-built_in">int</span> = <span class="hljs-number">10</span></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;搜索图片&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">try</span>:<br>            url = <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;self.base_url&#125;</span>/search/photos&quot;</span><br>            params = &#123;<br>                <span class="hljs-string">&quot;query&quot;</span>: query,<br>                <span class="hljs-string">&quot;per_page&quot;</span>: per_page,<br>                <span class="hljs-string">&quot;client_id&quot;</span>: self.access_key<br>            &#125;<br><br>            response = requests.get(url, params=params, timeout=<span class="hljs-number">10</span>)<br>            response.raise_for_status()<br><br>            data = response.json()<br>            results = data.get(<span class="hljs-string">&quot;results&quot;</span>, [])<br><br>            <span class="hljs-comment"># 提取图片URL</span><br>            photos = []<br>            <span class="hljs-keyword">for</span> result <span class="hljs-keyword">in</span> results:<br>                photos.append(&#123;<br>                    <span class="hljs-string">&quot;url&quot;</span>: result[<span class="hljs-string">&quot;urls&quot;</span>][<span class="hljs-string">&quot;regular&quot;</span>],<br>                    <span class="hljs-string">&quot;description&quot;</span>: result.get(<span class="hljs-string">&quot;description&quot;</span>, <span class="hljs-string">&quot;&quot;</span>),<br>                    <span class="hljs-string">&quot;photographer&quot;</span>: result[<span class="hljs-string">&quot;user&quot;</span>][<span class="hljs-string">&quot;name&quot;</span>]<br>                &#125;)<br><br>            <span class="hljs-keyword">return</span> photos<br><br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            logger.error(<span class="hljs-string">f&quot;搜索图片失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>            <span class="hljs-keyword">return</span> []<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_photo_url</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;获取单张图片URL&quot;&quot;&quot;</span><br>        photos = self.search_photos(query, per_page=<span class="hljs-number">1</span>)<br>        <span class="hljs-keyword">return</span> photos[<span class="hljs-number">0</span>].get(<span class="hljs-string">&quot;url&quot;</span>) <span class="hljs-keyword">if</span> photos <span class="hljs-keyword">else</span> <span class="hljs-literal">None</span><br></code></pre></td></tr></table></figure><p>这个服务类提供了两个方法：<code>search_photos</code>搜索多张图片，<code>get_photo_url</code>获取单张图片的 URL。我们在 API 路由中使用这个服务，为每个景点获取图片：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># backend/app/api/routes/trip.py</span><br><span class="hljs-keyword">from</span> app.services.unsplash_service <span class="hljs-keyword">import</span> UnsplashService<br><br>unsplash_service = UnsplashService(settings.unsplash_access_key)<br><br><span class="hljs-meta">@router.post(<span class="hljs-params"><span class="hljs-string">&quot;/plan&quot;</span>, response_model=TripPlan</span>)</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">create_trip_plan</span>(<span class="hljs-params">request: TripPlanRequest</span>) -&gt; TripPlan:<br>    <span class="hljs-comment"># 生成旅行计划</span><br>    trip_plan = trip_planner_agent.plan_trip(request)<br><br>    <span class="hljs-comment"># 为每个景点获取图片</span><br>    <span class="hljs-keyword">for</span> day <span class="hljs-keyword">in</span> trip_plan.days:<br>        <span class="hljs-keyword">for</span> attraction <span class="hljs-keyword">in</span> day.attractions:<br>            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> attraction.image_url:<br>                image_url = unsplash_service.get_photo_url(<br>                    <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;attraction.name&#125;</span> <span class="hljs-subst">&#123;trip_plan.city&#125;</span>&quot;</span><br>                )<br>                attraction.image_url = image_url<br><br>    <span class="hljs-keyword">return</span> trip_plan<br></code></pre></td></tr></table></figure><p>注意我们没有把 Unsplash 封装成 Tool 或 MCP 工具，而是直接在 API 路由中调用。这是因为图片搜索不需要 Agent 的智能决策，只是一个简单的数据增强步骤。如果你想让 Agent 能够自主决定是否需要图片，或者选择不同的图片来源，可以考虑把它封装成 Tool。</p><h2 id="13-5-前端开发详解"><a href="#13-5-前端开发详解" class="headerlink" title="13.5 前端开发详解"></a>13.5 前端开发详解</h2><h3 id="13-5-1-前后端分离的-Web-架构"><a href="#13-5-1-前后端分离的-Web-架构" class="headerlink" title="13.5.1 前后端分离的 Web 架构"></a>13.5.1 前后端分离的 Web 架构</h3><p>在开始前端开发之前，我们需要理解现代 Web 应用的架构模式。在早期的 Web 开发中，前端和后端是混在一起的，比如 PHP、JSP 这样的技术，HTML 模板和业务逻辑代码写在同一个文件里。这种方式在小项目中很方便，但在大型项目中会遇到很多问题：前端和后端开发者需要频繁协调，代码难以复用，测试困难。</p><p>现代 Web 应用普遍采用<strong>前后端分离</strong>的架构。后端只负责提供 API 接口，返回 JSON 格式的数据。前端是一个独立的应用，通过 HTTP 请求调用后端 API，获取数据后渲染页面。这种架构有几个明显的优势：前端和后端可以独立开发、独立部署、独立测试；前端可以是 Web 应用、移动应用或桌面应用，都使用同一套后端 API；前端可以使用现代的框架和工具链，提供更好的用户体验。</p><p>在我们的智能旅行助手项目中，后端是用 Python 和 FastAPI 实现的，提供了一个核心 API 接口<code>POST /api/trip/plan</code>，接收旅行需求，返回旅行计划。前端是用 Vue 3 和 TypeScript 实现的，是一个单页应用(SPA)，用户在浏览器中填写表单，点击”开始规划”按钮，前端发送 HTTP 请求到后端，等待响应，然后渲染结果页面。整个过程中，页面不会刷新，用户体验很流畅。</p><p>前端技术栈的选择需要考虑几个因素：开发效率、性能、生态系统、学习曲线。如表 13.2 所示，该项目选择了以下技术栈：</p><div align="center">  <p>表 13.2 前端技术栈</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/13-figures/13-table-2.png" alt="" width="85%"/></div><p>项目的目录结构是这样的：</p><figure class="highlight axapta"><table><tr><td class="gutter"><pre><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><code class="hljs axapta">frontend/<br>├── src/<br>│   ├── views/              <span class="hljs-meta"># 页面组件</span><br>│   │   ├── Home.vue        <span class="hljs-meta"># 首页(表单)</span><br>│   │   └── Result.vue      <span class="hljs-meta"># 结果页</span><br>│   ├── services/           <span class="hljs-meta"># API服务</span><br>│   │   └── api.ts<br>│   ├── types/              <span class="hljs-meta"># 类型定义</span><br>│   │   └── <span class="hljs-keyword">index</span>.ts<br>│   ├── router/             <span class="hljs-meta"># 路由配置</span><br>│   │   └── <span class="hljs-keyword">index</span>.ts<br>│   ├── App.vue<br>│   └── main.ts<br>├── package.json<br>├── vite.config.ts<br>└── tsconfig.json<br></code></pre></td></tr></table></figure><p>其中<code>views</code>目录存放页面组件，<code>services</code>目录存放 API 调用逻辑，<code>types</code>目录存放 TypeScript 类型定义，<code>router</code>目录存放路由配置。</p><h3 id="13-5-2-类型定义"><a href="#13-5-2-类型定义" class="headerlink" title="13.5.2 类型定义"></a>13.5.2 类型定义</h3><p>在 13.2 节中，我们在后端使用 Pydantic 定义了数据模型，比如<code>Location</code>、<code>Attraction</code>、<code>DayPlan</code>、<code>TripPlan</code>等。在前端，我们需要定义对应的 TypeScript 类型。</p><p>让我们看看如何定义这些类型。首先是最基础的<code>Location</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></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-comment">// frontend/src/types/index.ts</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Location</span> &#123;<br>  <span class="hljs-attr">longitude</span>: <span class="hljs-built_in">number</span><br>  <span class="hljs-attr">latitude</span>: <span class="hljs-built_in">number</span><br>&#125;<br></code></pre></td></tr></table></figure><p>这个类型定义和后端的 Pydantic 模型完全对应。注意 TypeScript 使用<code>interface</code>关键字定义类型，字段类型用冒号分隔，不需要默认值。</p><p>接下来是<code>Attraction</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><code class="hljs typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Attraction</span> &#123;<br>  <span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span><br>  <span class="hljs-attr">address</span>: <span class="hljs-built_in">string</span><br>  <span class="hljs-attr">location</span>: <span class="hljs-title class_">Location</span><br>  <span class="hljs-attr">visit_duration</span>: <span class="hljs-built_in">number</span><br>  <span class="hljs-attr">description</span>: <span class="hljs-built_in">string</span><br>  category?: <span class="hljs-built_in">string</span><br>  rating?: <span class="hljs-built_in">number</span><br>  image_url?: <span class="hljs-built_in">string</span><br>  ticket_price?: <span class="hljs-built_in">number</span><br>&#125;<br></code></pre></td></tr></table></figure><p>注意这里使用了<code>Location</code>类型作为字段类型，这就是嵌套类型。问号<code>?</code>表示可选字段，对应后端 Pydantic 模型中的<code>Optional</code>。</p><p>类似地，我们定义<code>Meal</code>、<code>Hotel</code>、<code>Budget</code>、<code>WeatherInfo</code>等类型。最后是顶层的<code>TripPlan</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></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">TripPlan</span> &#123;<br>  <span class="hljs-attr">city</span>: <span class="hljs-built_in">string</span><br>  <span class="hljs-attr">start_date</span>: <span class="hljs-built_in">string</span><br>  <span class="hljs-attr">end_date</span>: <span class="hljs-built_in">string</span><br>  <span class="hljs-attr">days</span>: <span class="hljs-title class_">DayPlan</span>[]<br>  <span class="hljs-attr">weather_info</span>: <span class="hljs-title class_">WeatherInfo</span>[]<br>  <span class="hljs-attr">overall_suggestions</span>: <span class="hljs-built_in">string</span><br>  budget?: <span class="hljs-title class_">Budget</span><br>&#125;<br></code></pre></td></tr></table></figure><p>还有请求类型<code>TripPlanRequest</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></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">TripPlanRequest</span> &#123;<br>  <span class="hljs-attr">city</span>: <span class="hljs-built_in">string</span><br>  <span class="hljs-attr">start_date</span>: <span class="hljs-built_in">string</span><br>  <span class="hljs-attr">end_date</span>: <span class="hljs-built_in">string</span><br>  <span class="hljs-attr">days</span>: <span class="hljs-built_in">number</span><br>  <span class="hljs-attr">preferences</span>: <span class="hljs-built_in">string</span><br>  <span class="hljs-attr">budget</span>: <span class="hljs-built_in">string</span><br>  <span class="hljs-attr">transportation</span>: <span class="hljs-built_in">string</span><br>  <span class="hljs-attr">accommodation</span>: <span class="hljs-built_in">string</span><br>&#125;<br></code></pre></td></tr></table></figure><p>这些类型定义有什么用呢？首先，当我们调用 API 时，TypeScript 会检查我们传递的数据是否符合<code>TripPlanRequest</code>类型。如果我们不小心把<code>days</code>写成了字符串，TypeScript 会立即报错。其次，当我们接收 API 响应时，TypeScript 会检查响应数据是否符合<code>TripPlan</code>类型。如果后端返回的数据结构发生变化，前端会立即发现。最后，IDE 可以根据类型定义提供代码补全，我们输入<code>tripPlan.</code>时，IDE 会自动列出所有可用的字段。</p><h3 id="13-5-3-API-服务封装"><a href="#13-5-3-API-服务封装" class="headerlink" title="13.5.3 API 服务封装"></a>13.5.3 API 服务封装</h3><p>有了类型定义，我们就可以封装 API 调用了。我们创建一个<code>api.ts</code>文件，使用 Axios 来发送 HTTP 请求：</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><code class="hljs typescript"><span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;axios&#x27;</span><br><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> &#123; <span class="hljs-title class_">TripPlanRequest</span>,<span class="hljs-title class_">TripPlan</span> &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;../types&#x27;</span><br><br><span class="hljs-keyword">const</span> api = axios.<span class="hljs-title function_">create</span>(&#123;<br>  <span class="hljs-attr">baseURL</span>: <span class="hljs-string">&#x27;http://localhost:8000/api&#x27;</span>,<br>  <span class="hljs-attr">timeout</span>: <span class="hljs-number">120000</span>, <span class="hljs-comment">// 2分钟超时</span><br>  <span class="hljs-attr">headers</span>: &#123;<br>    <span class="hljs-string">&#x27;Content-Type&#x27;</span>: <span class="hljs-string">&#x27;application/json&#x27;</span><br>  &#125;<br>&#125;)<br></code></pre></td></tr></table></figure><p>这里我们创建了一个 Axios 实例，配置了基础 URL、超时时间和请求头。为什么超时时间设置为 2 分钟？因为生成旅行计划需要调用多个 Agent，每个 Agent 都要调用 LLM 和外部 API，整个过程可能需要 10-30 秒。如果超时时间太短，请求会被中断。</p><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><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><code class="hljs typescript"><span class="hljs-comment">// 请求拦截器</span><br>api.<span class="hljs-property">interceptors</span>.<span class="hljs-property">request</span>.<span class="hljs-title function_">use</span>(<br>  <span class="hljs-function"><span class="hljs-params">config</span> =&gt;</span> &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;发送请求：&#x27;</span>,config)<br>    <span class="hljs-keyword">return</span> config<br>  &#125;,<br>  <span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">reject</span>(error)<br>)<br><br><span class="hljs-comment">// 响应拦截器</span><br>api.<span class="hljs-property">interceptors</span>.<span class="hljs-property">response</span>.<span class="hljs-title function_">use</span>(<br>  <span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;收到响应：&#x27;</span>,response)<br>    <span class="hljs-keyword">return</span> response<br>  &#125;,<br>  <span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> &#123;<br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">&#x27;请求失败：&#x27;</span>,error)<br>    <span class="hljs-keyword">return</span> <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">reject</span>(error)<br>  &#125;<br>)<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-comment">// 生成旅行计划</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> generateTripPlan = <span class="hljs-keyword">async</span> (<span class="hljs-attr">request</span>: <span class="hljs-title class_">TripPlanRequest</span>): <span class="hljs-title class_">Promise</span>&lt;<span class="hljs-title class_">TripPlan</span>&gt; =&gt; &#123;<br>  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> api.<span class="hljs-property">post</span>&lt;<span class="hljs-title class_">TripPlan</span>&gt;(<span class="hljs-string">&#x27;/trip/plan&#x27;</span>,request)<br>  <span class="hljs-keyword">return</span> response.<span class="hljs-property">data</span><br>&#125;<br></code></pre></td></tr></table></figure><p>注意这个函数的类型签名：参数是<code>TripPlanRequest</code>类型，返回值是<code>Promise&lt;TripPlan&gt;</code>类型。这意味着 TypeScript 会检查调用者传递的参数是否符合要求，也会检查返回值的使用是否正确。</p><h3 id="13-5-4-Home-表单设计"><a href="#13-5-4-Home-表单设计" class="headerlink" title="13.5.4 Home 表单设计"></a>13.5.4 Home 表单设计</h3><p>Home 页面是用户的入口，包含一个表单，让用户填写旅行需求。我们使用 Vue 3 的 Composition API 来组织代码：</p><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><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><code class="hljs vue">&lt;script setup lang=&quot;ts&quot;&gt;<br>import &#123; ref &#125; from &#x27;vue&#x27;<br>import &#123; useRouter &#125; from &#x27;vue-router&#x27;<br>import &#123; message &#125; from &#x27;ant-design-vue&#x27;<br>import &#123; generateTripPlan &#125; from &#x27;@/services/api&#x27;<br>import type &#123; TripPlanRequest &#125; from &#x27;@/types&#x27;<br><br>const router = useRouter()<br>const loading = ref(false)<br>const loadingProgress = ref(0)<br>const loadingStatus = ref(&#x27;&#x27;)<br><br>const formData = ref&lt;TripPlanRequest&gt;(&#123;<br>  city: &#x27;&#x27;,<br>  start_date: &#x27;&#x27;,<br>  end_date: &#x27;&#x27;,<br>  days: 3,<br>  preferences: &#x27;历史文化&#x27;,<br>  budget: &#x27;中等&#x27;,<br>  transportation: &#x27;公共交通&#x27;,<br>  accommodation: &#x27;经济型酒店&#x27;<br>&#125;)<br>&lt;/script&gt;<br></code></pre></td></tr></table></figure><p>这里我们使用<code>ref</code>来创建响应式变量。<code>formData</code>是表单数据，类型是<code>TripPlanRequest</code>。<code>loading</code>表示是否正在加载，<code>loadingProgress</code>表示加载进度，<code>loadingStatus</code>表示加载状态文本。</p><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><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><code class="hljs typescript"><span class="hljs-keyword">const</span> <span class="hljs-title function_">handleSubmit</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params"></span>) =&gt; &#123;<br>  loading.<span class="hljs-property">value</span> = <span class="hljs-literal">true</span><br>  loadingProgress.<span class="hljs-property">value</span> = <span class="hljs-number">0</span><br>  <br>  <span class="hljs-comment">// 模拟进度更新</span><br>  <span class="hljs-keyword">const</span> progressInterval = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>    <span class="hljs-keyword">if</span> (loadingProgress.<span class="hljs-property">value</span> &lt; <span class="hljs-number">90</span>) &#123;<br>      loadingProgress.<span class="hljs-property">value</span> += <span class="hljs-number">10</span><br>      <span class="hljs-keyword">if</span> (loadingProgress.<span class="hljs-property">value</span> &lt;= <span class="hljs-number">30</span>) loadingStatus.<span class="hljs-property">value</span> = <span class="hljs-string">&#x27;🔍 正在搜索景点...&#x27;</span><br>      <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (loadingProgress.<span class="hljs-property">value</span> &lt;= <span class="hljs-number">50</span>) loadingStatus.<span class="hljs-property">value</span> = <span class="hljs-string">&#x27;🌤️ 正在查询天气...&#x27;</span><br>      <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (loadingProgress.<span class="hljs-property">value</span> &lt;= <span class="hljs-number">70</span>) loadingStatus.<span class="hljs-property">value</span> = <span class="hljs-string">&#x27;🏨 正在推荐酒店...&#x27;</span><br>      <span class="hljs-keyword">else</span> loadingStatus.<span class="hljs-property">value</span> = <span class="hljs-string">&#x27;📋 正在生成行程计划...&#x27;</span><br>    &#125;<br>  &#125;,<span class="hljs-number">500</span>)<br>  <br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-title function_">generateTripPlan</span>(formData.<span class="hljs-property">value</span>)<br>    <span class="hljs-built_in">clearInterval</span>(progressInterval)<br>    loadingProgress.<span class="hljs-property">value</span> = <span class="hljs-number">100</span><br>    router.<span class="hljs-title function_">push</span>(&#123; <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;result&#x27;</span>,<span class="hljs-attr">state</span>: &#123; <span class="hljs-attr">tripPlan</span>: response &#125; &#125;)<br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-built_in">clearInterval</span>(progressInterval)<br>    message.<span class="hljs-title function_">error</span>(<span class="hljs-string">&#x27;生成计划失败,请重试&#x27;</span>)<br>  &#125; <span class="hljs-keyword">finally</span> &#123;<br>    loading.<span class="hljs-property">value</span> = <span class="hljs-literal">false</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>这段代码做了几件事。首先，设置<code>loading</code>为 true，显示加载状态。然后，启动一个定时器，每 500 毫秒更新一次进度条和状态文本。这是一个模拟的进度，因为我们无法准确知道后端的处理进度。但这样可以让用户知道系统正在工作，而不是卡住了。</p><p>接着，调用<code>generateTripPlan</code>函数发送 API 请求。这是一个异步操作，我们使用<code>await</code>等待响应。如果请求成功，清除定时器，设置进度为 100%，然后跳转到结果页面，并把旅行计划数据传递过去。如果请求失败，显示错误消息。最后，无论成功还是失败，都设置<code>loading</code>为 false，隐藏加载状态。</p><p>模板部分使用 Ant Design Vue 的组件：</p><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><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><code class="hljs vue">&lt;template&gt;<br>  &lt;div class=&quot;home-container&quot;&gt;<br>    &lt;div class=&quot;page-header&quot;&gt;<br>      &lt;h1 class=&quot;page-title&quot;&gt;✈️ 智能旅行助手&lt;/h1&gt;<br>      &lt;p class=&quot;page-subtitle&quot;&gt;基于AI的个性化旅行规划&lt;/p&gt;<br>    &lt;/div&gt;<br>    <br>    &lt;a-card class=&quot;form-card&quot;&gt;<br>      &lt;a-form :model=&quot;formData&quot; @finish=&quot;handleSubmit&quot;&gt;<br>        &lt;a-form-item label=&quot;目的地城市&quot; name=&quot;city&quot; :rules=&quot;[&#123; required: true &#125;]&quot;&gt;<br>          &lt;a-input v-model:value=&quot;formData.city&quot; placeholder=&quot;如：北京&quot; /&gt;<br>        &lt;/a-form-item&gt;<br>        <br>        &lt;!-- 更多表单项... --&gt;<br>        <br>        &lt;a-form-item&gt;<br>          &lt;a-button type=&quot;primary&quot; html-type=&quot;submit&quot; size=&quot;large&quot; :loading=&quot;loading&quot;&gt;<br>            开始规划<br>          &lt;/a-button&gt;<br>        &lt;/a-form-item&gt;<br>        <br>        &lt;!-- 加载进度条 --&gt;<br>        &lt;a-form-item v-if=&quot;loading&quot;&gt;<br>          &lt;a-progress :percent=&quot;loadingProgress&quot; status=&quot;active&quot; /&gt;<br>          &lt;p&gt;&#123;&#123; loadingStatus &#125;&#125;&lt;/p&gt;<br>        &lt;/a-form-item&gt;<br>      &lt;/a-form&gt;<br>    &lt;/a-card&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br></code></pre></td></tr></table></figure><p>注意<code>v-model:value</code>指令，它实现了双向数据绑定。当用户在输入框中输入内容时，<code>formData.city</code>会自动更新。当<code>formData.city</code>的值改变时，输入框的内容也会自动更新。</p><h3 id="13-5-5-Result-页面展示"><a href="#13-5-5-Result-页面展示" class="headerlink" title="13.5.5 Result 页面展示"></a>13.5.5 Result 页面展示</h3><p>Result 页面是整个应用的核心，展示生成的旅行计划。这个页面包含几个部分：行程概览、预算明细、地图可视化、每日行程详情、天气信息。</p><p>首先是地图可视化。我们使用高德地图 JS 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><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><code class="hljs typescript"><span class="hljs-keyword">import</span> <span class="hljs-title class_">AMapLoader</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@amap/amap-jsapi-loader&#x27;</span><br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">initMap</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params"></span>) =&gt; &#123;<br>  <span class="hljs-keyword">const</span> <span class="hljs-title class_">AMap</span> = <span class="hljs-keyword">await</span> <span class="hljs-title class_">AMapLoader</span>.<span class="hljs-title function_">load</span>(&#123;<br>    <span class="hljs-attr">key</span>: <span class="hljs-string">&#x27;your_amap_web_key&#x27;</span>,<br>    <span class="hljs-attr">version</span>: <span class="hljs-string">&#x27;2.0&#x27;</span><br>  &#125;)<br>  <br>  map = <span class="hljs-keyword">new</span> <span class="hljs-title class_">AMap</span>.<span class="hljs-title class_">Map</span>(<span class="hljs-string">&#x27;amap-container&#x27;</span>,&#123;<br>    <span class="hljs-attr">zoom</span>: <span class="hljs-number">12</span>,<br>    <span class="hljs-attr">center</span>: [<span class="hljs-number">116.397128</span>,<span class="hljs-number">39.916527</span>]<br>  &#125;)<br>  <br>  <span class="hljs-comment">// 添加景点标记</span><br>  tripPlan.<span class="hljs-property">value</span>.<span class="hljs-property">days</span>.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">day</span>) =&gt;</span> &#123;<br>    day.<span class="hljs-property">attractions</span>.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">attraction,index</span>) =&gt;</span> &#123;<br>      <span class="hljs-keyword">const</span> marker = <span class="hljs-keyword">new</span> <span class="hljs-title class_">AMap</span>.<span class="hljs-title class_">Marker</span>(&#123;<br>        <span class="hljs-attr">position</span>: [attraction.<span class="hljs-property">location</span>.<span class="hljs-property">longitude</span>,attraction.<span class="hljs-property">location</span>.<span class="hljs-property">latitude</span>],<br>        <span class="hljs-attr">title</span>: attraction.<span class="hljs-property">name</span>,<br>        <span class="hljs-attr">label</span>: &#123; <span class="hljs-attr">content</span>: <span class="hljs-string">`<span class="hljs-subst">$&#123;index + <span class="hljs-number">1</span>&#125;</span>`</span>,<span class="hljs-attr">direction</span>: <span class="hljs-string">&#x27;top&#x27;</span> &#125;<br>      &#125;)<br>      map.<span class="hljs-title function_">add</span>(marker)<br>    &#125;)<br>  &#125;)<br>&#125;<br></code></pre></td></tr></table></figure><p>这段代码首先加载高德地图 SDK，然后创建地图实例，最后遍历所有景点，为每个景点创建一个标记(Marker)。标记的位置是景点的经纬度坐标，这些坐标是从后端的<code>Attraction</code>对象中获取的。</p><p>导出功能使用<code>html2canvas</code>和<code>jsPDF</code>库。<code>html2canvas</code>可以把 DOM 元素转换成 Canvas，然后我们可以把 Canvas 导出为图片或 PDF：</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><code class="hljs typescript"><span class="hljs-keyword">import</span> html2canvas <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;html2canvas&#x27;</span><br><span class="hljs-keyword">import</span> jsPDF <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;jspdf&#x27;</span><br><br><span class="hljs-comment">// 导出为图片</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">exportAsImage</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params"></span>) =&gt; &#123;<br>  <span class="hljs-keyword">const</span> element = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;trip-plan-content&#x27;</span>)<br>  <span class="hljs-keyword">const</span> canvas = <span class="hljs-keyword">await</span> <span class="hljs-title function_">html2canvas</span>(element,&#123; <span class="hljs-attr">scale</span>: <span class="hljs-number">2</span> &#125;)<br>  <span class="hljs-keyword">const</span> link = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">&#x27;a&#x27;</span>)<br>  link.<span class="hljs-property">download</span> = <span class="hljs-string">`<span class="hljs-subst">$&#123;tripPlan.value.city&#125;</span>旅行计划.png`</span><br>  link.<span class="hljs-property">href</span> = canvas.<span class="hljs-title function_">toDataURL</span>()<br>  link.<span class="hljs-title function_">click</span>()<br>&#125;<br><br><span class="hljs-comment">// 导出为PDF</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">exportAsPDF</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params"></span>) =&gt; &#123;<br>  <span class="hljs-keyword">const</span> element = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;trip-plan-content&#x27;</span>)<br>  <span class="hljs-keyword">const</span> canvas = <span class="hljs-keyword">await</span> <span class="hljs-title function_">html2canvas</span>(element,&#123; <span class="hljs-attr">scale</span>: <span class="hljs-number">2</span> &#125;)<br>  <span class="hljs-keyword">const</span> imgData = canvas.<span class="hljs-title function_">toDataURL</span>(<span class="hljs-string">&#x27;image/png&#x27;</span>)<br>  <span class="hljs-keyword">const</span> pdf = <span class="hljs-keyword">new</span> <span class="hljs-title function_">jsPDF</span>(<span class="hljs-string">&#x27;p&#x27;</span>,<span class="hljs-string">&#x27;mm&#x27;</span>,<span class="hljs-string">&#x27;a4&#x27;</span>)<br>  <span class="hljs-keyword">const</span> imgWidth = <span class="hljs-number">210</span><br>  <span class="hljs-keyword">const</span> imgHeight = (canvas.<span class="hljs-property">height</span> * imgWidth) / canvas.<span class="hljs-property">width</span><br>  pdf.<span class="hljs-title function_">addImage</span>(imgData,<span class="hljs-string">&#x27;PNG&#x27;</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,imgWidth,imgHeight)<br>  pdf.<span class="hljs-title function_">save</span>(<span class="hljs-string">`<span class="hljs-subst">$&#123;tripPlan.value.city&#125;</span>旅行计划.pdf`</span>)<br>&#125;<br></code></pre></td></tr></table></figure><p>通过这些前端技术，我们实现了一个完整的 Web 应用。用户可以在浏览器中填写表单，提交请求，等待 AI 生成旅行计划，然后查看详细的行程安排，在地图上看到景点位置，还可以导出为图片或 PDF。整个过程流畅自然，这就是现代 Web 应用的魅力。</p><h2 id="13-6-功能实现详解"><a href="#13-6-功能实现详解" class="headerlink" title="13.6 功能实现详解"></a>13.6 功能实现详解</h2><p>本节介绍智能旅行助手的核心功能实现，包括预算计算、加载进度条、行程编辑、导出功能和侧边导航。</p><h3 id="13-6-1-预算计算功能"><a href="#13-6-1-预算计算功能" class="headerlink" title="13.6.1 预算计算功能"></a>13.6.1 预算计算功能</h3><p>在规划旅行时，预算是一个非常重要的考虑因素。用户需要知道这次旅行大概要花多少钱，钱都花在哪里。我们的智能旅行助手提供了自动预算计算功能，将费用分为四大类：景点门票、酒店住宿、餐饮和交通。</p><p>预算计算的逻辑在哪里实现呢？我们选择在后端的 PlannerAgent 中实现。为什么不在前端计算？因为预算的估算需要基于景点的门票价格、酒店的价格范围、餐饮的标准等信息，这些信息都是 PlannerAgent 在生成行程时已经获取的。如果在前端计算，就需要重复这些逻辑，而且可能不准确。</p><p>在 PlannerAgent 的提示词中，我们明确要求 LLM 生成预算信息：</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></pre></td><td class="code"><pre><code class="hljs python">PLANNER_AGENT_PROMPT = <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">你是行程规划专家。</span><br><span class="hljs-string"></span><br><span class="hljs-string">**输出格式：**</span><br><span class="hljs-string">严格按照以下JSON格式返回：</span><br><span class="hljs-string">&#123;</span><br><span class="hljs-string">  ...</span><br><span class="hljs-string">  &quot;budget&quot;: &#123;</span><br><span class="hljs-string">    &quot;total_attractions&quot;: 180,</span><br><span class="hljs-string">    &quot;total_hotels&quot;: 1200,</span><br><span class="hljs-string">    &quot;total_meals&quot;: 480,</span><br><span class="hljs-string">    &quot;total_transportation&quot;: 200,</span><br><span class="hljs-string">    &quot;total&quot;: 2060</span><br><span class="hljs-string">  &#125;</span><br><span class="hljs-string">&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">**规划要求：**</span><br><span class="hljs-string">...</span><br><span class="hljs-string">7. 包含预算信息,根据景点门票、酒店价格、餐饮标准和交通方式估算</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p>LLM 会根据行程中的景点、酒店、餐饮安排，估算每一项的费用。比如，如果行程中包含故宫(门票 60 元)、天坛(门票 15 元)、颐和园(门票 30 元)，那么景点门票总费用就是 105 元。如果是 3 天 2 晚的行程，酒店是经济型(每晚 300 元)，那么酒店总费用就是 600 元。</p><p>在前端，我们使用 Ant Design Vue 的 Statistic 组件来展示预算信息。这个组件专门用于展示统计数据,支持数字动画、前缀后缀、自定义样式等：</p><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><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><code class="hljs vue">&lt;a-card v-if=&quot;tripPlan.budget&quot; title=&quot;💰 预算明细&quot;&gt;<br>  &lt;a-row :gutter=&quot;16&quot;&gt;<br>    &lt;a-col :span=&quot;6&quot;&gt;<br>      &lt;a-statistic title=&quot;景点门票&quot; :value=&quot;tripPlan.budget.total_attractions&quot; suffix=&quot;元&quot; /&gt;<br>    &lt;/a-col&gt;<br>    &lt;a-col :span=&quot;6&quot;&gt;<br>      &lt;a-statistic title=&quot;酒店住宿&quot; :value=&quot;tripPlan.budget.total_hotels&quot; suffix=&quot;元&quot; /&gt;<br>    &lt;/a-col&gt;<br>    &lt;a-col :span=&quot;6&quot;&gt;<br>      &lt;a-statistic title=&quot;餐饮费用&quot; :value=&quot;tripPlan.budget.total_meals&quot; suffix=&quot;元&quot; /&gt;<br>    &lt;/a-col&gt;<br>    &lt;a-col :span=&quot;6&quot;&gt;<br>      &lt;a-statistic title=&quot;交通费用&quot; :value=&quot;tripPlan.budget.total_transportation&quot; suffix=&quot;元&quot; /&gt;<br>    &lt;/a-col&gt;<br>  &lt;/a-row&gt;<br>  &lt;a-divider /&gt;<br>  &lt;a-row&gt;<br>    &lt;a-col :span=&quot;24&quot; style=&quot;text-align: center;&quot;&gt;<br>      &lt;a-statistic<br>        title=&quot;预估总费用&quot;<br>        :value=&quot;tripPlan.budget.total&quot;<br>        suffix=&quot;元&quot;<br>        :value-style=&quot;&#123; color: &#x27;#cf1322&#x27;,fontSize: &#x27;32px&#x27;,fontWeight: &#x27;bold&#x27; &#125;&quot;<br>      /&gt;<br>    &lt;/a-col&gt;<br>  &lt;/a-row&gt;<br>&lt;/a-card&gt;<br></code></pre></td></tr></table></figure><p>这段代码使用了栅格布局(<code>a-row</code>和<code>a-col</code>)，将四项费用并排显示。每项费用使用一个<code>a-statistic</code>组件，显示标题和数值。最后用一个分隔线(<code>a-divider</code>)隔开，下面显示总费用，使用红色大字体突出显示。</p><p>注意<code>v-if=&quot;tripPlan.budget&quot;</code>这个条件渲染。因为预算信息是可选的(在 Pydantic 模型中定义为<code>Optional[Budget]</code>)，如果 LLM 没有生成预算信息，这个卡片就不会显示。这体现了前端对数据的容错处理。</p><h3 id="13-6-2-加载进度条"><a href="#13-6-2-加载进度条" class="headerlink" title="13.6.2 加载进度条"></a>13.6.2 加载进度条</h3><p>生成旅行计划是一个耗时的操作。后端需要依次调用 AttractionSearchAgent、WeatherQueryAgent、HotelAgent 和 PlannerAgent，每个 Agent 都要调用 LLM 和外部 API。整个过程可能需要 10-30 秒。如果用户点击”开始规划”按钮后，页面没有任何反馈，用户会以为系统卡住了，可能会刷新页面或重复点击。</p><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><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><code class="hljs typescript"><span class="hljs-keyword">const</span> loading = <span class="hljs-title function_">ref</span>(<span class="hljs-literal">false</span>)<br><span class="hljs-keyword">const</span> loadingProgress = <span class="hljs-title function_">ref</span>(<span class="hljs-number">0</span>)<br><span class="hljs-keyword">const</span> loadingStatus = <span class="hljs-title function_">ref</span>(<span class="hljs-string">&#x27;&#x27;</span>)<br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">handleSubmit</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params"></span>) =&gt; &#123;<br>  loading.<span class="hljs-property">value</span> = <span class="hljs-literal">true</span><br>  loadingProgress.<span class="hljs-property">value</span> = <span class="hljs-number">0</span><br><br>  <span class="hljs-comment">// 模拟进度更新</span><br>  <span class="hljs-keyword">const</span> progressInterval = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>    <span class="hljs-keyword">if</span> (loadingProgress.<span class="hljs-property">value</span> &lt; <span class="hljs-number">90</span>) &#123;<br>      loadingProgress.<span class="hljs-property">value</span> += <span class="hljs-number">10</span><br>      <span class="hljs-keyword">if</span> (loadingProgress.<span class="hljs-property">value</span> &lt;= <span class="hljs-number">30</span>) loadingStatus.<span class="hljs-property">value</span> = <span class="hljs-string">&#x27;🔍 正在搜索景点...&#x27;</span><br>      <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (loadingProgress.<span class="hljs-property">value</span> &lt;= <span class="hljs-number">50</span>) loadingStatus.<span class="hljs-property">value</span> = <span class="hljs-string">&#x27;🌤️ 正在查询天气...&#x27;</span><br>      <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (loadingProgress.<span class="hljs-property">value</span> &lt;= <span class="hljs-number">70</span>) loadingStatus.<span class="hljs-property">value</span> = <span class="hljs-string">&#x27;🏨 正在推荐酒店...&#x27;</span><br>      <span class="hljs-keyword">else</span> loadingStatus.<span class="hljs-property">value</span> = <span class="hljs-string">&#x27;📋 正在生成行程计划...&#x27;</span><br>    &#125;<br>  &#125;, <span class="hljs-number">500</span>)<br><br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-title function_">generateTripPlan</span>(formData.<span class="hljs-property">value</span>)<br>    <span class="hljs-built_in">clearInterval</span>(progressInterval)<br>    loadingProgress.<span class="hljs-property">value</span> = <span class="hljs-number">100</span><br>    loadingStatus.<span class="hljs-property">value</span> = <span class="hljs-string">&#x27;✅ 完成！&#x27;</span><br>    router.<span class="hljs-title function_">push</span>(&#123; <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;result&#x27;</span>, <span class="hljs-attr">state</span>: &#123; <span class="hljs-attr">tripPlan</span>: response &#125; &#125;)<br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-built_in">clearInterval</span>(progressInterval)<br>    message.<span class="hljs-title function_">error</span>(<span class="hljs-string">&#x27;生成计划失败&#x27;</span>)<br>  &#125; <span class="hljs-keyword">finally</span> &#123;<br>    loading.<span class="hljs-property">value</span> = <span class="hljs-literal">false</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="13-6-3-行程编辑功能"><a href="#13-6-3-行程编辑功能" class="headerlink" title="13.6.3 行程编辑功能"></a>13.6.3 行程编辑功能</h3><p>AI 生成的旅行计划虽然很智能，但可能不完全符合用户的个人需求。比如，用户可能不喜欢某个景点，想删除它；或者想调整景点的游览顺序。我们提供了行程编辑功能，让用户可以自定义行程。</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></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">const</span> editMode = <span class="hljs-title function_">ref</span>(<span class="hljs-literal">false</span>)<br><span class="hljs-keyword">const</span> originalPlan = ref&lt;<span class="hljs-title class_">TripPlan</span> | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>)<br><br><span class="hljs-comment">// 进入编辑模式</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">toggleEditMode</span> = (<span class="hljs-params"></span>) =&gt; &#123;<br>  editMode.<span class="hljs-property">value</span> = <span class="hljs-literal">true</span><br>  originalPlan.<span class="hljs-property">value</span> = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(<span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(tripPlan.<span class="hljs-property">value</span>))<br>&#125;<br></code></pre></td></tr></table></figure><p>注意这里使用了<code>JSON.parse(JSON.stringify(...))</code>来深拷贝对象。为什么不直接赋值？因为 JavaScript 中对象是引用类型，如果直接赋值，<code>originalPlan</code>和<code>tripPlan</code>会指向同一个对象，修改一个会影响另一个。深拷贝可以创建一个完全独立的副本。</p><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><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-comment">// 移动景点</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">moveAttraction</span> = (<span class="hljs-params">dayIndex: <span class="hljs-built_in">number</span>,attractionIndex: <span class="hljs-built_in">number</span>,direction: <span class="hljs-string">&#x27;up&#x27;</span> | <span class="hljs-string">&#x27;down&#x27;</span></span>) =&gt; &#123;<br>  <span class="hljs-keyword">const</span> attractions = tripPlan.<span class="hljs-property">value</span>.<span class="hljs-property">days</span>[dayIndex].<span class="hljs-property">attractions</span><br>  <span class="hljs-keyword">const</span> newIndex = direction === <span class="hljs-string">&#x27;up&#x27;</span> ? attractionIndex - <span class="hljs-number">1</span> : attractionIndex + <span class="hljs-number">1</span><br>  <br>  <span class="hljs-keyword">if</span> (newIndex &gt;= <span class="hljs-number">0</span> &amp;&amp; newIndex &lt; attractions.<span class="hljs-property">length</span>) &#123;<br>    [attractions[attractionIndex],attractions[newIndex]] = <br>    [attractions[newIndex],attractions[attractionIndex]]<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>这里使用了 ES6 的解构赋值语法来交换两个元素。<code>[a,b] = [b,a]</code>是一个很优雅的交换方式，不需要临时变量。</p><p>删除景点使用数组的<code>splice</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></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-comment">// 删除景点</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">deleteAttraction</span> = (<span class="hljs-params">dayIndex: <span class="hljs-built_in">number</span>,attractionIndex: <span class="hljs-built_in">number</span></span>) =&gt; &#123;<br>  tripPlan.<span class="hljs-property">value</span>.<span class="hljs-property">days</span>[dayIndex].<span class="hljs-property">attractions</span>.<span class="hljs-title function_">splice</span>(attractionIndex,<span class="hljs-number">1</span>)<br>&#125;<br></code></pre></td></tr></table></figure><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><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><code class="hljs typescript"><span class="hljs-comment">// 保存修改</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">saveChanges</span> = (<span class="hljs-params"></span>) =&gt; &#123;<br>  editMode.<span class="hljs-property">value</span> = <span class="hljs-literal">false</span><br>  message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&#x27;修改已保存&#x27;</span>)<br>  <span class="hljs-title function_">initMap</span>()  <span class="hljs-comment">// 重新初始化地图</span><br>&#125;<br><br><span class="hljs-comment">// 取消编辑</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">cancelEdit</span> = (<span class="hljs-params"></span>) =&gt; &#123;<br>  <span class="hljs-keyword">if</span> (originalPlan.<span class="hljs-property">value</span>) &#123;<br>    tripPlan.<span class="hljs-property">value</span> = originalPlan.<span class="hljs-property">value</span><br>  &#125;<br>  editMode.<span class="hljs-property">value</span> = <span class="hljs-literal">false</span><br>&#125;<br></code></pre></td></tr></table></figure><p>在模板中，我们根据<code>editMode</code>的值显示不同的 UI。编辑模式下，每个景点旁边会显示上移、下移、删除按钮：</p><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></pre></td><td class="code"><pre><code class="hljs vue">&lt;div v-if=&quot;editMode&quot; class=&quot;edit-buttons&quot;&gt;<br>  &lt;a-button size=&quot;small&quot; @click=&quot;moveAttraction(dayIndex,index,&#x27;up&#x27;)&quot;&gt;上移&lt;/a-button&gt;<br>  &lt;a-button size=&quot;small&quot; @click=&quot;moveAttraction(dayIndex,index,&#x27;down&#x27;)&quot;&gt;下移&lt;/a-button&gt;<br>  &lt;a-button size=&quot;small&quot; danger @click=&quot;deleteAttraction(dayIndex,index)&quot;&gt;删除&lt;/a-button&gt;<br>&lt;/div&gt;<br></code></pre></td></tr></table></figure><h3 id="13-6-4-导出功能"><a href="#13-6-4-导出功能" class="headerlink" title="13.6.4 导出功能"></a>13.6.4 导出功能</h3><p>用户生成了满意的旅行计划后，可能想保存下来或分享给朋友。我们提供了两种导出方式：导出为图片和导出为 PDF。</p><p>导出功能的核心是<code>html2canvas</code>库。这个库可以把 DOM 元素转换成 Canvas，然后我们可以把 Canvas 导出为图片。但这里有一个技术难点：地图是用 Canvas 渲染的，而<code>html2canvas</code>在处理嵌套 Canvas 时存在兼容性问题。</p><p>我们尝试了多种解决方案，包括将地图 Canvas 转换成图片后再导出，但由于高德地图的 Canvas 渲染机制和跨域限制，这个方案并没有完全解决问题。在实际项目中，可能需要考虑以下替代方案：</p><ol><li><strong>使用高德地图的静态地图 API</strong>：调用<code>maps_staticmap</code>工具生成静态地图图片，替代动态地图</li><li><strong>分开导出</strong>：地图和行程内容分开导出，最后在后端合并</li><li><strong>使用截图服务</strong>：使用 Puppeteer 等无头浏览器在服务端截图</li><li><strong>简化导出内容</strong>：导出时隐藏地图，只导出文字内容</li></ol><p>目前的实现中，我们采用了简化方案，在导出时暂时隐藏地图部分，只导出行程的文字内容和景点信息。虽然这不是最理想的方案，但可以保证导出功能的可用性。</p><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><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><code class="hljs typescript"><span class="hljs-keyword">import</span> html2canvas <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;html2canvas&#x27;</span><br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">exportAsImage</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params"></span>) =&gt; &#123;<br>  <span class="hljs-keyword">const</span> element = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;trip-plan-content&#x27;</span>)<br>  <span class="hljs-keyword">if</span> (!element) <span class="hljs-keyword">return</span><br>  <br>  <span class="hljs-keyword">const</span> canvas = <span class="hljs-keyword">await</span> <span class="hljs-title function_">html2canvas</span>(element,&#123;<br>    <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">&#x27;#ffffff&#x27;</span>,<br>    <span class="hljs-attr">scale</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-attr">useCORS</span>: <span class="hljs-literal">true</span><br>  &#125;)<br>  <br>  <span class="hljs-keyword">const</span> link = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">&#x27;a&#x27;</span>)<br>  link.<span class="hljs-property">download</span> = <span class="hljs-string">`<span class="hljs-subst">$&#123;tripPlan.value.city&#125;</span>旅行计划.png`</span><br>  link.<span class="hljs-property">href</span> = canvas.<span class="hljs-title function_">toDataURL</span>(<span class="hljs-string">&#x27;image/png&#x27;</span>)<br>  link.<span class="hljs-title function_">click</span>()<br>  message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&#x27;导出成功！&#x27;</span>)<br>&#125;<br></code></pre></td></tr></table></figure><p><code>scale: 2</code>表示使用 2 倍分辨率，这样导出的图片更清晰。<code>useCORS: true</code>允许跨域加载图片，这对于景点图片(来自 Unsplash)很重要。</p><p>导出为 PDF 需要额外的步骤：先转换成 Canvas，再转换成图片，最后添加到 PDF 中：</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></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">import</span> jsPDF <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;jspdf&#x27;</span><br><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">exportAsPDF</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params"></span>) =&gt; &#123;<br>  <span class="hljs-comment">// 先截取地图</span><br>  <span class="hljs-keyword">await</span> <span class="hljs-title function_">captureMapImage</span>()<br>  <br>  <span class="hljs-keyword">const</span> element = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;trip-plan-content&#x27;</span>)<br>  <span class="hljs-keyword">if</span> (!element) <span class="hljs-keyword">return</span><br>  <br>  <span class="hljs-keyword">const</span> canvas = <span class="hljs-keyword">await</span> <span class="hljs-title function_">html2canvas</span>(element,&#123;<br>    <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">&#x27;#ffffff&#x27;</span>,<br>    <span class="hljs-attr">scale</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-attr">useCORS</span>: <span class="hljs-literal">true</span>,<br>    <span class="hljs-attr">allowTaint</span>: <span class="hljs-literal">true</span><br>  &#125;)<br>  <br>  <span class="hljs-comment">// 恢复地图</span><br>  <span class="hljs-title function_">restoreMap</span>()<br>  <br>  <span class="hljs-keyword">const</span> pdf = <span class="hljs-keyword">new</span> <span class="hljs-title function_">jsPDF</span>(<span class="hljs-string">&#x27;p&#x27;</span>,<span class="hljs-string">&#x27;mm&#x27;</span>,<span class="hljs-string">&#x27;a4&#x27;</span>)<br>  <span class="hljs-keyword">const</span> imgData = canvas.<span class="hljs-title function_">toDataURL</span>(<span class="hljs-string">&#x27;image/png&#x27;</span>)<br>  <span class="hljs-keyword">const</span> imgWidth = <span class="hljs-number">210</span>  <span class="hljs-comment">// A4宽度</span><br>  <span class="hljs-keyword">const</span> imgHeight = (canvas.<span class="hljs-property">height</span> * imgWidth) / canvas.<span class="hljs-property">width</span><br>  <br>  pdf.<span class="hljs-title function_">addImage</span>(imgData,<span class="hljs-string">&#x27;PNG&#x27;</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,imgWidth,imgHeight)<br>  pdf.<span class="hljs-title function_">save</span>(<span class="hljs-string">`<span class="hljs-subst">$&#123;tripPlan.value.city&#125;</span>旅行计划.pdf`</span>)<br>  message.<span class="hljs-title function_">success</span>(<span class="hljs-string">&#x27;导出成功！&#x27;</span>)<br>&#125;<br></code></pre></td></tr></table></figure><p>这里需要计算图片的高度，保持宽高比。A4 纸的宽度是 210mm，我们根据 Canvas 的宽高比计算出对应的高度。</p><h3 id="13-6-5-侧边导航与锚点跳转"><a href="#13-6-5-侧边导航与锚点跳转" class="headerlink" title="13.6.5 侧边导航与锚点跳转"></a>13.6.5 侧边导航与锚点跳转</h3><p>Result 页面的内容很多，包括行程概览、预算明细、地图、每日行程、天气信息等。如果用户想快速跳转到某个部分，需要滚动很长的距离。我们提供了侧边导航和锚点跳转功能，让用户可以快速定位。</p><p>侧边导航使用 Ant Design Vue 的 Menu 组件：</p><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></pre></td><td class="code"><pre><code class="hljs vue">&lt;a-menu<br>  v-model:selectedKeys=&quot;[activeSection]&quot;<br>  mode=&quot;inline&quot;<br>  @click=&quot;scrollToSection&quot;<br>&gt;<br>  &lt;a-menu-item key=&quot;overview&quot;&gt;📋 行程概览&lt;/a-menu-item&gt;<br>  &lt;a-menu-item key=&quot;budget&quot;&gt;💰 预算明细&lt;/a-menu-item&gt;<br>  &lt;a-menu-item key=&quot;map&quot;&gt;🗺️ 地图&lt;/a-menu-item&gt;<br>  &lt;a-menu-item key=&quot;days&quot;&gt;📅 每日行程&lt;/a-menu-item&gt;<br>  &lt;a-menu-item key=&quot;weather&quot;&gt;🌤️ 天气&lt;/a-menu-item&gt;<br>&lt;/a-menu&gt;<br></code></pre></td></tr></table></figure><p>点击菜单项时，调用<code>scrollToSection</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></pre></td><td class="code"><pre><code class="hljs typescript"><span class="hljs-keyword">const</span> activeSection = <span class="hljs-title function_">ref</span>(<span class="hljs-string">&#x27;overview&#x27;</span>)<br><br><span class="hljs-comment">// 滚动到指定区域</span><br><span class="hljs-keyword">const</span> <span class="hljs-title function_">scrollToSection</span> = (<span class="hljs-params">&#123; key &#125;: &#123; key: <span class="hljs-built_in">string</span> &#125;</span>) =&gt; &#123;<br>  activeSection.<span class="hljs-property">value</span> = key<br>  <span class="hljs-keyword">const</span> element = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(key)<br>  <span class="hljs-keyword">if</span> (element) &#123;<br>    element.<span class="hljs-title function_">scrollIntoView</span>(&#123; <span class="hljs-attr">behavior</span>: <span class="hljs-string">&#x27;smooth&#x27;</span>,<span class="hljs-attr">block</span>: <span class="hljs-string">&#x27;start&#x27;</span> &#125;)<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p><code>scrollIntoView</code>是浏览器原生的 API，可以让元素滚动到可视区域。<code>behavior: &#39;smooth&#39;</code>表示平滑滚动，而不是瞬间跳转。<code>block: &#39;start&#39;</code>表示元素的顶部对齐到可视区域的顶部。</p><p>在页面的各个部分，我们需要添加对应的 id：</p><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></pre></td><td class="code"><pre><code class="hljs vue">&lt;div id=&quot;overview&quot;&gt;<br>  &lt;!-- 行程概览内容 --&gt;<br>&lt;/div&gt;<br><br>&lt;div id=&quot;budget&quot;&gt;<br>  &lt;!-- 预算明细内容 --&gt;<br>&lt;/div&gt;<br><br>&lt;div id=&quot;map&quot;&gt;<br>  &lt;!-- 地图内容 --&gt;<br>&lt;/div&gt;<br></code></pre></td></tr></table></figure><p>这样，当用户点击侧边导航的某个菜单项时，页面会平滑滚动到对应的部分。</p><p>通过这些功能的实现，我们的智能旅行助手不仅能够生成旅行计划，还提供了丰富的交互功能：预算计算让用户了解费用，加载进度条让等待不再焦虑，行程编辑让计划更符合个人需求，导出功能让计划可以分享和保存，侧边导航让长页面易于浏览。这些功能的组合，构成了一个完整、易用、实用的 Web 应用。</p><h2 id="13-7-结语"><a href="#13-7-结语" class="headerlink" title="13.7 结语"></a>13.7 结语</h2><p>恭喜你完成了第十三章的学习！</p><p>通过本章，你不仅学会了如何构建一个完整的智能旅行助手应用，更重要的是掌握了：</p><ol><li><strong>系统设计思维</strong>： 如何将复杂问题分解为多个简单任务</li><li><strong>工程实践能力</strong>： 如何将理论知识转化为可运行的代码</li><li><strong>全栈开发能力</strong>： 如何整合前后端技术栈</li><li><strong>AI 应用开发</strong>： 如何利用 LLM 构建实用的应用</li></ol><p>这个项目是一个起点，而不是终点。你可以基于这个项目：</p><ul><li>添加更多功能</li><li>优化用户体验</li><li>扩展到其他领域(如智能购物助手、智能学习助手等)</li><li>部署到生产环境，服务真实用户</li></ul><p>最好的学习方式是实践。不要只是阅读代码，而是要动手修改、扩展、优化。每一次实践都会让你对多 Agent 系统有更深的理解。</p><p>祝你在 AI 应用开发的道路上越走越远！</p>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC13%E7%AB%A0-%E6%99%BA%E8%83%BD%E6%97%85%E8%A1%8C%E5%8A%A9%E6%89%8B/</id>
    <link href="http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC13%E7%AB%A0-%E6%99%BA%E8%83%BD%E6%97%85%E8%A1%8C%E5%8A%A9%E6%89%8B/"/>
    <published>2026-03-02T04:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="第十三章-智能旅行助手"><a href="#第十三章-智能旅行助手" class="headerlink" title="第十三章 智能旅行助手"></a>第十三章 智能旅行助手</h1><p>在前面的章节中，我们从零开始构建了 HelloAgents 框架，实]]>
    </summary>
    <title>第十三章 智能旅行助手</title>
    <updated>2026-03-08T09:24:16.347Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="第十二章-智能体性能评估"><a href="#第十二章-智能体性能评估" class="headerlink" title="第十二章 智能体性能评估"></a>第十二章 智能体性能评估</h1><p>在前面的章节中，我们构建了 HelloAgents 框架的核心功能，实现了多种智能体范式、工具系统、记忆机制和强化学习训练等。在构建智能体系统时，我们还需要解决一个核心问题：<strong>如何客观地评估智能体的性能？</strong> 具体来说，我们需要回答以下问题：</p><ol><li>智能体是否具备预期的能力？</li><li>在不同任务上的表现如何？</li><li>与其他智能体相比处于什么水平？</li></ol><p>本章将为 HelloAgents 增加<strong>性能评估系统（Evaluation System）</strong>。我们将深入理解智能体评估的理论基础，并实现评估的工具。</p><h2 id="12-1-智能体评估基础"><a href="#12-1-智能体评估基础" class="headerlink" title="12.1 智能体评估基础"></a>12.1 智能体评估基础</h2><h3 id="12-1-1-为何需要智能体评估"><a href="#12-1-1-为何需要智能体评估" class="headerlink" title="12.1.1 为何需要智能体评估"></a>12.1.1 为何需要智能体评估</h3><p>我们现在的 SimpleAgent，它已经具备了强大的推理和工具调用能力。让我们看一个典型的使用场景：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> SearchTool<br><br><span class="hljs-comment"># 创建LLM和智能体</span><br>llm = HelloAgentsLLM()<br><br><span class="hljs-comment"># 创建一个强调工具使用的系统提示词</span><br>system_prompt = <span class="hljs-string">&quot;&quot;&quot;你是一个AI助手，可以使用搜索工具来获取最新信息。</span><br><span class="hljs-string"></span><br><span class="hljs-string">当需要搜索信息时，请使用以下格式：</span><br><span class="hljs-string">[TOOL_CALL:search:搜索关键词]</span><br><span class="hljs-string"></span><br><span class="hljs-string">例如：</span><br><span class="hljs-string">- [TOOL_CALL:search:最新AI新闻]</span><br><span class="hljs-string">- [TOOL_CALL:search:Python编程教程]</span><br><span class="hljs-string"></span><br><span class="hljs-string">请在回答问题前先使用搜索工具获取最新信息。&quot;&quot;&quot;</span><br><br>agent = SimpleAgent(name=<span class="hljs-string">&quot;AI助手&quot;</span>, llm=llm, system_prompt=system_prompt)<br><br><span class="hljs-comment"># 添加搜索工具</span><br>agent.add_tool(SearchTool())<br><br><span class="hljs-comment"># 示例：使用搜索工具回答问题</span><br>response = agent.run(<span class="hljs-string">&quot;最新的AI技术发展趋势是什么？&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n回答：<span class="hljs-subst">&#123;response&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>这个智能体能正常工作，但我们面临一个核心问题：如何客观地评估它的性能？当我们优化提示词或更换 LLM 模型后，如何知道是否真的有改进？在部署到生产环境前，如何保证智能体的可靠性？这些问题都需要通过系统化的评估来解决。</p><p>智能体评估的核心价值在于提供标准化的方法来衡量智能体的能力。通过评估，我们可以用具体的数字指标量化智能体的表现，客观比较不同设计方案的优劣，及时发现智能体在特定场景下的弱点，并向用户证明智能体的可靠性。</p><p>与传统软件测试不同，智能体评估面临着独特的挑战。首先是输出的不确定性，同一问题可能有多个正确答案，很难用简单的对错来判断。其次是评估标准的多样性，不同任务需要不同的评估方法，工具调用需要检查函数签名，问答任务需要评估语义相似度。最后是评估成本的高昂，每次评估都需要大量的 API 调用，成本可能达到数百元甚至更多。</p><p>为了应对这些挑战，学术界和工业界提出了多个标准化的<strong>评估基准（Benchmark）</strong>。这些基准提供了统一的数据集、评估指标和评分方法，使我们能够在相同的标准下评估和对比不同的智能体系统。</p><h3 id="12-1-2-主流评估基准概览"><a href="#12-1-2-主流评估基准概览" class="headerlink" title="12.1.2 主流评估基准概览"></a>12.1.2 主流评估基准概览</h3><p>智能体评估领域已经涌现出多个具有影响力的基准测试。下面介绍一些主流的评估基准和指标：</p><p><strong>（1）工具调用能力评估</strong></p><p>工具调用是智能体的核心能力之一。智能体需要理解用户意图，选择合适的工具，并正确构造函数调用。相关的评估基准包括：</p><ul><li><strong>BFCL (Berkeley Function Calling Leaderboard)</strong><sup>[1]</sup>：UC Berkeley 推出，包含 1120+测试样本，涵盖 simple、multiple、parallel、irrelevance 四个类别，使用 AST 匹配算法评估，数据集规模适中，社区活跃。</li><li><strong>ToolBench</strong><sup>[2]</sup>：清华大学推出，包含 16000+真实 API 调用场景，覆盖真实世界的复杂工具使用场景。</li><li><strong>API-Bank</strong><sup>[3]</sup>：Microsoft Research 推出，包含 53 个常用 API 工具，专注于评估智能体对 API 文档的理解和调用能力。</li></ul><p><strong>（2）通用能力评估</strong></p><p>评估智能体在真实世界任务中的综合表现，包括多步推理、知识运用、多模态理解等能力：</p><ul><li><strong>GAIA (General AI Assistants)</strong><sup>[4]</sup>：Meta AI 和 Hugging Face 联合推出，包含 466 个真实世界问题，分为 Level 1&#x2F;2&#x2F;3 三个难度级别，评估多步推理、工具使用、文件处理、网页浏览等能力，使用准精确匹配（Quasi Exact Match）算法，任务真实且综合性强。</li><li><strong>AgentBench</strong><sup>[5]</sup>：清华大学推出，包含 8 个不同领域的任务，全面评估智能体的通用能力。</li><li><strong>WebArena</strong><sup>[6]</sup>：CMU 推出，评估智能体在真实网页环境中的任务完成能力和网页交互能力。</li></ul><p><strong>（3）多智能体协作评估</strong></p><p>评估多个智能体协同工作的能力：</p><ul><li><strong>ChatEval</strong><sup>[7]</sup>：评估多智能体对话系统的质量。</li><li><strong>SOTOPIA</strong><sup>[8]</sup>：评估智能体在社交场景中的互动能力。</li><li><strong>自定义协作场景</strong>：根据具体应用场景设计的评估任务。</li></ul><p><strong>（4）常用评估指标</strong></p><p>不同基准使用不同的评估指标，常见的包括：</p><ul><li><strong>准确性指标</strong>：Accuracy（准确率）、Exact Match（精确匹配）、F1 Score（F1 分数），用于衡量答案的正确性。</li><li><strong>效率指标</strong>：Response Time（响应时间）、Token Usage（Token 使用量），用于衡量执行效率。</li><li><strong>鲁棒性指标</strong>：Error Rate（错误率）、Failure Recovery（故障恢复），用于衡量容错能力。</li><li><strong>协作指标</strong>：Communication Efficiency（通信效率）、Task Completion（任务完成度），用于衡量协作效果。</li></ul><h3 id="12-1-3-HelloAgents-评估体系设计"><a href="#12-1-3-HelloAgents-评估体系设计" class="headerlink" title="12.1.3 HelloAgents 评估体系设计"></a>12.1.3 HelloAgents 评估体系设计</h3><p>考虑到学习曲线和实用性，本章将重点介绍以下评估场景：</p><ol><li><p><strong>BFCL</strong>：评估工具调用能力</p><ul><li>选择理由：数据集规模适中，评估指标清晰，社区活跃</li><li>适用场景：评估智能体的函数调用准确性</li></ul></li><li><p><strong>GAIA</strong>：评估通用 AI 助手能力</p><ul><li>选择理由：任务真实，难度分级，综合性强</li><li>适用场景：评估智能体的综合问题解决能力</li></ul></li><li><p><strong>数据生成质量评估</strong>：评估 LLM 生成数据质量</p><ul><li>选择理由：通过这个案例可以完整体验如何使用 Agent 创造数据，评估数据的完整演示。</li><li>适用场景：评估生成的训练数据、测试数据的质量</li><li>评估方法：LLM Judge、Win Rate、人工验证</li></ul></li></ol><p>通过这三个评估场景，我们将构建一个完整的评估体系，如图 12.1 展示了我们的评估系统构建思路。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/12-figures/12-1.png" alt="" width="85%"/>  <p>图 12.1 HelloAgents 评估体系架构图</p></div><h3 id="12-1-4-本章学习目标与快速体验"><a href="#12-1-4-本章学习目标与快速体验" class="headerlink" title="12.1.4 本章学习目标与快速体验"></a>12.1.4 本章学习目标与快速体验</h3><p>让我们先看看第十二章的学习内容：</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><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><code class="hljs awk">hello_agents/<br>├── evaluation/                         <span class="hljs-comment"># 评估模块</span><br>│   └── benchmarks/                     <span class="hljs-comment"># 评估基准实现</span><br>│       ├── bfcl/                       <span class="hljs-comment"># BFCL评估实现</span><br>│       │   ├── dataset.py              <span class="hljs-comment"># BFCL数据集加载器</span><br>│       │   ├── evaluator.py            <span class="hljs-comment"># BFCL评估器（AST匹配）</span><br>│       │   ├── metrics.py              <span class="hljs-comment"># BFCL专用指标</span><br>│       │   └── ast_matcher.py          <span class="hljs-comment"># AST匹配算法</span><br>│       ├── gaia/                       <span class="hljs-comment"># GAIA评估实现</span><br>│       │   ├── dataset.py              <span class="hljs-comment"># GAIA数据集加载器</span><br>│       │   ├── evaluator.py            <span class="hljs-comment"># GAIA评估器（准精确匹配）</span><br>│       │   ├── metrics.py              <span class="hljs-comment"># GAIA专用指标</span><br>│       │   └── quasi_exact_match.py    <span class="hljs-comment"># 准精确匹配算法</span><br>│       └── data_generation/            <span class="hljs-comment"># 数据生成评估实现</span><br>│           ├── dataset.py              <span class="hljs-comment"># AIME数据集加载器</span><br>│           ├── llm_judge.py            <span class="hljs-comment"># LLM Judge评估器</span><br>│           └── win_rate.py             <span class="hljs-comment"># Win Rate评估器</span><br>└── tools<span class="hljs-regexp">/builtin/</span>                      <span class="hljs-comment"># 内置工具模块</span><br>    ├── bfcl_evaluation_tool.py         <span class="hljs-comment"># BFCL评估工具</span><br>    ├── gaia_evaluation_tool.py         <span class="hljs-comment"># GAIA评估工具</span><br>    ├── llm_judge_tool.py               <span class="hljs-comment"># LLM Judge工具</span><br>    └── win_rate_tool.py                <span class="hljs-comment"># Win Rate工具</span><br></code></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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 安装HelloAgents框架（第12章版本）</span><br>pip install <span class="hljs-string">&quot;hello-agents[evaluation]==0.2.7&quot;</span><br><br><span class="hljs-comment"># 设置环境变量</span><br><span class="hljs-built_in">export</span> HF_TOKEN=<span class="hljs-string">&quot;your_huggingface_token&quot;</span>     <span class="hljs-comment"># 用于GAIA数据集(后续也会有设置步骤)</span><br><br><span class="hljs-comment"># 由于 `bfcl-eval` 官方包强制要求 numpy&lt;=2.0.0, 和HelloAgents 主依赖版本存在冲突,因此需要单独安装</span><br>pip install <span class="hljs-string">&quot;numpy==1.26.4&quot;</span> bfcl-eval<br></code></pre></td></tr></table></figure><p>在接下来的章节中，我们将深入学习每种评估方法的详细用法和介绍。</p><h2 id="12-2-BFCL：工具调用能力评估"><a href="#12-2-BFCL：工具调用能力评估" class="headerlink" title="12.2 BFCL：工具调用能力评估"></a>12.2 BFCL：工具调用能力评估</h2><h3 id="12-2-1-BFCL-基准介绍"><a href="#12-2-1-BFCL-基准介绍" class="headerlink" title="12.2.1 BFCL 基准介绍"></a>12.2.1 BFCL 基准介绍</h3><p>BFCL (Berkeley Function Calling Leaderboard) 是由加州大学伯克利分校推出的函数调用能力评估基准<sup>[1]</sup>。在智能体系统中，工具调用（Tool Calling）是核心能力之一。智能体需要完成以下任务：</p><ol><li><strong>理解任务需求</strong>：从用户的自然语言描述中提取关键信息</li><li><strong>选择合适工具</strong>：从可用工具集中选择最适合的工具</li><li><strong>构造函数调用</strong>：正确填写函数名和参数</li><li><strong>处理复杂场景</strong>：支持多函数调用、并行调用等高级场景</li></ol><p>BFCL 基准包含四个评估类别，难度递增。从最基础的单函数调用（Simple）开始，逐步增加到需要调用多个函数的场景（Multiple），再到需要并行调用多个函数的复杂场景（Parallel），最后是需要判断是否需要调用函数的场景（Irrelevance）。这四个类别覆盖了智能体在实际应用中可能遇到的各种工具调用场景，如表 12.1 所示：</p><div align="center">  <p>表 12.1 BFCL 基准中的四个评估类别</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/12-figures/12-table-1.png" alt="" width="85%"/></div>BFCL 的评估流程遵循标准的基准测试流程：首先加载数据集并选择评估类别，然后运行智能体获取预测结果，接着将预测结果解析为抽象语法树（AST），最后通过 AST 匹配算法判断预测是否正确。整个流程会遍历所有测试样本，最终计算出准确率等评估指标并生成评估报告。完整的评估流程如图 12.2 所示：<div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/12-figures/12-2.png" alt="" width="85%"/>  <p>图 12.2 BFCL 评估流程图</p></div><strong>（1）BFCL 数据集结构</strong><p>BFCL 数据集采用 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><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><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;simple_001&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;question&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;What&#x27;s the weather like in Beijing today?&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;function&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>    <span class="hljs-punctuation">&#123;</span><br>      <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;get_weather&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;description&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Get the current weather for a location&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;parameters&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>        <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;object&quot;</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-attr">&quot;properties&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>          <span class="hljs-attr">&quot;location&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>            <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;string&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;description&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;The city name&quot;</span><br>          <span class="hljs-punctuation">&#125;</span><br>        <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-attr">&quot;required&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;location&quot;</span><span class="hljs-punctuation">]</span><br>      <span class="hljs-punctuation">&#125;</span><br>    <span class="hljs-punctuation">&#125;</span><br>  <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;ground_truth&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>    <span class="hljs-punctuation">&#123;</span><br>      <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;get_weather&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;arguments&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>        <span class="hljs-attr">&quot;location&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Beijing&quot;</span><br>      <span class="hljs-punctuation">&#125;</span><br>    <span class="hljs-punctuation">&#125;</span><br>  <span class="hljs-punctuation">]</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p><strong>关键字段说明：</strong></p><ul><li><code>question</code>: 用户的自然语言请求</li><li><code>function</code>: 可用的函数列表（包含函数签名和描述）</li><li><code>ground_truth</code>: 标准答案（期望的函数调用）</li></ul><p><strong>（2）AST 匹配说明</strong></p><p>BFCL 使用<strong>AST 匹配（Abstract Syntax Tree Matching）</strong>作为核心评估算法，因此下文可以了解一下评估的策略。</p><p>BFCL 使用抽象语法树（AST）进行智能匹配，而不是简单的字符串匹配。AST 匹配的核心思想是：<strong>将函数调用解析为语法树，然后比较树的结构和节点值</strong>。</p><p>给定预测的函数调用 $P$ 和标准答案 $G$，AST 匹配函数定义为：</p><p>$$<br>\text{AST_Match}(P, G) &#x3D; \begin{cases}<br>1 &amp; \text{if } \text{AST}(P) \equiv \text{AST}(G) \<br>0 &amp; \text{otherwise}<br>\end{cases}<br>$$</p><p>其中 $\text{AST}(x)$ 表示将函数调用解析为抽象语法树，$\equiv$ 表示语法树等价。</p><p>两个语法树等价需要满足三个核心条件：函数名必须完全一致（精确匹配），参数键值对集合相等（忽略顺序），以及每个参数的值在语义上等价（例如 <code>2+3</code> 等价于 <code>5</code>）。在具体的匹配过程中，函数名匹配要求字符串精确匹配，例如 <code>get_weather</code> 和 <code>get_temperature</code> 被视为不同的函数。参数匹配则使用 AST 进行智能比较，允许参数顺序不同（<code>f(a=1, b=2)</code> 等价于 <code>f(b=2, a=1)</code>），允许等价表达式（<code>f(x=2+3)</code> 等价于 <code>f(x=5)</code>），也允许不同的字符串表示（<code>f(s=&quot;hello&quot;)</code> 等价于 <code>f(s=&#39;hello&#39;)</code>）。对于多函数调用的场景，匹配算法要求调用相同数量的函数，每个函数调用都必须匹配，但调用顺序可以不同（使用集合匹配）。</p><p><strong>AST 匹配示例：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 示例1：参数顺序不同（匹配成功）</span><br>预测: get_weather(city=<span class="hljs-string">&quot;Beijing&quot;</span>, unit=<span class="hljs-string">&quot;celsius&quot;</span>)<br>标准: get_weather(unit=<span class="hljs-string">&quot;celsius&quot;</span>, city=<span class="hljs-string">&quot;Beijing&quot;</span>)<br>结果: ✅ 匹配成功<br><br><span class="hljs-comment"># 示例2：等价表达式（匹配成功）</span><br>预测: calculate(x=<span class="hljs-number">2</span>+<span class="hljs-number">3</span>)<br>标准: calculate(x=<span class="hljs-number">5</span>)<br>结果: ✅ 匹配成功<br><br><span class="hljs-comment"># 示例3：函数名错误（匹配失败）</span><br>预测: get_temperature(city=<span class="hljs-string">&quot;Beijing&quot;</span>)<br>标准: get_weather(city=<span class="hljs-string">&quot;Beijing&quot;</span>)<br>结果: ❌ 匹配失败<br><br><span class="hljs-comment"># 示例4：参数值错误（匹配失败）</span><br>预测: get_weather(city=<span class="hljs-string">&quot;Shanghai&quot;</span>)<br>标准: get_weather(city=<span class="hljs-string">&quot;Beijing&quot;</span>)<br>结果: ❌ 匹配失败<br></code></pre></td></tr></table></figure><p><strong>（3）BFCL 评估指标</strong></p><p>BFCL 使用以下指标评估智能体性能：</p><p><strong>1. 准确率 (Accuracy)</strong></p><p>准确率是最核心的指标，定义为 AST 匹配成功的样本比例：</p><p>$$<br>\text{Accuracy} &#x3D; \frac{1}{N} \sum_{i&#x3D;1}^{N} \text{AST_Match}(P_i, G_i)<br>$$</p><p>其中：</p><ul><li>$N$ 是总样本数</li><li>$P_i$ 是第 $i$ 个样本的预测结果</li><li>$G_i$ 是第 $i$ 个样本的标准答案</li><li>$\text{AST_Match}(P_i, G_i) \in {0, 1}$ 是 AST 匹配函数</li></ul><p><strong>2. AST 匹配率 (AST Match Rate)</strong></p><p>与准确率相同，强调使用 AST 匹配算法：</p><p>$$<br>\text{AST Match Rate} &#x3D; \text{Accuracy}<br>$$</p><p><strong>3. 分类准确率 (Category-wise Accuracy)</strong></p><p>对于每个类别 $c \in {\text{simple}, \text{multiple}, \text{parallel}, \ldots}$，计算该类别的准确率：</p><p>$$<br>\text{Accuracy}<em>c &#x3D; \frac{1}{|D_c|} \sum</em>{i \in D_c} \text{AST_Match}(P_i, G_i)<br>$$</p><p>其中 $D_c$ 是类别 $c$ 的样本集合，$|D_c|$ 是该类别的样本数。</p><p><strong>4. 加权准确率 (Weighted Accuracy)</strong></p><p>考虑不同类别的难度权重：</p><p>$$<br>\text{Weighted Accuracy} &#x3D; \sum_{c} w_c \cdot \text{Accuracy}_c<br>$$</p><p>其中 $w_c$ 是类别 $c$ 的权重，满足 $\sum_c w_c &#x3D; 1$。</p><p><strong>5. 错误率 (Error Rate)</strong></p><p>未能正确调用函数的样本比例：</p><p>$$<br>\text{Error Rate} &#x3D; 1 - \text{Accuracy} &#x3D; \frac{1}{N} \sum_{i&#x3D;1}^{N} (1 - \text{AST_Match}(P_i, G_i))<br>$$</p><p><strong>指标解释：</strong></p><ul><li><strong>Accuracy &#x3D; 1.0</strong>：所有样本都完全正确</li><li><strong>Accuracy &#x3D; 0.8</strong>：80%的样本正确，20%的样本错误</li><li><strong>Accuracy &#x3D; 0.0</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 假设评估结果</span><br>simple_accuracy = <span class="hljs-number">0.95</span>      <span class="hljs-comment"># Simple类别：95%正确</span><br>multiple_accuracy = <span class="hljs-number">0.82</span>    <span class="hljs-comment"># Multiple类别：82%正确</span><br>parallel_accuracy = <span class="hljs-number">0.68</span>    <span class="hljs-comment"># Parallel类别：68%正确</span><br><br><span class="hljs-comment"># 加权准确率（假设权重相等）</span><br>weighted_accuracy = (<span class="hljs-number">0.95</span> + <span class="hljs-number">0.82</span> + <span class="hljs-number">0.68</span>) / <span class="hljs-number">3</span> = <span class="hljs-number">0.817</span><br></code></pre></td></tr></table></figure><p><strong>（4）BFCL 官方评估工具</strong></p><p>BFCL 提供官方 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></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 安装BFCL评估工具</span><br>pip install bfcl<br><br><span class="hljs-comment"># 运行官方评估</span><br>bfcl evaluate \<br>    --model-result-path ./results.json \<br>    --test-category simple_python<br></code></pre></td></tr></table></figure><p>使用官方评估工具的优势在于：它使用官方的 AST 匹配算法，评估结果与排行榜完全一致，支持所有 BFCL v4 类别，并且能够自动生成详细的评估报告。</p><h3 id="12-2-2-获取-BFCL-数据集"><a href="#12-2-2-获取-BFCL-数据集" class="headerlink" title="12.2.2 获取 BFCL 数据集"></a>12.2.2 获取 BFCL 数据集</h3><p>BFCL 数据集可以通过以下方式获取：</p><p><strong>方法 1：从官方 GitHub 仓库克隆（推荐）</strong></p><p>这是最可靠的方式，可以获取完整的数据集和 ground truth：</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><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 克隆BFCL仓库</span><br>git <span class="hljs-built_in">clone</span> https://github.com/ShishirPatil/gorilla.git temp_gorilla<br><span class="hljs-built_in">cd</span> temp_gorilla/berkeley-function-call-leaderboard<br><br><span class="hljs-comment"># 查看BFCL v4数据集</span><br><span class="hljs-built_in">ls</span> bfcl_eval/data/<br><span class="hljs-comment"># 输出: BFCL_v4_simple_python.json  BFCL_v4_multiple.json  BFCL_v4_parallel.json  ...</span><br><br><span class="hljs-comment"># 查看ground truth</span><br><span class="hljs-built_in">ls</span> bfcl_eval/data/possible_answer/<br><span class="hljs-comment"># 输出: BFCL_v4_simple_python.json  BFCL_v4_multiple.json  ...</span><br></code></pre></td></tr></table></figure><p>推荐这种方式的原因是：它包含完整的 ground truth（标准答案），数据格式与官方评估工具完全一致，可以直接使用官方评估脚本，并且支持 BFCL v4 最新版本。</p><p><strong>方法 2：使用 HelloAgents 加载官方数据</strong></p><p>克隆仓库后，使用 HelloAgents 加载数据：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.evaluation <span class="hljs-keyword">import</span> BFCLDataset<br><br><span class="hljs-comment"># 加载BFCL官方数据</span><br>dataset = BFCLDataset(<br>    bfcl_data_dir=<span class="hljs-string">&quot;./temp_gorilla/berkeley-function-call-leaderboard/bfcl_eval/data&quot;</span>,<br>    category=<span class="hljs-string">&quot;simple_python&quot;</span>  <span class="hljs-comment"># BFCL v4类别</span><br>)<br><br><span class="hljs-comment"># 加载数据（包括测试数据和ground truth）</span><br>data = dataset.load()<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 加载了 <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(data)&#125;</span> 个测试样本&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 加载了 <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(dataset.ground_truth)&#125;</span> 个ground truth&quot;</span>)<br><span class="hljs-comment"># 输出:</span><br><span class="hljs-comment"># ✅ 加载了 400 个测试样本</span><br><span class="hljs-comment"># ✅ 加载了 400 个ground truth</span><br></code></pre></td></tr></table></figure><p>这个加载器的工作原理是：首先从<code>bfcl_eval/data/</code>加载测试数据，然后从<code>bfcl_eval/data/possible_answer/</code>加载 ground truth，接着自动合并测试数据和 ground truth，最后保留原始 BFCL 数据格式。其中 BFCL v4 数据集类别可以在表 12.2 查看。</p><div align="center">  <p>表 12.2 BFCL 基准中的四个评估类别</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/12-figures/12-table-2.png" alt="" width="85%"/></div><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 获取所有支持的类别</span><br>categories = dataset.get_available_categories()<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;支持的类别: <span class="hljs-subst">&#123;categories&#125;</span>&quot;</span>)<br><span class="hljs-comment"># 输出: [&#x27;simple_python&#x27;, &#x27;simple_java&#x27;, &#x27;simple_javascript&#x27;, &#x27;multiple&#x27;, ...]</span><br></code></pre></td></tr></table></figure><h3 id="12-2-3-在-HelloAgents-中实现-BFCL-评估"><a href="#12-2-3-在-HelloAgents-中实现-BFCL-评估" class="headerlink" title="12.2.3 在 HelloAgents 中实现 BFCL 评估"></a>12.2.3 在 HelloAgents 中实现 BFCL 评估</h3><p>现在让我们看看如何在 HelloAgents 框架中实现 BFCL 评估。我们提供了三种使用方式：</p><p><strong>方式 1：使用 BFCLEvaluationTool（推荐）</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> BFCLEvaluationTool<br><br><span class="hljs-comment"># 1. 创建要评估的智能体</span><br>llm = HelloAgentsLLM()<br>agent = SimpleAgent(name=<span class="hljs-string">&quot;TestAgent&quot;</span>, llm=llm)<br><br><span class="hljs-comment"># 2. 创建BFCL评估工具</span><br>bfcl_tool = BFCLEvaluationTool()<br><br><span class="hljs-comment"># 3. 运行评估（自动完成所有步骤）</span><br>results = bfcl_tool.run(<br>    agent=agent,<br>    category=<span class="hljs-string">&quot;simple_python&quot;</span>,  <span class="hljs-comment"># 评估类别</span><br>    max_samples=<span class="hljs-number">5</span>              <span class="hljs-comment"># 评估样本数（0表示全部）</span><br>)<br><br><span class="hljs-comment"># 4. 查看结果</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;准确率: <span class="hljs-subst">&#123;results[<span class="hljs-string">&#x27;overall_accuracy&#x27;</span>]:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;正确数: <span class="hljs-subst">&#123;results[<span class="hljs-string">&#x27;correct_samples&#x27;</span>]&#125;</span>/<span class="hljs-subst">&#123;results[<span class="hljs-string">&#x27;total_samples&#x27;</span>]&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>运行输出：</strong></p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><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></pre></td><td class="code"><pre><code class="hljs asciidoc">============================================================<br>BFCL一键评估<br>============================================================<br><br>配置:<br><span class="hljs-code">   评估类别: simple_python</span><br><span class="hljs-code">   样本数量: 5</span><br><span class="hljs-code">   智能体: TestAgent</span><br><br>============================================================<br>步骤1: 运行HelloAgents评估<br>============================================================<br>✅ BFCL数据集加载完成<br><span class="hljs-code">   数据目录: ./temp_gorilla/berkeley-function-call-leaderboard/bfcl_eval/data</span><br><span class="hljs-code">   类别: simple_python</span><br><span class="hljs-code">   样本数: 400</span><br><span class="hljs-code">   Ground truth数: 400</span><br><br>🔧 开始 BFCL 评估...<br><span class="hljs-code">   进度: 1/5</span><br><span class="hljs-code">   进度: 5/5</span><br><br>✅ BFCL 评估完成<br><span class="hljs-code">   总体准确率: 100.00%</span><br><span class="hljs-code">   simple_python: 100.00% (5/5)</span><br><br>📊 评估结果:<br><span class="hljs-code">   准确率: 100.00%</span><br><span class="hljs-code">   正确数: 5/5</span><br><br>============================================================<br>步骤2: 导出BFCL格式结果<br>============================================================<br>✅ BFCL格式结果已导出<br><span class="hljs-code">   输出文件: ./evaluation_results/bfcl_official/BFCL_v4_simple_python_result.json</span><br><br>============================================================<br>步骤3: 运行BFCL官方评估<br>============================================================<br>✅ 结果文件已复制到: ./result/Qwen_Qwen3-8B/BFCL_v4_simple_python_result.json<br><br>🔄 运行命令: bfcl evaluate --model Qwen/Qwen3-8B --test-category simple_python --partial-eval<br><br>============================================================<br>BFCL官方评估结果<br>============================================================<br>📊 评估结果汇总:<br>Model,Overall Acc,simple_python<br>Qwen/Qwen3-8B,100.00,100.00<br><br>🎯 最终结果:<br><span class="hljs-code">   准确率: 100.00%</span><br><span class="hljs-code">   正确数: 5/5</span><br><br>============================================================<br>步骤4: 生成评估报告<br>============================================================<br>📄 报告已生成: ./evaluation_reports/bfcl_report_20251011_005938.md<br><br>准确率: 100.00%<br>正确数: 5/5<br></code></pre></td></tr></table></figure><p><strong>自动生成的 Markdown 报告：</strong></p><p>评估完成后，会自动生成一份详细的 Markdown 报告，包含：</p><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><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><code class="hljs markdown"><span class="hljs-section"># BFCL评估报告</span><br><span class="hljs-strong">**生成时间**</span>: 2025-10-11 00:59:38<br><br><span class="hljs-section">## 📊 评估概览</span><br><br><span class="hljs-bullet">-</span> <span class="hljs-strong">**智能体**</span>: TestAgent<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**评估类别**</span>: simple<span class="hljs-emphasis">_python</span><br><span class="hljs-emphasis">- <span class="hljs-strong">**总体准确率**</span>: 100.00%</span><br><span class="hljs-emphasis">- <span class="hljs-strong">**正确样本数**</span>: 5/5</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">## 📈 详细指标</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">### 分类准确率</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">- <span class="hljs-strong">**simple_python**</span>: 100.00% (5/5)</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">## 📝 样本详情</span><br><span class="hljs-emphasis"></span><br><span class="hljs-emphasis">| 样本ID | 问题 | 预测结果 | 正确答案 | 是否正确 |</span><br><span class="hljs-emphasis">|--------|------|----------|----------|----------|</span><br><span class="hljs-emphasis">| simple_</span>python<span class="hljs-emphasis">_0 | Find the area of a triangle... | [&#123;&#x27;name&#x27;: &#x27;calculate_</span>triangle<span class="hljs-emphasis">_area&#x27;...&#125;] | [&#123;&#x27;function_</span>name&#x27;: &#123;&#x27;base&#x27;: [10]...&#125;&#125;] | ✅ |<br>| simple<span class="hljs-emphasis">_python_</span>1 | Calculate the factorial of 5... | [&#123;&#x27;name&#x27;: &#x27;calculate<span class="hljs-emphasis">_factorial&#x27;...&#125;] | [&#123;&#x27;function_</span>name&#x27;: &#123;&#x27;number&#x27;: [5]&#125;&#125;] | ✅ |<br>...<br><br><span class="hljs-section">## 📊 准确率可视化</span><br>准确率: ██████████████████████████████████████████████████ 100.00%<br><br><span class="hljs-section">## 💡 建议</span><br><span class="hljs-bullet">-</span> ✅ 表现优秀！智能体在工具调用方面表现出色。<br></code></pre></td></tr></table></figure><p><strong>方式 2：使用一键评估脚本</strong></p><p>适合命令行快速评估，在这一章配套的代码案例里，我们提供了<code>04_run_bfcl_evaluation.py</code>，支持直接命令行调用测评：</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><code class="hljs bash"><span class="hljs-comment"># 运行评估脚本</span><br>python chapter12/04_run_bfcl_evaluation.py --category simple_python --samples 10<br><br><span class="hljs-comment"># 指定模型名称（用于BFCL官方评估）</span><br>python examples/04_run_bfcl_evaluation.py \<br>    --category simple_python \<br>    --samples 10 \<br>    --model-name <span class="hljs-string">&quot;Qwen/Qwen3-8B&quot;</span><br></code></pre></td></tr></table></figure><p>脚本支持三个参数：<code>--category</code>指定评估类别（默认 simple_python），<code>--samples</code>指定评估样本数（默认 5，0 表示全部），<code>--model-name</code>指定模型名称用于 BFCL 官方评估（默认 Qwen&#x2F;Qwen3-8B）。</p><p><strong>方式 3：直接使用 Dataset 和 Evaluator</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.evaluation <span class="hljs-keyword">import</span> BFCLDataset, BFCLEvaluator<br><br><span class="hljs-comment"># 1. 创建智能体</span><br>llm = HelloAgentsLLM()<br>agent = SimpleAgent(name=<span class="hljs-string">&quot;TestAgent&quot;</span>, llm=llm)<br><br><span class="hljs-comment"># 2. 加载数据集</span><br>dataset = BFCLDataset(<br>    bfcl_data_dir=<span class="hljs-string">&quot;./temp_gorilla/berkeley-function-call-leaderboard/bfcl_eval/data&quot;</span>,<br>    category=<span class="hljs-string">&quot;simple_python&quot;</span><br>)<br>data = dataset.load()<br><br><span class="hljs-comment"># 3. 创建评估器</span><br>evaluator = BFCLEvaluator(<br>    dataset=dataset,<br>    category=<span class="hljs-string">&quot;simple_python&quot;</span>,<br>    evaluation_mode=<span class="hljs-string">&quot;ast&quot;</span>  <span class="hljs-comment"># 使用AST匹配模式</span><br>)<br><br><span class="hljs-comment"># 4. 运行评估</span><br>results = evaluator.evaluate(agent, max_samples=<span class="hljs-number">10</span>)<br><br><span class="hljs-comment"># 5. 查看结果</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;准确率: <span class="hljs-subst">&#123;results[<span class="hljs-string">&#x27;overall_accuracy&#x27;</span>]:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;正确数: <span class="hljs-subst">&#123;results[<span class="hljs-string">&#x27;correct_samples&#x27;</span>]&#125;</span>/<span class="hljs-subst">&#123;results[<span class="hljs-string">&#x27;total_samples&#x27;</span>]&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 6. 导出BFCL格式结果（可选）</span><br>evaluator.export_to_bfcl_format(<br>    results,<br>    output_path=<span class="hljs-string">&quot;./evaluation_results/my_results.json&quot;</span><br>)<br></code></pre></td></tr></table></figure><p>通过以上三种方式，我们可以根据不同的需求选择合适的评估方法。如果只是想快速了解智能体的表现，使用 BFCLEvaluationTool 的一键评估最为便捷；如果需要批量评估或集成到 CI&#x2F;CD 流程，使用命令行脚本更加合适；如果需要深度定制评估流程或集成到自己的系统中，直接使用 Dataset 和 Evaluator 提供了最大的灵活性。</p><h3 id="12-2-4-BFCL-官方评估工具集成"><a href="#12-2-4-BFCL-官方评估工具集成" class="headerlink" title="12.2.4 BFCL 官方评估工具集成"></a>12.2.4 BFCL 官方评估工具集成</h3><p>前面我们学习了如何使用 HelloAgents 内置的评估功能。实际上，<code>BFCLEvaluationTool</code>已经<strong>自动集成了 BFCL 官方评估工具</strong>，让你能够获得权威的、可对比的评估结果。</p><p>整个评估流程包括四个步骤：首先从 BFCL v4 数据集加载测试数据，然后使用 HelloAgents 运行评估获取智能体的预测结果，接着将结果导出为 BFCL 官方格式（JSONL），最后使用官方评估脚本计算最终分数。这个流程确保了评估结果与 BFCL 排行榜完全一致，如图 12.3 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/12-figures/12-3.png" alt="" width="85%"/>  <p>图 12.3 Helloagents 载入 BFCL 评估过程</p></div>使用`BFCLEvaluationTool`时，官方评估会<strong>自动运行</strong>（默认启用）：<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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> BFCLEvaluationTool<br><br><span class="hljs-comment"># 创建智能体</span><br>llm = HelloAgentsLLM()<br>agent = SimpleAgent(name=<span class="hljs-string">&quot;TestAgent&quot;</span>, llm=llm)<br><br><span class="hljs-comment"># 创建评估工具</span><br>bfcl_tool = BFCLEvaluationTool()<br><br><span class="hljs-comment"># 运行评估（自动运行官方评估）</span><br>results = bfcl_tool.run(<br>    agent=agent,<br>    category=<span class="hljs-string">&quot;simple_python&quot;</span>,<br>    max_samples=<span class="hljs-number">5</span>,<br>    <span class="hljs-comment"># run_official_eval=True  # 默认为True，可以省略</span><br>    model_name=<span class="hljs-string">&quot;Qwen/Qwen3-8B&quot;</span>  <span class="hljs-comment"># 可选，指定模型名称</span><br>)<br></code></pre></td></tr></table></figure><p>工具会自动执行完整的评估流程：首先运行 HelloAgents 评估获取预测结果，然后将结果导出为 BFCL 格式并保存到<code>evaluation_results/bfcl_official/</code>目录，接着复制结果文件到<code>result/&#123;model_name&#125;/</code>目录以符合官方评估工具的要求，随后运行 BFCL 官方评估命令计算分数，最后显示官方评估结果并生成 Markdown 格式的评估报告。</p><p><strong>官方评估输出示例：</strong></p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><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><code class="hljs asciidoc">============================================================<br>步骤3: 运行BFCL官方评估<br>============================================================<br><br>✅ 结果文件已复制到:<br><span class="hljs-code">   ./result/Qwen_Qwen3-8B/BFCL_v4_simple_python_result.json</span><br><br>🔄 运行命令: bfcl evaluate --model Qwen/Qwen3-8B --test-category simple_python --partial-eval<br><br>============================================================<br>BFCL官方评估结果<br>============================================================<br><br>📊 评估结果汇总:<br>Model,Overall Acc,simple_python<br>Qwen/Qwen3-8B,100.00,100.00<br><br>🎯 最终结果:<br><span class="hljs-code">   准确率: 100.00%</span><br><span class="hljs-code">   正确数: 5/5</span><br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 禁用官方评估</span><br>results = bfcl_tool.run(<br>    agent=agent,<br>    category=<span class="hljs-string">&quot;simple_python&quot;</span>,<br>    max_samples=<span class="hljs-number">5</span>,<br>    run_official_eval=<span class="hljs-literal">False</span>  <span class="hljs-comment"># 禁用官方评估</span><br>)<br><br><span class="hljs-comment"># 然后手动运行官方评估</span><br><span class="hljs-keyword">import</span> subprocess<br>subprocess.run([<br>    <span class="hljs-string">&quot;bfcl&quot;</span>, <span class="hljs-string">&quot;evaluate&quot;</span>,<br>    <span class="hljs-string">&quot;--model&quot;</span>, <span class="hljs-string">&quot;Qwen/Qwen3-8B&quot;</span>,<br>    <span class="hljs-string">&quot;--test-category&quot;</span>, <span class="hljs-string">&quot;simple_python&quot;</span>,<br>    <span class="hljs-string">&quot;--partial-eval&quot;</span><br>])<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 运行评估</span><br>results = bfcl_tool.run(agent, category=<span class="hljs-string">&quot;simple_python&quot;</span>, max_samples=<span class="hljs-number">5</span>)<br><br><span class="hljs-comment"># 手动生成报告</span><br>report = bfcl_tool.generate_report(<br>    results,<br>    output_file=<span class="hljs-string">&quot;./my_reports/custom_report.md&quot;</span><br>)<br><br><span class="hljs-comment"># 打印报告内容</span><br><span class="hljs-built_in">print</span>(report)<br></code></pre></td></tr></table></figure><h3 id="12-2-5-核心组件实现细节"><a href="#12-2-5-核心组件实现细节" class="headerlink" title="12.2.5 核心组件实现细节"></a>12.2.5 核心组件实现细节</h3><p>在前面的小节中，我们学习了如何使用 BFCL 评估工具。现在让我们深入了解 HelloAgents 评估系统的核心组件是如何实现的。理解这些实现细节不仅能帮助你更好地使用评估系统，还能让你根据自己的需求进行定制和扩展。</p><p><strong>（1）BFCLDataset：数据集加载器</strong></p><p>BFCLDataset 负责加载和管理 BFCL 数据集：</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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">BFCLDataset</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;BFCL数据集加载器&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, category: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;simple&quot;</span>, local_data_path: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span></span>):<br>        self.category = category<br>        self.local_data_path = local_data_path<br>        self.data = []<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">load</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]]:<br>        <span class="hljs-string">&quot;&quot;&quot;加载数据集&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 优先从本地加载</span><br>        <span class="hljs-keyword">if</span> self.local_data_path:<br>            <span class="hljs-keyword">return</span> self._load_from_local()<br>        <span class="hljs-comment"># 否则从Hugging Face加载</span><br>        <span class="hljs-keyword">return</span> self._load_from_huggingface()<br></code></pre></td></tr></table></figure><p>因为 BFCL 的数据集就在官方的仓库内，所以这里建议的方式是直接在本地 clone 一份进行测评。当找不到时才到 huggingface 进行加载。</p><p><strong>（2）BFCLEvaluator：评估执行器</strong></p><p>BFCLEvaluator 负责执行评估流程。它的核心是<code>evaluate()</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><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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">BFCLEvaluator</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;BFCL评估器&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">evaluate</span>(<span class="hljs-params">self, agent: <span class="hljs-type">Any</span>, max_samples: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">int</span>] = <span class="hljs-literal">None</span></span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;执行评估&quot;&quot;&quot;</span><br>        results = []<br><br>        <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> self.dataset[:max_samples]:<br>            <span class="hljs-comment"># 1. 构造提示词</span><br>            prompt = self._build_prompt(item)<br><br>            <span class="hljs-comment"># 2. 调用智能体</span><br>            response = agent.run(prompt)<br><br>            <span class="hljs-comment"># 3. 提取函数调用</span><br>            predicted_calls = self._extract_function_calls(response)<br><br>            <span class="hljs-comment"># 4. 与标准答案对比</span><br>            is_correct = self._compare_calls(predicted_calls, item[<span class="hljs-string">&quot;ground_truth&quot;</span>])<br><br>            results.append(&#123;<br>                <span class="hljs-string">&quot;id&quot;</span>: item[<span class="hljs-string">&quot;id&quot;</span>],<br>                <span class="hljs-string">&quot;prediction&quot;</span>: predicted_calls,<br>                <span class="hljs-string">&quot;ground_truth&quot;</span>: item[<span class="hljs-string">&quot;ground_truth&quot;</span>],<br>                <span class="hljs-string">&quot;is_correct&quot;</span>: is_correct<br>            &#125;)<br><br>        <span class="hljs-keyword">return</span> &#123;<span class="hljs-string">&quot;results&quot;</span>: results, <span class="hljs-string">&quot;total_samples&quot;</span>: <span class="hljs-built_in">len</span>(results)&#125;<br></code></pre></td></tr></table></figure><p>这个评估器的设计包含三个核心要点：首先是提示词构造，需要将数据集中的问题和函数定义转换为智能体可理解的提示词；其次是函数调用提取，需要从智能体的响应中提取函数调用，并支持多种格式（JSON、代码块等）；最后是 AST 匹配，使用抽象语法树进行函数调用对比，这比简单的字符串匹配更准确。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_extract_function_calls</span>(<span class="hljs-params">self, response: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]]:<br>    <span class="hljs-string">&quot;&quot;&quot;从响应中提取函数调用</span><br><span class="hljs-string"></span><br><span class="hljs-string">    支持多种格式：</span><br><span class="hljs-string">    1. JSON格式：&#123;&quot;name&quot;: &quot;func&quot;, &quot;arguments&quot;: &#123;...&#125;&#125;</span><br><span class="hljs-string">    2. 代码块格式：```python\nfunc(arg1=val1)\n```</span><br><span class="hljs-string">    3. 纯文本格式：func(arg1=val1)</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    calls = []<br><br>    <span class="hljs-comment"># 尝试JSON解析</span><br>    <span class="hljs-keyword">try</span>:<br>        json_match = re.search(<span class="hljs-string">r&#x27;\&#123;.*\&#125;&#x27;</span>, response, re.DOTALL)<br>        <span class="hljs-keyword">if</span> json_match:<br>            data = json.loads(json_match.group())<br>            <span class="hljs-keyword">if</span> <span class="hljs-built_in">isinstance</span>(data, <span class="hljs-built_in">dict</span>) <span class="hljs-keyword">and</span> <span class="hljs-string">&quot;name&quot;</span> <span class="hljs-keyword">in</span> data:<br>                calls.append(data)<br>            <span class="hljs-keyword">elif</span> <span class="hljs-built_in">isinstance</span>(data, <span class="hljs-built_in">list</span>):<br>                calls.extend(data)<br>    <span class="hljs-keyword">except</span> json.JSONDecodeError:<br>        <span class="hljs-keyword">pass</span><br><br>    <span class="hljs-comment"># 尝试代码块提取</span><br>    code_blocks = re.findall(<span class="hljs-string">r&#x27;```(?:python)?\n(.*?)\n```&#x27;</span>, response, re.DOTALL)<br>    <span class="hljs-keyword">for</span> code <span class="hljs-keyword">in</span> code_blocks:<br>        <span class="hljs-comment"># 解析Python函数调用</span><br>        parsed_calls = self._parse_python_calls(code)<br>        calls.extend(parsed_calls)<br><br>    <span class="hljs-keyword">return</span> calls<br></code></pre></td></tr></table></figure><p><strong>（3）BFCLMetrics：指标计算器</strong></p><p>BFCLMetrics 负责计算各种评估指标：</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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">BFCLMetrics</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;BFCL指标计算器&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">compute_metrics</span>(<span class="hljs-params">self, results: <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]]</span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;计算所有指标&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> &#123;<br>            <span class="hljs-string">&quot;accuracy&quot;</span>: self._compute_accuracy(results),<br>            <span class="hljs-string">&quot;ast_match_rate&quot;</span>: self._compute_ast_match_rate(results),<br>            <span class="hljs-string">&quot;parameter_accuracy&quot;</span>: self._compute_parameter_accuracy(results),<br>            <span class="hljs-string">&quot;f1_score&quot;</span>: self._compute_f1_score(results),<br>            <span class="hljs-string">&quot;category_statistics&quot;</span>: self._compute_category_stats(results)<br>        &#125;<br></code></pre></td></tr></table></figure><p><strong>AST 匹配的实现</strong>：</p><p>AST 匹配是 BFCL 评估的核心技术。它比简单的字符串匹配更智能，能够识别语义等价的函数调用：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_ast_match</span>(<span class="hljs-params">self, pred_call: <span class="hljs-type">Dict</span>, true_call: <span class="hljs-type">Dict</span></span>) -&gt; <span class="hljs-built_in">bool</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;使用AST匹配函数调用</span><br><span class="hljs-string"></span><br><span class="hljs-string">    AST匹配的优势：</span><br><span class="hljs-string">    1. 忽略参数顺序：func(a=1, b=2) 等价于 func(b=2, a=1)</span><br><span class="hljs-string">    2. 识别等价表达式：2+3 等价于 5</span><br><span class="hljs-string">    3. 忽略空格和格式差异</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 1. 函数名必须完全匹配</span><br>    <span class="hljs-keyword">if</span> pred_call.get(<span class="hljs-string">&quot;name&quot;</span>) != true_call.get(<span class="hljs-string">&quot;name&quot;</span>):<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span><br><br>    <span class="hljs-comment"># 2. 将参数转换为AST节点</span><br>    pred_args = self._args_to_ast(pred_call.get(<span class="hljs-string">&quot;arguments&quot;</span>, &#123;&#125;))<br>    true_args = self._args_to_ast(true_call.get(<span class="hljs-string">&quot;arguments&quot;</span>, &#123;&#125;))<br><br>    <span class="hljs-comment"># 3. 比较AST节点</span><br>    <span class="hljs-keyword">return</span> ast.dump(pred_args) == ast.dump(true_args)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">_args_to_ast</span>(<span class="hljs-params">self, args: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]</span>) -&gt; ast.AST:<br>    <span class="hljs-string">&quot;&quot;&quot;将参数字典转换为AST节点&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 构造一个虚拟的函数调用</span><br>    code = <span class="hljs-string">f&quot;func(<span class="hljs-subst">&#123;<span class="hljs-string">&#x27;, &#x27;</span>.join(<span class="hljs-string">f&#x27;<span class="hljs-subst">&#123;k&#125;</span>=<span class="hljs-subst">&#123;<span class="hljs-built_in">repr</span>(v)&#125;</span>&#x27;</span> <span class="hljs-keyword">for</span> k, v <span class="hljs-keyword">in</span> args.items())&#125;</span>)&quot;</span><br>    tree = ast.parse(code)<br>    <span class="hljs-keyword">return</span> tree.body[<span class="hljs-number">0</span>].value  <span class="hljs-comment"># 返回Call节点</span><br></code></pre></td></tr></table></figure><p><strong>（4）工具化封装：BFCLEvaluationTool</strong></p><p>最后，我们将这些组件封装成一个 Tool，让它可以被智能体直接调用：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">BFCLEvaluationTool</span>(<span class="hljs-title class_ inherited__">Tool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;BFCL评估工具&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, local_data_path: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span></span>):<br>        <span class="hljs-built_in">super</span>().__init__(<br>            name=<span class="hljs-string">&quot;bfcl_evaluation&quot;</span>,<br>            description=<span class="hljs-string">&quot;评估智能体的工具调用能力&quot;</span><br>        )<br>        self.dataset = <span class="hljs-literal">None</span><br>        self.evaluator = <span class="hljs-literal">None</span><br>        self.metrics_calculator = BFCLMetrics()<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, parameters: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;执行评估&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 1. 加载数据集</span><br>        self.dataset = BFCLDataset(...)<br><br>        <span class="hljs-comment"># 2. 创建评估器</span><br>        self.evaluator = BFCLEvaluator(...)<br><br>        <span class="hljs-comment"># 3. 运行评估</span><br>        results = self.evaluator.evaluate(...)<br><br>        <span class="hljs-comment"># 4. 计算指标</span><br>        metrics = self.metrics_calculator.compute_metrics(...)<br><br>        <span class="hljs-comment"># 5. 返回JSON结果</span><br>        <span class="hljs-keyword">return</span> json.dumps(results, ensure_ascii=<span class="hljs-literal">False</span>)<br></code></pre></td></tr></table></figure><p>这个工具的设计遵循三个核心原则：首先继承 Tool 基类以遵循 HelloAgents 的工具规范，确保与框架的无缝集成；其次进行严格的参数验证，检查必需参数并提供友好的错误提示，提升用户体验；最后对结果进行格式化，返回 JSON 字符串以便于解析和展示。通过这种模块化的设计，我们实现了一个既易用又灵活的评估系统，用户可以直接使用高层的 Tool 接口快速完成评估，也可以深入到底层组件进行定制以满足特殊需求。</p><h3 id="12-2-6-扩展与优化建议"><a href="#12-2-6-扩展与优化建议" class="headerlink" title="12.2.6 扩展与优化建议"></a>12.2.6 扩展与优化建议</h3><p>通过前面的学习，我们已经掌握了如何使用 HelloAgents 进行 BFCL 评估。需要注意的是，我们目前的实现是基于 SimpleAgent 的简单复现，主要完成了 BFCL 评估的基础功能。在实际应用中，BFCL 基准包含多个难度级别和场景，要在排行榜上获得更高的分数，还需要进一步的优化和扩展。</p><p><strong>（1）当前实现的局限性</strong></p><p>我们当前的 SimpleAgent 实现主要聚焦于评估流程的搭建，在工具调用能力上还有提升空间。SimpleAgent 使用自定义的工具调用格式<code>[TOOL_CALL:tool_name:parameters]</code>，这种格式需要 LLM 主动学习和使用，在复杂场景下的表现可能不如使用原生函数调用（Function Calling）的智能体。此外，我们目前只测试了 simple_python 等基础类别，对于 multiple、parallel、irrelevance 等更复杂的场景，还需要针对性的优化。</p><p><strong>（2）提升 BFCL 分数的方向</strong></p><p>要进一步提升 BFCL 评估分数，可以从以下几个方向入手。首先是优化智能体的工具调用能力，可以考虑使用支持原生函数调用的 LLM（如 GPT-4、Claude 等），或者改进提示词让 LLM 更好地理解工具调用格式。其次是扩展工具库，BFCL 测试中涉及各种类型的函数，可以根据测试数据集的特点，预先实现常用的工具类型，提高智能体的工具覆盖率。第三是针对不同难度级别设计不同的策略，例如在 multiple 场景下需要智能体能够规划多步骤的工具调用序列，在 parallel 场景下需要识别可以并行执行的工具调用，在 irrelevance 场景下需要判断是否真的需要调用工具。</p><p><strong>（3）实践建议</strong></p><p>对于想要在 BFCL 上取得更好成绩的开发者，建议采用以下实践策略。首先，从 simple 类别开始，确保基础的单函数调用能够稳定工作，这是后续优化的基础。然后，逐步测试 multiple、parallel 等更复杂的类别，分析失败案例，找出智能体的薄弱环节。在优化过程中，可以参考 BFCL 排行榜上的高分模型，学习它们的设计思路和优化技巧。同时，建议使用官方评估工具进行验证，确保优化后的结果与排行榜标准一致。</p><p>这里总结一些评估时可以进一步处理的建议：</p><p><strong>1. 渐进式评估</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 第一步：快速测试（5个样本）</span><br>results_quick = bfcl_tool.run(agent, category=<span class="hljs-string">&quot;simple_python&quot;</span>, max_samples=<span class="hljs-number">5</span>)<br><br><span class="hljs-comment"># 第二步：中等规模测试（50个样本）</span><br><span class="hljs-keyword">if</span> results_quick[<span class="hljs-string">&#x27;overall_accuracy&#x27;</span>] &gt; <span class="hljs-number">0.8</span>:<br>    results_medium = bfcl_tool.run(agent, category=<span class="hljs-string">&quot;simple_python&quot;</span>, max_samples=<span class="hljs-number">50</span>)<br><br><span class="hljs-comment"># 第三步：完整评估（全部样本）</span><br><span class="hljs-keyword">if</span> results_medium[<span class="hljs-string">&#x27;overall_accuracy&#x27;</span>] &gt; <span class="hljs-number">0.8</span>:<br>    results_full = bfcl_tool.run(agent, category=<span class="hljs-string">&quot;simple_python&quot;</span>, max_samples=<span class="hljs-number">0</span>)<br></code></pre></td></tr></table></figure><p><strong>2. 多类别评估</strong></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></pre></td><td class="code"><pre><code class="hljs python">categories = [<span class="hljs-string">&quot;simple_python&quot;</span>, <span class="hljs-string">&quot;multiple&quot;</span>, <span class="hljs-string">&quot;parallel&quot;</span>, <span class="hljs-string">&quot;irrelevance&quot;</span>]<br><br><span class="hljs-keyword">for</span> category <span class="hljs-keyword">in</span> categories:<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n评估类别: <span class="hljs-subst">&#123;category&#125;</span>&quot;</span>)<br>    results = bfcl_tool.run(agent, category=category, max_samples=<span class="hljs-number">10</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;准确率: <span class="hljs-subst">&#123;results[<span class="hljs-string">&#x27;overall_accuracy&#x27;</span>]:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>3. 对比评估</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 配置1：默认提示词</span><br>agent1 = SimpleAgent(name=<span class="hljs-string">&quot;Agent-Default&quot;</span>, llm=llm)<br>results1 = bfcl_tool.run(agent1, category=<span class="hljs-string">&quot;simple_python&quot;</span>, max_samples=<span class="hljs-number">10</span>)<br><br><span class="hljs-comment"># 配置2：优化提示词</span><br>agent2 = SimpleAgent(name=<span class="hljs-string">&quot;Agent-Optimized&quot;</span>, llm=llm)<br><span class="hljs-comment"># ... 设置优化的系统提示词 ...</span><br>results2 = bfcl_tool.run(agent2, category=<span class="hljs-string">&quot;simple_python&quot;</span>, max_samples=<span class="hljs-number">10</span>)<br><br><span class="hljs-comment"># 对比结果</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;默认配置准确率: <span class="hljs-subst">&#123;results1[<span class="hljs-string">&#x27;overall_accuracy&#x27;</span>]:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;优化配置准确率: <span class="hljs-subst">&#123;results2[<span class="hljs-string">&#x27;overall_accuracy&#x27;</span>]:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>如果你的评估结果很好，可以考虑提交到 BFCL 官方排行榜！</p><p><strong>步骤 1：准备提交材料</strong></p><ol><li>模型描述文档</li><li>评估结果文件（所有类别）</li><li>模型访问方式（API 或开源链接）</li></ol><p><strong>步骤 2：提交到 GitHub</strong></p><p>访问 BFCL 官方仓库，按照说明提交 Pull Request：</p><ul><li>仓库地址：<a href="https://github.com/ShishirPatil/gorilla">https://github.com/ShishirPatil/gorilla</a></li><li>提交指南：参考<code>CONTRIBUTING.md</code></li></ul><p><strong>步骤 3：等待审核</strong></p><p>BFCL 团队会审核你的提交，验证结果的准确性。审核通过后，你的模型将出现在官方排行榜上！</p><h2 id="12-3-GAIA：通用-AI-助手能力评估"><a href="#12-3-GAIA：通用-AI-助手能力评估" class="headerlink" title="12.3 GAIA：通用 AI 助手能力评估"></a>12.3 GAIA：通用 AI 助手能力评估</h2><h3 id="12-3-1-GAIA-基准介绍"><a href="#12-3-1-GAIA-基准介绍" class="headerlink" title="12.3.1 GAIA 基准介绍"></a>12.3.1 GAIA 基准介绍</h3><p>GAIA (General AI Assistants) 是由 Meta AI 和 Hugging Face 联合推出的评估基准，专注于评估 AI 助手的<strong>通用能力</strong><sup>[2]</sup>。与 BFCL 专注于工具调用不同，GAIA 评估的是智能体在真实世界任务中的综合表现。</p><p>GAIA 的设计理念是：<strong>真实世界的问题往往需要多种能力的综合运用</strong>。一个优秀的 AI 助手不仅需要调用工具，还需要：</p><ul><li><strong>多步推理</strong>：将复杂问题分解为多个子问题</li><li><strong>知识运用</strong>：利用内置知识和外部知识库</li><li><strong>多模态理解</strong>：处理文本、图片、文件等多种输入</li><li><strong>网页浏览</strong>：从互联网获取最新信息</li><li><strong>文件操作</strong>：读取和处理各种格式的文件</li></ul><p><strong>（1）GAIA 数据集结构</strong></p><p>了解 GAIA 的评估理念后，让我们深入了解 GAIA 数据集的具体结构。GAIA 包含 466 个精心设计的真实世界问题，这些问题按照复杂度和所需推理步骤分为三个难度级别，从简单的零步推理任务到需要多步复杂推理的困难任务，全面覆盖了智能体在实际应用中可能遇到的各种场景，如表 12.3 所示：</p><div align="center">  <p>表 12.3 GAIA 数据集难度级别分布</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/12-figures/12-table-3.png" alt="" width="85%"/></div>关于 GAIA 数据集的样本示例可以参考下面的代码片段：<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><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;task_id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;gaia_001&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;Question&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;What is the total population of the top 3 most populous cities in California?&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;Level&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">2</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;Final answer&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;12847521&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;file_name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;file_path&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;Annotator Metadata&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;Steps&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>      <span class="hljs-string">&quot;Search for most populous cities in California&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-string">&quot;Get population data for top 3 cities&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-string">&quot;Sum the populations&quot;</span><br>    <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;Number of steps&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;How long did this take?&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;5 minutes&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;Tools&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;web_search&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;calculator&quot;</span><span class="hljs-punctuation">]</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p><strong>关键字段说明：</strong></p><ul><li><code>Question</code>: 问题描述</li><li><code>Level</code>: 难度级别（1-3）</li><li><code>Final answer</code>: 标准答案（可能是数字、文本或文件）</li><li><code>file_name/file_path</code>: 附件文件（如果有）</li><li><code>Annotator Metadata</code>: 标注者提供的元数据（推理步骤、所需工具等）</li></ul><p><strong>（2）准精确匹配介绍</strong></p><p>GAIA 使用<strong>准精确匹配（Quasi Exact Match）</strong>评估算法，这是 GAIA 官方定义的评估标准。该算法的核心思想是：<strong>先对答案进行归一化处理，然后进行精确匹配</strong>。</p><p>给定预测答案 $A_{\text{pred}}$ 和标准答案 $A_{\text{true}}$，准精确匹配函数定义为：</p><p>$$<br>\text{Quasi_Exact_Match}(A_{\text{pred}}, A_{\text{true}}) &#x3D; \begin{cases}<br>1 &amp; \text{if } \mathcal{N}(A_{\text{pred}}) &#x3D; \mathcal{N}(A_{\text{true}}) \<br>0 &amp; \text{otherwise}<br>\end{cases}<br>$$</p><p>其中 $\mathcal{N}(\cdot)$ 是归一化函数，根据答案类型应用不同的规则。</p><p>归一化函数根据答案类型应用不同的规则。对于数字类型，需要移除逗号分隔符（<code>1,000</code> → <code>1000</code>）和单位符号（<code>$100</code> → <code>100</code>，<code>50%</code> → <code>50</code>），例如<code>&quot;$1,234.56&quot;</code>归一化为<code>&quot;1234.56&quot;</code>。对于字符串类型，需要转换为小写（<code>&quot;Apple&quot;</code> → <code>&quot;apple&quot;</code>）、移除冠词（<code>&quot;the apple&quot;</code> → <code>&quot;apple&quot;</code>）、移除多余空格（<code>&quot;hello  world&quot;</code> → <code>&quot;hello world&quot;</code>）和移除末尾标点（<code>&quot;hello.&quot;</code> → <code>&quot;hello&quot;</code>），例如<code>&quot;The United States&quot;</code>归一化为<code>&quot;united states&quot;</code>。对于列表类型，需要按逗号分隔元素，对每个元素应用字符串归一化，按字母顺序排序后重新连接，例如<code>&quot;Paris, London, Berlin&quot;</code>归一化为<code>&quot;berlin,london,paris&quot;</code>。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 数字答案</span><br>原始答案: <span class="hljs-string">&quot;$1,234.56&quot;</span><br>归一化后: <span class="hljs-string">&quot;1234.56&quot;</span><br><br><span class="hljs-comment"># 字符串答案</span><br>原始答案: <span class="hljs-string">&quot;The United States of America&quot;</span><br>归一化后: <span class="hljs-string">&quot;united states of america&quot;</span><br><br><span class="hljs-comment"># 列表答案</span><br>原始答案: <span class="hljs-string">&quot;Paris, London, Berlin&quot;</span><br>归一化后: <span class="hljs-string">&quot;berlin, london, paris&quot;</span><br></code></pre></td></tr></table></figure><p><strong>（3）GAIA 评估指标</strong></p><p>GAIA 使用以下指标评估智能体性能：</p><p><strong>1. 精确匹配率 (Exact Match Rate)</strong></p><p>精确匹配率是 GAIA 的核心指标，定义为准精确匹配成功的样本比例：</p><p>$$<br>\text{Exact Match Rate} &#x3D; \frac{1}{N} \sum_{i&#x3D;1}^{N} \text{Quasi_Exact_Match}(A_{\text{pred},i}, A_{\text{true},i})<br>$$</p><p>其中：</p><ul><li>$N$ 是总样本数</li><li>$A_{\text{pred},i}$ 是第 $i$ 个样本的预测答案</li><li>$A_{\text{true},i}$ 是第 $i$ 个样本的标准答案</li><li>$\text{Quasi_Exact_Match}(\cdot, \cdot) \in {0, 1}$ 是准精确匹配函数</li></ul><p><strong>2. 分级准确率 (Level-wise Accuracy)</strong></p><p>对于每个难度级别 $\ell \in {1, 2, 3}$，计算该级别的准确率：</p><p>$$<br>\text{Accuracy}<em>\ell &#x3D; \frac{1}{|D_\ell|} \sum</em>{i \in D_\ell} \text{Quasi_Exact_Match}(A_{\text{pred},i}, A_{\text{true},i})<br>$$</p><p>其中 $D_\ell$ 是难度级别 $\ell$ 的样本集合，$|D_\ell|$ 是该级别的样本数。</p><p><strong>3. 难度递进下降率 (Difficulty Progression Drop Rate)</strong></p><p>衡量智能体在难度增加时的性能衰减：</p><p>$$<br>\text{Drop Rate}_{\ell \to \ell+1} &#x3D; \frac{\text{Accuracy}<em>\ell - \text{Accuracy}</em>{\ell+1}}{\text{Accuracy}_\ell}<br>$$</p><ul><li>$\text{Drop Rate}_{1 \to 2}$：从 Level 1 到 Level 2 的下降率</li><li>$\text{Drop Rate}_{2 \to 3}$：从 Level 2 到 Level 3 的下降率</li></ul><p><strong>4. 平均推理步骤数 (Average Reasoning Steps)</strong></p><p>评估智能体完成任务所需的平均步骤数：</p><p>$$<br>\text{Avg Steps} &#x3D; \frac{1}{N_{\text{correct}}} \sum_{i \in \text{Correct}} \text{steps}_i<br>$$</p><p>其中 $N_{\text{correct}}$ 是正确回答的样本数，$\text{steps}_i$ 是第 $i$ 个样本的推理步骤数。</p><p><strong>指标解释：</strong></p><ul><li><strong>Exact Match Rate &#x3D; 1.0</strong>：所有样本都完全正确</li><li><strong>Exact Match Rate &#x3D; 0.5</strong>：50%的样本正确，50%的样本错误</li><li><strong>Drop Rate &#x3D; 0.3</strong>：难度增加导致准确率下降 30%</li><li><strong>Drop Rate &#x3D; 0.0</strong>：难度增加不影响准确率（理想情况）</li></ul><p><strong>评估示例：</strong></p><p>假设我们评估了 10 个样本，结果可以参考表 12.4 所示：</p><div align="center">  <p>表 12.4 GAIA 数据集难度级别分布</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/12-figures/12-table-4.png" alt="" width="85%"/></div><p>如果要计算这个案例的指标的话，可以参考下面的 Python 脚本。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 1. 精确匹配率</span><br>total_samples = <span class="hljs-number">10</span><br>correct_samples = <span class="hljs-number">7</span>  <span class="hljs-comment"># 样本1,2,3,5,6,8,9</span><br>exact_match_rate = correct_samples / total_samples = <span class="hljs-number">0.70</span>  <span class="hljs-comment"># 70%</span><br><br><span class="hljs-comment"># 2. 分级准确率</span><br>level_1_correct = <span class="hljs-number">3</span>  <span class="hljs-comment"># 样本1,2,3</span><br>level_1_total = <span class="hljs-number">3</span><br>level_1_accuracy = <span class="hljs-number">3</span> / <span class="hljs-number">3</span> = <span class="hljs-number">1.00</span>  <span class="hljs-comment"># 100%</span><br><br>level_2_correct = <span class="hljs-number">2</span>  <span class="hljs-comment"># 样本5,6</span><br>level_2_total = <span class="hljs-number">3</span><br>level_2_accuracy = <span class="hljs-number">2</span> / <span class="hljs-number">3</span> = <span class="hljs-number">0.67</span>  <span class="hljs-comment"># 67%</span><br><br>level_3_correct = <span class="hljs-number">2</span>  <span class="hljs-comment"># 样本8,9</span><br>level_3_total = <span class="hljs-number">4</span><br>level_3_accuracy = <span class="hljs-number">2</span> / <span class="hljs-number">4</span> = <span class="hljs-number">0.50</span>  <span class="hljs-comment"># 50%</span><br><br><span class="hljs-comment"># 3. 难度递进下降率</span><br>drop_rate_1_to_2 = (<span class="hljs-number">1.00</span> - <span class="hljs-number">0.67</span>) / <span class="hljs-number">1.00</span> = <span class="hljs-number">0.33</span>  <span class="hljs-comment"># 33%</span><br>drop_rate_2_to_3 = (<span class="hljs-number">0.67</span> - <span class="hljs-number">0.50</span>) / <span class="hljs-number">0.67</span> = <span class="hljs-number">0.25</span>  <span class="hljs-comment"># 25%</span><br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;精确匹配率: <span class="hljs-subst">&#123;exact_match_rate:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)  <span class="hljs-comment"># 70.00%</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Level 1准确率: <span class="hljs-subst">&#123;level_1_accuracy:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)  <span class="hljs-comment"># 100.00%</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Level 2准确率: <span class="hljs-subst">&#123;level_2_accuracy:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)  <span class="hljs-comment"># 66.67%</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Level 3准确率: <span class="hljs-subst">&#123;level_3_accuracy:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)  <span class="hljs-comment"># 50.00%</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Level 1→2 下降率: <span class="hljs-subst">&#123;drop_rate_1_to_2:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)  <span class="hljs-comment"># 33.00%</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Level 2→3 下降率: <span class="hljs-subst">&#123;drop_rate_2_to_3:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)  <span class="hljs-comment"># 25.00%</span><br></code></pre></td></tr></table></figure><p><strong>结果分析：</strong></p><ul><li><strong>整体表现</strong>：70%的精确匹配率，表现良好</li><li><strong>难度敏感性</strong>：从 Level 1 到 Level 2 下降 33%，说明智能体在中等难度任务上有明显衰减</li><li><strong>能力边界</strong>：Level 3 准确率为 50%，说明智能体在复杂任务上仍有提升空间</li></ul><p>下降率越大，说明智能体在处理复杂任务时的能力衰减越明显。</p><p><strong>（4）GAIA 官方系统提示词</strong></p><p>GAIA 要求使用特定的系统提示词，确保模型输出符合评估格式：</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></pre></td><td class="code"><pre><code class="hljs python">GAIA_SYSTEM_PROMPT = <span class="hljs-string">&quot;&quot;&quot;You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER].</span><br><span class="hljs-string"></span><br><span class="hljs-string">YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.</span><br><span class="hljs-string"></span><br><span class="hljs-string">If you are asked for a number, don&#x27;t use comma to write your number neither use units such as $ or percent sign unless specified otherwise.</span><br><span class="hljs-string"></span><br><span class="hljs-string">If you are asked for a string, don&#x27;t use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise.</span><br><span class="hljs-string"></span><br><span class="hljs-string">If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string.&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p>GAIA 对答案格式有严格的要求：答案必须以<code>FINAL ANSWER: [答案]</code>的格式给出；对于数字类型的答案，不使用逗号分隔符和单位符号；对于字符串类型的答案，不使用冠词和缩写；对于列表类型的答案，使用逗号分隔并按字母顺序排列。</p><h3 id="12-3-2-获取-GAIA-数据集"><a href="#12-3-2-获取-GAIA-数据集" class="headerlink" title="12.3.2 获取 GAIA 数据集"></a>12.3.2 获取 GAIA 数据集</h3><p><strong>重要提示</strong>：GAIA 是<strong>受限数据集（Gated Dataset）</strong>，需要先在 HuggingFace 上申请访问权限。</p><p><strong>步骤 1：申请访问权限</strong></p><ol><li>访问 <a href="https://huggingface.co/datasets/gaia-benchmark/GAIA">https://huggingface.co/datasets/gaia-benchmark/GAIA</a></li><li>点击”Request access”按钮</li><li>填写申请表单（通常会在几秒内批准）</li><li>获取你的 HuggingFace Token：<a href="https://huggingface.co/settings/tokens">https://huggingface.co/settings/tokens</a></li></ol><p><strong>步骤 2：配置环境变量</strong></p><p>在<code>.env</code>文件中添加你的 HuggingFace Token：</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><code class="hljs bash"><span class="hljs-comment"># HuggingFace API 配置</span><br>HF_TOKEN=hf_your_token_here<br></code></pre></td></tr></table></figure><p><strong>方法 1：使用 HelloAgents 自动下载（推荐）</strong></p><p>HelloAgents 会自动处理 GAIA 数据集的下载和缓存：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.evaluation <span class="hljs-keyword">import</span> GAIADataset<br><span class="hljs-keyword">import</span> os<br><br><span class="hljs-comment"># 确保设置了HF_TOKEN，如果设置了.env无需这一行</span><br>os.environ[<span class="hljs-string">&quot;HF_TOKEN&quot;</span>] = <span class="hljs-string">&quot;hf_your_token_here&quot;</span><br><br><span class="hljs-comment"># 自动下载到 ./data/gaia/</span><br>dataset = GAIADataset(<br>    dataset_name=<span class="hljs-string">&quot;gaia-benchmark/GAIA&quot;</span>,<br>    split=<span class="hljs-string">&quot;validation&quot;</span>,  <span class="hljs-comment"># 或 &quot;test&quot;</span><br>    level=<span class="hljs-number">1</span>  <span class="hljs-comment"># 可选: 1, 2, 3, None(全部)</span><br>)<br>items = dataset.load()<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;加载了 <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(items)&#125;</span> 个测试样本&quot;</span>)<br><span class="hljs-comment"># 输出: 加载了 53 个测试样本 (Level 1)</span><br></code></pre></td></tr></table></figure><p><strong>工作原理</strong>：</p><ul><li>首次运行时，使用<code>snapshot_download</code>下载整个数据集到<code>./data/gaia/</code></li><li>数据集包含 114 个文件（问题、图片、PDF 等材料）</li><li>后续使用直接从本地加载，速度很快</li></ul><p><strong>数据集目录结构</strong>：</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><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><code class="hljs stylus">./data/gaia/<br>├── <span class="hljs-number">2023</span>/<br>│   ├── validation/<br>│   │   ├── metadata<span class="hljs-selector-class">.jsonl</span>  (<span class="hljs-number">165</span>个问题)<br>│   │   ├── *<span class="hljs-selector-class">.png</span>, *<span class="hljs-selector-class">.pdf</span>, *<span class="hljs-selector-class">.csv</span>, *<span class="hljs-selector-class">.xlsx</span>  (附件文件)<br>│   └── test/<br>│       ├── metadata<span class="hljs-selector-class">.jsonl</span>  (<span class="hljs-number">301</span>个问题)<br>│       └── ... (附件文件)<br>├── GAIA<span class="hljs-selector-class">.py</span><br>└── README.md<br></code></pre></td></tr></table></figure><p><strong>方法 2：手动下载</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> huggingface_hub <span class="hljs-keyword">import</span> snapshot_download<br><span class="hljs-keyword">import</span> os<br><br><span class="hljs-comment"># 设置Token</span><br>os.environ[<span class="hljs-string">&quot;HF_TOKEN&quot;</span>] = <span class="hljs-string">&quot;hf_your_token_here&quot;</span><br><br><span class="hljs-comment"># 下载数据集</span><br>snapshot_download(<br>    repo_id=<span class="hljs-string">&quot;gaia-benchmark/GAIA&quot;</span>,<br>    repo_type=<span class="hljs-string">&quot;dataset&quot;</span>,<br>    local_dir=<span class="hljs-string">&quot;./data/gaia&quot;</span>,<br>    token=os.getenv(<span class="hljs-string">&quot;HF_TOKEN&quot;</span>)<br>)<br></code></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 查看数据集统计</span><br>stats = dataset.get_statistics()<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;总样本数: <span class="hljs-subst">&#123;stats[<span class="hljs-string">&#x27;total_samples&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;级别分布: <span class="hljs-subst">&#123;stats[<span class="hljs-string">&#x27;level_distribution&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-comment"># 输出:</span><br><span class="hljs-comment"># 总样本数: 165</span><br><span class="hljs-comment"># 级别分布: &#123;1: 53, 2: 62, 3: 50&#125;</span><br></code></pre></td></tr></table></figure><h3 id="12-3-3-在-HelloAgents-中实现-GAIA-评估"><a href="#12-3-3-在-HelloAgents-中实现-GAIA-评估" class="headerlink" title="12.3.3 在 HelloAgents 中实现 GAIA 评估"></a>12.3.3 在 HelloAgents 中实现 GAIA 评估</h3><p>与 BFCL 类似，我们提供两种评估方式，推荐使用<strong>方式 1</strong>。</p><p><strong>方式 1：使用 GAIAEvaluationTool 一键评估</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> GAIAEvaluationTool<br><br><span class="hljs-comment"># GAIA官方系统提示词（来自论文）</span><br>GAIA_SYSTEM_PROMPT = <span class="hljs-string">&quot;&quot;&quot;You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER].</span><br><span class="hljs-string"></span><br><span class="hljs-string">YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.</span><br><span class="hljs-string"></span><br><span class="hljs-string">If you are asked for a number, don&#x27;t use comma to write your number neither use units such as $ or percent sign unless specified otherwise.</span><br><span class="hljs-string"></span><br><span class="hljs-string">If you are asked for a string, don&#x27;t use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise.</span><br><span class="hljs-string"></span><br><span class="hljs-string">If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string.&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># 1. 创建智能体（使用GAIA官方系统提示词）</span><br>llm = HelloAgentsLLM()<br>agent = SimpleAgent(<br>    name=<span class="hljs-string">&quot;TestAgent&quot;</span>,<br>    llm=llm,<br>    system_prompt=GAIA_SYSTEM_PROMPT  <span class="hljs-comment"># 关键：使用GAIA官方提示词</span><br>)<br><br><span class="hljs-comment"># 2. 创建GAIA评估工具</span><br>gaia_tool = GAIAEvaluationTool()<br><br><span class="hljs-comment"># 3. 一键运行评估</span><br>results = gaia_tool.run(<br>    agent=agent,<br>    level=<span class="hljs-number">1</span>,  <span class="hljs-comment"># Level 1: 简单任务</span><br>    max_samples=<span class="hljs-number">5</span>,  <span class="hljs-comment"># 评估5个样本</span><br>    export_results=<span class="hljs-literal">True</span>,  <span class="hljs-comment"># 导出GAIA格式结果</span><br>    generate_report=<span class="hljs-literal">True</span>  <span class="hljs-comment"># 生成评估报告</span><br>)<br><br><span class="hljs-comment"># 4. 查看结果</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;精确匹配率: <span class="hljs-subst">&#123;results[<span class="hljs-string">&#x27;exact_match_rate&#x27;</span>]:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;部分匹配率: <span class="hljs-subst">&#123;results[<span class="hljs-string">&#x27;partial_match_rate&#x27;</span>]:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;正确数: <span class="hljs-subst">&#123;results[<span class="hljs-string">&#x27;exact_matches&#x27;</span>]&#125;</span>/<span class="hljs-subst">&#123;results[<span class="hljs-string">&#x27;total_samples&#x27;</span>]&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>运行结果：</strong></p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><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></pre></td><td class="code"><pre><code class="hljs asciidoc">============================================================<br>GAIA一键评估<br>============================================================<br><br>配置:<br><span class="hljs-code">   智能体: TestAgent</span><br><span class="hljs-code">   难度级别: 1</span><br><span class="hljs-code">   样本数量: 5</span><br><br>============================================================<br>步骤1: 运行HelloAgents评估<br>============================================================<br><span class="hljs-code">   正在从HuggingFace下载: gaia-benchmark/GAIA</span><br><span class="hljs-code">   📥 下载GAIA数据集...</span><br><span class="hljs-code">   ✓ 数据集下载完成</span><br><span class="hljs-code">   ✓ 加载了 165 个样本</span><br>✅ GAIA数据集加载完成<br><span class="hljs-code">   数据源: gaia-benchmark/GAIA</span><br><span class="hljs-code">   分割: validation</span><br><span class="hljs-code">   级别: 1</span><br><span class="hljs-code">   样本数: 53</span><br><br>🌟 开始 GAIA 评估...<br><span class="hljs-code">   样本数量: 5</span><br><span class="hljs-code">   进度: 5/5</span><br>✅ GAIA 评估完成<br><span class="hljs-code">   精确匹配率: 80.00%</span><br><span class="hljs-code">   部分匹配率: 80.00%</span><br><br>============================================================<br>步骤2: 导出GAIA格式结果<br>============================================================<br>✅ GAIA格式结果已导出<br><span class="hljs-code">   输出文件: evaluation_results\gaia_official\gaia_level1_result_20251011_012648.jsonl</span><br><span class="hljs-code">   样本数: 5</span><br><span class="hljs-code">   包含推理轨迹: True</span><br>📄 提交说明已生成: evaluation_results\gaia_official\SUBMISSION_GUIDE_20251011_012648.md<br><br>============================================================<br>步骤3: 生成评估报告<br>============================================================<br>📄 报告已生成: evaluation_reports\gaia_report_20251011_012648.md<br><br>============================================================<br>🎯 最终结果<br>============================================================<br><span class="hljs-code">   精确匹配率: 80.00%</span><br><span class="hljs-code">   部分匹配率: 80.00%</span><br><span class="hljs-code">   正确数: 4/5</span><br></code></pre></td></tr></table></figure><p>评估完成后会自动生成三类文件：首先是 GAIA 格式结果文件（<code>evaluation_results/gaia_official/gaia_level1_result_*.jsonl</code>），采用 JSONL 格式（每行一个 JSON 对象），可直接用于提交到 GAIA 排行榜；其次是提交说明文件（<code>evaluation_results/gaia_official/SUBMISSION_GUIDE_*.md</code>），包含详细的提交步骤、结果文件格式说明和注意事项；最后是评估报告（<code>evaluation_reports/gaia_report_*.md</code>），包含评估结果摘要、详细指标、样本详情和可视化图表。</p><p><strong>注意</strong>：如果你发现生成的评估结果不理想（例如准确率较低），这是正常现象。虽然 Level 1 是一步推理任务，但仍然需要智能体具备工具调用能力（如搜索引擎、计算器等）才能正确回答问题。我们当前使用的 SimpleAgent 主要用于演示评估流程，在工具调用能力上还有提升空间。</p><p><strong>方式 2：使用 Dataset + Evaluator（灵活定制）</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.evaluation <span class="hljs-keyword">import</span> GAIADataset, GAIAEvaluator<br><br><span class="hljs-comment"># 1. 加载数据集</span><br>dataset = GAIADataset(level=<span class="hljs-number">1</span>)<br>items = dataset.load()<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;加载了 <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(items)&#125;</span> 个样本&quot;</span>)<br><br><span class="hljs-comment"># 2. 创建评估器</span><br>evaluator = GAIAEvaluator(dataset=dataset, level=<span class="hljs-number">1</span>)<br><br><span class="hljs-comment"># 3. 运行评估</span><br>results = evaluator.evaluate(agent, max_samples=<span class="hljs-number">5</span>)<br><br><span class="hljs-comment"># 4. 导出GAIA格式结果</span><br>evaluator.export_to_gaia_format(<br>    results,<br>    <span class="hljs-string">&quot;gaia_results.jsonl&quot;</span>,<br>    include_reasoning=<span class="hljs-literal">True</span><br>)<br></code></pre></td></tr></table></figure><p>生成的评估报告（<code>gaia_report_*.md</code>）可参考下面的文件：</p><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><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section"># GAIA评估报告</span><br><br><span class="hljs-strong">**生成时间**</span>: 2025-10-11 01:26:48<br><br><span class="hljs-section">## 📊 评估概览</span><br><br><span class="hljs-bullet">-</span> <span class="hljs-strong">**智能体**</span>: TestAgent<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**难度级别**</span>: 1<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**总样本数**</span>: 2<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**精确匹配数**</span>: 1<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**部分匹配数**</span>: 1<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**精确匹配率**</span>: 50.00%<br><span class="hljs-bullet">-</span> <span class="hljs-strong">**部分匹配率**</span>: 50.00%<br><br><span class="hljs-section">## 📈 详细指标</span><br><br><span class="hljs-section">### 分级准确率</span><br><br><span class="hljs-bullet">-</span> <span class="hljs-strong">**Level 1**</span>: 50.00% 精确 / 50.00% 部分 (1/2)<br><br><span class="hljs-section">## 📝 样本详情（前10个）</span><br><br>| 任务ID | 级别 | 预测答案 | 正确答案 | 精确匹配 | 部分匹配 |<br>|--------|------|----------|----------|----------|----------|<br>| e1fc63a2-da7a-432f-be78-7c4a95598703 | 1 | 24000 | 17 | ❌ | ❌ |<br>| 8e867cd7-cff9-4e6c-867a-ff5ddc2550be | 1 | 3 | 3 | ✅ | ✅ |<br><br><span class="hljs-section">## 📊 准确率可视化</span><br><br>精确匹配: █████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░ 50.00%<br>部分匹配: █████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░ 50.00%<br><br><br><span class="hljs-section">## 💡 建议</span><br><br><span class="hljs-bullet">-</span> ⚠️ 表现一般，需要改进。<br><span class="hljs-bullet">-</span> 💡 建议检查工具使用和多步推理能力。<br></code></pre></td></tr></table></figure><p>**生成的 GAIA 格式结果（<code>gaia_level1_result_*.jsonl</code>）：<strong></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;task_id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;e1fc63a2-da7a-432f-be78-7c4a95598703&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;model_answer&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;24000&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;reasoning_trace&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;24000&quot;</span><span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;task_id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;8e867cd7-cff9-4e6c-867a-ff5ddc2550be&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;model_answer&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;3&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;reasoning_trace&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;3&quot;</span><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><h3 id="12-3-4-提交结果到-GAIA-官方排行榜"><a href="#12-3-4-提交结果到-GAIA-官方排行榜" class="headerlink" title="12.3.4 提交结果到 GAIA 官方排行榜"></a>12.3.4 提交结果到 GAIA 官方排行榜</h3><p>使用 GAIAEvaluationTool 运行评估后，会在<code>evaluation_results/gaia_official/</code>目录下生成提交所需的文件和详细的提交说明。</p><ol><li><p></strong>GAIA 格式结果文件**：<code>gaia_level1_result_*.jsonl</code></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;task_id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;xxx&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;model_answer&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;答案&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;reasoning_trace&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;推理过程&quot;</span><span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#123;</span><span class="hljs-attr">&quot;task_id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;yyy&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;model_answer&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;答案&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">&quot;reasoning_trace&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;推理过程&quot;</span><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure></li><li><p><strong>提交说明文件</strong>：<code>SUBMISSION_GUIDE_*.md</code></p></li></ol><p>打开自动生成的<code>SUBMISSION_GUIDE_*.md</code>文件，里面包含完整的提交指南：</p><p>具体来说，打开浏览器，访问：</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs awk">https:<span class="hljs-regexp">//</span>huggingface.co<span class="hljs-regexp">/spaces/g</span>aia-benchmark/leaderboard<br></code></pre></td></tr></table></figure><p>如图 12.4 所示，提交表单中填写信息即可：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/12-figures/12-4.png" alt="" width="85%"/>  <p>图 12.4 BFCL 评估流程图</p></div><p>提交前，可以手动检查生成的 JSONL 文件：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> json<br><br><span class="hljs-comment"># 读取结果文件</span><br><span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(<span class="hljs-string">&quot;evaluation_results/gaia_official/gaia_level1_result_*.jsonl&quot;</span>, <span class="hljs-string">&quot;r&quot;</span>) <span class="hljs-keyword">as</span> f:<br>    <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> f:<br>        result = json.loads(line)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Task ID: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;task_id&#x27;</span>]&#125;</span>&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Answer: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;model_answer&#x27;</span>]&#125;</span>&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Reasoning: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;reasoning_trace&#x27;</span>]&#125;</span>&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;-&quot;</span> * <span class="hljs-number">50</span>)<br></code></pre></td></tr></table></figure><h3 id="12-3-5-核心组件实现细节"><a href="#12-3-5-核心组件实现细节" class="headerlink" title="12.3.5 核心组件实现细节"></a>12.3.5 核心组件实现细节</h3><p>GAIA 评估系统的实现与 BFCL 类似，但针对通用能力评估有一些特殊的设计。</p><p><strong>（1）GAIADataset：支持多模态的数据加载器</strong></p><p>GAIA 数据集的特殊之处在于它包含多模态数据（文本、文件、图片等）：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">GAIADataset</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;GAIA数据集加载器</span><br><span class="hljs-string"></span><br><span class="hljs-string">    支持从HuggingFace加载GAIA数据集（受限数据集）</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        level: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">int</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        split: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;validation&quot;</span>,</span><br><span class="hljs-params">        local_data_dir: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span></span><br><span class="hljs-params">    </span>):<br>        self.level = level<br>        self.split = split<br>        self.local_data_dir = local_data_dir <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;./data/gaia&quot;</span><br>        self.data = []<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">load</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]]:<br>        <span class="hljs-string">&quot;&quot;&quot;加载数据集&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 从HuggingFace下载</span><br>        items = self._load_from_huggingface()<br><br>        <span class="hljs-comment"># 按级别过滤</span><br>        <span class="hljs-keyword">if</span> self.level:<br>            items = [item <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> items <span class="hljs-keyword">if</span> item.get(<span class="hljs-string">&quot;level&quot;</span>) == self.level]<br><br>        self.data = items<br>        <span class="hljs-keyword">return</span> items<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_load_from_huggingface</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]]:<br>        <span class="hljs-string">&quot;&quot;&quot;从HuggingFace下载GAIA数据集&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">from</span> huggingface_hub <span class="hljs-keyword">import</span> snapshot_download<br>        <span class="hljs-keyword">import</span> json<br><br>        <span class="hljs-comment"># 下载数据集</span><br>        repo_id = <span class="hljs-string">&quot;gaia-benchmark/GAIA&quot;</span><br>        local_dir = snapshot_download(<br>            repo_id=repo_id,<br>            repo_type=<span class="hljs-string">&quot;dataset&quot;</span>,<br>            local_dir=self.local_data_dir,<br>            local_dir_use_symlinks=<span class="hljs-literal">False</span><br>        )<br><br>        <span class="hljs-comment"># 加载JSONL文件</span><br>        data_file = Path(local_dir) / <span class="hljs-string">&quot;2023&quot;</span> / self.split / <span class="hljs-string">&quot;metadata.jsonl&quot;</span><br>        items = []<br>        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(data_file, <span class="hljs-string">&#x27;r&#x27;</span>, encoding=<span class="hljs-string">&#x27;utf-8&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>            <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> f:<br>                item = json.loads(line)<br>                items.append(self._standardize_item(item))<br><br>        <span class="hljs-keyword">return</span> items<br></code></pre></td></tr></table></figure><p><strong>（2）GAIAEvaluator：实现 GAIA 官方评估算法</strong></p><p>GAIA 的评估使用<strong>准精确匹配（Quasi Exact Match）</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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">GAIAEvaluator</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;GAIA评估器</span><br><span class="hljs-string"></span><br><span class="hljs-string">    实现GAIA官方的准精确匹配（Quasi Exact Match）评估算法</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">evaluate</span>(<span class="hljs-params">self, agent: <span class="hljs-type">Any</span>, max_samples: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">int</span>] = <span class="hljs-literal">None</span></span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;执行评估&quot;&quot;&quot;</span><br>        dataset_items = self.dataset.load()<br><br>        <span class="hljs-keyword">if</span> max_samples:<br>            dataset_items = dataset_items[:max_samples]<br><br>        results = []<br>        <span class="hljs-keyword">for</span> i, item <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(dataset_items, <span class="hljs-number">1</span>):<br>            <span class="hljs-comment"># 1. 构造提示词</span><br>            prompt = self._build_prompt(item[<span class="hljs-string">&quot;question&quot;</span>], item)<br><br>            <span class="hljs-comment"># 2. 调用智能体</span><br>            response = agent.run(prompt)<br><br>            <span class="hljs-comment"># 3. 提取答案（GAIA格式：FINAL ANSWER: [答案]）</span><br>            predicted_answer = self._extract_answer(response)<br><br>            <span class="hljs-comment"># 4. 归一化答案（GAIA官方规则）</span><br>            normalized_pred = self._normalize_answer(predicted_answer)<br>            normalized_truth = self._normalize_answer(item[<span class="hljs-string">&quot;final_answer&quot;</span>])<br><br>            <span class="hljs-comment"># 5. 准精确匹配</span><br>            exact_match = (normalized_pred == normalized_truth)<br><br>            results.append(&#123;<br>                <span class="hljs-string">&quot;task_id&quot;</span>: item[<span class="hljs-string">&quot;task_id&quot;</span>],<br>                <span class="hljs-string">&quot;predicted&quot;</span>: predicted_answer,<br>                <span class="hljs-string">&quot;expected&quot;</span>: item[<span class="hljs-string">&quot;final_answer&quot;</span>],<br>                <span class="hljs-string">&quot;exact_match&quot;</span>: exact_match,<br>                <span class="hljs-string">&quot;level&quot;</span>: item.get(<span class="hljs-string">&quot;level&quot;</span>, <span class="hljs-number">0</span>)<br>            &#125;)<br><br>        <span class="hljs-keyword">return</span> self._format_results(results)<br></code></pre></td></tr></table></figure><p>GAIA 使用特定的归一化规则来处理不同类型的答案：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_normalize_answer</span>(<span class="hljs-params">self, answer: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;标准化答案字符串（GAIA官方标准化规则）</span><br><span class="hljs-string"></span><br><span class="hljs-string">    规则：</span><br><span class="hljs-string">    1. 数字：移除逗号分隔符和单位符号</span><br><span class="hljs-string">    2. 字符串：移除冠词、转小写、移除多余空格</span><br><span class="hljs-string">    3. 列表：逗号分隔，按字母顺序排序</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> answer:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;</span><br><br>    answer = answer.strip()<br><br>    <span class="hljs-comment"># 检查是否是逗号分隔的列表</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-string">&#x27;,&#x27;</span> <span class="hljs-keyword">in</span> answer:<br>        parts = [self._normalize_single_answer(p.strip()) <span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> answer.split(<span class="hljs-string">&#x27;,&#x27;</span>)]<br>        parts.sort()  <span class="hljs-comment"># GAIA要求按字母顺序排序</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;,&#x27;</span>.join(parts)<br>    <span class="hljs-keyword">else</span>:<br>        <span class="hljs-keyword">return</span> self._normalize_single_answer(answer)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">_normalize_single_answer</span>(<span class="hljs-params">self, answer: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;标准化单个答案（不包含逗号的答案）&quot;&quot;&quot;</span><br>    answer = answer.strip().lower()<br><br>    <span class="hljs-comment"># 移除常见的冠词</span><br>    articles = [<span class="hljs-string">&#x27;the&#x27;</span>, <span class="hljs-string">&#x27;a&#x27;</span>, <span class="hljs-string">&#x27;an&#x27;</span>]<br>    words = answer.split()<br>    <span class="hljs-keyword">if</span> words <span class="hljs-keyword">and</span> words[<span class="hljs-number">0</span>] <span class="hljs-keyword">in</span> articles:<br>        words = words[<span class="hljs-number">1</span>:]<br>        answer = <span class="hljs-string">&#x27; &#x27;</span>.join(words)<br><br>    <span class="hljs-comment"># 移除货币符号和百分号</span><br>    answer = answer.replace(<span class="hljs-string">&#x27;$&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>).replace(<span class="hljs-string">&#x27;%&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>).replace(<span class="hljs-string">&#x27;€&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>).replace(<span class="hljs-string">&#x27;£&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)<br><br>    <span class="hljs-comment"># 移除数字中的逗号分隔符</span><br>    answer = re.sub(<span class="hljs-string">r&#x27;(\d),(\d)&#x27;</span>, <span class="hljs-string">r&#x27;\1\2&#x27;</span>, answer)<br><br>    <span class="hljs-comment"># 移除多余空格</span><br>    answer = <span class="hljs-string">&#x27; &#x27;</span>.join(answer.split())<br><br>    <span class="hljs-comment"># 移除末尾的标点符号</span><br>    answer = answer.rstrip(<span class="hljs-string">&#x27;.,;:!?&#x27;</span>)<br><br>    <span class="hljs-keyword">return</span> answer<br></code></pre></td></tr></table></figure><p>GAIA 要求模型输出格式为<code>FINAL ANSWER: [答案]</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><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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_extract_answer</span>(<span class="hljs-params">self, response: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;从响应中提取答案（GAIA格式）</span><br><span class="hljs-string"></span><br><span class="hljs-string">    GAIA要求答案格式为：FINAL ANSWER: [答案]</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 首先尝试提取GAIA官方格式的答案</span><br>    final_answer_pattern = <span class="hljs-string">r&#x27;FINAL ANSWER:\s*(.+?)(?:\n|$)&#x27;</span><br>    <span class="hljs-keyword">match</span> = re.search(final_answer_pattern, response, re.IGNORECASE | re.MULTILINE)<br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span>:<br>        answer = <span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>).strip()<br>        <span class="hljs-comment"># 移除可能的方括号</span><br>        answer = answer.strip(<span class="hljs-string">&#x27;[]&#x27;</span>)<br>        <span class="hljs-keyword">return</span> answer<br><br>    <span class="hljs-comment"># 备用方案：查找其他答案标记</span><br>    answer_patterns = [<br>        <span class="hljs-string">r&#x27;答案[：:]\s*(.+)&#x27;</span>,<br>        <span class="hljs-string">r&#x27;最终答案[：:]\s*(.+)&#x27;</span>,<br>        <span class="hljs-string">r&#x27;Final answer[：:]\s*(.+)&#x27;</span>,<br>        <span class="hljs-string">r&#x27;Answer[：:]\s*(.+)&#x27;</span>,<br>    ]<br><br>    <span class="hljs-keyword">for</span> pattern <span class="hljs-keyword">in</span> answer_patterns:<br>        <span class="hljs-keyword">match</span> = re.search(pattern, response, re.IGNORECASE)<br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span>:<br>            <span class="hljs-keyword">return</span> <span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>).strip()<br><br>    <span class="hljs-comment"># 如果没有找到标记，返回最后一个非空行</span><br>    lines = response.strip().split(<span class="hljs-string">&#x27;\n&#x27;</span>)<br>    <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> <span class="hljs-built_in">reversed</span>(lines):<br>        line = line.strip()<br>        <span class="hljs-keyword">if</span> line <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> line.startswith(<span class="hljs-string">&#x27;#&#x27;</span>):<br>            <span class="hljs-keyword">return</span> line<br><br>    <span class="hljs-keyword">return</span> response.strip()<br></code></pre></td></tr></table></figure><p>评估完成后，可以导出为 GAIA 官方要求的 JSONL 格式：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">export_to_gaia_format</span>(<span class="hljs-params"></span><br><span class="hljs-params">    self,</span><br><span class="hljs-params">    results: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>],</span><br><span class="hljs-params">    output_path: <span class="hljs-type">Union</span>[<span class="hljs-built_in">str</span>, Path],</span><br><span class="hljs-params">    include_reasoning: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span></span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-literal">None</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;导出为GAIA官方格式（JSONL）</span><br><span class="hljs-string"></span><br><span class="hljs-string">    GAIA要求的格式：</span><br><span class="hljs-string">    &#123;&quot;task_id&quot;: &quot;xxx&quot;, &quot;model_answer&quot;: &quot;答案&quot;, &quot;reasoning_trace&quot;: &quot;推理过程&quot;&#125;</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    output_path = Path(output_path)<br>    output_path.parent.mkdir(parents=<span class="hljs-literal">True</span>, exist_ok=<span class="hljs-literal">True</span>)<br><br>    <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(output_path, <span class="hljs-string">&#x27;w&#x27;</span>, encoding=<span class="hljs-string">&#x27;utf-8&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>        <span class="hljs-keyword">for</span> result <span class="hljs-keyword">in</span> results.get(<span class="hljs-string">&quot;detailed_results&quot;</span>, []):<br>            entry = &#123;<br>                <span class="hljs-string">&quot;task_id&quot;</span>: result[<span class="hljs-string">&quot;task_id&quot;</span>],<br>                <span class="hljs-string">&quot;model_answer&quot;</span>: result[<span class="hljs-string">&quot;predicted&quot;</span>]<br>            &#125;<br><br>            <span class="hljs-keyword">if</span> include_reasoning:<br>                entry[<span class="hljs-string">&quot;reasoning_trace&quot;</span>] = result.get(<span class="hljs-string">&quot;response&quot;</span>, result[<span class="hljs-string">&quot;predicted&quot;</span>])<br><br>            f.write(json.dumps(entry, ensure_ascii=<span class="hljs-literal">False</span>) + <span class="hljs-string">&#x27;\n&#x27;</span>)<br></code></pre></td></tr></table></figure><p><strong>（3）GAIAEvaluationTool：一键评估工具</strong></p><p>GAIAEvaluationTool 封装了完整的评估流程，提供一键评估功能：</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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">GAIAEvaluationTool</span>(<span class="hljs-title class_ inherited__">Tool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;GAIA评估工具</span><br><span class="hljs-string"></span><br><span class="hljs-string">    提供一键评估功能：</span><br><span class="hljs-string">    1. 运行HelloAgents评估</span><br><span class="hljs-string">    2. 导出GAIA格式结果</span><br><span class="hljs-string">    3. 生成评估报告</span><br><span class="hljs-string">    4. 生成提交说明</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        agent: <span class="hljs-type">Any</span>,</span><br><span class="hljs-params">        level: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">int</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        max_samples: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">int</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        local_data_dir: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        export_results: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span>,</span><br><span class="hljs-params">        generate_report: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span></span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;执行GAIA一键评估&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 步骤1: 运行HelloAgents评估</span><br>        results = self._run_evaluation(agent, level, max_samples, local_data_dir)<br><br>        <span class="hljs-comment"># 步骤2: 导出GAIA格式结果</span><br>        <span class="hljs-keyword">if</span> export_results:<br>            self._export_results(results)<br><br>        <span class="hljs-comment"># 步骤3: 生成评估报告</span><br>        <span class="hljs-keyword">if</span> generate_report:<br>            self.generate_report(results)<br><br>        <span class="hljs-keyword">return</span> results<br></code></pre></td></tr></table></figure><p>GAIAEvaluationTool 会自动生成评估报告：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">generate_report</span>(<span class="hljs-params"></span><br><span class="hljs-params">    self,</span><br><span class="hljs-params">    results: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>],</span><br><span class="hljs-params">    output_file: <span class="hljs-type">Optional</span>[<span class="hljs-type">Union</span>[<span class="hljs-built_in">str</span>, Path]] = <span class="hljs-literal">None</span></span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;生成评估报告&quot;&quot;&quot;</span><br>    report = <span class="hljs-string">f&quot;&quot;&quot;# GAIA评估报告</span><br><span class="hljs-string"></span><br><span class="hljs-string">**生成时间**: <span class="hljs-subst">&#123;datetime.now().strftime(<span class="hljs-string">&quot;%Y-%m-%d %H:%M:%S&quot;</span>)&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">## 📊 评估概览</span><br><span class="hljs-string"></span><br><span class="hljs-string">- **智能体**: <span class="hljs-subst">&#123;results.get(<span class="hljs-string">&quot;agent_name&quot;</span>, <span class="hljs-string">&quot;Unknown&quot;</span>)&#125;</span></span><br><span class="hljs-string">- **难度级别**: <span class="hljs-subst">&#123;results.get(<span class="hljs-string">&quot;level_filter&quot;</span>) <span class="hljs-keyword">or</span> <span class="hljs-string">&#x27;全部&#x27;</span>&#125;</span></span><br><span class="hljs-string">- **总样本数**: <span class="hljs-subst">&#123;results.get(<span class="hljs-string">&quot;total_samples&quot;</span>, <span class="hljs-number">0</span>)&#125;</span></span><br><span class="hljs-string">- **精确匹配数**: <span class="hljs-subst">&#123;results.get(<span class="hljs-string">&quot;exact_matches&quot;</span>, <span class="hljs-number">0</span>)&#125;</span></span><br><span class="hljs-string">- **精确匹配率**: <span class="hljs-subst">&#123;results.get(<span class="hljs-string">&quot;exact_match_rate&quot;</span>, <span class="hljs-number">0</span>):<span class="hljs-number">.2</span>%&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">## 📈 详细指标</span><br><span class="hljs-string"></span><br><span class="hljs-string">### 分级准确率</span><br><span class="hljs-string"></span><br><span class="hljs-string"><span class="hljs-subst">&#123;self._format_level_metrics(results.get(<span class="hljs-string">&quot;level_metrics&quot;</span>, &#123;&#125;</span>))&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 📝 样本详情（前10个）</span><br><span class="hljs-string"></span><br><span class="hljs-string"><span class="hljs-subst">&#123;self._format_sample_details(results.get(<span class="hljs-string">&quot;detailed_results&quot;</span>, [])[:<span class="hljs-number">10</span>])&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">## 📊 准确率可视化</span><br><span class="hljs-string"></span><br><span class="hljs-string"><span class="hljs-subst">&#123;self._format_visualization(results.get(<span class="hljs-string">&quot;exact_match_rate&quot;</span>, <span class="hljs-number">0</span>))&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">## 💡 建议</span><br><span class="hljs-string"></span><br><span class="hljs-string"><span class="hljs-subst">&#123;self._format_suggestions(results.get(<span class="hljs-string">&quot;exact_match_rate&quot;</span>, <span class="hljs-number">0</span>))&#125;</span></span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br>    <span class="hljs-comment"># 保存报告</span><br>    <span class="hljs-keyword">if</span> output_file <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br>        output_dir = Path(<span class="hljs-string">&quot;./evaluation_reports&quot;</span>)<br>        output_dir.mkdir(parents=<span class="hljs-literal">True</span>, exist_ok=<span class="hljs-literal">True</span>)<br>        output_file = output_dir / <span class="hljs-string">f&quot;gaia_report_<span class="hljs-subst">&#123;datetime.now().strftime(<span class="hljs-string">&#x27;%Y%m%d_%H%M%S&#x27;</span>)&#125;</span>.md&quot;</span><br><br>    <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(output_file, <span class="hljs-string">&#x27;w&#x27;</span>, encoding=<span class="hljs-string">&#x27;utf-8&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>        f.write(report)<br><br>    <span class="hljs-keyword">return</span> report<br></code></pre></td></tr></table></figure><h2 id="12-4-数据生成质量评估"><a href="#12-4-数据生成质量评估" class="headerlink" title="12.4 数据生成质量评估"></a>12.4 数据生成质量评估</h2><p>在 AI 系统开发中，高质量的训练数据是系统性能的基础。本节介绍如何使用 HelloAgents 框架评估生成数据的质量，以 AIME（美国数学邀请赛）<sup>[9]</sup>风格的数学题目生成为例。</p><p>AIME 是美国数学协会（MAA）主办的中等难度数学竞赛，介于 AMC 10&#x2F;12 和美国数学奥林匹克（USAMO）之间。AIME 题目具有鲜明的特点：每道题的答案都是 0 到 999 之间的整数，题目涵盖代数、几何、数论、组合、概率等多个数学领域，需要多步推理但不涉及高深理论，难度适中（相当于 AIME 第 6-9 题的水平）。这些特点使得 AIME 题目成为评估数学题目生成质量的理想基准：答案格式统一便于自动化评估，题目难度适中适合大规模生成。我们使用 HuggingFace 上的<code>TianHongZXY/aime-1983-2025</code>数据集作为参考，该数据集包含从 1983 年到 2025 年的 900 多道 AIME 真题，为我们的生成和评估提供了丰富的参考样本。</p><h3 id="12-4-1-评估方法概述"><a href="#12-4-1-评估方法概述" class="headerlink" title="12.4.1 评估方法概述"></a>12.4.1 评估方法概述</h3><p>在数据生成质量评估中，我们采用三种互补的评估方法：LLM Judge、Win Rate 和人工打分。选择这三种方法有两个重要原因。首先，从方法论角度来看，这些是当前智能体领域常用的自动化测评方案，也是许多学术论文中的主流做法，具有广泛的认可度和实践基础。其次，从适用性角度来看，这三种方法天然适合我们的评估场景：LLM Judge 和 Win Rate 用于评估题目生成质量（从正确性、清晰度、难度匹配等维度进行多维度评估），而人工打分用于评估答案生成质量（通过人类专家验证答案的准确性），这种分工非常合理且易于理解。</p><p>下面我们详细介绍这三种评估方法的具体实现。整个案例的实现流程如图 12.5 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/12-figures/12-5.png" alt="" width="85%"/>  <p>图 12.5 数据生成质量评估流程图</p></div><strong>（1）LLM Judge 评估</strong><p><strong>设计动机</strong>：在数据生成质量评估中，我们需要对大量生成的题目进行快速、一致的质量评估。传统的人工评估虽然准确，但成本高、效率低，难以应对大规模数据生成的需求。LLM Judge 通过使用大语言模型作为评委，可以自动化地从多个维度评估生成数据的质量，不仅大幅提升评估效率，还能保持评估标准的一致性。更重要的是，LLM Judge 可以提供详细的评分理由和改进建议，帮助我们理解生成数据的优缺点，为后续优化提供方向。</p><p>在我们的实现中，LLM Judge 从四个关键维度评估 AIME 题目的质量：</p><div align="center">  <p>表 12.5 LLM Judge 评估 AIME 题目的维度</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/12-figures/12-table-5.png" alt="" width="85%"/></div><p>有了四个维度的评分后，我们需要将这些评分汇总成整体的评估指标。我们定义了三个关键指标来衡量生成题目的质量水平：</p><p><strong>评估指标</strong>：</p><p><strong>1. 平均分（Average Score）</strong>：计算所有题目在四个维度上的平均得分，反映生成题目的整体质量水平。<br>$$<br>\text{Average Score} &#x3D; \frac{1}{N} \sum_{i&#x3D;1}^{N} \frac{\sum_{d&#x3D;1}^{4} S_{i,d}}{4}<br>$$</p><p><strong>2. 及格率（Pass Rate）</strong>：统计平均分达到 3.5 分及以上的题目比例，反映生成题目的基本质量保障。</p><p>$$<br>\text{Pass Rate} &#x3D; \frac{|{i : \text{Score}_i \geq 3.5}|}{N}<br>$$</p><p><strong>3. 优秀率（Excellent Rate）</strong>：统计平均分达到 4.5 分及以上的题目比例，反映生成题目的高质量占比。</p><p>$$<br>\text{Excellent Rate} &#x3D; \frac{|{i : \text{Score}_i \geq 4.5}|}{N}<br>$$</p><p>其中：</p><ul><li>$N$ 是评估的题目总数</li><li>$S_{i,d}$ 是第 $i$ 个题目在第 $d$ 个维度的得分（1-5 分）</li><li>$\text{Score}_i$ 是第 $i$ 个题目的平均分（四个维度得分的平均值）</li></ul><p>这三个指标从不同角度反映生成质量：平均分给出整体水平，及格率保证基本质量，优秀率衡量高质量产出能力。</p><p><strong>（2）Win Rate 评估</strong></p><p><strong>设计动机</strong>：虽然 LLM Judge 可以提供多维度的绝对评分，但我们还需要一个相对评估指标来衡量生成题目与真题的质量差距。Win Rate 评估通过成对对比的方式，让 LLM 直接判断生成题目和真题哪个更好，这种相对比较比绝对评分更符合人类的判断习惯，也更容易发现生成题目的相对优势和劣势。理想情况下，如果生成题目的质量接近真题，Win Rate 应该在 50%左右（即生成题目和真题各有 50%的胜率）。这个指标简单直观，可以快速判断生成系统的整体质量水平。</p><p>在我们的实现中，Win Rate 评估通过以下图 12.6 所示流程进行评估：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/12-figures/12-6.png" alt="" width="85%"/>  <p>图 12.6 数据生成质量评估流程图</p></div><p>在成对对比评估中，每次比较会产生三种可能的结果：生成题目获胜（Win）、真题获胜（Loss）或平局（Tie）。我们通过统计这三种结果的比例来评估生成题目的质量：</p><p><strong>评估指标</strong>：</p><p><strong>1. 胜率（Win Rate）</strong>：生成题目被判定为更好的比例，反映生成题目相对于真题的优势。</p><p>$$<br>\text{Win Rate} &#x3D; \frac{\text{Wins}}{\text{Total Comparisons}}<br>$$</p><p><strong>2. 败率（Loss Rate）</strong>：真题被判定为更好的比例，反映生成题目相对于真题的劣势。</p><p>$$<br>\text{Loss Rate} &#x3D; \frac{\text{Losses}}{\text{Total Comparisons}}<br>$$</p><p><strong>3. 平局率（Tie Rate）</strong>：两者被判定为质量相当的比例，反映生成题目与真题的相似程度。</p><p>$$<br>\text{Tie Rate} &#x3D; \frac{\text{Ties}}{\text{Total Comparisons}}<br>$$</p><p>其中，Total Comparisons 是总的对比次数，Wins、Losses 和 Ties 分别是生成题目获胜、失败和平局的次数。这三个指标满足：Win Rate + Loss Rate + Tie Rate &#x3D; 100%。</p><p><strong>理想结果</strong>：Win Rate ≈ 50%（说明生成质量接近真题）。如果 Win Rate 显著低于 50%，说明生成题目质量不如真题，需要优化生成策略；如果 Win Rate 显著高于 50%，可能说明生成题目在某些方面超越了真题，或者评估标准存在偏差。</p><p><strong>（3）人工验证</strong></p><p><strong>设计动机</strong>：尽管 LLM Judge 和 Win Rate 可以自动化评估题目质量，但对于数学题目这种需要严格逻辑推理的内容，人工验证仍然是不可或缺的。特别是在评估答案生成质量时，需要人类专家验证答案的准确性、解答步骤的完整性和数学推理的严密性。此外，人工验证还可以发现自动化评估可能遗漏的问题，如题目的创新性、趣味性等主观因素。为了提高人工验证的效率和体验，我们开发了基于 Gradio 的 Web 界面，让验证者可以方便地浏览题目、评分、标注状态和添加评论，大大降低了人工验证的门槛。</p><p>在我们的实现中，人工验证通过以下步骤进行：</p><ol><li>阅读题目、答案、解答</li><li>评分（1-5 分）：正确性、清晰度、难度匹配、完整性</li><li>标注状态：<ul><li>✅ approved（通过）</li><li>❌ rejected（拒绝）</li><li>🔄 needs_revision（需修改）</li></ul></li><li>添加评论</li></ol><h3 id="12-4-2-系统架构"><a href="#12-4-2-系统架构" class="headerlink" title="12.4.2 系统架构"></a>12.4.2 系统架构</h3><p>数据生成与评估系统采用模块化设计：</p><figure class="highlight 1c"><table><tr><td class="gutter"><pre><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><code class="hljs 1c">data_generation/<br>├── aime_generator.py              <span class="hljs-meta"># AIME题目生成器</span><br>├── human_verification_ui.py       <span class="hljs-meta"># 人工验证界面</span><br>├── run_complete_evaluation.py     <span class="hljs-meta"># 完整评估流程</span><br>│<br>├── generated_data/                <span class="hljs-meta"># 生成的数据</span><br>│   ├── aime_generated_XXXXXX.json<br>│   └── generation_report_XXXXXX.md<br>│<br>└── evaluation_results/            <span class="hljs-meta"># 评估结果</span><br>    └── XXXXXX/<br>        ├── llm_judge/<br>        ├── win_rate/<br>        └── comprehensive_report.md<br></code></pre></td></tr></table></figure><p>系统包含四个核心组件：首先是 AIMEGenerator（题目生成器），使用 HelloAgents 框架生成 AIME 风格题目，支持批量生成和进度保存，并能自动处理 API 速率限制；其次是 LLMJudgeTool（LLM Judge 评估工具），提供 4 维度质量评估，自动生成 JSON 结果和 Markdown 报告；第三是 WinRateTool（Win Rate 评估工具），通过成对对比评估计算胜率、败率和平局率；最后是 HumanVerificationUI（人工验证界面），基于 Gradio Web 界面，支持评分和状态标注。</p><h3 id="12-4-3-AIME-题目生成器实现"><a href="#12-4-3-AIME-题目生成器实现" class="headerlink" title="12.4.3 AIME 题目生成器实现"></a>12.4.3 AIME 题目生成器实现</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">AIMEGenerator</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;AIME Problem Generator&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        llm: HelloAgentsLLM = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        delay_seconds: <span class="hljs-built_in">float</span> = <span class="hljs-number">1.0</span>,</span><br><span class="hljs-params">        use_reference_examples: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span>,</span><br><span class="hljs-params">        reference_dataset: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;TianHongZXY/aime-1983-2025&quot;</span></span><br><span class="hljs-params">    </span>):<br>        self.llm = llm <span class="hljs-keyword">or</span> HelloAgentsLLM()<br>        self.agent = SimpleAgent(<br>            name=<span class="hljs-string">&quot;AIME Generator&quot;</span>,<br>            llm=self.llm,<br>            system_prompt=<span class="hljs-string">&quot;You are a professional mathematics competition problem designer.&quot;</span><br>        )<br>        self.delay_seconds = delay_seconds<br>        self.use_reference_examples = use_reference_examples<br><br>        <span class="hljs-comment"># Load reference examples from 900+ AIME problems (1983-2025)</span><br>        <span class="hljs-keyword">if</span> use_reference_examples:<br>            dataset = load_dataset(reference_dataset, split=<span class="hljs-string">&quot;test&quot;</span>)<br>            self.reference_examples = <span class="hljs-built_in">list</span>(dataset)<br></code></pre></td></tr></table></figure><p>我们的目标是生成类似风格的数据集，所以从 900+道 AIME 真题（1983-2025）中随机选择参考样例</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></pre></td><td class="code"><pre><code class="hljs python">GENERATION_PROMPT = <span class="hljs-string">&quot;&quot;&quot;You are a professional mathematics competition problem designer, skilled in creating AIME (American Invitational Mathematics Examination) style problems.</span><br><span class="hljs-string"></span><br><span class="hljs-string">【Reference Example】(For style reference only, please generate a completely different problem)</span><br><span class="hljs-string">Problem: &#123;example_problem&#125;</span><br><span class="hljs-string">Answer: &#123;example_answer&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">AIME Problem Characteristics:</span><br><span class="hljs-string">1. Answer: An integer between 0 and 999</span><br><span class="hljs-string">2. Topics: Algebra, Geometry, Number Theory, Combinatorics, Probability, etc.</span><br><span class="hljs-string">3. Style: Requires multi-step reasoning, but no advanced theory</span><br><span class="hljs-string">4. Difficulty: Medium to hard (similar to AIME problems 6-9)</span><br><span class="hljs-string"></span><br><span class="hljs-string">Please generate a **completely different** AIME-style mathematics problem, including:</span><br><span class="hljs-string">1. Problem statement (clear and complete, different from the reference)</span><br><span class="hljs-string">2. Answer (an integer between 0 and 999, different from the reference)</span><br><span class="hljs-string">3. Detailed solution (including all reasoning steps)</span><br><span class="hljs-string">4. Topic classification (Algebra/Geometry/Number Theory/Combinatorics/Probability)</span><br><span class="hljs-string"></span><br><span class="hljs-string">Please output in the following JSON format:</span><br><span class="hljs-string">&#123;</span><br><span class="hljs-string">    &quot;problem&quot;: &quot;Problem statement in English&quot;,</span><br><span class="hljs-string">    &quot;answer&quot;: 123,</span><br><span class="hljs-string">    &quot;solution&quot;: &quot;Detailed solution steps in English&quot;,</span><br><span class="hljs-string">    &quot;topic&quot;: &quot;Algebra&quot;</span><br><span class="hljs-string">&#125;</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p>我们选择使用英文生成题目有四个重要原因：首先是与 AIME 真题保持一致（AIME 是英文竞赛，生成英文题目更合理），其次是确保评估的公平性（LLM Judge 评估时英文 vs 英文更公平），第三是便于国际化（英文题目可以被更广泛使用），最后是避免翻译问题（不需要担心中英文翻译的准确性）。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">generate_and_save</span>(<span class="hljs-params">self, num_problems: <span class="hljs-built_in">int</span> = <span class="hljs-number">30</span>, output_dir: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;data_generation/generated_data&quot;</span></span>):<br>    <span class="hljs-string">&quot;&quot;&quot;Generate and save problems with intelligent delay&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># Clean old checkpoints</span><br>    <span class="hljs-keyword">for</span> file <span class="hljs-keyword">in</span> os.listdir(output_dir):<br>        <span class="hljs-keyword">if</span> file.startswith(<span class="hljs-string">&quot;checkpoint_&quot;</span>) <span class="hljs-keyword">and</span> file.endswith(<span class="hljs-string">&quot;.json&quot;</span>):<br>            os.remove(os.path.join(output_dir, file))<br><br>    <span class="hljs-comment"># Generate with tqdm progress bar</span><br>    <span class="hljs-keyword">with</span> tqdm(total=num_problems, desc=<span class="hljs-string">&quot;Generating AIME problems&quot;</span>, unit=<span class="hljs-string">&quot;problem&quot;</span>) <span class="hljs-keyword">as</span> pbar:<br>        last_call_time = <span class="hljs-number">0</span><br><br>        <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(num_problems):<br>            <span class="hljs-comment"># Ensure minimum delay between API calls</span><br>            <span class="hljs-keyword">if</span> last_call_time &gt; <span class="hljs-number">0</span>:<br>                elapsed = time.time() - last_call_time<br>                <span class="hljs-keyword">if</span> elapsed &lt; self.delay_seconds:<br>                    wait_time = self.delay_seconds - elapsed<br>                    time.sleep(wait_time)<br><br>            <span class="hljs-comment"># Generate problem (randomly select reference example)</span><br>            start_time = time.time()<br>            problem = self.generate_single()<br>            last_call_time = time.time()<br>            generation_time = last_call_time - start_time<br><br>            <span class="hljs-comment"># Update progress bar</span><br>            pbar.set_postfix(&#123;<br>                <span class="hljs-string">&quot;topic&quot;</span>: problem.get(<span class="hljs-string">&#x27;topic&#x27;</span>, <span class="hljs-string">&#x27;N/A&#x27;</span>),<br>                <span class="hljs-string">&quot;answer&quot;</span>: problem.get(<span class="hljs-string">&#x27;answer&#x27;</span>, <span class="hljs-string">&#x27;N/A&#x27;</span>),<br>                <span class="hljs-string">&quot;time&quot;</span>: <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;generation_time:<span class="hljs-number">.1</span>f&#125;</span>s&quot;</span><br>            &#125;)<br>            pbar.update(<span class="hljs-number">1</span>)<br><br>    <span class="hljs-keyword">return</span> generated_data_path<br></code></pre></td></tr></table></figure><p>LaTeX 数学公式支持：</p><p>生成的 AIME 题目包含 LaTeX 数学公式（如 <code>$\frac&#123;a&#125;&#123;b&#125;$</code>、<code>$\sqrt&#123;x&#125;$</code>），需要特殊处理 JSON 解析：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_parse_response</span>(<span class="hljs-params">self, response: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;解析LLM响应（支持LaTeX数学公式）&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">import</span> re<br><br>    <span class="hljs-comment"># 提取JSON部分</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;```json&quot;</span> <span class="hljs-keyword">in</span> response:<br>        json_str = response.split(<span class="hljs-string">&quot;```json&quot;</span>)[<span class="hljs-number">1</span>].split(<span class="hljs-string">&quot;```&quot;</span>)[<span class="hljs-number">0</span>].strip()<br>    <span class="hljs-keyword">else</span>:<br>        json_str = response.strip()<br><br>    <span class="hljs-keyword">try</span>:<br>        problem_data = json.loads(json_str)<br>    <span class="hljs-keyword">except</span> json.JSONDecodeError:<br>        <span class="hljs-comment"># 修复LaTeX转义问题：将 \frac 转为 \\frac</span><br>        <span class="hljs-comment"># 正则表达式：找到未转义的反斜杠</span><br>        fixed_json_str = re.sub(<span class="hljs-string">r&#x27;(?&lt;!\\)\\(?![&quot;\\/bfnrtu])&#x27;</span>, <span class="hljs-string">r&#x27;\\\\&#x27;</span>, json_str)<br>        problem_data = json.loads(fixed_json_str)<br><br>    <span class="hljs-keyword">return</span> problem_data<br></code></pre></td></tr></table></figure><p>LaTeX 公式中的反斜杠（如 <code>\frac</code>、<code>\sqrt</code>）在 JSON 中是非法的转义字符，会导致解析失败：</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs pgsql">Invalid \<span class="hljs-keyword">escape</span>: <span class="hljs-type">line</span> <span class="hljs-number">4</span> <span class="hljs-keyword">column</span> <span class="hljs-number">185</span> (<span class="hljs-type">char</span> <span class="hljs-number">375</span>)<br></code></pre></td></tr></table></figure><p>通过正则表达式将未转义的反斜杠替换为双反斜杠，使其在 JSON 中合法。</p><h3 id="12-4-4-LLM-Judge-评估工具"><a href="#12-4-4-LLM-Judge-评估工具" class="headerlink" title="12.4.4 LLM Judge 评估工具"></a>12.4.4 LLM Judge 评估工具</h3><p>LLM Judge 工具使用 LLM 作为评委，对生成的题目进行多维度评估。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">LLMJudgeTool</span>(<span class="hljs-title class_ inherited__">Tool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;LLM Judge评估工具&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, params: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;运行LLM Judge评估&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 1. 加载生成数据</span><br>        gen_dataset = AIDataset(dataset_type=<span class="hljs-string">&quot;generated&quot;</span>, data_path=params[<span class="hljs-string">&quot;generated_data_path&quot;</span>])<br>        gen_problems = gen_dataset.load()<br><br>        <span class="hljs-comment"># 2. 加载参考数据（AIME 2025）</span><br>        ref_dataset = AIDataset(dataset_type=<span class="hljs-string">&quot;real&quot;</span>, year=<span class="hljs-number">2025</span>)<br>        ref_problems = ref_dataset.load()<br><br>        <span class="hljs-comment"># 3. 创建评估器</span><br>        evaluator = LLMJudgeEvaluator(llm=self.llm, judge_model=params.get(<span class="hljs-string">&quot;judge_model&quot;</span>, <span class="hljs-string">&quot;gpt-4o&quot;</span>))<br><br>        <span class="hljs-comment"># 4. 运行评估</span><br>        results = evaluator.evaluate_batch(gen_problems, max_samples=params.get(<span class="hljs-string">&quot;max_samples&quot;</span>))<br><br>        <span class="hljs-comment"># 5. 保存结果</span><br>        evaluator.export_results(results, result_file)<br><br>        <span class="hljs-comment"># 6. 生成报告</span><br>        self._generate_report(results, report_file)<br><br>        <span class="hljs-keyword">return</span> json.dumps(&#123;<span class="hljs-string">&quot;status&quot;</span>: <span class="hljs-string">&quot;success&quot;</span>, <span class="hljs-string">&quot;metrics&quot;</span>: results[<span class="hljs-string">&quot;metrics&quot;</span>]&#125;)<br></code></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></pre></td><td class="code"><pre><code class="hljs python">EVALUATION_PROMPT = <span class="hljs-string">&quot;&quot;&quot;请评估以下AIME数学题目的质量。</span><br><span class="hljs-string"></span><br><span class="hljs-string">题目：</span><br><span class="hljs-string">&#123;problem&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">答案：&#123;answer&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">解答：</span><br><span class="hljs-string">&#123;solution&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">请从以下4个维度评分（1-5分）：</span><br><span class="hljs-string"></span><br><span class="hljs-string">1. &lt;strong&gt;正确性 (Correctness)&lt;/strong&gt;：数学逻辑是否正确，答案是否准确</span><br><span class="hljs-string">2. &lt;strong&gt;清晰度 (Clarity)&lt;/strong&gt;：问题表述是否清晰，解答是否易懂</span><br><span class="hljs-string">3. &lt;strong&gt;难度匹配 (Difficulty Match)&lt;/strong&gt;：难度是否符合AIME标准（中等偏难）</span><br><span class="hljs-string">4. &lt;strong&gt;完整性 (Completeness)&lt;/strong&gt;：解答步骤是否完整，是否包含必要的推理</span><br><span class="hljs-string"></span><br><span class="hljs-string">请按以下JSON格式输出：</span><br><span class="hljs-string">&#123;</span><br><span class="hljs-string">    &quot;correctness&quot;: 5,</span><br><span class="hljs-string">    &quot;clarity&quot;: 4,</span><br><span class="hljs-string">    &quot;difficulty_match&quot;: 4,</span><br><span class="hljs-string">    &quot;completeness&quot;: 5,</span><br><span class="hljs-string">    &quot;comments&quot;: &quot;评价理由&quot;</span><br><span class="hljs-string">&#125;</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p><strong>评估报告示例</strong>：</p><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section"># LLM Judge评估报告</span><br><br><span class="hljs-section">## 总体评分</span><br><br><span class="hljs-bullet">-</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>平均总分<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 4.2/5.0<br><span class="hljs-bullet">-</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>通过率<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 85.0% (≥3.5分)<br><span class="hljs-bullet">-</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>优秀率<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 40.0% (≥4.5分)<br><br><span class="hljs-section">## 各维度评分</span><br><br>| 维度 | 平均分 | 评级 |<br>|------|--------|------|<br>| 正确性 | 4.3/5.0 | 良好 ⭐⭐⭐⭐ |<br>| 清晰度 | 4.1/5.0 | 良好 ⭐⭐⭐⭐ |<br>| 难度匹配 | 4.0/5.0 | 良好 ⭐⭐⭐⭐ |<br>| 完整性 | 4.4/5.0 | 良好 ⭐⭐⭐⭐ |<br></code></pre></td></tr></table></figure><h3 id="12-4-5-Win-Rate-评估工具"><a href="#12-4-5-Win-Rate-评估工具" class="headerlink" title="12.4.5 Win Rate 评估工具"></a>12.4.5 Win Rate 评估工具</h3><p>Win Rate 工具通过成对对比评估生成数据相对于真题的质量。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">WinRateTool</span>(<span class="hljs-title class_ inherited__">Tool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;Win Rate评估工具&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, params: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;运行Win Rate评估&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 1. 加载生成数据</span><br>        gen_dataset = AIDataset(dataset_type=<span class="hljs-string">&quot;generated&quot;</span>, data_path=params[<span class="hljs-string">&quot;generated_data_path&quot;</span>])<br>        gen_problems = gen_dataset.load()<br><br>        <span class="hljs-comment"># 2. 加载参考数据（AIME 2025）</span><br>        ref_dataset = AIDataset(dataset_type=<span class="hljs-string">&quot;real&quot;</span>, year=<span class="hljs-number">2025</span>)<br>        ref_problems = ref_dataset.load()<br><br>        <span class="hljs-comment"># 3. 创建评估器</span><br>        evaluator = WinRateEvaluator(llm=self.llm, judge_model=params.get(<span class="hljs-string">&quot;judge_model&quot;</span>, <span class="hljs-string">&quot;gpt-4o&quot;</span>))<br><br>        <span class="hljs-comment"># 4. 运行评估</span><br>        results = evaluator.evaluate_win_rate(gen_problems, ref_problems, num_comparisons=params.get(<span class="hljs-string">&quot;num_comparisons&quot;</span>))<br><br>        <span class="hljs-comment"># 5. 保存结果和报告</span><br>        evaluator.export_results(results, result_file)<br>        self._generate_report(results, report_file)<br><br>        <span class="hljs-keyword">return</span> json.dumps(&#123;<span class="hljs-string">&quot;status&quot;</span>: <span class="hljs-string">&quot;success&quot;</span>, <span class="hljs-string">&quot;metrics&quot;</span>: results[<span class="hljs-string">&quot;metrics&quot;</span>]&#125;)<br></code></pre></td></tr></table></figure><p>AIDataset 负责加载生成数据和 AIME 真题数据，支持两种数据类型：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">AIDataset</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;AI数据集加载器</span><br><span class="hljs-string"></span><br><span class="hljs-string">    支持两种数据类型：</span><br><span class="hljs-string">    1. generated: 生成的数据（JSON格式）</span><br><span class="hljs-string">    2. real: AIME真题（从HuggingFace加载）</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        dataset_type: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;generated&quot;</span>,</span><br><span class="hljs-params">        data_path: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        year: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">int</span>] = <span class="hljs-literal">None</span></span><br><span class="hljs-params">    </span>):<br>        self.dataset_type = dataset_type<br>        self.data_path = data_path<br>        self.year = year  <span class="hljs-comment"># 仅用于real类型，默认2025</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">load</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]]:<br>        <span class="hljs-string">&quot;&quot;&quot;加载数据集&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> self.dataset_type == <span class="hljs-string">&quot;generated&quot;</span>:<br>            <span class="hljs-keyword">return</span> self._load_generated_data()<br>        <span class="hljs-keyword">elif</span> self.dataset_type == <span class="hljs-string">&quot;real&quot;</span>:<br>            <span class="hljs-keyword">return</span> self._load_real_data()<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_load_real_data</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]]:<br>        <span class="hljs-string">&quot;&quot;&quot;从HuggingFace加载AIME 2025真题&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">from</span> huggingface_hub <span class="hljs-keyword">import</span> snapshot_download<br><br>        <span class="hljs-comment"># 使用AIME 2025数据集</span><br>        repo_id = <span class="hljs-string">&quot;math-ai/aime25&quot;</span><br><br>        <span class="hljs-comment"># 下载数据集</span><br>        local_dir = snapshot_download(<br>            repo_id=repo_id,<br>            repo_type=<span class="hljs-string">&quot;dataset&quot;</span><br>        )<br><br>        <span class="hljs-comment"># 读取JSONL文件</span><br>        data_file = <span class="hljs-built_in">list</span>(Path(local_dir).glob(<span class="hljs-string">&quot;*.jsonl&quot;</span>))[<span class="hljs-number">0</span>]<br>        data = []<br>        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(data_file, <span class="hljs-string">&#x27;r&#x27;</span>, encoding=<span class="hljs-string">&#x27;utf-8&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>            <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> f:<br>                <span class="hljs-keyword">if</span> line.strip():<br>                    data.append(json.loads(line))<br><br>        <span class="hljs-comment"># 统一数据格式（AIME 2025使用小写字段名）</span><br>        problems = []<br>        <span class="hljs-keyword">for</span> idx, item <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(data):<br>            problem = &#123;<br>                <span class="hljs-string">&quot;problem_id&quot;</span>: item.get(<span class="hljs-string">&quot;id&quot;</span>, <span class="hljs-string">f&quot;aime_2025_<span class="hljs-subst">&#123;idx&#125;</span>&quot;</span>),<br>                <span class="hljs-string">&quot;problem&quot;</span>: item.get(<span class="hljs-string">&quot;problem&quot;</span>, <span class="hljs-string">&quot;&quot;</span>),<br>                <span class="hljs-string">&quot;answer&quot;</span>: item.get(<span class="hljs-string">&quot;answer&quot;</span>, <span class="hljs-string">&quot;&quot;</span>),<br>                <span class="hljs-string">&quot;solution&quot;</span>: item.get(<span class="hljs-string">&quot;solution&quot;</span>, <span class="hljs-string">&quot;&quot;</span>),  <span class="hljs-comment"># AIME 2025没有solution字段</span><br>            &#125;<br>            problems.append(problem)<br><br>        <span class="hljs-keyword">return</span> problems<br></code></pre></td></tr></table></figure><p>我们选择只使用 AIME 2025 数据集有四个原因：首先是数据的时效性（2025 年是最新的 AIME 竞赛数据），其次是简化维护（只维护一个数据集，代码更简洁），第三是格式统一（JSONL 格式，字段名统一为小写），最后是代表性充分（30 道题目足以评估生成质量）。</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><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><code class="hljs python">COMPARISON_PROMPT = <span class="hljs-string">&quot;&quot;&quot;请比较以下两个AIME数学题目的质量，判断哪个更好。</span><br><span class="hljs-string"></span><br><span class="hljs-string">【题目A - 生成题目】</span><br><span class="hljs-string">问题：&#123;problem_a&#125;</span><br><span class="hljs-string">答案：&#123;answer_a&#125;</span><br><span class="hljs-string">解答：&#123;solution_a&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">【题目B - AIME真题】</span><br><span class="hljs-string">问题：&#123;problem_b&#125;</span><br><span class="hljs-string">答案：&#123;answer_b&#125;</span><br><span class="hljs-string">解答：&#123;solution_b&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">请从以下方面比较：</span><br><span class="hljs-string">1. 数学逻辑的严谨性</span><br><span class="hljs-string">2. 问题表述的清晰度</span><br><span class="hljs-string">3. 难度的合理性</span><br><span class="hljs-string">4. 解答的完整性</span><br><span class="hljs-string"></span><br><span class="hljs-string">请按以下JSON格式输出：</span><br><span class="hljs-string">&#123;</span><br><span class="hljs-string">    &quot;winner&quot;: &quot;A&quot; 或 &quot;B&quot; 或 &quot;Tie&quot;,</span><br><span class="hljs-string">    &quot;reason&quot;: &quot;判断理由&quot;</span><br><span class="hljs-string">&#125;</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p><strong>评估报告示例</strong>：</p><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section"># Win Rate评估报告</span><br><br><span class="hljs-section">## 胜率统计</span><br><br>| 指标 | 数值 | 百分比 |<br>|------|------|--------|<br>| 生成数据胜出 | 9次 | 45.0% |<br>| AIME真题胜出 | 8次 | 40.0% |<br>| 平局 | 3次 | 15.0% |<br><br><span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>Win Rate<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 45.0%<br><br>✅ <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>良好<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 生成数据质量接近参考数据（差距&lt;10%）。<br></code></pre></td></tr></table></figure><h3 id="12-4-6-人工验证界面"><a href="#12-4-6-人工验证界面" class="headerlink" title="12.4.6 人工验证界面"></a>12.4.6 人工验证界面</h3><p>使用 Gradio 创建 Web 界面，支持人工验证生成的题目。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">HumanVerificationUI</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;人工验证界面&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">launch</span>(<span class="hljs-params">self, share: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">False</span></span>):<br>        <span class="hljs-string">&quot;&quot;&quot;启动Gradio界面&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">with</span> gr.Blocks(title=<span class="hljs-string">&quot;AIME题目人工验证&quot;</span>) <span class="hljs-keyword">as</span> demo:<br>            gr.Markdown(<span class="hljs-string">&quot;# 🎯 AIME题目人工验证系统&quot;</span>)<br><br>            <span class="hljs-keyword">with</span> gr.Row():<br>                <span class="hljs-keyword">with</span> gr.Column(scale=<span class="hljs-number">2</span>):<br>                    <span class="hljs-comment"># 题目显示区域</span><br>                    problem_text = gr.Textbox(label=<span class="hljs-string">&quot;问题描述&quot;</span>, lines=<span class="hljs-number">5</span>, interactive=<span class="hljs-literal">False</span>)<br>                    answer_text = gr.Textbox(label=<span class="hljs-string">&quot;答案&quot;</span>, interactive=<span class="hljs-literal">False</span>)<br>                    solution_text = gr.Textbox(label=<span class="hljs-string">&quot;解答过程&quot;</span>, lines=<span class="hljs-number">10</span>, interactive=<span class="hljs-literal">False</span>)<br><br>                <span class="hljs-keyword">with</span> gr.Column(scale=<span class="hljs-number">1</span>):<br>                    <span class="hljs-comment"># 评分区域</span><br>                    correctness_slider = gr.Slider(<span class="hljs-number">1</span>, <span class="hljs-number">5</span>, value=<span class="hljs-number">3</span>, step=<span class="hljs-number">1</span>, label=<span class="hljs-string">&quot;正确性&quot;</span>)<br>                    clarity_slider = gr.Slider(<span class="hljs-number">1</span>, <span class="hljs-number">5</span>, value=<span class="hljs-number">3</span>, step=<span class="hljs-number">1</span>, label=<span class="hljs-string">&quot;清晰度&quot;</span>)<br>                    difficulty_slider = gr.Slider(<span class="hljs-number">1</span>, <span class="hljs-number">5</span>, value=<span class="hljs-number">3</span>, step=<span class="hljs-number">1</span>, label=<span class="hljs-string">&quot;难度匹配&quot;</span>)<br>                    completeness_slider = gr.Slider(<span class="hljs-number">1</span>, <span class="hljs-number">5</span>, value=<span class="hljs-number">3</span>, step=<span class="hljs-number">1</span>, label=<span class="hljs-string">&quot;完整性&quot;</span>)<br><br>                    <span class="hljs-comment"># 状态选择</span><br>                    status_radio = gr.Radio(<br>                        choices=[<span class="hljs-string">&quot;approved&quot;</span>, <span class="hljs-string">&quot;rejected&quot;</span>, <span class="hljs-string">&quot;needs_revision&quot;</span>],<br>                        value=<span class="hljs-string">&quot;approved&quot;</span>,<br>                        label=<span class="hljs-string">&quot;状态&quot;</span><br>                    )<br><br>                    <span class="hljs-comment"># 验证按钮</span><br>                    verify_btn = gr.Button(<span class="hljs-string">&quot;✅ 提交验证&quot;</span>, variant=<span class="hljs-string">&quot;primary&quot;</span>)<br><br>            demo.launch(share=share, server_name=<span class="hljs-string">&quot;127.0.0.1&quot;</span>, server_port=<span class="hljs-number">7860</span>)<br></code></pre></td></tr></table></figure><p><strong>使用方法</strong>：</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><code class="hljs bash"><span class="hljs-comment"># 启动人工验证界面</span><br>python data_generation/human_verification_ui.py data_generation/generated_data/aime_generated_XXXXXX.json<br><br><span class="hljs-comment"># 打开浏览器访问</span><br>http://127.0.0.1:7860<br></code></pre></td></tr></table></figure><p>最终效果可以参考图 12.7 所示，对于题目的正确性，最好人工打标 Review：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/12-figures/12-7.png" alt="" width="85%"/>  <p>图 12.7 AIME 试题人工验证页面</p></div><p><strong>验证流程</strong>：</p><ol><li>浏览器打开验证界面</li><li>阅读题目、答案、解答</li><li>从 4 个维度评分（1-5 分）</li><li>选择验证状态（approved&#x2F;rejected&#x2F;needs_revision）</li><li>添加评论（可选）</li><li>点击”提交验证”</li><li>查看下一题</li></ol><p><strong>验证结果保存</strong>：</p><p>验证结果自动保存为 <code>&lt;data_path&gt;_verifications.json</code>：</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;gen_aime_1&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;problem_id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;gen_aime_1&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;scores&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>      <span class="hljs-attr">&quot;correctness&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">5</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;clarity&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">4</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;difficulty_match&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">4</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;completeness&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">5</span><br>    <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;total_score&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">4.5</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;status&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;approved&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;comments&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;题目质量很好，逻辑严谨&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;verified_at&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-01-10T12:00:00&quot;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><h3 id="12-4-7-完整评估流程"><a href="#12-4-7-完整评估流程" class="headerlink" title="12.4.7 完整评估流程"></a>12.4.7 完整评估流程</h3><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">run_complete_evaluation</span>(<span class="hljs-params"></span><br><span class="hljs-params">    num_problems: <span class="hljs-built_in">int</span> = <span class="hljs-number">30</span>,</span><br><span class="hljs-params">    delay_seconds: <span class="hljs-built_in">float</span> = <span class="hljs-number">3.0</span></span><br><span class="hljs-params"></span>):<br>    <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">    运行完整评估流程</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        num_problems: 生成题目数量</span><br><span class="hljs-string">        delay_seconds: 每次生成之间的延迟（秒），避免API速率限制</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 步骤1: 生成AIME题目</span><br>    generator = AIMEGenerator(delay_seconds=delay_seconds)<br>    generated_data_path = generator.generate_and_save(<br>        num_problems=num_problems,<br>        output_dir=<span class="hljs-string">&quot;data_generation/generated_data&quot;</span><br>    )<br><br>    <span class="hljs-comment"># 步骤2: 评估</span><br>    <span class="hljs-comment"># 创建评估结果目录</span><br>    timestamp = datetime.now().strftime(<span class="hljs-string">&quot;%Y%m%d_%H%M%S&quot;</span>)<br>    evaluation_dir = <span class="hljs-string">f&quot;data_generation/evaluation_results/<span class="hljs-subst">&#123;timestamp&#125;</span>&quot;</span><br>    os.makedirs(evaluation_dir, exist_ok=<span class="hljs-literal">True</span>)<br>    os.makedirs(os.path.join(evaluation_dir, <span class="hljs-string">&quot;llm_judge&quot;</span>), exist_ok=<span class="hljs-literal">True</span>)<br>    os.makedirs(os.path.join(evaluation_dir, <span class="hljs-string">&quot;win_rate&quot;</span>), exist_ok=<span class="hljs-literal">True</span>)<br><br>    <span class="hljs-comment"># 创建LLM</span><br>    llm = HelloAgentsLLM()<br><br>    <span class="hljs-comment"># 步骤2.1: LLM Judge评估</span><br>    llm_judge_result = <span class="hljs-literal">None</span><br>    <span class="hljs-keyword">try</span>:<br>        llm_judge_tool = LLMJudgeTool(llm=llm)<br>        llm_judge_result_json = llm_judge_tool.run(&#123;<br>            <span class="hljs-string">&quot;generated_data_path&quot;</span>: generated_data_path,<br>            <span class="hljs-string">&quot;reference_year&quot;</span>: <span class="hljs-number">2025</span>,<br>            <span class="hljs-string">&quot;max_samples&quot;</span>: num_problems,<br>            <span class="hljs-string">&quot;output_dir&quot;</span>: os.path.join(evaluation_dir, <span class="hljs-string">&quot;llm_judge&quot;</span>),<br>            <span class="hljs-string">&quot;judge_model&quot;</span>: <span class="hljs-string">&quot;gpt-4o&quot;</span><br>        &#125;)<br>        llm_judge_result = json.loads(llm_judge_result_json)<br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;❌ LLM Judge评估失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 步骤2.2: Win Rate评估</span><br>    win_rate_result = <span class="hljs-literal">None</span><br>    <span class="hljs-keyword">try</span>:<br>        win_rate_tool = WinRateTool(llm=llm)<br>        win_rate_result_json = win_rate_tool.run(&#123;<br>            <span class="hljs-string">&quot;generated_data_path&quot;</span>: generated_data_path,<br>            <span class="hljs-string">&quot;reference_year&quot;</span>: <span class="hljs-number">2025</span>,<br>            <span class="hljs-string">&quot;num_comparisons&quot;</span>: <span class="hljs-built_in">min</span>(num_problems, <span class="hljs-number">20</span>),<br>            <span class="hljs-string">&quot;output_dir&quot;</span>: os.path.join(evaluation_dir, <span class="hljs-string">&quot;win_rate&quot;</span>),<br>            <span class="hljs-string">&quot;judge_model&quot;</span>: <span class="hljs-string">&quot;gpt-4o&quot;</span><br>        &#125;)<br>        win_rate_result = json.loads(win_rate_result_json)<br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;❌ Win Rate评估失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 步骤3: 生成综合报告</span><br>    comprehensive_report_path = <span class="hljs-literal">None</span><br>    <span class="hljs-keyword">if</span> llm_judge_result <span class="hljs-keyword">or</span> win_rate_result:<br>        comprehensive_report_path = os.path.join(evaluation_dir, <span class="hljs-string">&quot;comprehensive_report.md&quot;</span>)<br>        report = generate_comprehensive_report(<br>            generated_data_path,<br>            llm_judge_result,<br>            win_rate_result<br>        )<br>        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(comprehensive_report_path, <span class="hljs-string">&#x27;w&#x27;</span>, encoding=<span class="hljs-string">&#x27;utf-8&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>            f.write(report)<br><br>    <span class="hljs-keyword">return</span> &#123;<br>        <span class="hljs-string">&quot;generated_data_path&quot;</span>: generated_data_path,<br>        <span class="hljs-string">&quot;llm_judge_result&quot;</span>: llm_judge_result,<br>        <span class="hljs-string">&quot;win_rate_result&quot;</span>: win_rate_result,<br>        <span class="hljs-string">&quot;comprehensive_report_path&quot;</span>: comprehensive_report_path<br>    &#125;<br></code></pre></td></tr></table></figure><p><strong>运行方法</strong>：</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><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><code class="hljs bash"><span class="hljs-comment"># 基本用法（默认3秒延迟）</span><br>python data_generation/run_complete_evaluation.py 30<br><br><span class="hljs-comment"># 自定义延迟（推荐3-5秒，避免API速率限制）</span><br>python data_generation/run_complete_evaluation.py 30 3.0<br><br><span class="hljs-comment"># 参数说明：</span><br><span class="hljs-comment"># - 30: 生成题目数量</span><br><span class="hljs-comment"># - 3.0: 每次生成之间的延迟（秒）</span><br><br><span class="hljs-comment"># 说明：</span><br><span class="hljs-comment"># - 生成阶段：从900+道AIME真题（1983-2025）中随机选择参考样例</span><br><span class="hljs-comment"># - 评估阶段：与AIME 2025年真题进行质量对比</span><br><span class="hljs-comment"># - 数据集来源：math-ai/aime25（JSONL格式）</span><br></code></pre></td></tr></table></figure><p><strong>输出示例</strong>：</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><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></pre></td><td class="code"><pre><code class="hljs asciidoc">================================================================================<br>🚀 AIME数据生成与评估完整流程<br>================================================================================<br><br>配置信息:<br><span class="hljs-code">  - 生成题目数量: 30</span><br><span class="hljs-code">  - API延迟: 3.0秒/题</span><br><span class="hljs-code">  - 生成参考数据: TianHongZXY/aime-1983-2025（900+道题）</span><br><span class="hljs-code">  - 评估参考: AIME 2025真题</span><br><br>================================================================================<br>📝 步骤1: 生成AIME题目<br>================================================================================<br>📚 加载AIME真题数据集: TianHongZXY/aime-1983-2025<br><span class="hljs-code">   ✓ 已加载 963 道参考题目</span><br><br>🎯 开始生成AIME题目<br><span class="hljs-code">   目标数量: 30</span><br><span class="hljs-code">   生成模型: gpt-4o</span><br><span class="hljs-code">   延迟设置: 3.0秒/题</span><br><br>生成AIME题目:  100%|██████████| 30/30 [01:30&lt;00:00, 3.00s/题, 主题=Algebra, 答案=123, 耗时=3.0s]<br><br>✅ 步骤1完成！生成数据保存在: data_generation/generated_data/aime_generated_20250110_120000.json<br><br>🎯 步骤2.1: LLM Judge评估 (vs AIME 2025)<br><br>✅ LLM Judge评估完成！<br><span class="hljs-code">   平均总分: 4.2/5.0</span><br><span class="hljs-code">   通过率: 85.0%</span><br><br>🏆 步骤2.2: Win Rate评估 (vs AIME 2025)<br><br>✅ Win Rate评估完成！<br><span class="hljs-code">   Win Rate: 45.0%</span><br><br>================================================================================<br>📊 步骤3: 生成综合报告<br>================================================================================<br><br>✅ 综合报告已保存: data_generation/evaluation_results/20250110_120000/comprehensive_report.md<br><br>================================================================================<br>🎉 完整评估流程完成！<br>================================================================================<br><br>📁 输出文件:<br><span class="hljs-code">   - 生成数据: data_generation/generated_data/aime_generated_20250110_120000.json</span><br><span class="hljs-code">   - 评估结果目录: data_generation/evaluation_results/20250110_120000</span><br><span class="hljs-code">   - LLM Judge报告: data_generation/evaluation_results/20250110_120000/llm_judge/llm_judge_report_20250110_120000.md</span><br><span class="hljs-code">   - Win Rate报告: data_generation/evaluation_results/20250110_120000/win_rate/win_rate_report_20250110_120000.md</span><br><span class="hljs-code">   - 综合报告: data_generation/evaluation_results/20250110_120000/comprehensive_report.md</span><br><br>💡 下一步:<br><span class="hljs-code">   1. 查看综合报告: data_generation/evaluation_results/20250110_120000/comprehensive_report.md</span><br><span class="hljs-code">   2. 运行人工验证: python data_generation/human_verification_ui.py data_generation/generated_data/aime_generated_20250110_120000.json</span><br></code></pre></td></tr></table></figure><h3 id="12-4-8-综合评估报告"><a href="#12-4-8-综合评估报告" class="headerlink" title="12.4.8 综合评估报告"></a>12.4.8 综合评估报告</h3><p>系统自动生成综合评估报告，汇总所有评估结果。以下是示例报告：</p><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><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></pre></td><td class="code"><pre><code class="hljs markdown"><span class="hljs-section"># AIME数据生成与评估综合报告</span><br><br><span class="hljs-section">## 1. 基本信息</span><br><br><span class="hljs-bullet">-</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>生成时间<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 2025-01-10 12:00:00<br><span class="hljs-bullet">-</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>生成题目数量<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 30<br><span class="hljs-bullet">-</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>参考AIME年份<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 2025<br><br><span class="hljs-section">## 2. 数据生成统计</span><br><br><span class="hljs-section">### 主题分布</span><br><br>| 主题 | 数量 | 占比 |<br>|------|------|------|<br>| 代数 | 10 | 33.3% |<br>| 几何 | 8 | 26.7% |<br>| 数论 | 7 | 23.3% |<br>| 组合 | 3 | 10.0% |<br>| 概率 | 2 | 6.7% |<br><br><span class="hljs-section">## 3. LLM Judge评估结果</span><br><br><span class="hljs-section">### 总体评分</span><br><br><span class="hljs-bullet">-</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>平均总分<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 4.2/5.0<br><span class="hljs-bullet">-</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>通过率<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 85.0% (≥3.5分)<br><span class="hljs-bullet">-</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>优秀率<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 40.0% (≥4.5分)<br><br><span class="hljs-section">### 各维度评分</span><br><br>| 维度 | 平均分 | 评级 |<br>|------|--------|------|<br>| 正确性 | 4.3/5.0 | 良好 ⭐⭐⭐⭐ |<br>| 清晰度 | 4.1/5.0 | 良好 ⭐⭐⭐⭐ |<br>| 难度匹配 | 4.0/5.0 | 良好 ⭐⭐⭐⭐ |<br>| 完整性 | 4.4/5.0 | 良好 ⭐⭐⭐⭐ |<br><br><span class="hljs-section">## 4. Win Rate评估结果</span><br><br><span class="hljs-section">### 胜率统计</span><br><br>| 指标 | 数值 | 百分比 |<br>|------|------|--------|<br>| 生成数据胜出 | 9次 | 45.0% |<br>| AIME真题胜出 | 8次 | 40.0% |<br>| 平局 | 3次 | 15.0% |<br><br><span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>Win Rate<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 45.0%<br><br>✅ <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>良好<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 生成数据质量接近参考数据（差距&lt;10%）。<br><br><span class="hljs-section">## 5. 综合结论</span><br><br>基于LLM Judge和Win Rate两种评估方法的结果：<br><br><span class="hljs-bullet">1.</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>LLM Judge评估<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 生成数据的平均质量为 <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>4.2/5.0<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span><br><span class="hljs-bullet">2.</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>Win Rate评估<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 生成数据相对于AIME 2025真题的胜率为 <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>45.0%<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span><br><br>✅ <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>结论<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 生成数据质量<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>优秀<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>，达到或超过AIME真题水平。可以用于实际应用。<br><br><span class="hljs-section">## 6. 改进建议</span><br><br><span class="hljs-bullet">-</span> ✅ 继续保持当前的生成策略<br><span class="hljs-bullet">-</span> ✅ 可以考虑增加生成数量<br><span class="hljs-bullet">-</span> ✅ 建议进行人工验证以确保质量<br><br><span class="hljs-section">## 7. 下一步行动</span><br><br><span class="hljs-bullet">1.</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>人工验证<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 运行 <span class="hljs-code">`python data_generation/human_verification_ui.py &lt;data_path&gt;`</span> 进行人工验证<br><span class="hljs-bullet">2.</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>查看详细结果<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>:<br><span class="hljs-bullet">   -</span> LLM Judge详细报告<br><span class="hljs-bullet">   -</span> Win Rate详细报告<br><span class="hljs-bullet">3.</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span></span>数据使用<span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span></span>: 如果质量满意，可以将生成的数据用于训练或测试<br></code></pre></td></tr></table></figure><p>基于实际使用经验，总结以下内容：</p><p>在数据生成方面，应该使用合适的延迟时间（2-3 秒）避免 API 速率限制，启用检查点保存以避免中断损失，先小批量测试（10 个）确认无问题后再大批量生成，并定期检查生成质量及时调整提示词。在评估策略上，建议结合 LLM Judge 和 Win Rate 两种方法，其中 LLM Judge 用于绝对质量评估，Win Rate 用于相对质量对比，人工验证用于最终质量把关。质量标准方面，建议 LLM Judge 平均分达到 4.0&#x2F;5.0 以上，Win Rate 达到 45%以上（接近 50%），通过率达到 80%以上，人工验证通过率达到 90%以上。在迭代优化过程中，应根据评估结果调整生成提示词，分析低分题目的共同问题，参考高分题目的优点，持续改进生成策略。</p><p>通过本节的学习，我们掌握了如何使用 HelloAgents 框架进行数据生成质量评估，包括 LLM Judge 评估、Win Rate 评估和人工验证三种方法。这套完整的评估体系可以确保生成数据的高质量，为 AI 系统的训练和测试提供可靠的数据支持。</p><p>对于 LLM Judge 和 Win Rate 评估，HelloAgents 也进行了工具集成，并提供了完整的示例代码。如果你对这两种评估方法的具体实现细节感兴趣，同样可以参考示例代码。</p><h2 id="12-5-本章小结"><a href="#12-5-本章小结" class="headerlink" title="12.5 本章小结"></a>12.5 本章小结</h2><p>在本章中，我们为 HelloAgents 框架构建了一个完整的性能评估系统。让我们回顾一下学到的核心内容：</p><p><strong>（1）评估体系概览</strong></p><p>我们建立了一个三层评估体系，全面覆盖智能体的不同能力维度。首先是工具调用能力评估（BFCL），专注于评估智能体的函数调用准确性，包含 simple、multiple、parallel、irrelevance 四个类别，使用 AST 匹配技术进行精确评估。其次是通用能力评估（GAIA），评估智能体的综合问题解决能力，包含三个难度级别共 466 个真实世界问题，关注多步推理、工具使用、文件处理等能力。第三是数据生成质量评估（AIME），评估 LLM 生成数据的质量，使用 LLM Judge 和 Win Rate 两种方法，支持人工验证和综合报告生成，确保生成数据达到参考数据的质量标准。</p><p><strong>（2）核心技术要点</strong></p><p>在技术实现上，我们采用了六个核心技术要点。首先是模块化设计，评估系统采用三层架构：数据层（Dataset 负责数据加载和管理）、评估层（Evaluator 负责执行评估流程）和指标层（Metrics 负责计算各种评估指标）。其次是工具化封装，所有评估功能都封装成 Tool，可以被智能体直接调用、集成到工作流中或通过统一接口使用。第三是 AST 匹配技术，使用抽象语法树匹配函数调用，比简单字符串匹配更智能，能够忽略参数顺序、识别等价表达式和忽略格式差异。第四是多模态支持，GAIA 评估支持文本问题、附件文件和图片输入等多模态数据。第五是 LLM Judge 评估，使用 LLM 作为评委评估生成数据质量，提供多维度评分（正确性、清晰度、难度匹配、完整性）、自动化评估流程、详细评估报告，并支持自定义评估维度和标准。第六是 Win Rate 对比评估，通过成对对比评估生成质量（生成数据 vs 参考数据），由 LLM 判断哪个更好并计算胜率统计，接近 50%表示质量相当。</p><p><strong>（3）扩展方向</strong></p><p>基于本章的评估系统，你可以在四个方向上进行扩展。首先是添加新的评估基准，可以参考 BFCL 和 GAIA 的实现模式，实现 Dataset、Evaluator、Metrics 三个组件，并封装成 Tool 供使用。其次是自定义评估指标，在 Metrics 类中添加新的指标计算方法，根据具体应用场景设计指标。第三是集成到 CI&#x2F;CD 流程，在代码提交时自动运行评估，设置性能阈值防止性能退化，生成评估报告并归档。第四是扩展数据生成评估，支持更多数据类型（代码、对话、文档等），添加更多评估维度（创新性、多样性等），集成更多参考数据集，支持多模型对比评估。</p><p><strong>恭喜你完成了第十二章的学习！</strong> 🎉</p><p>评估是智能体开发的重要环节，它让我们能够：</p><ul><li>客观衡量智能体的能力</li><li>发现和修复问题</li><li>持续改进系统</li></ul><p>在下一章中，我们将探讨如何将 HelloAgents 框架应用于实际项目中。</p><p><strong>继续加油！</strong> 💪</p><h2 id="习题"><a href="#习题" class="headerlink" title="习题"></a>习题</h2><blockquote><p><strong>提示</strong>：部分习题没有标准答案，重点在于培养学习者对智能体性能评估的综合理解和实践能力。</p></blockquote><ol><li><p>本章介绍了多个智能体评估基准。请分析：</p><ul><li>在 12.1.2 节中介绍了 BFCL、GAIA、AgentBench 等评估基准。请对比 BFCL 和 GAIA：它们分别评估智能体的哪些核心能力？为什么 BFCL 使用 AST 匹配算法，而 GAIA 使用准精确匹配（Quasi Exact Match）？这两种评估方法各有什么优缺点？</li><li>假设你要构建一个”智能客服系统”，需要评估以下能力：（1）理解用户意图的准确性；（2）调用后台 API 的正确性；（3）回答的友好性和专业性；（4）处理异常情况的鲁棒性。请为每个能力选择或设计合适的评估指标和方法。</li><li>在 12.1.1 节中提到，智能体评估面临”输出不确定性”、”评估标准多样性”、”评估成本高昂”三大挑战。请针对每个挑战提出具体的解决方案，并分析方案的可行性和局限性。</li></ul></li><li><p>BFCL（Berkeley Function Calling Leaderboard）是评估工具调用能力的重要基准。基于 12.2 节的内容，请深入思考：</p><blockquote><p><strong>提示</strong>：这是一道动手实践题，建议实际操作</p></blockquote><ul><li>在 12.2.3 节的 AST 匹配算法中，我们通过比较抽象语法树来判断函数调用是否正确。请分析：为什么 AST 匹配比简单的字符串匹配更合适？在什么情况下 AST 匹配可能会产生误判（假阳性或假阴性）？如何改进 AST 匹配算法来提高准确性？</li><li>BFCL 数据集包含 simple、multiple、parallel、irrelevance 四个类别。请为每个类别设计 2-3 个新的测试样本，要求能够测试智能体在该类别下的边界情况或容易出错的场景。</li><li>请基于 12.2.4 节的代码，扩展 BFCL 评估器，添加以下功能：（1）支持评估工具调用的执行顺序（对于有依赖关系的多个工具调用）；（2）评估工具调用的效率（如是否使用了最少的调用次数）；（3）生成详细的错误分析报告（如哪些类型的错误最常见）。</li></ul></li><li><p>GAIA（General AI Assistants）评估智能体的综合能力。基于 12.3 节的内容，请完成以下扩展实践：</p><blockquote><p><strong>提示</strong>：这是一道动手实践题，建议实际操作</p></blockquote><ul><li>在 12.3.2 节中介绍了 GAIA 的三个难度级别（Level 1&#x2F;2&#x2F;3）。请分析：这三个级别在任务复杂度、所需能力、评估标准等方面有什么差异？如果要设计 Level 4（超高难度），应该包含什么类型的任务？</li><li>GAIA 使用”准精确匹配”算法来评估答案的正确性。请分析：这种方法如何处理答案的多样性（如”42”、”四十二”、”42.0”都应该被认为是正确的）？在什么情况下准精确匹配可能不够用？请设计一个更智能的答案匹配算法，能够处理语义等价的答案。</li><li>请基于 12.3.4 节的代码，实现一个”自定义 GAIA 评估集”：选择一个特定领域（如医疗、法律、金融），设计 10 个真实世界问题，并实现完整的评估流程。要求问题涵盖不同难度级别，并提供标准答案和评分标准。</li></ul></li><li><p>LLM Judge 是使用大语言模型进行评估的新兴方法。基于 12.4 节的内容，请深入分析：</p><ul><li>在 12.4.2 节中，我们使用 GPT-4 作为评判者来评估智能体的回答质量。请分析：LLM Judge 相比传统的规则匹配或指标计算有什么优势？它存在哪些潜在的偏见或局限性（如对某些回答风格的偏好、对长度的敏感性）？</li><li>LLM Judge 的评分标准设计至关重要。请为以下三个不同的评估场景设计详细的评分标准（包括评分维度、权重、示例）：（1）代码生成质量评估；（2）创意写作质量评估；（3）技术文档质量评估。</li><li>在 12.4.3 节中提到，可以使用多个 LLM Judge 进行”评审团”式评估。请设计一个”多评委评估系统”：使用 3-5 个不同的 LLM（如 GPT-4、Claude、Qwen）作为评委，如何聚合它们的评分？如何处理评委之间的分歧？如何检测和过滤异常评分？</li></ul></li><li><p>智能体评估的实践应用需要考虑多个方面。请思考：</p><ul><li>在实际项目中，评估往往需要在”评估成本”和”评估质量”之间权衡。请设计一个”分层评估策略”：（1）快速评估（低成本，用于日常开发迭代）；（2）标准评估（中等成本，用于版本发布前）；（3）全面评估（高成本，用于重大更新或对外发布）。每层应该包含哪些评估项目？如何设计评估流程？</li><li>智能体的性能可能随时间变化（如依赖的外部 API 变化、用户需求变化）。请设计一个”持续评估系统”：能够定期自动运行评估，监控智能体性能的变化趋势，并在性能下降时及时告警。这个系统应该包含哪些组件？如何设计告警规则？</li><li>评估结果需要以清晰的方式呈现给不同的受众（如开发者、产品经理、用户）。请设计一个”评估报告生成系统”：能够根据受众类型自动生成不同详细程度的报告。开发者报告应该包含哪些技术细节？产品经理报告应该突出哪些业务指标？用户报告应该如何简化和可视化？</li></ul></li></ol><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>[1] Patil, S. G., Zhang, T., Wang, X., &amp; Gonzalez, J. E. (2023). Gorilla: Large Language Model Connected with Massive APIs. arXiv preprint arXiv:2305.15334.</p><p>[2] Qin, Y., Liang, S., Ye, Y., Zhu, K., Yan, L., Lu, Y., … &amp; Sun, M. (2023). ToolLLM: Facilitating Large Language Models to Master 16000+ Real-world APIs. arXiv preprint arXiv:2307.16789.</p><p>[3] Li, M., Zhao, Y., Yu, B., Song, F., Li, H., Yu, H., … &amp; Li, Y. (2023). Api-bank: A comprehensive benchmark for tool-augmented llms. arXiv preprint arXiv:2304.08244.</p><p>[4] Mialon, G., Dessì, R., Lomeli, M., Nalmpantis, C., Pasunuru, R., Raileanu, R., … &amp; Scialom, T. (2023). GAIA: a benchmark for General AI Assistants. arXiv preprint arXiv:2311.12983.</p><p>[5] Liu, X., Yu, H., Zhang, H., Xu, Y., Lei, X., Lai, H., … &amp; Zhang, D. (2023). AgentBench: Evaluating LLMs as Agents. arXiv preprint arXiv:2308.03688.</p><p>[6] Zhou, S., Xu, F. F., Zhu, H., Zhou, X., Lo, R., Sridhar, A., … &amp; Neubig, G. (2023). WebArena: A Realistic Web Environment for Building Autonomous Agents. arXiv preprint arXiv:2307.13854.</p><p>[7] Chan, C. M., Chen, W., Su, Y., Yu, J., Xue, W., Zhang, S., … &amp; Liu, Z. (2023). ChatEval: Towards Better LLM-based Evaluators through Multi-Agent Debate. arXiv preprint arXiv:2308.07201.</p><p>[8] Zhou, X., Zhu, H., Mathur, L., Zhang, R., Yu, H., Qi, Z., … &amp; Neubig, G. (2023). SOTOPIA: Interactive Evaluation for Social Intelligence in Language Agents. arXiv preprint arXiv:2310.11667.</p><p>[9] Mathematical Association of America. (2024). American Invitational Mathematics Examination (AIME). Retrieved from <a href="https://www.maa.org/math-competitions/invitational-competitions/aime">https://www.maa.org/math-competitions/invitational-competitions/aime</a></p>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC12%E7%AB%A0-%E6%99%BA%E8%83%BD%E4%BD%93%E6%80%A7%E8%83%BD%E8%AF%84%E4%BC%B0/</id>
    <link href="http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC12%E7%AB%A0-%E6%99%BA%E8%83%BD%E4%BD%93%E6%80%A7%E8%83%BD%E8%AF%84%E4%BC%B0/"/>
    <published>2026-03-02T02:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="第十二章-智能体性能评估"><a href="#第十二章-智能体性能评估" class="headerlink" title="第十二章 智能体性能评估"></a>第十二章 智能体性能评估</h1><p>在前面的章节中，我们构建了 HelloAgents 框架的核]]>
    </summary>
    <title>第十二章 智能体性能评估</title>
    <updated>2026-03-08T09:24:16.345Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="第十一章-Agentic-RL"><a href="#第十一章-Agentic-RL" class="headerlink" title="第十一章 Agentic-RL"></a>第十一章 Agentic-RL</h1><h2 id="11-1-从-LLM-训练到-Agentic-RL"><a href="#11-1-从-LLM-训练到-Agentic-RL" class="headerlink" title="11.1 从 LLM 训练到 Agentic RL"></a>11.1 从 LLM 训练到 Agentic RL</h2><p>在前面的章节中，我们实现了多种智能体范式和通信协议。不过智能体处理更复杂的任务时表现不佳，自然会有疑问:<strong>如何让智能体具备更强的推理能力?如何让智能体学会更好地使用工具?如何让智能体能够自我改进?</strong></p><p>这正是 Agentic RL(基于强化学习的智能体训练)要解决的核心问题。本章将为 HelloAgents 框架引入强化学习训练能力，让你能够训练出具备推理、工具使用等高级能力的智能体。我们将从 LLM 训练的基础知识开始，逐步深入到监督微调(Supervised Fine-Tuning，SFT)、群组相对策略优化(Group Relative Policy Optimization， GRPO)等实用技术，最终构建一个完整的智能体训练 pipeline。</p><h3 id="11-1-1-从强化学习到-Agentic-RL"><a href="#11-1-1-从强化学习到-Agentic-RL" class="headerlink" title="11.1.1 从强化学习到 Agentic RL"></a>11.1.1 从强化学习到 Agentic RL</h3><p>在第二章的 2.4.2 节中，我们介绍了基于强化学习的智能体。强化学习(Reinforcement Learning， RL)是一种专注于解决序贯决策问题的学习范式，它通过智能体与环境的直接交互，在”试错”中学习如何最大化长期收益。</p><p>现在，让我们将这个框架应用到 LLM 智能体上。考虑一个数学问题求解智能体，它需要回答这样的问题:</p><figure class="highlight applescript"><table><tr><td class="gutter"><pre><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><code class="hljs applescript">问题: Janet&#x27;s ducks lay <span class="hljs-number">16</span> eggs per <span class="hljs-built_in">day</span>. She eats three <span class="hljs-keyword">for</span> breakfast<br><span class="hljs-keyword">every</span> morning <span class="hljs-keyword">and</span> bakes muffins <span class="hljs-keyword">for</span> her friends <span class="hljs-keyword">every</span> <span class="hljs-built_in">day</span> <span class="hljs-keyword">with</span> four.<br>She sells <span class="hljs-keyword">the</span> remainder <span class="hljs-keyword">at</span> <span class="hljs-keyword">the</span> farmers&#x27; market daily <span class="hljs-keyword">for</span> $<span class="hljs-number">2</span> per fresh<br>duck egg. How much <span class="hljs-keyword">in</span> dollars <span class="hljs-keyword">does</span> she make <span class="hljs-keyword">every</span> <span class="hljs-built_in">day</span> <span class="hljs-keyword">at</span> <span class="hljs-keyword">the</span> farmers&#x27; market?<br></code></pre></td></tr></table></figure><p>这个问题需要多步推理:首先计算 Janet 每天剩余的鸡蛋数量(16 - 3 - 4 &#x3D; 9)，然后计算她的收入(9 × 2 &#x3D; 18)。我们可以将这个任务映射到强化学习框架:</p><ul><li><strong>智能体</strong>:基于 LLM 的推理系统</li><li><strong>环境</strong>:数学问题和验证系统</li><li><strong>状态</strong>:当前的问题描述和已有的推理步骤</li><li><strong>行动</strong>:生成下一步推理或最终答案</li><li><strong>奖励</strong>:答案是否正确(正确+1，错误 0)</li></ul><p>传统的监督学习方法存在三个核心局限:一是数据质量完全决定训练质量，模型只能模仿训练数据，难以超越;二是缺乏探索能力，只能被动学习人类提供的路径;三是难以优化长期目标，无法精确优化多步推理的中间过程。</p><p>强化学习提供了新的可能性。通过让智能体自主生成多个候选答案并根据正确性获得奖励，它可以学习哪些推理路径更优、哪些步骤是关键，甚至发现比人类标注更好的解题方法<sup>[8]</sup>。这就是 Agentic RL 的核心思想:将 LLM 作为可学习策略，嵌入智能体的感知-决策-执行循环，通过强化学习优化多步任务表现。</p><h3 id="11-1-2-LLM-训练全景图"><a href="#11-1-2-LLM-训练全景图" class="headerlink" title="11.1.2 LLM 训练全景图"></a>11.1.2 LLM 训练全景图</h3><p>在深入 Agentic RL 之前，我们需要先理解 LLM 训练的完整流程。一个强大的 LLM(如 GPT、Claude、Qwen)的诞生，通常要经历两个主要阶段:预训练(Pretraining)和后训练(Post-training)。如图 11.1 所示，这两个阶段构成了 LLM 从”语言模型”到”对话助手”的完整演化路径。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-1.png" alt="" width="85%"/>  <p>图 11.1 LLM 训练全景图</p></div><p><strong>预训练阶段</strong>是 LLM 训练的第一阶段，目标是让模型学习语言的基本规律和世界知识。这个阶段使用海量的文本数据(通常是数 TB 级别)，通过自监督学习的方式训练模型。最常见的预训练任务是因果语言建模(Causal Language Modeling)，也称为下一个词预测(Next Token Prediction)。</p><p>给定一个文本序列 $x_1, x_2, …, x_t$，模型需要预测下一个词 $x_{t+1}$:</p><p>$$<br>\mathcal{L}<em>{\text{pretrain}} &#x3D; -\sum</em>{t&#x3D;1}^{T} \log P(x_t | x_1, x_2, …, x_{t-1}; \theta)<br>$$</p><p>其中 $\theta$ 是模型参数，$P(x_t | x_1, …, x_{t-1}; \theta)$ 是模型预测的下一个词的概率分布，目标是最小化负对数似然，即最大化预测正确词的概率。例如，给定文本”The cat sat on the”，模型需要预测下一个词最可能是”mat”。通过在海量文本上进行这样的训练，模型逐渐学会语法规则(什么样的词序是合法的)、语义知识(词与词之间的关系)、世界知识(关于世界的事实性信息)以及基础的推理能力。</p><p>预训练阶段的特点是数据量巨大、计算成本高、学到的是通用的语言理解和生成能力、采用无监督学习。</p><p><strong>后训练阶段</strong>则是要解决预训练模型的不足。预训练后的模型虽然具备了强大的语言能力，但它只是一个”预测下一个词”的模型，并不知道如何遵循人类的指令、生成有帮助无害诚实的回答、拒绝不当的请求，以及以对话的方式与人交互。后训练阶段就是要解决这些问题，让模型对齐人类的偏好和价值观。</p><p>后训练通常包含三个步骤。第一步是<strong>监督微调(SFT)</strong><sup>[15]</sup>，目标是让模型学会遵循指令和对话格式。训练数据是(prompt， completion)对，训练目标与预训练类似，仍然是最大化正确输出的概率:</p><p>$$<br>\mathcal{L}<em>{\text{SFT}} &#x3D; -\sum</em>{i&#x3D;1}^{N} \log P(y_i | x_i; \theta)<br>$$</p><p>其中 $x_i$ 是输入提示(prompt)，$y_i$ 是期望的输出，$N$ 是训练样本数量。SFT 的特点是数据量较小、需要人工标注、快速见效、主要学习任务格式和基本能力。</p><p>第二步是<strong>奖励建模(RM)</strong>。SFT 后的模型虽然能遵循指令，但生成的回答质量参差不齐。我们需要一种方式来评估回答的质量，这就是奖励模型的作用<sup>[13,14]</sup>。奖励模型的训练数据是偏好对比数据,包含同一个问题的两个回答,一个更好(chosen),一个更差(rejected)。奖励模型的训练目标是学习人类的偏好:</p><p>$$<br>\mathcal{L}<em>{\text{RM}} &#x3D; -\mathbb{E}</em>{(x, y_w, y_l)} [\log \sigma(r_\phi(x, y_w) - r_\phi(x, y_l))]<br>$$</p><p>其中 $r_\phi(x, y)$ 是奖励模型，输入是(提示，回答)对，输出是质量分数;$y_w$ 是更好的回答(chosen)，$y_l$ 是更差的回答(rejected)，$\sigma$ 是 sigmoid 函数，目标是让奖励模型给更好的回答更高的分数。</p><p>第三步是<strong>强化学习微调</strong>。有了奖励模型后，我们就可以用强化学习来优化语言模型，让它生成更高质量的回答。最经典的算法是 PPO(Proximal Policy Optimization)<sup>[1]</sup>，训练目标是:</p><p>$$<br>J_{\text{PPO}} &#x3D; \mathbb{E}<em>{x, y \sim \pi_\theta} [r_\phi(x, y)] - \beta \cdot D</em>{KL}(\pi_\theta || \pi_{\text{ref}})<br>$$</p><p>其中 $\pi_\theta$ 是当前策略，即语言模型，$\pi_{\text{ref}}$ 是参考策略，这个场景下可以是 SFT 模型，$r_\phi(x, y)$ 是奖励模型的评分，$D_{KL}$ 是 KL 散度，目的是为了防止模型偏离太远，$\beta$ 是平衡系数。这个目标函数的含义是:最大化奖励，同时不要偏离原始模型太远。</p><p>传统的 RLHF(Reinforcement Learning from Human Feedback)<sup>[5]</sup>需要大量人工标注偏好数据，成本高昂。为了降低成本，研究者提出了 RLAIF(Reinforcement Learning from AI Feedback)<sup>[7]</sup>，用强大的 AI 模型(如 GPT-4)来替代人类标注员。RLAIF 的工作流程是:用 SFT 模型生成多个候选回答，用强大的 AI 模型对回答进行评分和排序，用 AI 的评分训练奖励模型，用奖励模型进行强化学习。实验表明，RLAIF 的效果接近甚至超过 RLHF，同时成本大幅降低<sup>[11]</sup>。</p><h3 id="11-1-3-Agentic-RL-的核心理念"><a href="#11-1-3-Agentic-RL-的核心理念" class="headerlink" title="11.1.3 Agentic RL 的核心理念"></a>11.1.3 Agentic RL 的核心理念</h3><p>在理解了 LLM 的基础训练流程后，让我们来看看 Agentic RL 与传统训练方法的区别。传统的后训练(我们称之为 PBRFT: Preference-Based Reinforcement Fine-Tuning)主要关注单轮对话的质量优化:给定一个用户问题，模型生成一个回答，然后根据回答的质量获得奖励。这种方式适合优化对话助手，但对于需要多步推理、工具使用、长期规划的智能体任务来说，就显得力不从心了。</p><p><strong>Agentic RL</strong>则是一种新的范式，它将 LLM 视为一个可学习的策略，嵌入在一个顺序决策循环中。在这个框架下，智能体需要在动态环境中与外部世界交互，执行多步行动来完成复杂任务，获得中间反馈来指导后续决策，优化长期累积奖励而非单步奖励。</p><p>让我们通过一个具体例子来理解这个区别。在 PBRFT 场景中，用户问”请解释什么是强化学习”，模型生成完整回答，然后根据回答质量直接给分。而在 Agentic RL 场景中，用户请求”帮我分析这个 GitHub 仓库的代码质量”，智能体需要经历多个步骤:首先调用 GitHub API 获取仓库信息，成功获得仓库结构和文件列表，得到+0.1 的奖;然后读取主要代码文件，成功获得代码内容，得到+0.1 的奖励;接着分析代码质量合理，得到+0.2 的奖励;最后生成分析报告质量高，得到+0.6 的奖励。总奖励是所有步骤的累积:1.0。</p><p>可以看到，Agentic RL 的关键特征是多步交互、每一步的行动都会改变环境状态、每一步都可以获得反馈、优化整个任务的完成质量。</p><p>强化学习是基于马尔可夫决策过程(Markov Decision Process， MDP)框架进行形式化的。MDP 由五元组 $(S, A, P, R, \gamma)$ 定义:状态空间$S$、行动空间$A$、状态转移函数$P(s’|s,a)$、奖励函数$R(s,a)$、折扣因子$\gamma$。让我们从 MDP 的角度对比 PBRFT 和 Agentic RL，如表 11.1 所示。</p><div align="center">  <p>表 11.1 PBRFT 与 Agentic RL 对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-table-1.png" alt="" width="85%"/></div><p>在状态方面，PBRFT 的状态 $s_0$ 仅由用户提示构成，时间跨度 $T&#x3D;1$(单步)，状态不变化，可以表示为 $s_0 &#x3D; \text{prompt}$。而 Agentic RL 的状态 $s_t$ 包含历史观察和上下文，时间跨度 $T \gg 1$(多步)，状态随行动演化，可以表示为 $s_t &#x3D; (\text{prompt}, o_1, o_2, …, o_t)$，其中 $o_t$ 是第 $t$ 步的观察(如工具返回结果、环境反馈等)。</p><p>在行动方面，PBRFT 的行动空间只有文本生成，单一行动类型，表示为 $a &#x3D; y \sim \pi_\theta(y|s_0)$。而 Agentic RL 的行动空间包含文本生成、工具调用、环境操作等多种类型，表示为 $a_t \in {a_t^{\text{text}}, a_t^{\text{tool}}}$，例如 $a_t^{\text{text}}$ 是生成思考过程或回答，$a_t^{\text{tool}}$ 是调用计算器、搜索引擎等工具。</p><p>在转移函数方面，PBRFT 无状态转移，表示为 $P(s’|s,a) &#x3D; \delta(s’ - s_{\text{terminal}})$。而 Agentic RL 的状态根据行动和环境动态变化，表示为 $s_{t+1} \sim P(s_{t+1}|s_t, a_t)$，例如调用搜索工具后，状态会包含搜索结果。</p><p>在奖励方面，PBRFT 只有单步奖励 $r(s_0, a)$，仅在任务结束时给予，表示为 $R_{\text{PBRFT}} &#x3D; r(s_0, y)$，通常由奖励模型给出: $r(s_0, y) &#x3D; r_\phi(s_0, y)$。而 Agentic RL 有多步奖励 $r(s_t, a_t)$，可以在中间步骤给予部分奖励，表示为:</p><p>$$<br>R_{\text{Agentic}} &#x3D; \sum_{t&#x3D;0}^{T} \gamma^t r(s_t, a_t)<br>$$</p><p>其中 $\gamma \in [0,1]$ 是折扣因子，$r(s_t, a_t)$ 可以是稀疏奖励(只在任务完成时给予,如答案正确 +1)、密集奖励(每步都给予，如工具调用成功 +0.1)或结合两者的混合奖励。</p><p>在目标函数方面，PBRFT 最大化单步期望奖励:</p><p>$$<br>J_{\text{PBRFT}}(\theta) &#x3D; \mathbb{E}_{s_0, y \sim \pi_\theta} [r(s_0, y)]<br>$$</p><p>而 Agentic RL 最大化累积折扣奖励:</p><p>$$<br>J_{\text{Agentic}}(\theta) &#x3D; \mathbb{E}<em>{\tau \sim \pi_\theta} \left[\sum</em>{t&#x3D;0}^{T} \gamma^t r(s_t, a_t)\right]<br>$$</p><p>其中 $\tau &#x3D; (s_0, a_0, s_1, a_1, …, s_T)$ 是完整的轨迹(trajectory)。</p><p>这种转变不仅仅是技术细节的差异，而是思维方式的根本转变。PBRFT 思维关注”如何让模型生成更好的单个回答”，优化回答质量，关注语言表达，进行单步决策。而 Agentic RL 思维关注”如何让智能体完成复杂任务”，优化任务完成度，关注行动策略，进行多步规划。这种转变使得 LLM 从”对话助手”进化为”自主智能体”，能够主动寻找信息、知道何时、如何使用外部工具、为了最终目标，愿意执行看似”绕路”的中间步骤、从错误学习。</p><p>Agentic RL 的目标是赋予 LLM 智能体六大核心能力，如图 11.2 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-2.png" alt="" width="85%"/>  <p>图 11.2 Agentic RL 的六大核心能力</p></div><p><strong>推理(Reasoning)</strong>是指从给定信息中逻辑地得出结论的过程，是智能体的核心能力。传统的 CoT 提示方法依赖少样本示例，泛化能力有限;SFT 只能模仿训练数据中的推理模式，难以创新。强化学习的优势在于通过试错学习有效的推理策略，发现训练数据中没有的推理路径，学会何时需要深度思考、何时可以快速回答。推理任务可以建模为序列决策问题，给定问题 $q$，智能体需要生成推理链 $c &#x3D; (c_1, c_2, …, c_n)$ 和最终答案 $a$。奖励函数通常设计为 $r(q, c, a) &#x3D; 1$ if $a &#x3D; a^*$ else $0$，训练目标是 $\max_\theta \mathbb{E}_{q, (c,a) \sim \pi_\theta} [r(q, c, a)]$。通过这种方式，模型学会生成高质量的推理链，而不仅仅是记忆答案。</p><p><strong>工具使用(Tool Use)</strong>是指智能体调用外部工来完成任务的能力。在工具使用任务中，行动空间扩展为 $a_t \in {a_t^{\text{think}}, a_t^{\text{tool}}}$,其中 $a_t^{\text{think}}$ 是生成思考过程,$a_t^{\text{tool}} &#x3D; (\text{tool_name}， \text{arguments})$ 是调用工具。强化学习让智能体学会何时需要使用工具、选择哪个工具、如何组合多个工具。例如，在解决数学问题时，智能体需要学会何时使用计算器、何时使用代码解释器、何时直接推理。</p><p><strong>记忆(Memory)</strong>是指智能体保持和重用过去信息的能力，对于长期任务至关重要。LLM 的上下文窗口有限，静态检索策略(如 RAG)无法针对任务优化。强化学习让智能体学会记忆管理策略:决定哪些信息值得记住、何时更新记忆、何时删除过时信息。这类似于人类的工作记忆，我们会主动管理大脑中的信息，保留重要的、遗忘无关的。</p><p><strong>规划(Planning)</strong>是指制定行动序列以达成目标的能力。传统的 CoT 是线性思考，无法回溯;提示工程使用静态规划模板，难以适应新情况。强化学习让智能体学会动态规划:通过试错发现有效的行动序列，学会权衡短期和长期收益。例如，在多步任务中，智能体可能需要先执行一些看似”绕路”的步骤，例如收集信息，才能最终完成任务。</p><p><strong>自我改进(Self-Improvement)</strong>是指智能体回顾自身输出、纠正错误并优化策略的能力。强化学习让智能体学会自我反思:识别自己的错误、分析失败原因、调整策略。这种能力使得智能体能够在没有人工干预的情况下持续改进，类似于人类的”从错误中学习”。</p><p><strong>感知(Perception)</strong>是指理解多模态信息的能力。例如，强化学习可以提升视觉推理能力，让模型学会使用视觉工具，学会视觉规划。这使得智能体不仅能理解文本，还能理解和操作视觉世界。</p><h3 id="11-1-4-HelloAgents-的-Agentic-RL-设计"><a href="#11-1-4-HelloAgents-的-Agentic-RL-设计" class="headerlink" title="11.1.4 HelloAgents 的 Agentic RL 设计"></a>11.1.4 HelloAgents 的 Agentic RL 设计</h3><p>在理解了 Agentic RL 的核心理念后，让我们看看如何在 HelloAgents 框架中实现这些能力。</p><p>在技术选型上，我们集成了 TRL(Transformer Reinforcement Learning)框架<sup>[9]</sup>，模型选择 Qwen3-0.6B<sup>[10]</sup>。TRL 是 Hugging Face 的强化学习库，成熟稳定、功能完整、易于集成。Qwen3-0.6B 是阿里云的小型语言模型，0.6B 参数适合普通 GPU 训练，性能优秀且开源免费。</p><p>HelloAgents 的 Agentic RL 模块采用四层架构设计，如图 11.3 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-3.png" alt="" width="85%"/>  <p>图 11.3 HelloAgents Agentic RL 架构</p></div><p>最底层是<strong>数据集层</strong>，包含<code>GSM8KDataset</code>类、<code>create_sft_dataset()</code>函数和<code>create_rl_dataset()</code>函数，负责数据加载和格式转换。第二层是<strong>奖励函数层</strong>，包含<code>MathRewardFunction</code>基类、<code>AccuracyReward</code>准确率奖励、<code>LengthPenaltyReward</code>长度惩罚、<code>StepReward</code>步骤奖励，以及便捷创建函数<code>create_*_reward()</code>，负责定义什么是好的行为。第三层是<strong>训练器层</strong>，包含<code>SFTTrainerWrapper</code>和<code>GRPOTrainerWrapper</code>，负责具体的训练逻辑和 LoRA 支持。最顶层是<strong>统一接口层</strong>，提供<code>RLTrainingTool</code>统一训练工具，支持四种操作:<code>action="train"</code>(训练模型)、<code>action="load_dataset"</code>(加载数据集)、<code>action="create_reward"</code>(创建奖励函数)、<code>action="evaluate"</code>(评估模型)。</p><h3 id="11-1-5-快速上手示例"><a href="#11-1-5-快速上手示例" class="headerlink" title="11.1.5 快速上手示例"></a>11.1.5 快速上手示例</h3><p>在深入学习之前，让我们先快速体验一下完整的训练流程。由于这一章的理论部分比较多，实战需要调试的地方也十分繁琐，因此不专注于构造工具而是学会应用。首先安装 HelloAgents 框架:</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></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 安装HelloAgents框架(第11章版本)</span><br>pip install <span class="hljs-string">&quot;hello-agents[rl]==0.2.5&quot;</span><br><br><span class="hljs-comment"># 或者从源码安装</span><br><span class="hljs-built_in">cd</span> HelloAgents<br>pip install -e <span class="hljs-string">&quot;.[rl]&quot;</span><br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> sys<br><span class="hljs-keyword">import</span> json<br><br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><br><span class="hljs-comment"># 创建RL训练工具</span><br>rl_tool = RLTrainingTool()<br><br><span class="hljs-comment"># 1. 快速测试:SFT训练(10个样本，1个epoch)</span><br>sft_result_str = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>，<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;sft&quot;</span>,<br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/quick_test_sft&quot;</span>,<br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">10</span>,      <span class="hljs-comment"># 只用10个样本快速测试</span><br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">1</span>,        <span class="hljs-comment"># 只训练1轮</span><br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>        <span class="hljs-comment"># 使用LoRA加速训练</span><br>&#125;)<br><br>sft_result = json.loads(sft_result_str)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n✓ SFT训练完成,模型保存在: <span class="hljs-subst">&#123;sft_result[<span class="hljs-string">&#x27;output_dir&#x27;</span>]&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 2. GRPO训练(5个样本,1个epoch)</span><br>grpo_result_str = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,  <span class="hljs-comment"># 使用基础模型</span><br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/quick_test_grpo&quot;</span>,<br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">5</span>,       <span class="hljs-comment"># 只用5个样本快速测试</span><br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">1</span>,<br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">2</span>,        <span class="hljs-comment"># 必须能被num_generations(8)整除,使用2</span><br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span><br>&#125;)<br><br>grpo_result = json.loads(grpo_result_str)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n✓ GRPO训练完成,模型保存在: <span class="hljs-subst">&#123;grpo_result[<span class="hljs-string">&#x27;output_dir&#x27;</span>]&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 3. 评估模型</span><br>eval_result_str = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;evaluate&quot;</span>,<br>    <span class="hljs-string">&quot;model_path&quot;</span>: <span class="hljs-string">&quot;./models/quick_test_grpo&quot;</span>,<br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">10</span>,      <span class="hljs-comment"># 在10个测试样本上评估</span><br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span><br>&#125;)<br><br>eval_result = json.loads(eval_result_str)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n✓ 评估完成:&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 准确率: <span class="hljs-subst">&#123;eval_result[<span class="hljs-string">&#x27;accuracy&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 平均奖励: <span class="hljs-subst">&#123;eval_result[<span class="hljs-string">&#x27;average_reward&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 测试样本数: <span class="hljs-subst">&#123;eval_result[<span class="hljs-string">&#x27;num_samples&#x27;</span>]&#125;</span>&quot;</span>)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n&quot;</span> + <span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;🎉 恭喜!你已经完成了第一个Agentic RL模型的训练!&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n模型路径:&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  SFT模型: <span class="hljs-subst">&#123;sft_result[<span class="hljs-string">&#x27;output_dir&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  GRPO模型: <span class="hljs-subst">&#123;grpo_result[<span class="hljs-string">&#x27;output_dir&#x27;</span>]&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>这个快速示例展示了完整的训练流程:SFT 训练让模型学习基础的推理格式和对话模式，GRPO 训练通过强化学习优化推理策略提升准确率，模型评估在测试集上评估训练效果。另外跑完之后准确率很低是正常现象，因为现在模型只见过 0.7%的训练样本，并且只运行了一轮。</p><h2 id="11-2-数据集与奖励函数"><a href="#11-2-数据集与奖励函数" class="headerlink" title="11.2 数据集与奖励函数"></a>11.2 数据集与奖励函数</h2><p>数据集和奖励函数是强化学习训练的两大基石。数据集定义了智能体要学习的任务，奖励函数定义了什么是好的行为。在本节中，我们将学习如何准备训练数据和设计奖励函数。</p><h3 id="11-2-1-GSM8K-数学推理数据集"><a href="#11-2-1-GSM8K-数学推理数据集" class="headerlink" title="11.2.1 GSM8K 数学推理数据集"></a>11.2.1 GSM8K 数学推理数据集</h3><p>数学推理是评估 LLM 推理能力的理想任务。首先，数学问题有明确的正确答案，可以自动评估，不需要人工标注或复杂的奖励模型。其次，解决数学问题需要分解问题、逐步推导，这正是多步推理的典型场景。最后，学到的推理能力可以迁移到其他领域，具有很强的泛化性。相比之下，开放式问答任务(如”如何学习编程?”)的答案质量难以客观评估，需要大量人工标注。</p><p>GSM8K(Grade School Math 8K)<sup>[4]</sup>是一个高质量的小学数学应用题数据集。如表 11.2 所示，数据集包含 7，473 个训练样本和 1，319 个测试样本，难度为小学数学水平(2-8 年级)，题型为应用题，需要 2-8 步推理才能得出答案。</p><div align="center">  <p>表 11.2 GSM8K 数据集统计</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-table-2.png" alt="" width="85%"/></div>让我们看一个典型的 GSM8K 问题:<figure class="highlight clean"><table><tr><td class="gutter"><pre><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><code class="hljs clean">问题: Natalia sold clips to <span class="hljs-number">48</span> <span class="hljs-keyword">of</span> her friends <span class="hljs-keyword">in</span> April, and then she sold half <br>      <span class="hljs-keyword">as</span> many clips <span class="hljs-keyword">in</span> May. How many clips did Natalia sell altogether <span class="hljs-keyword">in</span> April <br>      and May?<br><br>答案: Natalia sold <span class="hljs-number">48</span>/<span class="hljs-number">2</span> = &lt;&lt;<span class="hljs-number">48</span>/<span class="hljs-number">2</span>=<span class="hljs-number">24</span>&gt;&gt;<span class="hljs-number">24</span> clips <span class="hljs-keyword">in</span> May.<br>      Natalia sold <span class="hljs-number">48</span>+<span class="hljs-number">24</span> = &lt;&lt;<span class="hljs-number">48</span>+<span class="hljs-number">24</span>=<span class="hljs-number">72</span>&gt;&gt;<span class="hljs-number">72</span> clips altogether <span class="hljs-keyword">in</span> April and May.<br>      #### <span class="hljs-number">72</span><br><br>最终答案: <span class="hljs-number">72</span><br></code></pre></td></tr></table></figure><p>这个问题需要两步推理:首先计算 5 月份卖出的数量(48 的一半)，然后计算总数(4 月+5 月)。答案中的<code>&lt;&lt;48/2=24&gt;&gt;</code>是中间计算步骤的标记，<code>#### 72</code>标记最终答案。</p><p>GSM8K 数据集需要转换为不同的格式，以适应不同的训练方法，如图 11.4 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-4.png" alt="" width="85%"/>  <p>图 11.4 GSM8K 数据格式转换</p></div><p>原始格式直接来自数据集，包含问题(question)和答案(answer，含解题步骤)，适合人类阅读。SFT 格式用于监督微调，将问题转换为对话格式的 prompt，将完整解答作为 completion。例如:</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></pre></td><td class="code"><pre><code class="hljs python">&#123;<br>    <span class="hljs-string">&quot;prompt&quot;</span>: <span class="hljs-string">&quot;&lt;|im_start|&gt;user\nNatalia sold clips to 48 of her friends...&lt;|im_end|&gt;\n&lt;|im_start|&gt;assistant\n&quot;</span>,<br>    <span class="hljs-string">&quot;completion&quot;</span>: <span class="hljs-string">&quot;Let me solve this step by step.\n\nStep 1: ...\n\nFinal Answer: 72&lt;|im_end|&gt;&quot;</span><br>&#125;<br></code></pre></td></tr></table></figure><p>关键点是使用模型的对话模板(如 Qwen 的<code>&lt;|im_start|&gt;</code>标记)，prompt 包含用户问题，completion 包含完整的解题过程和答案。这样模型可以学习如何格式化输出、如何分步推理。</p><p>RL 格式用于强化学习，只提供问题和正确答案，不提供解题过程。例如:</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></pre></td><td class="code"><pre><code class="hljs python">&#123;<br>    <span class="hljs-string">&quot;prompt&quot;</span>: <span class="hljs-string">&quot;&lt;|im_start|&gt;user\nNatalia sold clips to 48 of her friends...&lt;|im_end|&gt;\n&lt;|im_start|&gt;assistant\n&quot;</span>,<br>    <span class="hljs-string">&quot;ground_truth&quot;</span>: <span class="hljs-string">&quot;72&quot;</span><br>&#125;<br></code></pre></td></tr></table></figure><p>关键点是 prompt 与 SFT 相同，但 ground_truth 只包含最终答案(用于计算奖励)，模型需要自己生成完整的推理过程。这种设计迫使模型学会自主推理，而不是简单地记忆答案。</p><p>如表 11.3 所示，三种格式各有用途。</p><div align="center">  <p>表 11.3 数据格式对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-table-3.png" alt="" width="85%"/></div>HelloAgents 提供了便捷的数据集加载函数。让我们通过代码来加载和查看数据集:<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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><span class="hljs-keyword">import</span> json<br><br><span class="hljs-comment"># 创建工具</span><br>rl_tool = RLTrainingTool()<br><br><span class="hljs-comment"># 1. 加载SFT格式数据集</span><br>sft_result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;load_dataset&quot;</span>,<br>    <span class="hljs-string">&quot;format&quot;</span>: <span class="hljs-string">&quot;sft&quot;</span>,<br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">5</span>  <span class="hljs-comment"># 只加载5个样本查看</span><br>&#125;)<br>sft_data = json.loads(sft_result)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;数据集大小: <span class="hljs-subst">&#123;sft_data[<span class="hljs-string">&#x27;dataset_size&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;数据格式: <span class="hljs-subst">&#123;sft_data[<span class="hljs-string">&#x27;format&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;样本字段: <span class="hljs-subst">&#123;sft_data[<span class="hljs-string">&#x27;sample_keys&#x27;</span>]&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 2. 加载RL格式数据集</span><br>rl_result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;load_dataset&quot;</span>,<br>    <span class="hljs-string">&quot;format&quot;</span>: <span class="hljs-string">&quot;rl&quot;</span>,<br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">5</span><br>&#125;)<br>rl_data = json.loads(rl_result)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;数据集大小: <span class="hljs-subst">&#123;rl_data[<span class="hljs-string">&#x27;dataset_size&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;数据格式: <span class="hljs-subst">&#123;rl_data[<span class="hljs-string">&#x27;format&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;样本字段: <span class="hljs-subst">&#123;rl_data[<span class="hljs-string">&#x27;sample_keys&#x27;</span>]&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>可以看到，SFT 格式包含完整的解题过程，用于监督学习;RL 格式只包含最终答案，模型需要自己生成推理过程。<code>max_samples</code>参数控制加载的样本数量，方便快速测试。</p><h3 id="11-2-2-奖励函数设计"><a href="#11-2-2-奖励函数设计" class="headerlink" title="11.2.2 奖励函数设计"></a>11.2.2 奖励函数设计</h3><p>奖励函数是强化学习的核心，它定义了什么是”好的行为”。一个好的奖励函数能够引导智能体学习到正确的策略，而一个糟糕的奖励函数可能导致训练失败或学到错误的行为。</p><p>在强化学习中，奖励函数 $r(s, a)$ 或 $r(s, a, s’)$ 为智能体的每个行动分配一个数值奖励。智能体的目标是最大化累积奖励:</p><p>$$<br>J(\theta) &#x3D; \mathbb{E}<em>{\tau \sim \pi_\theta} \left[\sum</em>{t&#x3D;0}^{T} \gamma^t r(s_t, a_t)\right]<br>$$</p><p>对于数学推理任务，我们可以简化为:</p><p>$$<br>r(q, a) &#x3D; f(a, a^*)<br>$$</p><p>其中 $q$ 是问题，$a$ 是模型生成的答案，$a^*$ 是正确答案，$f$ 是评估函数。</p><p>奖励函数的设计直接影响训练效果。好的奖励函数应该能清楚地定义什么是成功、能够提供梯度信号、不会产生过大的方差、容易调整和组合。糟糕的奖励函数可能只在任务结束时给奖励，中间步骤无反馈、存在奖励欺骗，使得智能体找到”作弊”方式获得高奖励、多个目标相互矛盾、方差过大，训练不收敛。</p><p>HelloAgents 提供了三种内置奖励函数，可以单独使用或组合使用，如图 11.5 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-5.png" alt="" width="85%"/>  <p>图 11.5 奖励函数设计</p></div><strong>（1）准确率奖励</strong><p>准确率奖励(AccuracyReward)是最基础的奖励函数，它只关心答案是否正确。数学定义为:</p><p>$$<br>r_{\text{acc}}(a, a^*) &#x3D; \begin{cases}<br>1 &amp; \text{if } a &#x3D; a^* \<br>0 &amp; \text{otherwise}<br>\end{cases}<br>$$</p><p>其中 $a$ 是模型生成的答案，$a^*$ 是正确答案。这是一个二值奖励函数，答案正确得 1 分，错误得 0 分。</p><p>实现时需要处理答案提取和比较。模型的输出可能包含大量文本，我们需要提取最终答案。常见的提取方法包括:查找”Final Answer:”后的数字、查找”####”标记后的数字、使用正则表达式提取最后一个数字。答案比较时需要处理数值精度(如 72.0 和 72 应该视为相同)、单位转换(如 1000 和 1k)、格式差异(如”72”和”seventy-two”)。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><span class="hljs-keyword">import</span> json<br>rl_tool = RLTrainingTool()<br><br><span class="hljs-comment"># 创建准确率奖励函数</span><br>reward_result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create_reward&quot;</span>,<br>    <span class="hljs-string">&quot;reward_type&quot;</span>: <span class="hljs-string">&quot;accuracy&quot;</span><br>&#125;)<br>reward_data = json.loads(reward_result)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;奖励类型: <span class="hljs-subst">&#123;reward_data[<span class="hljs-string">&#x27;reward_type&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;描述: <span class="hljs-subst">&#123;reward_data[<span class="hljs-string">&#x27;description&#x27;</span>]&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 注意: RLTrainingTool的create_reward操作返回的是配置信息,</span><br><span class="hljs-comment"># 实际的奖励函数会在训练时自动创建和使用</span><br></code></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></pre></td><td class="code"><pre><code class="hljs json">预测<span class="hljs-punctuation">:</span> <span class="hljs-number">72</span><span class="hljs-punctuation">,</span> 真实<span class="hljs-punctuation">:</span> <span class="hljs-number">72</span><span class="hljs-punctuation">,</span> 奖励<span class="hljs-punctuation">:</span> <span class="hljs-number">1.0</span><br>预测<span class="hljs-punctuation">:</span> <span class="hljs-number">72.0</span><span class="hljs-punctuation">,</span> 真实<span class="hljs-punctuation">:</span> <span class="hljs-number">72</span><span class="hljs-punctuation">,</span> 奖励<span class="hljs-punctuation">:</span> <span class="hljs-number">1.0</span><br>预测<span class="hljs-punctuation">:</span> <span class="hljs-number">73</span><span class="hljs-punctuation">,</span> 真实<span class="hljs-punctuation">:</span> <span class="hljs-number">72</span><span class="hljs-punctuation">,</span> 奖励<span class="hljs-punctuation">:</span> <span class="hljs-number">0.0</span><br></code></pre></td></tr></table></figure><p>准确率奖励的优点是简单直接，容易理解和实现，适合有明确正确答案的任务。缺点是奖励稀疏，只有答案完全正确才有奖励，无法区分”接近正确”和”完全错误”，可能导致训练初期缺乏有效反馈。</p><p><strong>（2）长度惩罚</strong></p><p>长度惩罚(LengthPenaltyReward)鼓励模型生成简洁的回答，避免冗长啰嗦。数学定义为:</p><p>$$<br>r_{\text{length}}(a, a^*, l) &#x3D; r_{\text{acc}}(a, a^*) - \alpha \cdot \max(0, l - l_{\text{target}})<br>$$</p><p>其中 $l$ 是生成文本的长度(字符数或 token 数)，$l_{\text{target}}$ 是目标长度，$\alpha$ 是惩罚系数(默认 0.001)。只有在答案正确的情况下才应用长度惩罚，避免模型为了减少惩罚而生成错误的短答案。</p><p>设计思路是:如果答案错误，奖励为 0(无论长度);如果答案正确且长度合理，奖励为 1;如果答案正确但过长，奖励为 $1 - \alpha \cdot (l - l_{\text{target}})$。例如，目标长度 200 字符，实际长度 500 字符，惩罚系数 0.001，则奖励为 $1 - 0.001 \times (500 - 200) &#x3D; 0.7$。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 创建长度惩罚奖励函数</span><br>reward_result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create_reward&quot;</span>,<br>    <span class="hljs-string">&quot;reward_type&quot;</span>: <span class="hljs-string">&quot;length_penalty&quot;</span>,<br>    <span class="hljs-string">&quot;max_length&quot;</span>: <span class="hljs-number">1024</span>,      <span class="hljs-comment"># 最大长度</span><br>    <span class="hljs-string">&quot;penalty_weight&quot;</span>: <span class="hljs-number">0.001</span>  <span class="hljs-comment"># 惩罚权重</span><br>&#125;)<br>reward_data = json.loads(reward_result)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;奖励类型: <span class="hljs-subst">&#123;reward_data[<span class="hljs-string">&#x27;reward_type&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;描述: <span class="hljs-subst">&#123;reward_data[<span class="hljs-string">&#x27;description&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;最大长度: <span class="hljs-subst">&#123;reward_data[<span class="hljs-string">&#x27;max_length&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;惩罚权重: <span class="hljs-subst">&#123;reward_data[<span class="hljs-string">&#x27;penalty_weight&#x27;</span>]&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>输出:</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><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><code class="hljs makefile"><span class="hljs-section">预测: 72, 真实: 72, 长度: 50, 奖励: 1.000</span><br><span class="hljs-section">预测: 72, 真实: 72, 长度: 200, 奖励: 1.000</span><br><span class="hljs-section">预测: 72, 真实: 72, 长度: 500, 奖励: 0.700</span><br><span class="hljs-section">预测: 73, 真实: 72, 长度: 50, 奖励: 0.000</span><br></code></pre></td></tr></table></figure><p>长度惩罚的优点是鼓励简洁表达，避免模型生成冗余内容，可以控制推理成本(更短的输出意味着更少的 token 消耗)。缺点是可能抑制详细推理，需要仔细调整惩罚系数，不同任务的最优长度差异很大。</p><p><strong>（3）步骤奖励</strong></p><p>步骤奖励(StepReward)鼓励模型生成清晰的推理步骤，提高可解释性。数学定义为:</p><p>$$<br>r_{\text{step}}(a, a^*, s) &#x3D; r_{\text{acc}}(a, a^*) + \beta \cdot s<br>$$</p><p>其中 $s$ 是检测到的推理步骤数量，$\beta$ 是步骤奖励系数(默认 0.1)。同样，只有在答案正确的情况下才给予步骤奖励。</p><p>步骤检测方法包括:查找”Step 1:”， “Step 2:”等标记、查找换行符数量、使用正则表达式匹配推理模式。例如，一个包含 3 个清晰步骤的正确答案，奖励为 $1 + 0.1 \times 3 &#x3D; 1.3$。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 创建步骤奖励函数</span><br>reward_result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create_reward&quot;</span>,<br>    <span class="hljs-string">&quot;reward_type&quot;</span>: <span class="hljs-string">&quot;step&quot;</span>,<br>    <span class="hljs-string">&quot;step_bonus&quot;</span>: <span class="hljs-number">0.1</span>  <span class="hljs-comment"># 每个步骤奖励0.1</span><br>&#125;)<br>reward_data = json.loads(reward_result)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;奖励类型: <span class="hljs-subst">&#123;reward_data[<span class="hljs-string">&#x27;reward_type&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;描述: <span class="hljs-subst">&#123;reward_data[<span class="hljs-string">&#x27;description&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;步骤奖励: <span class="hljs-subst">&#123;reward_data[<span class="hljs-string">&#x27;step_bonus&#x27;</span>]&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>输出:</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><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><code class="hljs makefile"><span class="hljs-section">预测: 72, 真实: 72, 步骤: 0, 奖励: 1.00</span><br><span class="hljs-section">预测: 72, 真实: 72, 步骤: 2, 奖励: 1.20</span><br><span class="hljs-section">预测: 72, 真实: 72, 步骤: 5, 奖励: 1.50</span><br><span class="hljs-section">预测: 73, 真实: 72, 步骤: 5, 奖励: 0.00</span><br></code></pre></td></tr></table></figure><p>步骤奖励的优点是鼓励可解释的推理，生成的答案更容易验证和调试，有助于模型学习系统化的思考方式。缺点是可能导致模型为了获得更多奖励生成冗余步骤，需要平衡步骤数量和答案质量，步骤检测可能不准确。</p><p>在实际应用中，我们通常会组合多个奖励函数，以平衡不同的目标。常见的组合策略包括:</p><p><strong>准确率 + 长度惩罚</strong>:鼓励简洁正确的答案，适合对话系统、问答系统。公式为:</p><p>$$<br>r &#x3D; r_{\text{acc}} - \alpha \cdot \max(0, l - l_{\text{target}})<br>$$</p><p><strong>准确率 + 步骤奖励</strong>:鼓励详细的推理过程，适合教育场景、可解释 AI。公式为:</p><p>$$<br>r &#x3D; r_{\text{acc}} + \beta \cdot s<br>$$</p><p><strong>三者平衡</strong>:全面优化答案质量、简洁性和可解释性。公式为:<br>$$<br>r &#x3D; r_{\text{acc}} - \alpha \cdot \max(0, l - l_{\text{target}}) + \beta \cdot s<br>$$</p><p>需要仔细调整权重 $\alpha$ 和 $\beta$，避免某个目标过度主导。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 组合奖励函数:准确率 + 长度惩罚 + 步骤奖励</span><br><span class="hljs-comment"># 注意: RLTrainingTool目前支持单一奖励类型</span><br><span class="hljs-comment"># 组合奖励需要在训练配置中通过reward_fn参数指定</span><br><span class="hljs-comment"># 这里展示如何配置不同类型的奖励函数</span><br><br><span class="hljs-comment"># 准确率奖励</span><br>accuracy_result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create_reward&quot;</span>,<br>    <span class="hljs-string">&quot;reward_type&quot;</span>: <span class="hljs-string">&quot;accuracy&quot;</span><br>&#125;)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;准确率奖励:&quot;</span>, json.loads(accuracy_result)[<span class="hljs-string">&#x27;description&#x27;</span>])<br><br><span class="hljs-comment"># 长度惩罚奖励</span><br>length_result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create_reward&quot;</span>,<br>    <span class="hljs-string">&quot;reward_type&quot;</span>: <span class="hljs-string">&quot;length_penalty&quot;</span>,<br>    <span class="hljs-string">&quot;max_length&quot;</span>: <span class="hljs-number">1024</span>,<br>    <span class="hljs-string">&quot;penalty_weight&quot;</span>: <span class="hljs-number">0.001</span><br>&#125;)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;长度惩罚奖励:&quot;</span>, json.loads(length_result)[<span class="hljs-string">&#x27;description&#x27;</span>])<br><br><span class="hljs-comment"># 步骤奖励</span><br>step_result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create_reward&quot;</span>,<br>    <span class="hljs-string">&quot;reward_type&quot;</span>: <span class="hljs-string">&quot;step&quot;</span>,<br>    <span class="hljs-string">&quot;step_bonus&quot;</span>: <span class="hljs-number">0.1</span><br>&#125;)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;步骤奖励:&quot;</span>, json.loads(step_result)[<span class="hljs-string">&#x27;description&#x27;</span>])<br></code></pre></td></tr></table></figure><p>输出:</p><figure class="highlight nestedtext"><table><tr><td class="gutter"><pre><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><code class="hljs nestedtext"><span class="hljs-attribute">组合奖励</span><span class="hljs-punctuation">:</span> <span class="hljs-string">1.200</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">准确率: 1.0</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">长度惩罚: -0.100</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">步骤奖励: +0.3</span><br></code></pre></td></tr></table></figure><p>如表 11.4 所示，不同奖励函数适合不同的应用场景。</p><div align="center">  <p>表 11.4 奖励函数对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-table-4.png" alt="" width="85%"/></div><h3 id="11-2-3-自定义数据集和奖励函数"><a href="#11-2-3-自定义数据集和奖励函数" class="headerlink" title="11.2.3 自定义数据集和奖励函数"></a>11.2.3 自定义数据集和奖励函数</h3><p>虽然 HelloAgents 提供了 GSM8K 数据集和常用奖励函数，但在实际应用中，你可能需要使用自己的数据集或设计特定的奖励函数。本节将介绍如何扩展框架。</p><p>在使用自定义数据集之前，需要了解两种训练格式的数据要求:</p><p><strong>SFT 格式</strong>:用于监督微调，需要包含以下字段:</p><ul><li><code>prompt</code>: 输入提示(包含 system 和 user 消息)</li><li><code>completion</code>: 期望的输出</li><li><code>text</code>: 完整的对话文本(可选)</li></ul><p><strong>RL 格式</strong>:用于强化学习，需要包含以下字段:</p><ul><li><code>question</code>: 原始问题</li><li><code>prompt</code>: 输入提示(包含 system 和 user 消息)</li><li><code>ground_truth</code>: 正确答案</li><li><code>full_answer</code>: 完整答案(包含推理过程)</li></ul><p><strong>（1）使用 format_math_dataset 转换</strong></p><p>最简单的方法是准备包含<code>question</code>和<code>answer</code>字段的原始数据，然后使用<code>format_math_dataset()</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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> datasets <span class="hljs-keyword">import</span> Dataset<br><span class="hljs-keyword">from</span> hello_agents.rl <span class="hljs-keyword">import</span> format_math_dataset<br><br><span class="hljs-comment"># 1. 准备原始数据</span><br>custom_data = [<br>    &#123;<br>        <span class="hljs-string">&quot;question&quot;</span>: <span class="hljs-string">&quot;What is 2+2?&quot;</span>,<br>        <span class="hljs-string">&quot;answer&quot;</span>: <span class="hljs-string">&quot;2+2=4. #### 4&quot;</span><br>    &#125;,<br>    &#123;<br>        <span class="hljs-string">&quot;question&quot;</span>: <span class="hljs-string">&quot;What is 5*3?&quot;</span>,<br>        <span class="hljs-string">&quot;answer&quot;</span>: <span class="hljs-string">&quot;5*3=15. #### 15&quot;</span><br>    &#125;,<br>    &#123;<br>        <span class="hljs-string">&quot;question&quot;</span>: <span class="hljs-string">&quot;What is 10+7?&quot;</span>,<br>        <span class="hljs-string">&quot;answer&quot;</span>: <span class="hljs-string">&quot;10+7=17. #### 17&quot;</span><br>    &#125;<br>]<br><br><span class="hljs-comment"># 2. 转换为Dataset对象</span><br>raw_dataset = Dataset.from_list(custom_data)<br><br><span class="hljs-comment"># 3. 转换为SFT格式</span><br>sft_dataset = format_math_dataset(<br>    dataset=raw_dataset,<br>    format_type=<span class="hljs-string">&quot;sft&quot;</span>,<br>    model_name=<span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span><br>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;SFT数据集: <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(sft_dataset)&#125;</span>个样本&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;字段: <span class="hljs-subst">&#123;sft_dataset.column_names&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 4. 转换为RL格式</span><br>rl_dataset = format_math_dataset(<br>    dataset=raw_dataset,<br>    format_type=<span class="hljs-string">&quot;rl&quot;</span>,<br>    model_name=<span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span><br>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;RL数据集: <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(rl_dataset)&#125;</span>个样本&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;字段: <span class="hljs-subst">&#123;rl_dataset.column_names&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>（2）直接传入自定义数据集</strong></p><p>使用 RLTrainingTool 时，可以通过<code>custom_dataset</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><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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><br>rl_tool = RLTrainingTool()<br><br><span class="hljs-comment"># SFT训练</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;sft&quot;</span>,<br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/custom_sft&quot;</span>,<br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">3</span>,<br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">4</span>,<br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>    <span class="hljs-string">&quot;custom_dataset&quot;</span>: sft_dataset  <span class="hljs-comment"># 直接传入自定义数据集</span><br>&#125;)<br><br><span class="hljs-comment"># GRPO训练</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/custom_grpo&quot;</span>,<br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>    <span class="hljs-string">&quot;custom_dataset&quot;</span>: rl_dataset  <span class="hljs-comment"># 直接传入自定义数据集</span><br>&#125;)<br></code></pre></td></tr></table></figure><p>（3）注册自定义数据集(推荐)</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 1. 注册数据集</span><br>rl_tool.register_dataset(<span class="hljs-string">&quot;my_math_dataset&quot;</span>, rl_dataset)<br><br><span class="hljs-comment"># 2. 使用注册的数据集</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>    <span class="hljs-string">&quot;dataset&quot;</span>: <span class="hljs-string">&quot;my_math_dataset&quot;</span>,  <span class="hljs-comment"># 使用注册的数据集名称</span><br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/custom_grpo&quot;</span>,<br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span><br>&#125;)<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">List</span><br><span class="hljs-keyword">import</span> re<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">custom_reward_function</span>(<span class="hljs-params"></span><br><span class="hljs-params">    completions: <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>],</span><br><span class="hljs-params">    **kwargs</span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">float</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">    自定义奖励函数</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        completions: 模型生成的完成文本列表</span><br><span class="hljs-string">        **kwargs: 其他参数,通常包含:</span><br><span class="hljs-string">            - ground_truth: 正确答案列表</span><br><span class="hljs-string">            - 其他数据集字段</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        奖励值列表(每个值在0.0-1.0之间)</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    ground_truths = kwargs.get(<span class="hljs-string">&quot;ground_truth&quot;</span>, [])<br>    rewards = []<br><br>    <span class="hljs-keyword">for</span> completion, truth <span class="hljs-keyword">in</span> <span class="hljs-built_in">zip</span>(completions, ground_truths):<br>        reward = <span class="hljs-number">0.0</span><br><br>        <span class="hljs-comment"># 提取答案</span><br>        numbers = re.findall(<span class="hljs-string">r&#x27;-?\d+\.?\d*&#x27;</span>, completion)<br>        <span class="hljs-keyword">if</span> numbers:<br>            <span class="hljs-keyword">try</span>:<br>                pred = <span class="hljs-built_in">float</span>(numbers[-<span class="hljs-number">1</span>])<br>                truth_num = <span class="hljs-built_in">float</span>(truth)<br>                error = <span class="hljs-built_in">abs</span>(pred - truth_num)<br><br>                <span class="hljs-comment"># 根据误差给予不同奖励</span><br>                <span class="hljs-keyword">if</span> error &lt; <span class="hljs-number">0.01</span>:<br>                    reward = <span class="hljs-number">1.0</span>  <span class="hljs-comment"># 完全正确</span><br>                <span class="hljs-keyword">elif</span> error &lt; <span class="hljs-number">1.0</span>:<br>                    reward = <span class="hljs-number">0.8</span>  <span class="hljs-comment"># 非常接近</span><br>                <span class="hljs-keyword">elif</span> error &lt; <span class="hljs-number">5.0</span>:<br>                    reward = <span class="hljs-number">0.5</span>  <span class="hljs-comment"># 接近</span><br><br>                <span class="hljs-comment"># 额外奖励:鼓励展示推理步骤</span><br>                <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;step&quot;</span> <span class="hljs-keyword">in</span> completion.lower() <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;=&quot;</span> <span class="hljs-keyword">in</span> completion:<br>                    reward += <span class="hljs-number">0.1</span><br><br>            <span class="hljs-keyword">except</span> ValueError:<br>                reward = <span class="hljs-number">0.0</span><br><br>        rewards.append(<span class="hljs-built_in">min</span>(reward, <span class="hljs-number">1.0</span>))  <span class="hljs-comment"># 限制最大值为1.0</span><br><br>    <span class="hljs-keyword">return</span> rewards<br></code></pre></td></tr></table></figure><p>有两种方式使用自定义奖励函数:</p><p><strong>（1）直接传入</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></pre></td><td class="code"><pre><code class="hljs python">result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/custom_grpo&quot;</span>,<br>    <span class="hljs-string">&quot;custom_dataset&quot;</span>: rl_dataset,<br>    <span class="hljs-string">&quot;custom_reward&quot;</span>: custom_reward_function  <span class="hljs-comment"># 直接传入奖励函数</span><br>&#125;)<br></code></pre></td></tr></table></figure><p><strong>（2）注册使用(推荐)</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><code class="hljs python"><span class="hljs-comment"># 1. 注册奖励函数</span><br>rl_tool.register_reward_function(<span class="hljs-string">&quot;my_reward&quot;</span>, custom_reward_function)<br><br><span class="hljs-comment"># 2. 使用注册的奖励函数</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>    <span class="hljs-string">&quot;dataset&quot;</span>: <span class="hljs-string">&quot;my_math_dataset&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/custom_grpo&quot;</span><br>    <span class="hljs-comment"># 奖励函数会自动使用与dataset同名的注册函数</span><br>&#125;)<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> datasets <span class="hljs-keyword">import</span> Dataset<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><span class="hljs-keyword">from</span> hello_agents.rl <span class="hljs-keyword">import</span> format_math_dataset<br><span class="hljs-keyword">import</span> re<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">List</span><br><br><span class="hljs-comment"># 1. 准备自定义数据</span><br>custom_data = [<br>    &#123;<span class="hljs-string">&quot;question&quot;</span>: <span class="hljs-string">&quot;What is 2+2?&quot;</span>, <span class="hljs-string">&quot;answer&quot;</span>: <span class="hljs-string">&quot;2+2=4. #### 4&quot;</span>&#125;,<br>    &#123;<span class="hljs-string">&quot;question&quot;</span>: <span class="hljs-string">&quot;What is 5+3?&quot;</span>, <span class="hljs-string">&quot;answer&quot;</span>: <span class="hljs-string">&quot;5+3=8. #### 8&quot;</span>&#125;,<br>    &#123;<span class="hljs-string">&quot;question&quot;</span>: <span class="hljs-string">&quot;What is 10+7?&quot;</span>, <span class="hljs-string">&quot;answer&quot;</span>: <span class="hljs-string">&quot;10+7=17. #### 17&quot;</span>&#125;<br>]<br><br><span class="hljs-comment"># 2. 转换为训练格式</span><br>raw_dataset = Dataset.from_list(custom_data)<br>rl_dataset = format_math_dataset(raw_dataset, format_type=<span class="hljs-string">&quot;rl&quot;</span>)<br><br><span class="hljs-comment"># 3. 定义自定义奖励函数</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">tolerant_reward</span>(<span class="hljs-params">completions: <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>], **kwargs</span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">float</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;带容差的奖励函数&quot;&quot;&quot;</span><br>    ground_truths = kwargs.get(<span class="hljs-string">&quot;ground_truth&quot;</span>, [])<br>    rewards = []<br><br>    <span class="hljs-keyword">for</span> completion, truth <span class="hljs-keyword">in</span> <span class="hljs-built_in">zip</span>(completions, ground_truths):<br>        numbers = re.findall(<span class="hljs-string">r&#x27;-?\d+\.?\d*&#x27;</span>, completion)<br>        <span class="hljs-keyword">if</span> numbers:<br>            <span class="hljs-keyword">try</span>:<br>                pred = <span class="hljs-built_in">float</span>(numbers[-<span class="hljs-number">1</span>])<br>                truth_num = <span class="hljs-built_in">float</span>(truth)<br>                error = <span class="hljs-built_in">abs</span>(pred - truth_num)<br><br>                <span class="hljs-keyword">if</span> error &lt; <span class="hljs-number">0.01</span>:<br>                    reward = <span class="hljs-number">1.0</span><br>                <span class="hljs-keyword">elif</span> error &lt; <span class="hljs-number">5.0</span>:<br>                    reward = <span class="hljs-number">0.5</span><br>                <span class="hljs-keyword">else</span>:<br>                    reward = <span class="hljs-number">0.0</span><br>            <span class="hljs-keyword">except</span> ValueError:<br>                reward = <span class="hljs-number">0.0</span><br>        <span class="hljs-keyword">else</span>:<br>            reward = <span class="hljs-number">0.0</span><br><br>        rewards.append(reward)<br><br>    <span class="hljs-keyword">return</span> rewards<br><br><span class="hljs-comment"># 4. 创建工具并注册</span><br>rl_tool = RLTrainingTool()<br>rl_tool.register_dataset(<span class="hljs-string">&quot;my_dataset&quot;</span>, rl_dataset)<br>rl_tool.register_reward_function(<span class="hljs-string">&quot;my_dataset&quot;</span>, tolerant_reward)<br><br><span class="hljs-comment"># 5. 训练</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,<br>    <span class="hljs-string">&quot;dataset&quot;</span>: <span class="hljs-string">&quot;my_dataset&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/custom_grpo&quot;</span>,<br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span><br>&#125;)<br></code></pre></td></tr></table></figure><h2 id="11-3-SFT-训练"><a href="#11-3-SFT-训练" class="headerlink" title="11.3 SFT 训练"></a>11.3 SFT 训练</h2><p>监督微调(Supervised Fine-Tuning， SFT)是强化学习训练的第一步，也是最重要的基础。SFT 让模型学习任务的基本格式、对话模式和初步的推理能力。没有 SFT 的基础，直接进行强化学习往往会失败，因为模型连基本的输出格式都不会。</p><h3 id="11-3-1-为什么需要-SFT"><a href="#11-3-1-为什么需要-SFT" class="headerlink" title="11.3.1 为什么需要 SFT"></a>11.3.1 为什么需要 SFT</h3><p>在开始强化学习之前，我们需要先进行 SFT 训练。这是因为预训练模型虽然具备强大的语言能力，但它并不知道如何完成特定任务。预训练模型的训练目标是预测下一个词，而不是解决数学问题或使用工具。预训练模型的输出格式是自由文本，而我们需要结构化的输出(如”Step 1: …， Step 2: …， Final Answer: …”)。预训练模型没有见过任务相关的数据，不知道什么是”好的”推理过程。</p><p>SFT 的作用是教会模型任务的基本规则。首先，学习输出格式，让模型知道如何组织答案(如使用”Step 1”， “Final Answer”等标记)。其次，学习推理模式，通过示例学习如何分解问题、逐步推导。再次，建立基线能力，为后续的强化学习提供一个合理的起点。最后，减少探索空间，强化学习不需要从零开始，可以在 SFT 的基础上优化。</p><p>让我们通过一个对比实验来理解 SFT 的重要性。假设我们直接用预训练模型解决 GSM8K 问题:</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> transformers <span class="hljs-keyword">import</span> AutoTokenizer, AutoModelForCausalLM<br><br><span class="hljs-comment"># 加载预训练模型</span><br>model_name = <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span><br>tokenizer = AutoTokenizer.from_pretrained(model_name)<br>model = AutoModelForCausalLM.from_pretrained(model_name)<br><br><span class="hljs-comment"># 测试问题</span><br>question = <span class="hljs-string">&quot;&quot;&quot;Natalia sold clips to 48 of her friends in April, and then she sold half as many clips in May. How many clips did Natalia sell altogether in April and May?&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># 构造输入</span><br>prompt = <span class="hljs-string">f&quot;&lt;|im_start|&gt;user\n<span class="hljs-subst">&#123;question&#125;</span>&lt;|im_end|&gt;\n&lt;|im_start|&gt;assistant\n&quot;</span><br>inputs = tokenizer(prompt, return_tensors=<span class="hljs-string">&quot;pt&quot;</span>)<br><br><span class="hljs-comment"># 生成回答</span><br>outputs = model.generate(**inputs, max_new_tokens=<span class="hljs-number">200</span>)<br>response = tokenizer.decode(outputs[<span class="hljs-number">0</span>], skip_special_tokens=<span class="hljs-literal">False</span>)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;预训练模型的回答:&quot;</span>)<br><span class="hljs-built_in">print</span>(response)<br></code></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></pre></td><td class="code"><pre><code class="hljs bash">&lt;|im_start|&gt;user<br>Natalia sold clips to 48 of her friends <span class="hljs-keyword">in</span> April, and <span class="hljs-keyword">then</span> she sold half as many clips <span class="hljs-keyword">in</span> May. How many clips did Natalia sell altogether <span class="hljs-keyword">in</span> April and May?&lt;|im_end|&gt;<br>&lt;|im_start|&gt;assistant<br>I can <span class="hljs-built_in">help</span> you with that. Let me think about this problem. Natalia sold clips to her friends. In April she sold 48 clips. In May she sold half as many. So we need to find out how many she sold <span class="hljs-keyword">in</span> May and <span class="hljs-keyword">then</span> add them together. But I<span class="hljs-string">&#x27;m not sure how to calculate half of 48. Maybe I should use a calculator? Or maybe I can just estimate...</span><br></code></pre></td></tr></table></figure><p>可以看到，预训练模型虽然理解了问题，但输出非常冗长、缺乏结构、没有明确的答案、推理过程混乱。这样的输出无法用于强化学习，因为我们无法提取答案、无法评估质量、无法提供有效的奖励信号。</p><p>现在让我们看看 SFT 后的模型:</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 加载SFT模型(假设已经训练好)</span><br>sft_model_path = <span class="hljs-string">&quot;./models/sft_model&quot;</span><br>sft_model = AutoModelForCausalLM.from_pretrained(sft_model_path)<br><br><span class="hljs-comment"># 使用相同的问题</span><br>outputs = sft_model.generate(**inputs, max_new_tokens=<span class="hljs-number">200</span>)<br>response = tokenizer.decode(outputs[<span class="hljs-number">0</span>], skip_special_tokens=<span class="hljs-literal">False</span>)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;SFT模型的回答:&quot;</span>)<br><span class="hljs-built_in">print</span>(response)<br></code></pre></td></tr></table></figure><p>SFT 模型的输出:</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><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs bash">&lt;|im_start|&gt;user<br>Natalia sold clips to 48 of her friends <span class="hljs-keyword">in</span> April, and <span class="hljs-keyword">then</span> she sold half as many clips <span class="hljs-keyword">in</span> May. How many clips did Natalia sell altogether <span class="hljs-keyword">in</span> April and May?&lt;|im_end|&gt;<br>&lt;|im_start|&gt;assistant<br>Let me solve this step by step.<br><br>Step 1: Calculate clips sold <span class="hljs-keyword">in</span> May<br>Natalia sold half as many clips <span class="hljs-keyword">in</span> May as <span class="hljs-keyword">in</span> April.<br>Clips <span class="hljs-keyword">in</span> May = 48 / 2 = 24<br><br>Step 2: Calculate total clips<br>Total = April + May = 48 + 24 = 72<br><br>Final Answer: 72&lt;|im_end|&gt;<br></code></pre></td></tr></table></figure><p>可以看到，SFT 模型的输出结构清晰(使用”Step 1”， “Step 2”， “Final Answer”标记)、推理正确、答案明确、格式统一。这样的输出可以用于强化学习，因为我们可以提取答案、计算奖励、优化策略。</p><p>如图 11.6 所示，SFT 是从预训练模型到强化学习的桥梁。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-6.png" alt="" width="85%"/>  <p>图 11.6 SFT 在训练流程中的作用</p></div><h3 id="11-3-2-LoRA-参数高效微调"><a href="#11-3-2-LoRA-参数高效微调" class="headerlink" title="11.3.2 LoRA:参数高效微调"></a>11.3.2 LoRA:参数高效微调</h3><p>直接微调整个模型需要大量的计算资源和显存。对于 Qwen3-0.6B(0.6B 参数)，全量微调需要约 12GB 显存(FP16)或 24GB 显存(FP32)。对于更大的模型(如 7B、13B)，全量微调几乎不可能在消费级 GPU 上进行。</p><p>LoRA(Low-Rank Adaptation)<sup>[3]</sup>是一种参数高效微调方法，它只训练少量的额外参数，而保持原模型参数冻结。LoRA 的核心思想是:模型微调时的参数变化可以用低秩矩阵表示。</p><p>假设原模型的权重矩阵为 $W \in \mathbb{R}^{d \times k}$，微调后的权重为 $W’ &#x3D; W + \Delta W$。LoRA 假设 $\Delta W$ 可以分解为两个低秩矩阵的乘积:</p><p>$$<br>\Delta W &#x3D; BA<br>$$</p><p>其中 $B \in \mathbb{R}^{d \times r}$, $A \in \mathbb{R}^{r \times k}$, $r \ll \min(d, k)$ 是秩(rank)。</p><p>前向传播时，输出为:</p><p>$$<br>h &#x3D; Wx + \Delta Wx &#x3D; Wx + BAx<br>$$</p><p>原模型参数 $W$ 保持冻结，只训练 $B$ 和 $A$。</p><p>参数量对比:原模型参数量为 $d \times k$，LoRA 参数量为 $d \times r + r \times k &#x3D; r(d + k)$。当 $r \ll \min(d, k)$ 时，LoRA 参数量远小于原模型。例如，对于 $d&#x3D;4096, k&#x3D;4096, r&#x3D;8$ 的情况，原模型参数量为 $4096 \times 4096 &#x3D; 16,777,216$，LoRA 参数量为 $8 \times (4096 + 4096) &#x3D; 65,536$，参数量减少了 256 倍!</p><p>因此可以总结 LoRA 的优势:显存占用大幅降低、训练速度更快、易于部署、防止过拟合。不过训练的效果通常情况会比全量调参更差一些。</p><p>如表 11.5 所示，LoRA 在不同模型规模下的效果对比。</p><div align="center">  <p>表 11.5 LoRA vs 全量微调对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-table-5.png" alt="" width="85%"/></div><p>LoRA 的关键超参数包括:秩(rank，r)，控制 LoRA 矩阵的秩，越大表达能力越强，但参数量也越多，典型值为 4-64，默认 8;Alpha($\alpha$)，LoRA 的缩放因子，实际更新为 $\Delta W &#x3D; \frac{\alpha}{r} BA$，控制 LoRA 的影响强度，典型值等于 rank;目标模块(target_modules)，指定哪些层应用 LoRA，通常选择注意力层(q_proj， k_proj， v_proj， o_proj)，也可以包括 MLP 层(gate_proj， up_proj， down_proj)。</p><h3 id="11-3-3-SFT-训练实战"><a href="#11-3-3-SFT-训练实战" class="headerlink" title="11.3.3 SFT 训练实战"></a>11.3.3 SFT 训练实战</h3><p>现在让我们使用 HelloAgents 进行 SFT 训练。完整的训练流程包括:准备数据集、配置 LoRA、设置训练参数、开始训练、保存模型。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><br><span class="hljs-comment"># 创建训练工具</span><br>rl_tool = RLTrainingTool()<br><br><span class="hljs-comment"># SFT训练</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-comment"># 训练配置</span><br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;sft&quot;</span>,<br>    <br>    <span class="hljs-comment"># 模型配置</span><br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/sft_model&quot;</span>,<br>    <br>    <span class="hljs-comment"># 数据配置</span><br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">100</span>,     <span class="hljs-comment"># 使用100个样本快速测试</span><br>    <br>    <span class="hljs-comment"># 训练参数</span><br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">3</span>,        <span class="hljs-comment"># 训练3轮</span><br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">4</span>,        <span class="hljs-comment"># 批次大小</span><br>    <span class="hljs-string">&quot;learning_rate&quot;</span>: <span class="hljs-number">5e-5</span>,  <span class="hljs-comment"># 学习率</span><br>    <br>    <span class="hljs-comment"># LoRA配置</span><br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,       <span class="hljs-comment"># 使用LoRA</span><br>    <span class="hljs-string">&quot;lora_rank&quot;</span>: <span class="hljs-number">8</span>,         <span class="hljs-comment"># LoRA秩</span><br>    <span class="hljs-string">&quot;lora_alpha&quot;</span>: <span class="hljs-number">16</span>,       <span class="hljs-comment"># LoRA alpha</span><br>&#125;)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n✓ 训练完成!&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 模型保存路径: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;model_path&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 训练样本数: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;num_samples&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 训练轮数: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;num_epochs&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 最终损失: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;final_loss&#x27;</span>]:<span class="hljs-number">.4</span>f&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>如果训练过程中损失逐渐下降，说明模型正在学习。</p><p><strong>（1）训练参数详解</strong></p><p>让我们详细了解各个训练参数的含义和调优建议。</p><p><strong>数据参数</strong>:</p><ul><li><code>max_samples</code>: 使用的训练样本数量。快速测试时可以用 100-1000 个样本，完整训练建议使用全部数据(7473 个样本)。更多数据通常带来更好的效果，但训练时间也更长。</li><li><code>split</code>: 数据集划分，默认”train”。可以设置为”train[:1000]”只使用前 1000 个样本。</li></ul><p><strong>训练参数</strong>:</p><ul><li><code>num_epochs</code>: 训练轮数。1 轮表示遍历整个数据集一次。太少(1-2 轮)可能欠拟合，太多(&gt;10 轮)可能过拟合。建议从 3 轮开始，观察损失曲线调整。</li><li><code>batch_size</code>: 每次更新使用的样本数。越大训练越稳定，但显存占用越高。建议根据显存调整:4GB 显存用 batch_size&#x3D;1-2，8GB 显存用 batch_size&#x3D;4-8，16GB 显存用 batch_size&#x3D;8-16。</li><li><code>learning_rate</code>: 学习率，控制参数更新的步长。太小(1e-6)收敛慢，太大(1e-3)可能不收敛。SFT 推荐 5e-5，LoRA 可以稍大(1e-4)。</li></ul><p><strong>LoRA 参数</strong>:</p><ul><li><code>use_lora</code>: 是否使用 LoRA。建议始终开启，除非有充足的显存。</li><li><code>lora_rank</code>: LoRA 秩，控制表达能力。4-8 适合小任务，16-32 适合复杂任务，64 适合大规模微调。</li><li><code>lora_alpha</code>: LoRA 缩放因子，通常设置为 rank 的 2 倍。rank&#x3D;8 时，alpha&#x3D;16;rank&#x3D;16 时，alpha&#x3D;32。</li></ul><p><strong>优化器参数</strong>:</p><ul><li><code>optimizer</code>: 优化器类型，默认”adamw”。AdamW 是最常用的选择，也可以尝试”sgd”或”adafactor”等。</li><li><code>weight_decay</code>: 权重衰减，防止过拟合。默认 0.01，可以尝试 0.001-0.1。</li><li><code>warmup_ratio</code>: 学习率预热比例。前 warmup_ratio 的步数学习率线性增加，然后线性衰减。默认 0.1(前 10%步数预热)。</li></ul><p><strong>（2）完整训练示例</strong></p><p>让我们进行一次完整的 SFT 训练，使用全部数据和最佳实践:</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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><br>rl_tool = RLTrainingTool()<br><br><span class="hljs-comment"># 完整SFT训练</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;sft&quot;</span>,<br><br>    <span class="hljs-comment"># 模型配置</span><br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/sft_full&quot;</span>,<br><br>    <span class="hljs-comment"># 数据配置</span><br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-literal">None</span>,    <span class="hljs-comment"># 使用全部数据(7473个样本)</span><br><br>    <span class="hljs-comment"># 训练参数</span><br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">3</span>,<br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">8</span>,<br>    <span class="hljs-string">&quot;learning_rate&quot;</span>: <span class="hljs-number">5e-5</span>,<br>    <span class="hljs-string">&quot;warmup_ratio&quot;</span>: <span class="hljs-number">0.1</span>,<br>    <span class="hljs-string">&quot;weight_decay&quot;</span>: <span class="hljs-number">0.01</span>,<br><br>    <span class="hljs-comment"># LoRA配置</span><br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>    <span class="hljs-string">&quot;lora_rank&quot;</span>: <span class="hljs-number">16</span>,        <span class="hljs-comment"># 使用更大的rank</span><br>    <span class="hljs-string">&quot;lora_alpha&quot;</span>: <span class="hljs-number">32</span>,<br>    <span class="hljs-string">&quot;lora_target_modules&quot;</span>: [<span class="hljs-string">&quot;q_proj&quot;</span>, <span class="hljs-string">&quot;k_proj&quot;</span>, <span class="hljs-string">&quot;v_proj&quot;</span>, <span class="hljs-string">&quot;o_proj&quot;</span>],<br><br>    <span class="hljs-comment"># 其他配置</span><br>    <span class="hljs-string">&quot;save_steps&quot;</span>: <span class="hljs-number">500</span>,      <span class="hljs-comment"># 每500步保存一次</span><br>    <span class="hljs-string">&quot;logging_steps&quot;</span>: <span class="hljs-number">100</span>,   <span class="hljs-comment"># 每100步记录一次</span><br>    <span class="hljs-string">&quot;eval_steps&quot;</span>: <span class="hljs-number">500</span>,      <span class="hljs-comment"># 每500步评估一次</span><br>&#125;)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;训练完成! 模型保存在: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;model_path&#x27;</span>]&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>这个配置适合在 8GB 显存的 GPU 上训练，预计耗时 30-60 分钟。</p><p><strong>（3）训练监控和调试</strong></p><p>在训练过程中，我们需要监控三个关键指标。损失(Loss)应该逐渐下降，如果不下降可能是学习率太小或数据有问题，如果下降后又上升则可能是学习率太大或出现过拟合。梯度范数(Gradient Norm)应该在 0.1-10 的合理范围内，过大(&gt;100)说明出现梯度爆炸需要降低学习率，过小(&lt;0.01)说明梯度消失需要检查模型配置。学习率(Learning Rate)应该按照 warmup 策略变化，前 10%步数线性增加，然后线性衰减到 0。</p><p>训练中常见的问题及解决方案:显存不足时可以减小 batch_size 或 max_length，使用梯度累积或更小的模型;训练速度慢时可以增大 batch_size，减少 logging 频率，或使用混合精度训练;损失不下降时可以增大学习率，检查数据格式，或增加训练轮数;过拟合时可以增大 weight_decay，减少训练轮数，或使用更多数据。</p><h3 id="11-3-4-模型评估"><a href="#11-3-4-模型评估" class="headerlink" title="11.3.4 模型评估"></a>11.3.4 模型评估</h3><p>训练完成后，我们需要评估模型的效果。评估指标包括:</p><ul><li><p><strong>准确率(Accuracy)</strong>:答案完全正确的比例，最直接的指标，范围 0-1，越高越好。</p></li><li><p><strong>平均奖励(Average Reward)</strong>:所有样本的平均奖励，综合考虑准确率、长度、步骤等因素，范围取决于奖励函数设计。</p></li><li><p><strong>推理质量(Reasoning Quality)</strong>:推理过程的清晰度和逻辑性，需要人工评估或使用专门的评估模型。</p></li></ul><p>使用 HelloAgents 评估模型:</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><br>rl_tool = RLTrainingTool()<br><br><span class="hljs-comment"># 评估SFT模型</span><br>eval_result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;evaluate&quot;</span>,<br>    <span class="hljs-string">&quot;model_path&quot;</span>: <span class="hljs-string">&quot;./models/sft_full&quot;</span>,<br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">100</span>,     <span class="hljs-comment"># 在100个测试样本上评估</span><br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>&#125;)<br><br>eval_data = json.loads(eval_result)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n评估结果:&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 准确率: <span class="hljs-subst">&#123;eval_data[<span class="hljs-string">&#x27;accuracy&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 平均奖励: <span class="hljs-subst">&#123;eval_data[<span class="hljs-string">&#x27;average_reward&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 测试样本数: <span class="hljs-subst">&#123;eval_data[<span class="hljs-string">&#x27;num_samples&#x27;</span>]&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>对于 Qwen3-0.6B 这样的小模型，SFT 后在 GSM8K 上达到 40-50%的准确率是正常的。通过强化学习，我们可以进一步提升到 60-70%。</p><p>为了更好地理解 SFT 的效果，我们可以对比不同阶段的模型:</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 评估预训练模型(未经SFT)</span><br>base_result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;evaluate&quot;</span>,<br>    <span class="hljs-string">&quot;model_path&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,<br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">100</span>,<br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">False</span>,<br>&#125;)<br>base_data = json.loads(base_result)<br><br><span class="hljs-comment"># 评估SFT模型</span><br>sft_result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;evaluate&quot;</span>,<br>    <span class="hljs-string">&quot;model_path&quot;</span>: <span class="hljs-string">&quot;./models/sft_full&quot;</span>,<br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">100</span>,<br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>&#125;)<br>sft_data = json.loads(sft_result)<br><br><span class="hljs-comment"># 对比结果</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;模型对比:&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;预训练模型准确率: <span class="hljs-subst">&#123;base_data[<span class="hljs-string">&#x27;accuracy&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;SFT模型准确率: <span class="hljs-subst">&#123;sft_data[<span class="hljs-string">&#x27;accuracy&#x27;</span>]&#125;</span>&quot;</span><br></code></pre></td></tr></table></figure><p>在本节中，我们学习了 SFT 的重要性(学习格式、建立基线)、LoRA 原理(低秩分解、参数高效)、SFT 训练实战(参数配置、训练监控)、模型评估(准确率、对比分析）。</p><h2 id="11-4-GRPO-训练"><a href="#11-4-GRPO-训练" class="headerlink" title="11.4 GRPO 训练"></a>11.4 GRPO 训练</h2><p>在完成 SFT 训练后，我们已经得到了一个能够生成结构化答案的模型。但是，SFT 模型只是学会了”模仿”训练数据中的推理过程，并没有真正学会”思考”。强化学习可以让模型通过试错来优化推理策略，从而超越训练数据的质量。</p><h3 id="11-4-1-从-PPO-到-GRPO"><a href="#11-4-1-从-PPO-到-GRPO" class="headerlink" title="11.4.1 从 PPO 到 GRPO"></a>11.4.1 从 PPO 到 GRPO</h3><p>在强化学习领域，PPO(Proximal Policy Optimization)<sup>[1]</sup>是最经典的算法之一。PPO 通过限制策略更新的幅度，保证训练的稳定性。但是，PPO 在 LLM 训练中存在一些问题:需要训练 Value Model(价值模型)，增加了训练复杂度和显存占用;需要同时维护四个模型(Policy Model、Reference Model、Value Model、Reward Model)，工程实现复杂;训练不稳定，容易出现奖励崩塌或策略退化。</p><p>GRPO(Group Relative Policy Optimization)<sup>[2]</sup>是一种简化的 PPO 变体，专门为 LLM 设计。GRPO 的核心思想是:不需要 Value Model，使用组内相对奖励代替绝对奖励;简化训练流程，只需要 Policy Model 和 Reference Model;提高训练稳定性，减少奖励崩塌的风险。</p><p>让我们通过数学公式来理解 GRPO 的原理。PPO 的目标函数为:</p><p>$$<br>J_{\text{PPO}}(\theta) &#x3D; \mathbb{E}<em>{s,a \sim \pi_\theta} \left[ \min\left( \frac{\pi_\theta(a|s)}{\pi</em>{\text{old}}(a|s)} A(s,a), \text{clip}\left(\frac{\pi_\theta(a|s)}{\pi_{\text{old}}(a|s)}, 1-\epsilon, 1+\epsilon\right) A(s,a) \right) \right]<br>$$</p><p>其中 $A(s,a)$ 是优势函数(Advantage)，需要 Value Model 来估计:</p><p>$$<br>A(s,a) &#x3D; Q(s,a) - V(s) &#x3D; r(s,a) + \gamma V(s’) - V(s)<br>$$</p><p>GRPO 的目标函数简化为:</p><p>$$<br>J_{\text{GRPO}}(\theta) &#x3D; \mathbb{E}<em>{s,a \sim \pi_\theta} \left[ \frac{\pi_\theta(a|s)}{\pi</em>{\text{ref}}(a|s)} \cdot (r(s,a) - \bar{r}<em>{\text{group}}) \right] - \beta \cdot D</em>{KL}(\pi_\theta || \pi_{\text{ref}})<br>$$</p><p>其中 $\bar{r}<em>{\text{group}}$ 是组内平均奖励，$\beta$ 是 KL 散度惩罚系数。关键区别在于:GRPO 使用 $r(s,a) - \bar{r}</em>{\text{group}}$ 代替优势函数 $A(s,a)$，不需要 Value Model;GRPO 使用组内相对奖励，减少奖励方差;GRPO 添加 KL 散度惩罚，防止策略偏离太远。</p><p>如图 11.7 所示，PPO 和 GRPO 的训练流程对比。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-7.png" alt="" width="85%"/>  <p>图 11.7 PPO vs GRPO 训练流程</p></div><p>可以看到，GRPO 省去了 Value Model 的训练，大大简化了流程。</p><p>如表 11.6 所示，PPO 和 GRPO 的详细对比。</p><div align="center">  <p>表 11.6 PPO vs GRPO 对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-table-6.png" alt="" width="85%"/></div><p>对于 LLM 训练，GRPO 是更好的选择，因为它更简单、更稳定、显存占用更低。</p><h3 id="11-4-2-GRPO-训练实战"><a href="#11-4-2-GRPO-训练实战" class="headerlink" title="11.4.2 GRPO 训练实战"></a>11.4.2 GRPO 训练实战</h3><p>现在让我们使用 HelloAgents 进行 GRPO 训练。GRPO 训练的前提是已经完成 SFT 训练，因为 GRPO 需要一个合理的初始策略。</p><p>基础 GRPO 训练示例:</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><br><span class="hljs-comment"># 创建训练工具</span><br>rl_tool = RLTrainingTool()<br><br><span class="hljs-comment"># GRPO训练</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-comment"># 训练配置</span><br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>    <br>    <span class="hljs-comment"># 模型配置</span><br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;./models/sft_full&quot;</span>,  <span class="hljs-comment"># 从SFT模型开始</span><br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/grpo_model&quot;</span>,<br>    <br>    <span class="hljs-comment"># 数据配置</span><br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">100</span>,     <span class="hljs-comment"># 使用100个样本快速测试</span><br>    <br>    <span class="hljs-comment"># 训练参数</span><br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">3</span>,<br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">4</span>,<br>    <span class="hljs-string">&quot;learning_rate&quot;</span>: <span class="hljs-number">1e-5</span>,  <span class="hljs-comment"># GRPO学习率通常比SFT小</span><br>    <br>    <span class="hljs-comment"># GRPO特定参数</span><br>    <span class="hljs-string">&quot;num_generations&quot;</span>: <span class="hljs-number">4</span>,   <span class="hljs-comment"># 每个问题生成4个答案</span><br>    <span class="hljs-string">&quot;kl_coef&quot;</span>: <span class="hljs-number">0.05</span>,        <span class="hljs-comment"># KL散度惩罚系数</span><br>    <br>    <span class="hljs-comment"># LoRA配置</span><br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>    <span class="hljs-string">&quot;lora_rank&quot;</span>: <span class="hljs-number">16</span>,<br>    <span class="hljs-string">&quot;lora_alpha&quot;</span>: <span class="hljs-number">32</span>,<br>    <br>    <span class="hljs-comment"># 奖励函数配置</span><br>    <span class="hljs-string">&quot;reward_type&quot;</span>: <span class="hljs-string">&quot;accuracy&quot;</span>,  <span class="hljs-comment"># 使用准确率奖励</span><br>&#125;)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n✓ 训练完成!&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 模型保存路径: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;model_path&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 训练样本数: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;num_samples&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 训练轮数: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;num_epochs&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - 平均奖励: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;average_reward&#x27;</span>]:<span class="hljs-number">.4</span>f&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>如果 GRPO 训练过程中平均奖励逐渐提升，KL 散度保持在合理范围内，说明训练正常进行。</p><p>GRPO 有一些特定的参数需要理解和调优。</p><p><strong>生成参数</strong>:</p><ul><li><code>num_generations</code>: 每个问题生成多少个答案。越多越好，但计算成本也越高。典型值为 4-8。生成多个答案的目的是计算组内相对奖励，增加训练信号的多样性。</li><li><code>max_new_tokens</code>: 每个答案最多生成多少个 token。太少可能截断答案，太多浪费计算。建议 256-512。</li><li><code>temperature</code>: 生成温度，控制随机性。0 表示贪婪解码，1 表示标准采样。GRPO 建议 0.7-1.0，保持一定的探索性。</li></ul><p><strong>优化参数</strong>:</p><ul><li><code>learning_rate</code>: GRPO 的学习率通常比 SFT 小，因为我们不想偏离 SFT 模型太远。建议 1e-5 到 5e-5。</li><li><code>kl_coef</code>: KL 散度惩罚系数，控制策略更新的幅度。太小(0.01)可能导致策略偏离太远，太大(0.5)可能限制学习。建议 0.05-0.1。</li><li><code>clip_range</code>: 策略比率裁剪范围，类似 PPO 的 epsilon。建议 0.2。</li></ul><p><strong>奖励参数</strong>:</p><ul><li><code>reward_type</code>: 奖励函数类型，可以是”accuracy”、”length_penalty”、”step”或”combined”。</li><li><code>reward_config</code>: 奖励函数的额外配置，如长度惩罚的目标长度、步骤奖励的系数等。</li></ul><p>让我们进行一次完整的 GRPO 训练，使用全部数据和最佳实践:</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><br>rl_tool = RLTrainingTool()<br><br><span class="hljs-comment"># 完整GRPO训练</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br><br>    <span class="hljs-comment"># 模型配置</span><br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;./models/sft_full&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/grpo_full&quot;</span>,<br>    <br>    <span class="hljs-comment"># 数据配置</span><br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-literal">None</span>,    <span class="hljs-comment"># 使用全部数据</span><br>    <br>    <span class="hljs-comment"># 训练参数</span><br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">3</span>,<br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">4</span>,<br>    <span class="hljs-string">&quot;learning_rate&quot;</span>: <span class="hljs-number">1e-5</span>,<br>    <span class="hljs-string">&quot;warmup_ratio&quot;</span>: <span class="hljs-number">0.1</span>,<br>    <br>    <span class="hljs-comment"># GRPO特定参数</span><br>    <span class="hljs-string">&quot;num_generations&quot;</span>: <span class="hljs-number">4</span>,<br>    <span class="hljs-string">&quot;max_new_tokens&quot;</span>: <span class="hljs-number">512</span>,<br>    <span class="hljs-string">&quot;temperature&quot;</span>: <span class="hljs-number">0.8</span>,<br>    <span class="hljs-string">&quot;kl_coef&quot;</span>: <span class="hljs-number">0.05</span>,<br>    <span class="hljs-string">&quot;clip_range&quot;</span>: <span class="hljs-number">0.2</span>,<br>    <br>    <span class="hljs-comment"># LoRA配置</span><br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>    <span class="hljs-string">&quot;lora_rank&quot;</span>: <span class="hljs-number">16</span>,<br>    <span class="hljs-string">&quot;lora_alpha&quot;</span>: <span class="hljs-number">32</span>,<br>    <br>    <span class="hljs-comment"># 奖励函数配置</span><br>    <span class="hljs-string">&quot;reward_type&quot;</span>: <span class="hljs-string">&quot;combined&quot;</span>,<br>    <span class="hljs-string">&quot;reward_config&quot;</span>: &#123;<br>        <span class="hljs-string">&quot;components&quot;</span>: [<br>            &#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;accuracy&quot;</span>, <span class="hljs-string">&quot;weight&quot;</span>: <span class="hljs-number">1.0</span>&#125;,<br>            &#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;length_penalty&quot;</span>, <span class="hljs-string">&quot;weight&quot;</span>: <span class="hljs-number">0.5</span>, <span class="hljs-string">&quot;target_length&quot;</span>: <span class="hljs-number">200</span>&#125;,<br>            &#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;step&quot;</span>, <span class="hljs-string">&quot;weight&quot;</span>: <span class="hljs-number">0.3</span>, <span class="hljs-string">&quot;step_bonus&quot;</span>: <span class="hljs-number">0.1</span>&#125;<br>        ]<br>    &#125;,<br>    <br>    <span class="hljs-comment"># 其他配置</span><br>    <span class="hljs-string">&quot;save_steps&quot;</span>: <span class="hljs-number">500</span>,<br>    <span class="hljs-string">&quot;logging_steps&quot;</span>: <span class="hljs-number">100</span>,<br>&#125;)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;训练完成! 模型保存在: <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;model_path&#x27;</span>]&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><h3 id="11-4-3-GRPO-训练过程解析"><a href="#11-4-3-GRPO-训练过程解析" class="headerlink" title="11.4.3 GRPO 训练过程解析"></a>11.4.3 GRPO 训练过程解析</h3><p>让我们深入理解 GRPO 的训练过程，看看每一步都发生了什么。</p><p><strong>（1）训练循环</strong></p><p>GRPO 的训练循环包括以下步骤:</p><ol><li><p><strong>采样阶段</strong>:对于每个问题，使用当前策略生成多个答案(<code>num_generations</code>个)。这些答案构成一个”组”，用于计算相对奖励。</p></li><li><p><strong>奖励计算</strong>:对每个生成的答案计算奖励 $r_i$。奖励可以是准确率、长度惩罚、步骤奖励或它们的组合。</p></li><li><p><strong>相对奖励</strong>:计算组内平均奖励 $\bar{r} &#x3D; \frac{1}{N}\sum_{i&#x3D;1}^{N} r_i$，然后计算相对奖励 $\hat{r}_i &#x3D; r_i - \bar{r}$。这样做的好处是减少奖励方差，使训练更稳定。</p></li><li><p><strong>策略更新</strong>:使用相对奖励更新策略，同时添加 KL 散度惩罚，防止策略偏离参考模型太远。</p></li><li><p><strong>重复</strong>:重复上述步骤，直到完成所有训练轮次。</p></li></ol><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 假设我们有一个问题</span><br>question = <span class="hljs-string">&quot;What is 48 + 24?&quot;</span><br><br><span class="hljs-comment"># 生成4个答案</span><br>answers = [<br>    <span class="hljs-string">&quot;48 + 24 = 72. Final Answer: 72&quot;</span>,      <span class="hljs-comment"># 正确</span><br>    <span class="hljs-string">&quot;48 + 24 = 72. Final Answer: 72&quot;</span>,      <span class="hljs-comment"># 正确</span><br>    <span class="hljs-string">&quot;48 + 24 = 70. Final Answer: 70&quot;</span>,      <span class="hljs-comment"># 错误</span><br>    <span class="hljs-string">&quot;Let me think... 72. Final Answer: 72&quot;</span> <span class="hljs-comment"># 正确但冗长</span><br>]<br><br><span class="hljs-comment"># 计算奖励(假设使用准确率 + 长度惩罚)</span><br>rewards = [<span class="hljs-number">1.0</span>, <span class="hljs-number">1.0</span>, <span class="hljs-number">0.0</span>, <span class="hljs-number">0.8</span>]  <span class="hljs-comment"># 第4个答案因为冗长被惩罚</span><br><br><span class="hljs-comment"># 计算组内平均奖励</span><br>avg_reward = (<span class="hljs-number">1.0</span> + <span class="hljs-number">1.0</span> + <span class="hljs-number">0.0</span> + <span class="hljs-number">0.8</span>) / <span class="hljs-number">4</span> = <span class="hljs-number">0.7</span><br><br><span class="hljs-comment"># 计算相对奖励</span><br>relative_rewards = [<br>    <span class="hljs-number">1.0</span> - <span class="hljs-number">0.7</span> = <span class="hljs-number">0.3</span>,   <span class="hljs-comment"># 正确且简洁,相对奖励为正</span><br>    <span class="hljs-number">1.0</span> - <span class="hljs-number">0.7</span> = <span class="hljs-number">0.3</span>,   <span class="hljs-comment"># 正确且简洁,相对奖励为正</span><br>    <span class="hljs-number">0.0</span> - <span class="hljs-number">0.7</span> = -<span class="hljs-number">0.7</span>,  <span class="hljs-comment"># 错误,相对奖励为负</span><br>    <span class="hljs-number">0.8</span> - <span class="hljs-number">0.7</span> = <span class="hljs-number">0.1</span>    <span class="hljs-comment"># 正确但冗长,相对奖励较小</span><br>]<br><br><span class="hljs-comment"># 策略更新:增加前两个答案的概率,减少第三个答案的概率</span><br></code></pre></td></tr></table></figure><p>可以看到，相对奖励机制鼓励模型生成”比平均水平更好”的答案，而不是简单地追求高奖励。这样可以减少奖励方差，提高训练稳定性。</p><p><strong>（2）KL 散度惩罚</strong></p><p>KL 散度惩罚是 GRPO 的关键组成部分，它防止策略偏离参考模型太远。KL 散度定义为:</p><p>$$<br>D_{KL}(\pi_\theta || \pi_{\text{ref}}) &#x3D; \mathbb{E}<em>{s,a \sim \pi_\theta} \left[ \log \frac{\pi_\theta(a|s)}{\pi</em>{\text{ref}}(a|s)} \right]<br>$$</p><p>在实践中，我们计算每个 token 的 KL 散度，然后求和:</p><p>$$<br>D_{KL} &#x3D; \sum_{t&#x3D;1}^{T} \log \frac{\pi_\theta(a_t|s, a_{&lt;t})}{\pi_{\text{ref}}(a_t|s, a_{&lt;t})}<br>$$</p><p>KL 散度越大，说明当前策略与参考模型差异越大。通过添加 KL 散度惩罚项 $-\beta \cdot D_{KL}$，我们限制策略更新的幅度，避免”遗忘”SFT 阶段学到的知识。</p><p><code>kl_coef</code> ($\beta$) 的选择很重要:</p><ul><li>太小(0.01):策略可能偏离太远，导致输出格式混乱或质量下降</li><li>太大(0.5):策略更新受限，学习缓慢，难以超越 SFT 模型</li><li>建议(0.05-0.1):平衡探索和稳定性</li></ul><p><strong>（3）训练监控</strong></p><p>在 GRPO 训练过程中，我们需要监控以下指标:</p><ul><li><p><strong>平均奖励(Average Reward)</strong>:应该逐渐上升。如果奖励不上升，可能是学习率太小、KL 惩罚太大、奖励函数设计不合理。如果奖励先升后降，可能是过拟合或奖励崩塌。</p></li><li><p><strong>KL 散度(KL Divergence)</strong>:应该保持在合理范围内(0.01-0.1)。如果 KL 散度过大(&gt;0.5)，说明策略偏离太远，需要增大 kl_coef 或降低学习率。如果 KL 散度过小(&lt;0.001)，说明策略几乎没有更新，需要减小 kl_coef 或增大学习率。</p></li><li><p><strong>准确率(Accuracy)</strong>:应该逐渐提升。这是最直观的指标，反映模型的实际能力。</p></li><li><p><strong>生成质量(Generation Quality)</strong>:需要人工检查生成的答案，确保格式正确、推理清晰。</p></li></ul><p>HelloAgents 集成了两种主流的训练监控工具:Weights &amp; Biases(wandb)和 TensorBoard。</p><p><strong>方式 1:使用 Weights &amp; Biases(推荐)</strong></p><p>Weights &amp; Biases 是目前最流行的机器学习实验跟踪平台，提供了强大的可视化和实验管理功能。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> os<br><br><span class="hljs-comment"># 1. 设置wandb(需要先注册账号: https://wandb.ai)</span><br>os.environ[<span class="hljs-string">&quot;WANDB_PROJECT&quot;</span>] = <span class="hljs-string">&quot;hello-agents-grpo&quot;</span>  <span class="hljs-comment"># 项目名称</span><br>os.environ[<span class="hljs-string">&quot;WANDB_LOG_MODEL&quot;</span>] = <span class="hljs-string">&quot;false&quot;</span>            <span class="hljs-comment"># 不上传模型文件</span><br><br><span class="hljs-comment"># 2. 在训练配置中启用wandb</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/grpo_monitored&quot;</span>,<br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>    <span class="hljs-comment"># wandb会自动记录所有训练指标</span><br>&#125;)<br><br><span class="hljs-comment"># 训练完成后,访问 https://wandb.ai 查看训练曲线</span><br></code></pre></td></tr></table></figure><p>wandb 会自动记录以下指标:</p><ul><li><code>train/reward</code>: 平均奖励</li><li><code>train/kl</code>: KL 散度</li><li><code>train/loss</code>: 训练损失</li><li><code>train/learning_rate</code>: 学习率</li><li><code>train/epoch</code>: 训练轮数</li></ul><p><strong>方式 2:使用 TensorBoard</strong></p><p>TensorBoard 是 TensorFlow 提供的可视化工具，也支持 PyTorch 训练。</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><code class="hljs python"><span class="hljs-comment"># 1. 训练时会自动在output_dir下创建tensorboard日志</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/grpo_tb&quot;</span>,<br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>&#125;)<br><br><span class="hljs-comment"># 2. 启动TensorBoard查看训练曲线</span><br><span class="hljs-comment"># 在命令行运行:</span><br><span class="hljs-comment"># tensorboard --logdir=./models/grpo_tb</span><br><span class="hljs-comment"># 然后访问 http://localhost:6006</span><br></code></pre></td></tr></table></figure><p><strong>方式 3:离线监控(无需外部工具)</strong></p><p>如果不想使用 wandb 或 TensorBoard，也可以通过训练日志进行监控:</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><code class="hljs python"><span class="hljs-comment"># 训练过程会打印详细日志</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/grpo_simple&quot;</span>,<br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">2</span>,<br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>&#125;)<br><br><span class="hljs-comment"># 日志示例:</span><br><span class="hljs-comment"># Epoch 1/2 | Step 100/500 | Reward: 0.45 | KL: 0.023 | Loss: 1.234</span><br><span class="hljs-comment"># Epoch 1/2 | Step 200/500 | Reward: 0.52 | KL: 0.031 | Loss: 1.156</span><br><span class="hljs-comment"># ...</span><br></code></pre></td></tr></table></figure><p>在 GRPO 训练中，可能会遇到一些问题。当奖励不上升时，可能是学习率太小或 KL 惩罚太大限制了策略更新，也可能是奖励函数设计不合理或 SFT 模型质量太差，此时可以增大学习率(从 1e-5 到 5e-5)、减小 kl_coef(从 0.1 到 0.05)、检查奖励函数或重新训练 SFT 模型。</p><p>当 KL 散度爆炸(超过 0.5 甚至 1.0)导致生成答案格式混乱时，通常是学习率太大或 KL 惩罚太小，或者奖励函数过于激进，可以降低学习率(从 5e-5 到 1e-5)、增大 kl_coef(从 0.05 到 0.1)、调整奖励函数或使用梯度裁剪。</p><p>当生成质量下降(准确率提升但格式混乱、推理不清晰)时，可能是奖励函数只关注准确率忽略了其他质量指标，或 KL 惩罚太小导致模型偏离 SFT 太远，或出现过拟合，此时应使用组合奖励函数同时优化多个指标、增大 kl_coef 保持一致性、减少训练轮数或增加训练数据。</p><p>GRPO 训练的显存占用比 SFT 高，因为需要同时生成多个答案并存储参考模型输出，容易出现 OOM。可以通过减小 num_generations(从 8 到 4)、batch_size(从 4 到 2)或 max_new_tokens(从 512 到 256)，或使用梯度检查点和混合精度训练来缓解。</p><h2 id="11-5-模型评估与分析"><a href="#11-5-模型评估与分析" class="headerlink" title="11.5 模型评估与分析"></a>11.5 模型评估与分析</h2><p>训练完成后，我们需要全面评估模型的性能，不仅要看准确率这一个指标，还要深入分析模型的推理质量、错误模式、泛化能力等。本节将介绍如何系统地评估和分析 Agentic RL 模型。</p><h3 id="11-5-1-评估指标体系"><a href="#11-5-1-评估指标体系" class="headerlink" title="11.5.1 评估指标体系"></a>11.5.1 评估指标体系</h3><p>一个好的评估体系应该是多维度的，从不同角度衡量模型的能力。我们将评估指标分为三类:准确性指标、效率指标、质量指标。</p><p><strong>（1）准确性指标</strong></p><p>准确性指标衡量模型是否能够得出正确答案。</p><p><strong>准确率(Accuracy)</strong>:最基本的指标，答案完全正确的比例。计算公式为:<br>$$<br>\text{Accuracy} &#x3D; \frac{\text{正确答案数}}{\text{总问题数}}<br>$$</p><p>优点是简单直观，易于理解和比较。缺点是无法区分”接近正确”和”完全错误”,对于复杂任务可能过于粗糙。</p><p><strong>Top-K 准确率</strong>:生成 K 个答案，只要有一个正确就算对。计算公式为:<br>$$<br>\text{Accuracy@K} &#x3D; \frac{\text{至少有一个正确答案的问题数}}{\text{总问题数}}<br>$$</p><p>这个指标反映了模型的”潜力”，即通过多次采样能否找到正确答案。</p><p><strong>数值误差(Numerical Error)</strong>:对于数学问题，可以计算预测值与真实值的误差。计算公式为:</p><p>$$<br>\text{Error} &#x3D; \frac{1}{N} \sum_{i&#x3D;1}^{N} |y_i - \hat{y}_i|<br>$$</p><p>这个指标可以区分”接近正确”(如预测 72.5，真实 72)和”完全错误”(如预测 100，真实 72)。</p><p><strong>（2）效率指标</strong></p><p>效率指标衡量模型生成答案的成本。</p><p><strong>平均长度(Average Length)</strong>:生成答案的平均 token 数。计算公式为:</p><p>$$<br>\text{Avg Length} &#x3D; \frac{1}{N} \sum_{i&#x3D;1}^{N} |y_i|<br>$$</p><p>更短的答案意味着更低的推理成本和更快的响应速度。</p><p><strong>推理步骤数(Reasoning Steps)</strong>:答案中包含的推理步骤数量。计算公式为:</p><p>$$<br>\text{Avg Steps} &#x3D; \frac{1}{N} \sum_{i&#x3D;1}^{N} s_i<br>$$</p><p>适当的步骤数(2-5 步)说明模型能够系统地分解问题，过多的步骤可能说明推理冗余。</p><p><strong>推理时间(Inference Time)</strong>:生成一个答案所需的时间。这个指标在实际部署中很重要，影响用户体验。</p><p><strong>（3）质量指标</strong></p><p>质量指标衡量答案的可读性和可解释性。</p><p><strong>格式正确率(Format Correctness)</strong>:答案是否符合预期格式(如包含”Step 1”， “Final Answer”等标记)。计算公式为:<br>$$<br>\text{Format Correctness} &#x3D; \frac{\text{格式正确的答案数}}{\text{总答案数}}<br>$$</p><p>格式正确是基本要求，格式混乱的答案即使结果正确也难以使用。</p><p><strong>推理连贯性(Reasoning Coherence)</strong>:推理步骤之间是否逻辑连贯。这个指标通常需要人工评估或使用专门的评估模型。</p><p><strong>可解释性(Explainability)</strong>:答案是否容易理解和验证。包含清晰步骤的答案比直接给出结果的答案更具可解释性。</p><p>如表 11.7 所示，不同指标的对比。</p><div align="center">  <p>表 11.7 评估指标对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-table-7.png" alt="" width="85%"/></div><h3 id="11-5-2-评估实战"><a href="#11-5-2-评估实战" class="headerlink" title="11.5.2 评估实战"></a>11.5.2 评估实战</h3><p>HelloAgents 提供了全面的评估功能，可以一次性计算多个指标。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><br>rl_tool = RLTrainingTool()<br><br><span class="hljs-comment"># 全面评估</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;全面评估GRPO模型&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;evaluate&quot;</span>,<br>    <span class="hljs-string">&quot;model_path&quot;</span>: <span class="hljs-string">&quot;./models/grpo_full&quot;</span>,<br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">200</span>,<br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>    <br>    <span class="hljs-comment"># 评估配置</span><br>    <span class="hljs-string">&quot;metrics&quot;</span>: [<br>        <span class="hljs-string">&quot;accuracy&quot;</span>,           <span class="hljs-comment"># 准确率</span><br>        <span class="hljs-string">&quot;accuracy_at_k&quot;</span>,      <span class="hljs-comment"># Top-K准确率</span><br>        <span class="hljs-string">&quot;average_length&quot;</span>,     <span class="hljs-comment"># 平均长度</span><br>        <span class="hljs-string">&quot;average_steps&quot;</span>,      <span class="hljs-comment"># 平均步骤数</span><br>        <span class="hljs-string">&quot;format_correctness&quot;</span>, <span class="hljs-comment"># 格式正确率</span><br>    ],<br>    <span class="hljs-string">&quot;k&quot;</span>: <span class="hljs-number">3</span>,  <span class="hljs-comment"># Top-3准确率</span><br>&#125;)<br><br><span class="hljs-comment"># 解析结果</span><br>eval_data = json.loads(result)<br><br><span class="hljs-comment"># 打印结果</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n评估结果:&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  准确率: <span class="hljs-subst">&#123;eval_data[<span class="hljs-string">&#x27;accuracy&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  平均奖励: <span class="hljs-subst">&#123;eval_data[<span class="hljs-string">&#x27;average_reward&#x27;</span>]&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  测试样本数: <span class="hljs-subst">&#123;eval_data[<span class="hljs-string">&#x27;num_samples&#x27;</span>]&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>我们可以对比预训练模型、SFT 模型、GRPO 模型的性能:</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 评估三个模型</span><br>models = [<br>    (<span class="hljs-string">&quot;预训练模型&quot;</span>, <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>, <span class="hljs-literal">False</span>),<br>    (<span class="hljs-string">&quot;SFT模型&quot;</span>, <span class="hljs-string">&quot;./models/sft_full&quot;</span>, <span class="hljs-literal">True</span>),<br>    (<span class="hljs-string">&quot;GRPO模型&quot;</span>, <span class="hljs-string">&quot;./models/grpo_full&quot;</span>, <span class="hljs-literal">True</span>),<br>]<br><br>results = []<br><span class="hljs-keyword">for</span> name, path, use_lora <span class="hljs-keyword">in</span> models:<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n评估<span class="hljs-subst">&#123;name&#125;</span>...&quot;</span>)<br>    result = rl_tool.run(&#123;<br>        <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;evaluate&quot;</span>,<br>        <span class="hljs-string">&quot;model_path&quot;</span>: path,<br>        <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">200</span>,<br>        <span class="hljs-string">&quot;use_lora&quot;</span>: use_lora,<br>        <span class="hljs-string">&quot;metrics&quot;</span>: [<span class="hljs-string">&quot;accuracy&quot;</span>, <span class="hljs-string">&quot;average_length&quot;</span>, <span class="hljs-string">&quot;format_correctness&quot;</span>],<br>    &#125;)<br>    results.append((name, result))<br><br><span class="hljs-comment"># 打印对比表格</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n&quot;</span> + <span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">70</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;<span class="hljs-subst">&#123;<span class="hljs-string">&#x27;模型&#x27;</span>:&lt;<span class="hljs-number">15</span>&#125;</span> <span class="hljs-subst">&#123;<span class="hljs-string">&#x27;准确率&#x27;</span>:&lt;<span class="hljs-number">12</span>&#125;</span> <span class="hljs-subst">&#123;<span class="hljs-string">&#x27;平均长度&#x27;</span>:&lt;<span class="hljs-number">15</span>&#125;</span> <span class="hljs-subst">&#123;<span class="hljs-string">&#x27;格式正确率&#x27;</span>:&lt;<span class="hljs-number">12</span>&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">70</span>)<br><span class="hljs-keyword">for</span> name, result <span class="hljs-keyword">in</span> results:<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;<span class="hljs-subst">&#123;name:&lt;<span class="hljs-number">15</span>&#125;</span> <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;accuracy&#x27;</span>]:&lt;<span class="hljs-number">12.2</span>%&#125;</span> <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;average_length&#x27;</span>]:&lt;<span class="hljs-number">15.1</span>f&#125;</span> <span class="hljs-subst">&#123;result[<span class="hljs-string">&#x27;format_correctness&#x27;</span>]:&lt;<span class="hljs-number">12.2</span>%&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">70</span>)<br></code></pre></td></tr></table></figure><h3 id="11-5-3-错误分析"><a href="#11-5-3-错误分析" class="headerlink" title="11.5.3 错误分析"></a>11.5.3 错误分析</h3><p>仅仅知道准确率是不够的，我们需要深入分析模型在哪些类型的问题上容易出错，从而指导后续改进。模型的错误可以分为四类:计算错误(推理步骤正确但计算出错，如”48&#x2F;2&#x3D;25”，说明数值计算能力不足)、推理错误(推理逻辑错误导致解题思路不对，如先加后除而非先除后加，说明逻辑推理能力不足)、理解错误(没有正确理解问题，如问题问”总共”但只计算了一部分，说明语言理解能力不足)、格式错误(答案正确但格式不符合要求，如缺少”Final Answer:”标记，说明格式学习不足)。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><br>rl_tool = RLTrainingTool()<br><br><span class="hljs-comment"># 评估并收集错误样本</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;evaluate&quot;</span>,<br>    <span class="hljs-string">&quot;model_path&quot;</span>: <span class="hljs-string">&quot;./models/grpo_full&quot;</span>,<br>    <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">200</span>,<br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>    <span class="hljs-string">&quot;return_details&quot;</span>: <span class="hljs-literal">True</span>,  <span class="hljs-comment"># 返回详细结果</span><br>&#125;)<br><br><span class="hljs-comment"># 分析错误样本</span><br>errors = result[<span class="hljs-string">&#x27;errors&#x27;</span>]  <span class="hljs-comment"># 错误样本列表</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;总错误数: <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(errors)&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 按错误类型分类</span><br>error_types = &#123;<br>    <span class="hljs-string">&quot;计算错误&quot;</span>: <span class="hljs-number">0</span>,<br>    <span class="hljs-string">&quot;推理错误&quot;</span>: <span class="hljs-number">0</span>,<br>    <span class="hljs-string">&quot;理解错误&quot;</span>: <span class="hljs-number">0</span>,<br>    <span class="hljs-string">&quot;格式错误&quot;</span>: <span class="hljs-number">0</span>,<br>&#125;<br><br><span class="hljs-keyword">for</span> error <span class="hljs-keyword">in</span> errors:<br>    question = error[<span class="hljs-string">&#x27;question&#x27;</span>]<br>    prediction = error[<span class="hljs-string">&#x27;prediction&#x27;</span>]<br>    ground_truth = error[<span class="hljs-string">&#x27;ground_truth&#x27;</span>]<br>    <br>    <span class="hljs-comment"># 简单的错误分类逻辑(实际应用中可能需要更复杂的分析)</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;Final Answer:&quot;</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> prediction:<br>        error_types[<span class="hljs-string">&quot;格式错误&quot;</span>] += <span class="hljs-number">1</span><br>    <span class="hljs-keyword">elif</span> <span class="hljs-string">&quot;Step&quot;</span> <span class="hljs-keyword">in</span> prediction:<br>        <span class="hljs-comment"># 有推理步骤,可能是计算或推理错误</span><br>        <span class="hljs-comment"># 这里需要更细致的分析</span><br>        error_types[<span class="hljs-string">&quot;计算错误&quot;</span>] += <span class="hljs-number">1</span><br>    <span class="hljs-keyword">else</span>:<br>        error_types[<span class="hljs-string">&quot;理解错误&quot;</span>] += <span class="hljs-number">1</span><br><br><span class="hljs-comment"># 打印错误分布</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n错误类型分布:&quot;</span>)<br><span class="hljs-keyword">for</span> error_type, count <span class="hljs-keyword">in</span> error_types.items():<br>    percentage = count / <span class="hljs-built_in">len</span>(errors) * <span class="hljs-number">100</span><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  <span class="hljs-subst">&#123;error_type&#125;</span>: <span class="hljs-subst">&#123;count&#125;</span> (<span class="hljs-subst">&#123;percentage:<span class="hljs-number">.1</span>f&#125;</span>%)&quot;</span>)<br></code></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><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs bash">总错误数: 76<br><br>错误类型分布:<br>  计算错误: 32 (42.1%)<br>  推理错误: 18 (23.7%)<br>  理解错误: 22 (28.9%)<br>  格式错误: 4 (5.3%)<br></code></pre></td></tr></table></figure><p>可以看到，计算错误是最主要的错误类型(42.1%)，说明模型的数值计算能力需要加强。格式错误很少(5.3%)，说明 SFT 训练效果良好。我们还可以分析模型在不同难度的问题上的表现:</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 按推理步骤数分组</span><br>step_groups = &#123;<br>    <span class="hljs-string">&quot;简单(1-2步)&quot;</span>: [],<br>    <span class="hljs-string">&quot;中等(3-4步)&quot;</span>: [],<br>    <span class="hljs-string">&quot;困难(5+步)&quot;</span>: [],<br>&#125;<br><br><span class="hljs-keyword">for</span> sample <span class="hljs-keyword">in</span> result[<span class="hljs-string">&#x27;details&#x27;</span>]:<br>    steps = sample[<span class="hljs-string">&#x27;ground_truth_steps&#x27;</span>]  <span class="hljs-comment"># 真实答案的步骤数</span><br>    correct = sample[<span class="hljs-string">&#x27;correct&#x27;</span>]<br>    <br>    <span class="hljs-keyword">if</span> steps &lt;= <span class="hljs-number">2</span>:<br>        step_groups[<span class="hljs-string">&quot;简单(1-2步)&quot;</span>].append(correct)<br>    <span class="hljs-keyword">elif</span> steps &lt;= <span class="hljs-number">4</span>:<br>        step_groups[<span class="hljs-string">&quot;中等(3-4步)&quot;</span>].append(correct)<br>    <span class="hljs-keyword">else</span>:<br>        step_groups[<span class="hljs-string">&quot;困难(5+步)&quot;</span>].append(correct)<br><br><span class="hljs-comment"># 计算每组的准确率</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n不同难度的准确率:&quot;</span>)<br><span class="hljs-keyword">for</span> group_name, results <span class="hljs-keyword">in</span> step_groups.items():<br>    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(results) &gt; <span class="hljs-number">0</span>:<br>        accuracy = <span class="hljs-built_in">sum</span>(results) / <span class="hljs-built_in">len</span>(results)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  <span class="hljs-subst">&#123;group_name&#125;</span>: <span class="hljs-subst">&#123;accuracy:<span class="hljs-number">.2</span>%&#125;</span> (<span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(results)&#125;</span>个样本)&quot;</span>)<br></code></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></pre></td><td class="code"><pre><code class="hljs bash">不同难度的准确率:<br>  简单(1-2步): 78.50% (85个样本)<br>  中等(3-4步): 58.30% (96个样本)<br>  困难(5+步): 31.60% (19个样本)<br></code></pre></td></tr></table></figure><p>可以看到，模型在简单问题上表现良好(78.5%)，但在困难问题上表现较差(31.6%)。这说明模型的多步推理能力还有待提升</p><h3 id="11-5-4-改进方向"><a href="#11-5-4-改进方向" class="headerlink" title="11.5.4 改进方向"></a>11.5.4 改进方向</h3><p>基于评估和分析结果，我们可以确定模型的改进方向，如图 11.8 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-8.png" alt="" width="85%"/>  <p>图 11.8 模型改进迭代流程</p></div><p>这是一个持续迭代的过程:训练模型 → 评估性能 → 分析错误 → 确定问题 → 选择改进方向 → 重新训练。通过多次迭代，模型性能会不断提升。</p><h2 id="11-6-完整训练流程实战"><a href="#11-6-完整训练流程实战" class="headerlink" title="11.6 完整训练流程实战"></a>11.6 完整训练流程实战</h2><p>在前面的章节中，我们分别学习了数据准备、SFT 训练、GRPO 训练和模型评估。现在，让我们把这些知识整合起来，完成一个端到端的 Agentic RL 训练流程。</p><h3 id="11-6-1-端到端训练流程"><a href="#11-6-1-端到端训练流程" class="headerlink" title="11.6.1 端到端训练流程"></a>11.6.1 端到端训练流程</h3><p>一个完整的 Agentic RL 训练流程包括以下阶段:数据准备、SFT 训练、SFT 评估、GRPO 训练、GRPO 评估、模型部署。如图 11.9 所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-9.png" alt="" width="85%"/>  <p>图 11.9 端到端训练流程</p></div><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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">完整的Agentic RL训练流程</span><br><span class="hljs-string">从数据准备到模型部署的端到端示例</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><span class="hljs-keyword">import</span> json<br><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">AgenticRLPipeline</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;Agentic RL训练流水线&quot;&quot;&quot;</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, config_path=<span class="hljs-string">&quot;config.json&quot;</span></span>):<br>        <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">        初始化训练流水线</span><br><span class="hljs-string">        </span><br><span class="hljs-string">        Args:</span><br><span class="hljs-string">            config_path: 配置文件路径</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        self.rl_tool = RLTrainingTool()<br>        self.config = self.load_config(config_path)<br>        self.results = &#123;&#125;<br>        <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">load_config</span>(<span class="hljs-params">self, config_path</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;加载配置文件&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(config_path, <span class="hljs-string">&#x27;r&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>            <span class="hljs-keyword">return</span> json.load(f)<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">log</span>(<span class="hljs-params">self, message</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;记录日志&quot;&quot;&quot;</span><br>        timestamp = datetime.now().strftime(<span class="hljs-string">&quot;%Y-%m-%d %H:%M:%S&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[<span class="hljs-subst">&#123;timestamp&#125;</span>] <span class="hljs-subst">&#123;message&#125;</span>&quot;</span>)<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">stage1_prepare_data</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;阶段1: 数据准备&quot;&quot;&quot;</span><br>        self.log(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br>        self.log(<span class="hljs-string">&quot;阶段1: 数据准备&quot;</span>)<br>        self.log(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br><br>        <span class="hljs-comment"># 加载并检查数据集</span><br>        result = self.rl_tool.run(&#123;<br>            <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;load_dataset&quot;</span>,<br>            <span class="hljs-string">&quot;format&quot;</span>: <span class="hljs-string">&quot;sft&quot;</span>,<br>            <span class="hljs-string">&quot;max_samples&quot;</span>: self.config[<span class="hljs-string">&quot;data&quot;</span>][<span class="hljs-string">&quot;max_samples&quot;</span>],<br>        &#125;)<br><br>        <span class="hljs-comment"># 解析JSON结果</span><br>        dataset_info = json.loads(result)<br><br>        self.log(<span class="hljs-string">f&quot;✓ 数据集加载完成&quot;</span>)<br>        self.log(<span class="hljs-string">f&quot;  - 样本数: <span class="hljs-subst">&#123;dataset_info[<span class="hljs-string">&#x27;dataset_size&#x27;</span>]&#125;</span>&quot;</span>)<br>        self.log(<span class="hljs-string">f&quot;  - 格式: <span class="hljs-subst">&#123;dataset_info[<span class="hljs-string">&#x27;format&#x27;</span>]&#125;</span>&quot;</span>)<br>        self.log(<span class="hljs-string">f&quot;  - 数据列: <span class="hljs-subst">&#123;<span class="hljs-string">&#x27;, &#x27;</span>.join(dataset_info[<span class="hljs-string">&#x27;sample_keys&#x27;</span>])&#125;</span>&quot;</span>)<br><br>        self.results[<span class="hljs-string">&quot;data&quot;</span>] = dataset_info<br><br>        <span class="hljs-keyword">return</span> dataset_info<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">stage2_sft_training</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;阶段2: SFT训练&quot;&quot;&quot;</span><br>        self.log(<span class="hljs-string">&quot;\n&quot;</span> + <span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br>        self.log(<span class="hljs-string">&quot;阶段2: SFT训练&quot;</span>)<br>        self.log(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br><br>        sft_config = self.config[<span class="hljs-string">&quot;sft&quot;</span>]<br><br>        result = self.rl_tool.run(&#123;<br>            <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>            <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;sft&quot;</span>,<br>            <span class="hljs-string">&quot;model_name&quot;</span>: self.config[<span class="hljs-string">&quot;model&quot;</span>][<span class="hljs-string">&quot;base_model&quot;</span>],<br>            <span class="hljs-string">&quot;output_dir&quot;</span>: sft_config[<span class="hljs-string">&quot;output_dir&quot;</span>],<br>            <span class="hljs-string">&quot;max_samples&quot;</span>: self.config[<span class="hljs-string">&quot;data&quot;</span>][<span class="hljs-string">&quot;max_samples&quot;</span>],<br>            <span class="hljs-string">&quot;num_epochs&quot;</span>: sft_config[<span class="hljs-string">&quot;num_epochs&quot;</span>],<br>            <span class="hljs-string">&quot;batch_size&quot;</span>: sft_config[<span class="hljs-string">&quot;batch_size&quot;</span>],<br>            <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>            <span class="hljs-comment"># 训练监控配置</span><br>            <span class="hljs-string">&quot;use_wandb&quot;</span>: self.config.get(<span class="hljs-string">&quot;monitoring&quot;</span>, &#123;&#125;).get(<span class="hljs-string">&quot;use_wandb&quot;</span>, <span class="hljs-literal">False</span>),<br>            <span class="hljs-string">&quot;use_tensorboard&quot;</span>: self.config.get(<span class="hljs-string">&quot;monitoring&quot;</span>, &#123;&#125;).get(<span class="hljs-string">&quot;use_tensorboard&quot;</span>, <span class="hljs-literal">True</span>),<br>            <span class="hljs-string">&quot;wandb_project&quot;</span>: self.config.get(<span class="hljs-string">&quot;monitoring&quot;</span>, &#123;&#125;).get(<span class="hljs-string">&quot;wandb_project&quot;</span>, <span class="hljs-literal">None</span>),<br>        &#125;)<br><br>        <span class="hljs-comment"># 解析JSON结果</span><br>        result_data = json.loads(result)<br><br>        self.log(<span class="hljs-string">f&quot;✓ SFT训练完成&quot;</span>)<br>        self.log(<span class="hljs-string">f&quot;  - 模型路径: <span class="hljs-subst">&#123;result_data[<span class="hljs-string">&#x27;output_dir&#x27;</span>]&#125;</span>&quot;</span>)<br>        self.log(<span class="hljs-string">f&quot;  - 状态: <span class="hljs-subst">&#123;result_data[<span class="hljs-string">&#x27;status&#x27;</span>]&#125;</span>&quot;</span>)<br><br>        self.results[<span class="hljs-string">&quot;sft_training&quot;</span>] = result_data<br><br>        <span class="hljs-keyword">return</span> result_data[<span class="hljs-string">&quot;output_dir&quot;</span>]<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">stage3_sft_evaluation</span>(<span class="hljs-params">self, model_path</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;阶段3: SFT评估&quot;&quot;&quot;</span><br>        self.log(<span class="hljs-string">&quot;\n&quot;</span> + <span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br>        self.log(<span class="hljs-string">&quot;阶段3: SFT评估&quot;</span>)<br>        self.log(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br>        <br>        result = self.rl_tool.run(&#123;<br>            <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;evaluate&quot;</span>,<br>            <span class="hljs-string">&quot;model_path&quot;</span>: model_path,<br>            <span class="hljs-string">&quot;max_samples&quot;</span>: self.config[<span class="hljs-string">&quot;eval&quot;</span>][<span class="hljs-string">&quot;max_samples&quot;</span>],<br>            <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>        &#125;)<br>        eval_data = json.loads(result)<br><br>        self.log(<span class="hljs-string">f&quot;✓ SFT评估完成&quot;</span>)<br>        self.log(<span class="hljs-string">f&quot;  - 准确率: <span class="hljs-subst">&#123;eval_data[<span class="hljs-string">&#x27;accuracy&#x27;</span>]&#125;</span>&quot;</span>)<br>        self.log(<span class="hljs-string">f&quot;  - 平均奖励: <span class="hljs-subst">&#123;eval_data[<span class="hljs-string">&#x27;average_reward&#x27;</span>]&#125;</span>&quot;</span>)<br><br>        self.results[<span class="hljs-string">&quot;sft_evaluation&quot;</span>] = eval_data<br><br>        <span class="hljs-keyword">return</span> eval_data<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">stage4_grpo_training</span>(<span class="hljs-params">self, sft_model_path</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;阶段4: GRPO训练&quot;&quot;&quot;</span><br>        self.log(<span class="hljs-string">&quot;\n&quot;</span> + <span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br>        self.log(<span class="hljs-string">&quot;阶段4: GRPO训练&quot;</span>)<br>        self.log(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br><br>        grpo_config = self.config[<span class="hljs-string">&quot;grpo&quot;</span>]<br><br>        result = self.rl_tool.run(&#123;<br>            <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>            <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>            <span class="hljs-string">&quot;model_name&quot;</span>: sft_model_path,<br>            <span class="hljs-string">&quot;output_dir&quot;</span>: grpo_config[<span class="hljs-string">&quot;output_dir&quot;</span>],<br>            <span class="hljs-string">&quot;max_samples&quot;</span>: self.config[<span class="hljs-string">&quot;data&quot;</span>][<span class="hljs-string">&quot;max_samples&quot;</span>],<br>            <span class="hljs-string">&quot;num_epochs&quot;</span>: grpo_config[<span class="hljs-string">&quot;num_epochs&quot;</span>],<br>            <span class="hljs-string">&quot;batch_size&quot;</span>: grpo_config[<span class="hljs-string">&quot;batch_size&quot;</span>],<br>            <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>            <span class="hljs-comment"># 训练监控配置</span><br>            <span class="hljs-string">&quot;use_wandb&quot;</span>: self.config.get(<span class="hljs-string">&quot;monitoring&quot;</span>, &#123;&#125;).get(<span class="hljs-string">&quot;use_wandb&quot;</span>, <span class="hljs-literal">False</span>),<br>            <span class="hljs-string">&quot;use_tensorboard&quot;</span>: self.config.get(<span class="hljs-string">&quot;monitoring&quot;</span>, &#123;&#125;).get(<span class="hljs-string">&quot;use_tensorboard&quot;</span>, <span class="hljs-literal">True</span>),<br>            <span class="hljs-string">&quot;wandb_project&quot;</span>: self.config.get(<span class="hljs-string">&quot;monitoring&quot;</span>, &#123;&#125;).get(<span class="hljs-string">&quot;wandb_project&quot;</span>, <span class="hljs-literal">None</span>),<br>        &#125;)<br><br>        <span class="hljs-comment"># 解析JSON结果</span><br>        result_data = json.loads(result)<br><br>        self.log(<span class="hljs-string">f&quot;✓ GRPO训练完成&quot;</span>)<br>        self.log(<span class="hljs-string">f&quot;  - 模型路径: <span class="hljs-subst">&#123;result_data[<span class="hljs-string">&#x27;output_dir&#x27;</span>]&#125;</span>&quot;</span>)<br>        self.log(<span class="hljs-string">f&quot;  - 状态: <span class="hljs-subst">&#123;result_data[<span class="hljs-string">&#x27;status&#x27;</span>]&#125;</span>&quot;</span>)<br><br>        self.results[<span class="hljs-string">&quot;grpo_training&quot;</span>] = result_data<br><br>        <span class="hljs-keyword">return</span> result_data[<span class="hljs-string">&quot;output_dir&quot;</span>]<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">stage5_grpo_evaluation</span>(<span class="hljs-params">self, model_path</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;阶段5: GRPO评估&quot;&quot;&quot;</span><br>        self.log(<span class="hljs-string">&quot;\n&quot;</span> + <span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br>        self.log(<span class="hljs-string">&quot;阶段5: GRPO评估&quot;</span>)<br>        self.log(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br>        <br>        result = self.rl_tool.run(&#123;<br>            <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;evaluate&quot;</span>,<br>            <span class="hljs-string">&quot;model_path&quot;</span>: model_path,<br>            <span class="hljs-string">&quot;max_samples&quot;</span>: self.config[<span class="hljs-string">&quot;eval&quot;</span>][<span class="hljs-string">&quot;max_samples&quot;</span>],<br>            <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>        &#125;)<br>        eval_data = json.loads(result)<br><br>        self.log(<span class="hljs-string">f&quot;✓ GRPO评估完成&quot;</span>)<br>        self.log(<span class="hljs-string">f&quot;  - 准确率: <span class="hljs-subst">&#123;eval_data[<span class="hljs-string">&#x27;accuracy&#x27;</span>]&#125;</span>&quot;</span>)<br>        self.log(<span class="hljs-string">f&quot;  - 平均奖励: <span class="hljs-subst">&#123;eval_data[<span class="hljs-string">&#x27;average_reward&#x27;</span>]&#125;</span>&quot;</span>)<br><br>        self.results[<span class="hljs-string">&quot;grpo_evaluation&quot;</span>] = eval_data<br><br>        <span class="hljs-keyword">return</span> eval_data<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">stage6_save_results</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;阶段6: 保存结果&quot;&quot;&quot;</span><br>        self.log(<span class="hljs-string">&quot;\n&quot;</span> + <span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br>        self.log(<span class="hljs-string">&quot;阶段6: 保存结果&quot;</span>)<br>        self.log(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br>        <br>        <span class="hljs-comment"># 保存训练结果</span><br>        results_path = <span class="hljs-string">&quot;training_results.json&quot;</span><br>        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(results_path, <span class="hljs-string">&#x27;w&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>            json.dump(self.results, f, indent=<span class="hljs-number">2</span>)<br>        <br>        self.log(<span class="hljs-string">f&quot;✓ 结果已保存到: <span class="hljs-subst">&#123;results_path&#125;</span>&quot;</span>)<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;运行完整流程&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-comment"># 阶段1: 数据准备</span><br>            self.stage1_prepare_data()<br>            <br>            <span class="hljs-comment"># 阶段2: SFT训练</span><br>            sft_model_path = self.stage2_sft_training()<br>            <br>            <span class="hljs-comment"># 阶段3: SFT评估</span><br>            self.stage3_sft_evaluation(sft_model_path)<br>            <br>            <span class="hljs-comment"># 阶段4: GRPO训练</span><br>            grpo_model_path = self.stage4_grpo_training(sft_model_path)<br>            <br>            <span class="hljs-comment"># 阶段5: GRPO评估</span><br>            self.stage5_grpo_evaluation(grpo_model_path)<br>            <br>            <span class="hljs-comment"># 阶段6: 保存结果</span><br>            self.stage6_save_results()<br>            <br>            self.log(<span class="hljs-string">&quot;\n&quot;</span> + <span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br>            self.log(<span class="hljs-string">&quot;✓ 训练流程完成!&quot;</span>)<br>            self.log(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br>            <br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            self.log(<span class="hljs-string">f&quot;\n✗ 训练失败: <span class="hljs-subst">&#123;<span class="hljs-built_in">str</span>(e)&#125;</span>&quot;</span>)<br>            <span class="hljs-keyword">raise</span><br><br><span class="hljs-comment"># 使用示例</span><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:<br>    <span class="hljs-comment"># 创建配置文件</span><br>    config = &#123;<br>        <span class="hljs-string">&quot;model&quot;</span>: &#123;<br>            <span class="hljs-string">&quot;base_model&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span><br>        &#125;,<br>        <span class="hljs-string">&quot;data&quot;</span>: &#123;<br>            <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">1000</span>  <span class="hljs-comment"># 使用1000个样本</span><br>        &#125;,<br>        <span class="hljs-string">&quot;sft&quot;</span>: &#123;<br>            <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/sft_model&quot;</span>,<br>            <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">3</span>,<br>            <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">8</span>,<br>        &#125;,<br>        <span class="hljs-string">&quot;grpo&quot;</span>: &#123;<br>            <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/grpo_model&quot;</span>,<br>            <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">3</span>,<br>            <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">4</span>,<br>        &#125;,<br>        <span class="hljs-string">&quot;eval&quot;</span>: &#123;<br>            <span class="hljs-string">&quot;max_samples&quot;</span>: <span class="hljs-number">200</span>,<br>            <span class="hljs-string">&quot;sft_accuracy_threshold&quot;</span>: <span class="hljs-number">0.40</span>  <span class="hljs-comment"># SFT准确率阈值</span><br>        &#125;,<br>        <span class="hljs-string">&quot;monitoring&quot;</span>: &#123;<br>            <span class="hljs-string">&quot;use_wandb&quot;</span>: <span class="hljs-literal">False</span>,  <span class="hljs-comment"># 是否使用Wandb</span><br>            <span class="hljs-string">&quot;use_tensorboard&quot;</span>: <span class="hljs-literal">True</span>,  <span class="hljs-comment"># 是否使用TensorBoard</span><br>            <span class="hljs-string">&quot;wandb_project&quot;</span>: <span class="hljs-string">&quot;agentic-rl-pipeline&quot;</span>  <span class="hljs-comment"># Wandb项目名</span><br>        &#125;<br>    &#125;<br>    <br>    <span class="hljs-comment"># 保存配置</span><br>    <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(<span class="hljs-string">&quot;config.json&quot;</span>, <span class="hljs-string">&#x27;w&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>        json.dump(config, f, indent=<span class="hljs-number">2</span>)<br>    <br>    <span class="hljs-comment"># 运行训练流程</span><br>    pipeline = AgenticRLPipeline(<span class="hljs-string">&quot;config.json&quot;</span>)<br>    pipeline.run()<br></code></pre></td></tr></table></figure><p>运行这个脚本，你将看到完整的训练过程。</p><p>运行小建议：</p><p><strong>从小规模开始</strong>:不要一开始就用全部数据训练。先用 100-1000 个样本快速迭代，验证流程和参数，确认效果后再扩大规模。这样可以节省大量时间和计算资源。</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><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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">check_data_quality</span>(<span class="hljs-params">dataset</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;检查数据质量&quot;&quot;&quot;</span><br>    issues = []<br><br>    <span class="hljs-comment"># 检查必需字段</span><br>    required_fields = [<span class="hljs-string">&quot;prompt&quot;</span>, <span class="hljs-string">&quot;completion&quot;</span>]<br>    <span class="hljs-keyword">for</span> field <span class="hljs-keyword">in</span> required_fields:<br>        <span class="hljs-keyword">if</span> field <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> dataset.column_names:<br>            issues.append(<span class="hljs-string">f&quot;缺少字段: <span class="hljs-subst">&#123;field&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 检查空值</span><br>    <span class="hljs-keyword">for</span> i, sample <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(dataset):<br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> sample[<span class="hljs-string">&quot;prompt&quot;</span>] <span class="hljs-keyword">or</span> <span class="hljs-keyword">not</span> sample[<span class="hljs-string">&quot;completion&quot;</span>]:<br>            issues.append(<span class="hljs-string">f&quot;样本<span class="hljs-subst">&#123;i&#125;</span>包含空值&quot;</span>)<br><br>    <span class="hljs-comment"># 检查重复</span><br>    prompts = [s[<span class="hljs-string">&quot;prompt&quot;</span>] <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> dataset]<br>    duplicates = <span class="hljs-built_in">len</span>(prompts) - <span class="hljs-built_in">len</span>(<span class="hljs-built_in">set</span>(prompts))<br>    <span class="hljs-keyword">if</span> duplicates &gt; <span class="hljs-number">0</span>:<br>        issues.append(<span class="hljs-string">f&quot;发现<span class="hljs-subst">&#123;duplicates&#125;</span>个重复样本&quot;</span>)<br><br>    <span class="hljs-keyword">return</span> issues<br><br><span class="hljs-comment"># 使用</span><br>issues = check_data_quality(dataset)<br><span class="hljs-keyword">if</span> issues:<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;数据质量问题:&quot;</span>)<br>    <span class="hljs-keyword">for</span> issue <span class="hljs-keyword">in</span> issues:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - <span class="hljs-subst">&#123;issue&#125;</span>&quot;</span>)<br><span class="hljs-keyword">else</span>:<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;✓ 数据质量检查通过&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>数据增强</strong>:如果数据量不足，可以考虑数据增强，如改写问题(保持答案不变)、生成相似问题、反向翻译(translate back)。但要注意保持数据质量，避免引入噪声。</p><h3 id="11-6-2-超参数调优"><a href="#11-6-2-超参数调优" class="headerlink" title="11.6.2 超参数调优"></a>11.6.2 超参数调优</h3><p>超参数调优是提升模型性能的关键。下面是一些常用的调优策略。</p><p><strong>（1）网格搜索</strong></p><p>网格搜索(Grid Search)是最简单的调优方法，遍历所有参数组合，选择最佳的一组。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 定义参数网格</span><br>param_grid = &#123;<br>    <span class="hljs-string">&quot;learning_rate&quot;</span>: [<span class="hljs-number">1e-5</span>, <span class="hljs-number">5e-5</span>, <span class="hljs-number">1e-4</span>],<br>    <span class="hljs-string">&quot;lora_rank&quot;</span>: [<span class="hljs-number">8</span>, <span class="hljs-number">16</span>, <span class="hljs-number">32</span>],<br>    <span class="hljs-string">&quot;kl_coef&quot;</span>: [<span class="hljs-number">0.05</span>, <span class="hljs-number">0.1</span>, <span class="hljs-number">0.2</span>],<br>&#125;<br><br>best_accuracy = <span class="hljs-number">0</span><br>best_params = <span class="hljs-literal">None</span><br><br><span class="hljs-comment"># 遍历所有组合</span><br><span class="hljs-keyword">for</span> lr <span class="hljs-keyword">in</span> param_grid[<span class="hljs-string">&quot;learning_rate&quot;</span>]:<br>    <span class="hljs-keyword">for</span> rank <span class="hljs-keyword">in</span> param_grid[<span class="hljs-string">&quot;lora_rank&quot;</span>]:<br>        <span class="hljs-keyword">for</span> kl <span class="hljs-keyword">in</span> param_grid[<span class="hljs-string">&quot;kl_coef&quot;</span>]:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;测试参数: lr=<span class="hljs-subst">&#123;lr&#125;</span>, rank=<span class="hljs-subst">&#123;rank&#125;</span>, kl=<span class="hljs-subst">&#123;kl&#125;</span>&quot;</span>)<br><br>            <span class="hljs-comment"># 训练模型</span><br>            result = rl_tool.run(&#123;<br>                <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>                <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>                <span class="hljs-string">&quot;learning_rate&quot;</span>: lr,<br>                <span class="hljs-string">&quot;lora_rank&quot;</span>: rank,<br>                <span class="hljs-string">&quot;kl_coef&quot;</span>: kl,<br>                <span class="hljs-comment"># 其他参数...</span><br>            &#125;)<br><br>            <span class="hljs-comment"># 评估模型</span><br>            eval_result = rl_tool.run(&#123;<br>                <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;evaluate&quot;</span>,<br>                <span class="hljs-string">&quot;model_path&quot;</span>: result[<span class="hljs-string">&quot;model_path&quot;</span>],<br>            &#125;)<br><br>            <span class="hljs-comment"># 更新最佳参数</span><br>            <span class="hljs-keyword">if</span> eval_result[<span class="hljs-string">&quot;accuracy&quot;</span>] &gt; best_accuracy:<br>                best_accuracy = eval_result[<span class="hljs-string">&quot;accuracy&quot;</span>]<br>                best_params = &#123;<span class="hljs-string">&quot;lr&quot;</span>: lr, <span class="hljs-string">&quot;rank&quot;</span>: rank, <span class="hljs-string">&quot;kl&quot;</span>: kl&#125;<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;最佳参数: <span class="hljs-subst">&#123;best_params&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;最佳准确率: <span class="hljs-subst">&#123;best_accuracy:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>网格搜索的优点是简单直接，能找到全局最优。缺点是计算成本高，参数多时不可行。</p><p><strong>（2）随机搜索</strong></p><p>随机搜索(Random Search)随机采样参数组合，比网格搜索更高效。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> random<br><br><span class="hljs-comment"># 定义参数范围</span><br>param_ranges = &#123;<br>    <span class="hljs-string">&quot;learning_rate&quot;</span>: (<span class="hljs-number">1e-6</span>, <span class="hljs-number">1e-4</span>),  <span class="hljs-comment"># 对数均匀分布</span><br>    <span class="hljs-string">&quot;lora_rank&quot;</span>: [<span class="hljs-number">4</span>, <span class="hljs-number">8</span>, <span class="hljs-number">16</span>, <span class="hljs-number">32</span>, <span class="hljs-number">64</span>],<br>    <span class="hljs-string">&quot;kl_coef&quot;</span>: (<span class="hljs-number">0.01</span>, <span class="hljs-number">0.5</span>),<br>&#125;<br><br>best_accuracy = <span class="hljs-number">0</span><br>best_params = <span class="hljs-literal">None</span><br><br><span class="hljs-comment"># 随机采样N次</span><br>N = <span class="hljs-number">10</span><br><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(N):<br>    <span class="hljs-comment"># 随机采样参数</span><br>    lr = <span class="hljs-number">10</span> ** random.uniform(-<span class="hljs-number">6</span>, -<span class="hljs-number">4</span>)  <span class="hljs-comment"># 对数均匀</span><br>    rank = random.choice(param_ranges[<span class="hljs-string">&quot;lora_rank&quot;</span>])<br>    kl = random.uniform(<span class="hljs-number">0.01</span>, <span class="hljs-number">0.5</span>)<br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[<span class="hljs-subst">&#123;i+<span class="hljs-number">1</span>&#125;</span>/<span class="hljs-subst">&#123;N&#125;</span>] 测试参数: lr=<span class="hljs-subst">&#123;lr:<span class="hljs-number">.2</span>e&#125;</span>, rank=<span class="hljs-subst">&#123;rank&#125;</span>, kl=<span class="hljs-subst">&#123;kl:<span class="hljs-number">.3</span>f&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 训练和评估(同上)</span><br>    <span class="hljs-comment"># ...</span><br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;最佳参数: <span class="hljs-subst">&#123;best_params&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;最佳准确率: <span class="hljs-subst">&#123;best_accuracy:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>随机搜索的优点是效率高，适合参数空间大的情况。缺点是可能错过最优解。</p><p><strong>（3）贝叶斯优化</strong></p><p>贝叶斯优化(Bayesian Optimization)使用概率模型指导搜索，更加智能。可以使用 Optuna 等库:</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> optuna<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">objective</span>(<span class="hljs-params">trial</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;优化目标函数&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 采样参数</span><br>    lr = trial.suggest_loguniform(<span class="hljs-string">&quot;learning_rate&quot;</span>, <span class="hljs-number">1e-6</span>, <span class="hljs-number">1e-4</span>)<br>    rank = trial.suggest_categorical(<span class="hljs-string">&quot;lora_rank&quot;</span>, [<span class="hljs-number">8</span>, <span class="hljs-number">16</span>, <span class="hljs-number">32</span>])<br>    kl = trial.suggest_uniform(<span class="hljs-string">&quot;kl_coef&quot;</span>, <span class="hljs-number">0.01</span>, <span class="hljs-number">0.5</span>)<br><br>    <span class="hljs-comment"># 训练模型</span><br>    result = rl_tool.run(&#123;<br>        <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>        <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>        <span class="hljs-string">&quot;learning_rate&quot;</span>: lr,<br>        <span class="hljs-string">&quot;lora_rank&quot;</span>: rank,<br>        <span class="hljs-string">&quot;kl_coef&quot;</span>: kl,<br>        <span class="hljs-comment"># 其他参数...</span><br>    &#125;)<br><br>    <span class="hljs-comment"># 评估模型</span><br>    eval_result = rl_tool.run(&#123;<br>        <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;evaluate&quot;</span>,<br>        <span class="hljs-string">&quot;model_path&quot;</span>: result[<span class="hljs-string">&quot;model_path&quot;</span>],<br>    &#125;)<br><br>    <span class="hljs-keyword">return</span> eval_result[<span class="hljs-string">&quot;accuracy&quot;</span>]<br><br><span class="hljs-comment"># 创建研究</span><br>study = optuna.create_study(direction=<span class="hljs-string">&quot;maximize&quot;</span>)<br>study.optimize(objective, n_trials=<span class="hljs-number">20</span>)<br><br><span class="hljs-comment"># 打印最佳参数</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;最佳参数: <span class="hljs-subst">&#123;study.best_params&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;最佳准确率: <span class="hljs-subst">&#123;study.best_value:<span class="hljs-number">.2</span>%&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>贝叶斯优化的优点是样本效率高，能快速找到好的参数。缺点是实现复杂，需要额外的库。</p><p>如表 11.8 所示，不同调优方法的对比。</p><div align="center">  <p>表 11.8 超参数调优方法对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-table-8.png" alt="" width="85%"/></div>### 11.6.3 分布式训练<p>当数据量和模型规模增大时，单 GPU 训练会变得非常缓慢。这时我们需要使用分布式训练来加速训练过程。HelloAgents 基于 TRL 和 Hugging Face Accelerate，天然支持多 GPU 和多节点分布式训练</p><p><strong>方案选择建议</strong>:</p><ul><li><strong>单机多卡(2-8 卡)</strong>: 使用 DDP，简单高效</li><li><strong>大模型(&gt;7B)</strong>: 使用 DeepSpeed ZeRO-2 或 ZeRO-3</li><li><strong>多节点集群</strong>: 使用 DeepSpeed ZeRO-3 + Offload</li></ul><p><strong>（1）配置 Accelerate</strong></p><p>首先需要创建 Accelerate 配置文件。运行以下命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">accelerate config<br></code></pre></td></tr></table></figure><p>根据提示选择配置:</p><figure class="highlight node-repl"><table><tr><td class="gutter"><pre><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><code class="hljs node-repl">In which compute environment are you running?<br><span class="hljs-meta prompt_">&gt;</span> <span class="language-javascript"><span class="hljs-title class_">This</span> machine</span><br><br>Which type of machine are you using?<br><span class="hljs-meta prompt_">&gt;</span> <span class="language-javascript">multi-<span class="hljs-variable constant_">GPU</span></span><br><br>How many different machines will you use?<br><span class="hljs-meta prompt_">&gt;</span> <span class="language-javascript"><span class="hljs-number">1</span></span><br><br>Do you wish to optimize your script with torch dynamo?<br><span class="hljs-meta prompt_">&gt;</span> <span class="language-javascript"><span class="hljs-variable constant_">NO</span></span><br><br>Do you want to use DeepSpeed?<br><span class="hljs-meta prompt_">&gt;</span> <span class="language-javascript"><span class="hljs-variable constant_">YES</span></span><br><br>Which DeepSpeed config file do you want to use?<br><span class="hljs-meta prompt_">&gt;</span> <span class="language-javascript"><span class="hljs-title class_">ZeRO</span>-<span class="hljs-number">2</span></span><br><br>How many GPU(s) should be used for distributed training?<br><span class="hljs-meta prompt_">&gt;</span> <span class="language-javascript"><span class="hljs-number">4</span></span><br></code></pre></td></tr></table></figure><p>这会在<code>~/.cache/huggingface/accelerate/default_config.yaml</code>生成配置文件。</p><p><strong>（2）使用 DDP 训练</strong></p><p><strong>数据并行(DDP)</strong>是最简单的分布式方案，每个 GPU 持有完整模型副本，数据被分割到各个 GPU 上。</p><p><strong>Accelerate 配置文件</strong> (<code>multi_gpu_ddp.yaml</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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">compute_environment:</span> <span class="hljs-string">LOCAL_MACHINE</span><br><span class="hljs-attr">distributed_type:</span> <span class="hljs-string">MULTI_GPU</span><br><span class="hljs-attr">num_processes:</span> <span class="hljs-number">4</span>  <span class="hljs-comment"># GPU数量</span><br><span class="hljs-attr">machine_rank:</span> <span class="hljs-number">0</span><br><span class="hljs-attr">num_machines:</span> <span class="hljs-number">1</span><br><span class="hljs-attr">gpu_ids:</span> <span class="hljs-string">all</span><br><span class="hljs-attr">mixed_precision:</span> <span class="hljs-string">fp16</span><br></code></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RLTrainingTool<br><br>rl_tool = RLTrainingTool()<br><br><span class="hljs-comment"># 训练代码完全不变</span><br>result = rl_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;train&quot;</span>,<br>    <span class="hljs-string">&quot;algorithm&quot;</span>: <span class="hljs-string">&quot;grpo&quot;</span>,<br>    <span class="hljs-string">&quot;model_name&quot;</span>: <span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>,<br>    <span class="hljs-string">&quot;output_dir&quot;</span>: <span class="hljs-string">&quot;./models/grpo_ddp&quot;</span>,<br>    <span class="hljs-string">&quot;num_epochs&quot;</span>: <span class="hljs-number">3</span>,<br>    <span class="hljs-string">&quot;batch_size&quot;</span>: <span class="hljs-number">4</span>,  <span class="hljs-comment"># 每个GPU的batch size</span><br>    <span class="hljs-string">&quot;use_lora&quot;</span>: <span class="hljs-literal">True</span>,<br>&#125;)<br></code></pre></td></tr></table></figure><p><strong>启动训练</strong>:</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><code class="hljs bash"><span class="hljs-comment"># 使用配置文件</span><br>accelerate launch --config_file multi_gpu_ddp.yaml train_script.py<br><br><span class="hljs-comment"># 或者直接指定参数</span><br>accelerate launch --num_processes 4 --mixed_precision fp16 train_script.py<br></code></pre></td></tr></table></figure><p></strong>（3）使用 DeepSpeed ZeRO 训练</strong></p><p></strong>DeepSpeed ZeRO</strong>通过分片优化器状态、梯度和模型参数，大幅降低显存占用，支持更大的模型和 batch size。</p><p></strong>ZeRO-2 配置文件</strong> (<code>deepspeed_zero2.yaml</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><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><code class="hljs yaml"><span class="hljs-attr">compute_environment:</span> <span class="hljs-string">LOCAL_MACHINE</span><br><span class="hljs-attr">distributed_type:</span> <span class="hljs-string">DEEPSPEED</span><br><span class="hljs-attr">num_processes:</span> <span class="hljs-number">4</span><br><span class="hljs-attr">machine_rank:</span> <span class="hljs-number">0</span><br><span class="hljs-attr">num_machines:</span> <span class="hljs-number">1</span><br><span class="hljs-attr">gpu_ids:</span> <span class="hljs-string">all</span><br><span class="hljs-attr">mixed_precision:</span> <span class="hljs-string">fp16</span><br><span class="hljs-attr">deepspeed_config:</span><br>  <span class="hljs-attr">gradient_accumulation_steps:</span> <span class="hljs-number">4</span><br>  <span class="hljs-attr">gradient_clipping:</span> <span class="hljs-number">1.0</span><br>  <span class="hljs-attr">offload_optimizer_device:</span> <span class="hljs-string">none</span><br>  <span class="hljs-attr">offload_param_device:</span> <span class="hljs-string">none</span><br>  <span class="hljs-attr">zero3_init_flag:</span> <span class="hljs-literal">false</span><br>  <span class="hljs-attr">zero_stage:</span> <span class="hljs-number">2</span>  <span class="hljs-comment"># ZeRO-2</span><br></code></pre></td></tr></table></figure><p></strong>ZeRO-3 配置文件</strong> (<code>deepspeed_zero3.yaml</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><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><code class="hljs yaml"><span class="hljs-attr">compute_environment:</span> <span class="hljs-string">LOCAL_MACHINE</span><br><span class="hljs-attr">distributed_type:</span> <span class="hljs-string">DEEPSPEED</span><br><span class="hljs-attr">num_processes:</span> <span class="hljs-number">4</span><br><span class="hljs-attr">machine_rank:</span> <span class="hljs-number">0</span><br><span class="hljs-attr">num_machines:</span> <span class="hljs-number">1</span><br><span class="hljs-attr">gpu_ids:</span> <span class="hljs-string">all</span><br><span class="hljs-attr">mixed_precision:</span> <span class="hljs-string">fp16</span><br><span class="hljs-attr">deepspeed_config:</span><br>  <span class="hljs-attr">gradient_accumulation_steps:</span> <span class="hljs-number">4</span><br>  <span class="hljs-attr">gradient_clipping:</span> <span class="hljs-number">1.0</span><br>  <span class="hljs-attr">offload_optimizer_device:</span> <span class="hljs-string">cpu</span>  <span class="hljs-comment"># 优化器状态卸载到CPU</span><br>  <span class="hljs-attr">offload_param_device:</span> <span class="hljs-string">cpu</span>      <span class="hljs-comment"># 参数卸载到CPU</span><br>  <span class="hljs-attr">zero3_init_flag:</span> <span class="hljs-literal">true</span><br>  <span class="hljs-attr">zero_stage:</span> <span class="hljs-number">3</span>  <span class="hljs-comment"># ZeRO-3</span><br></code></pre></td></tr></table></figure><p><strong>启动训练</strong>:</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><code class="hljs bash"><span class="hljs-comment"># ZeRO-2</span><br>accelerate launch --config_file deepspeed_zero2.yaml train_script.py<br><br><span class="hljs-comment"># ZeRO-3</span><br>accelerate launch --config_file deepspeed_zero3.yaml train_script.py<br></code></pre></td></tr></table></figure><p>如表 11.9 所示，这是 Qwen3-0.6B 模型用不同方式训练的显存对比:</p><div align="center">  <p>表 11.9 显存对比 (Qwen3-0.6B 模型)</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/11-figures/11-table-9.png" alt="" width="85%"/></div><p><strong>（4）多节点训练</strong></p><p>对于超大规模训练，可以使用多个节点(机器)。</p><p><strong>主节点配置</strong> (<code>multi_node_main.yaml</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><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><code class="hljs yaml"><span class="hljs-attr">compute_environment:</span> <span class="hljs-string">LOCAL_MACHINE</span><br><span class="hljs-attr">distributed_type:</span> <span class="hljs-string">DEEPSPEED</span><br><span class="hljs-attr">num_processes:</span> <span class="hljs-number">16</span>  <span class="hljs-comment"># 4节点 x 4GPU</span><br><span class="hljs-attr">machine_rank:</span> <span class="hljs-number">0</span>    <span class="hljs-comment"># 主节点</span><br><span class="hljs-attr">num_machines:</span> <span class="hljs-number">4</span><br><span class="hljs-attr">main_process_ip:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.1</span><span class="hljs-number">.100</span>  <span class="hljs-comment"># 主节点IP</span><br><span class="hljs-attr">main_process_port:</span> <span class="hljs-number">29500</span><br><span class="hljs-attr">gpu_ids:</span> <span class="hljs-string">all</span><br><span class="hljs-attr">mixed_precision:</span> <span class="hljs-string">fp16</span><br><span class="hljs-attr">deepspeed_config:</span><br>  <span class="hljs-attr">zero_stage:</span> <span class="hljs-number">3</span><br>  <span class="hljs-attr">offload_optimizer_device:</span> <span class="hljs-string">cpu</span><br>  <span class="hljs-attr">offload_param_device:</span> <span class="hljs-string">cpu</span><br></code></pre></td></tr></table></figure><p><strong>工作节点配置</strong> (修改<code>machine_rank</code>为 1, 2, 3):</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">machine_rank:</span> <span class="hljs-number">1</span>  <span class="hljs-comment"># 工作节点1</span><br><span class="hljs-comment"># 其他配置相同</span><br></code></pre></td></tr></table></figure><p><strong>启动训练</strong>:</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><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 在主节点上</span><br>accelerate launch --config_file multi_node_main.yaml train_script.py<br><br><span class="hljs-comment"># 在工作节点1上</span><br>accelerate launch --config_file multi_node_worker1.yaml train_script.py<br><br><span class="hljs-comment"># 在工作节点2上</span><br>accelerate launch --config_file multi_node_worker2.yaml train_script.py<br><br><span class="hljs-comment"># 在工作节点3上</span><br>accelerate launch --config_file multi_node_worker3.yaml train_script.py<br></code></pre></td></tr></table></figure><p><strong>（5）分布式训练最佳实践</strong></p><p><strong>1. Batch Size 调整</strong></p><p>分布式训练时，总 batch size &#x3D; <code>per_device_batch_size × num_gpus × gradient_accumulation_steps</code></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><code class="hljs python"><span class="hljs-comment"># 单GPU: batch_size=4, gradient_accumulation=4, 总batch=16</span><br><span class="hljs-comment"># 4GPU DDP: batch_size=4, gradient_accumulation=1, 总batch=16 (保持一致)</span><br></code></pre></td></tr></table></figure><p><strong>2. 学习率缩放</strong></p><p>使用线性缩放规则: <code>lr_new = lr_base × sqrt(total_batch_size_new / total_batch_size_base)</code></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><code class="hljs python"><span class="hljs-comment"># 基准: 单GPU, batch=16, lr=5e-5</span><br><span class="hljs-comment"># 4GPU: batch=64, lr=5e-5 × sqrt(64/16) = 1e-4</span><br></code></pre></td></tr></table></figure><p><strong>3. 监控和调试</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 启用详细日志</span><br>export ACCELERATE_LOG_LEVEL=INFO<br><br><span class="hljs-comment"># 启用NCCL调试(多节点)</span><br>export NCCL_DEBUG=INFO<br><br><span class="hljs-comment"># 检查GPU利用率</span><br>watch -n <span class="hljs-number">1</span> nvidia-smi<br></code></pre></td></tr></table></figure><h3 id="11-6-4-生产部署"><a href="#11-6-4-生产部署" class="headerlink" title="11.6.4 生产部署"></a>11.6.4 生产部署</h3><p>训练完成后，我们需要将模型部署到生产环境。下面是一些部署建议。</p><p><strong>（1）模型导出</strong></p><p>将 LoRA 权重合并到基础模型，方便部署:</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> transformers <span class="hljs-keyword">import</span> AutoModelForCausalLM, AutoTokenizer<br><span class="hljs-keyword">from</span> peft <span class="hljs-keyword">import</span> PeftModel<br><br><span class="hljs-comment"># 加载基础模型</span><br>base_model = AutoModelForCausalLM.from_pretrained(<span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>)<br><br><span class="hljs-comment"># 加载LoRA权重</span><br>model = PeftModel.from_pretrained(base_model, <span class="hljs-string">&quot;./models/grpo_model&quot;</span>)<br><br><span class="hljs-comment"># 合并权重</span><br>merged_model = model.merge_and_unload()<br><br><span class="hljs-comment"># 保存合并后的模型</span><br>merged_model.save_pretrained(<span class="hljs-string">&quot;./models/merged_model&quot;</span>)<br><br><span class="hljs-comment"># 保存tokenizer</span><br>tokenizer = AutoTokenizer.from_pretrained(<span class="hljs-string">&quot;Qwen/Qwen3-0.6B&quot;</span>)<br>tokenizer.save_pretrained(<span class="hljs-string">&quot;./models/merged_model&quot;</span>)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;✓ 模型已导出到: ./models/merged_model&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>（2）推理优化</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> transformers <span class="hljs-keyword">import</span> AutoModelForCausalLM, AutoTokenizer<br><span class="hljs-keyword">import</span> torch<br><br><span class="hljs-comment"># 加载模型(使用8-bit量化)</span><br>model = AutoModelForCausalLM.from_pretrained(<br>    <span class="hljs-string">&quot;./models/merged_model&quot;</span>,<br>    load_in_8bit=<span class="hljs-literal">True</span>,  <span class="hljs-comment"># 8-bit量化</span><br>    device_map=<span class="hljs-string">&quot;auto&quot;</span>,  <span class="hljs-comment"># 自动分配设备</span><br>)<br><br>tokenizer = AutoTokenizer.from_pretrained(<span class="hljs-string">&quot;./models/merged_model&quot;</span>)<br><br><span class="hljs-comment"># 推理</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">generate_answer</span>(<span class="hljs-params">question</span>):<br>    prompt = <span class="hljs-string">f&quot;&lt;|im_start|&gt;user\n<span class="hljs-subst">&#123;question&#125;</span>&lt;|im_end|&gt;\n&lt;|im_start|&gt;assistant\n&quot;</span><br>    inputs = tokenizer(prompt, return_tensors=<span class="hljs-string">&quot;pt&quot;</span>).to(model.device)<br><br>    outputs = model.generate(<br>        **inputs,<br>        max_new_tokens=<span class="hljs-number">512</span>,<br>        temperature=<span class="hljs-number">0.7</span>,<br>        do_sample=<span class="hljs-literal">True</span>,<br>    )<br><br>    response = tokenizer.decode(outputs[<span class="hljs-number">0</span>], skip_special_tokens=<span class="hljs-literal">False</span>)<br>    <span class="hljs-keyword">return</span> response<br><br><span class="hljs-comment"># 测试</span><br>question = <span class="hljs-string">&quot;What is 48 + 24?&quot;</span><br>answer = generate_answer(question)<br><span class="hljs-built_in">print</span>(answer)<br></code></pre></td></tr></table></figure><p><strong>（3）API 服务</strong></p><p>使用 FastAPI 创建推理服务:</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI<br><span class="hljs-keyword">from</span> pydantic <span class="hljs-keyword">import</span> BaseModel<br><span class="hljs-keyword">from</span> transformers <span class="hljs-keyword">import</span> AutoModelForCausalLM, AutoTokenizer<br><br>app = FastAPI()<br><br><span class="hljs-comment"># 加载模型</span><br>model = AutoModelForCausalLM.from_pretrained(<span class="hljs-string">&quot;./models/merged_model&quot;</span>)<br>tokenizer = AutoTokenizer.from_pretrained(<span class="hljs-string">&quot;./models/merged_model&quot;</span>)<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Question</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    text: <span class="hljs-built_in">str</span><br>    max_tokens: <span class="hljs-built_in">int</span> = <span class="hljs-number">512</span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Answer</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    text: <span class="hljs-built_in">str</span><br>    confidence: <span class="hljs-built_in">float</span><br><br><span class="hljs-meta">@app.post(<span class="hljs-params"><span class="hljs-string">&quot;/generate&quot;</span>, response_model=Answer</span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">generate</span>(<span class="hljs-params">question: Question</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;生成答案&quot;&quot;&quot;</span><br>    prompt = <span class="hljs-string">f&quot;&lt;|im_start|&gt;user\n<span class="hljs-subst">&#123;question.text&#125;</span>&lt;|im_end|&gt;\n&lt;|im_start|&gt;assistant\n&quot;</span><br>    inputs = tokenizer(prompt, return_tensors=<span class="hljs-string">&quot;pt&quot;</span>)<br><br>    outputs = model.generate(<br>        **inputs,<br>        max_new_tokens=question.max_tokens,<br>        temperature=<span class="hljs-number">0.7</span>,<br>        return_dict_in_generate=<span class="hljs-literal">True</span>,<br>        output_scores=<span class="hljs-literal">True</span>,<br>    )<br><br>    response = tokenizer.decode(outputs.sequences[<span class="hljs-number">0</span>], skip_special_tokens=<span class="hljs-literal">False</span>)<br><br>    <span class="hljs-comment"># 计算置信度(简化版)</span><br>    confidence = <span class="hljs-number">0.8</span>  <span class="hljs-comment"># 实际应该基于输出概率计算</span><br><br>    <span class="hljs-keyword">return</span> Answer(text=response, confidence=confidence)<br><br><span class="hljs-comment"># 运行: uvicorn api:app --host 0.0.0.0 --port 8000</span><br></code></pre></td></tr></table></figure><h2 id="11-8-本章小结"><a href="#11-8-本章小结" class="headerlink" title="11.8 本章小结"></a>11.8 本章小结</h2><p>在本章中，我们系统地学习了 Agentic RL 的理论和实践，从基础概念到完整的训练流程，从数据准备到模型部署。让我们回顾一下本章的主要内容。</p><p><strong>（1）Agentic RL 的本质</strong></p><p>Agentic RL 是将 LLM 作为可学习策略，嵌入到智能体的感知-决策-执行循环中，通过强化学习优化智能体在多步任务中的表现。它与传统的 PBRFT(Preference-Based Reinforcement Fine-Tuning)的核心区别在于:</p><ul><li><strong>任务性质</strong>:从单轮对话优化扩展到多步序贯决策</li><li><strong>状态空间</strong>:从静态提示扩展到动态演化的环境状态</li><li><strong>行动空间</strong>:从纯文本生成扩展到文本+工具+环境操作</li><li><strong>奖励设计</strong>:从单步质量评估扩展到长期累积回报</li><li><strong>优化目标</strong>:从短期响应质量扩展到长期任务成功</li></ul><p><strong>（2）六大核心能力</strong></p><p>Agentic RL 旨在提升智能体的六大核心能力:</p><ol><li><strong>推理(Reasoning)</strong>:多步逻辑推导，学习推理策略</li><li><strong>工具使用(Tool Use)</strong>:API&#x2F;工具调用，学会何时用、如何用</li><li><strong>记忆(Memory)</strong>:长期信息保持，学习记忆管理</li><li><strong>规划(Planning)</strong>:行动序列规划，学会动态规划</li><li><strong>自我改进(Self-Improvement)</strong>:自我反思优化，从错误中学习</li><li><strong>感知(Perception)</strong>:多模态理解，视觉推理和工具使用</li></ol><p><strong>（3）训练流程</strong></p><p>完整的 Agentic RL 训练流程包括:</p><ol><li><strong>预训练(Pretraining)</strong>:在大规模文本上学习语言知识(通常使用现成的预训练模型)</li><li><strong>监督微调(SFT)</strong>:学习任务格式和基础推理能力</li><li><strong>强化学习(RL)</strong>:通过试错优化推理策略，超越训练数据质量</li></ol><p>其中，SFT 是基础，RL 是提升。没有 SFT 的基础，RL 很难成功;没有 RL 的优化，模型只能模仿训练数据。</p><p>如果你想深入学习 Agentic RL，建议按照以下路径:</p><p>基础阶段</p><ol><li><strong>强化学习基础</strong>:学习 MDP、策略梯度、PPO 等基本概念</li><li><strong>LLM 基础</strong>:了解 Transformer、预训练、微调等技术</li><li><strong>实践 HelloAgents</strong>:运行本章的示例代码，理解完整流程</li></ol><p>进阶阶段</p><ol><li><strong>深入 TRL</strong>:学习 TRL 库的实现，理解 SFT 和 GRPO 等算法的细节</li><li><strong>自定义数据集</strong>:使用自己的数据集训练模型</li><li><strong>自定义奖励函数</strong>:设计适合自己任务的奖励函数</li><li><strong>参数调优</strong>:系统地调优超参数，提升模型性能</li></ol><p>高级阶段</p><ol><li><strong>多步推理</strong>:研究长序列推理任务</li><li><strong>工具学习</strong>:让智能体学会使用工具</li><li><strong>多智能体</strong>:研究多智能体协作</li><li><strong>前沿论文</strong>:阅读最新的研究论文，跟进前沿进展</li></ol><p>希望本章能够帮助你理解和掌握 Agentic RL 技术，在自己的项目中应用这些知识，构建更智能的 Agent 系统!</p><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>[1] Schulman, J., Wolski, F., Dhariwal, P., Radford, A., &amp; Klimov, O. (2017). Proximal Policy Optimization Algorithms. <em>arXiv preprint arXiv:1707.06347</em>.</p><p>[2] Shao, Z., Wang, P., Zhu, Q., Xu, R., Song, J., Zhang, M., … &amp; Guo, D. (2024). DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models. <em>arXiv preprint arXiv:2402.03300</em>.</p><p>[3] Hu, E. J., Shen, Y., Wallis, P., Allen-Zhu, Z., Li, Y., Wang, S., … &amp; Chen, W. (2021). LoRA: Low-Rank Adaptation of Large Language Models. <em>arXiv preprint arXiv:2106.09685</em>.</p><p>[4] Cobbe, K., Kosaraju, V., Bavarian, M., Chen, M., Jun, H., Kaiser, L., … &amp; Schulman, J. (2021). Training Verifiers to Solve Math Word Problems. <em>arXiv preprint arXiv:2110.14168</em>.</p><p>[5] Ouyang, L., Wu, J., Jiang, X., Almeida, D., Wainwright, C., Mishkin, P., … &amp; Lowe, R. (2022). Training language models to follow instructions with human feedback. <em>Advances in Neural Information Processing Systems</em>, 35, 27730-27744.</p><p>[6] Rafailov, R., Sharma, A., Mitchell, E., Ermon, S., Manning, C. D., &amp; Finn, C. (2023). Direct Preference Optimization: Your Language Model is Secretly a Reward Model. <em>arXiv preprint arXiv:2305.18290</em>.</p><p>[7] Lee, H., Phatale, S., Mansoor, H., Lu, K., Mesnard, T., Bishop, C., … &amp; Rastogi, A. (2023). RLAIF: Scaling Reinforcement Learning from Human Feedback with AI Feedback. <em>arXiv preprint arXiv:2309.00267</em>.</p><p>[8] Wei, J., Wang, X., Schuurmans, D., Bosma, M., Ichter, B., Xia, F., … &amp; Zhou, D. (2022). Chain-of-Thought Prompting Elicits Reasoning in Large Language Models. <em>Advances in Neural Information Processing Systems</em>, 35, 24824-24837.</p><p>[9] von Werra, L., Belkada, Y., Tunstall, L., Beeching, E., Thrush, T., Lambert, N., &amp; Huang, S. (2020). TRL: Transformer Reinforcement Learning. <em>GitHub repository</em>. <a href="https://github.com/huggingface/trl">https://github.com/huggingface/trl</a></p><p>[10] Qwen Team. (2025). Qwen3 Technical Report. <em>arXiv preprint arXiv:2505.09388</em>.</p><p>[11] Bai, Y., Jones, A., Ndousse, K., Askell, A., Chen, A., DasSarma, N., … &amp; Kaplan, J. (2022). Training a Helpful and Harmless Assistant with Reinforcement Learning from Human Feedback. <em>arXiv preprint arXiv:2204.05862</em>.</p><p>[12] Wang, X., Wei, J., Schuurmans, D., Le, Q., Chi, E., Narang, S., … &amp; Zhou, D. (2022). Self-Consistency Improves Chain of Thought Reasoning in Language Models. <em>arXiv preprint arXiv:2203.11171</em>.</p><p>[13] Christiano, P. F., Leike, J., Brown, T., Martic, M., Legg, S., &amp; Amodei, D. (2017). Deep Reinforcement Learning from Human Preferences. <em>Advances in Neural Information Processing Systems</em>, 30.</p><p>[14] Stiennon, N., Ouyang, L., Wu, J., Ziegler, D., Lowe, R., Voss, C., … &amp; Christiano, P. F. (2020). Learning to summarize with human feedback. <em>Advances in Neural Information Processing Systems</em>, 33, 3008-3021.</p><p>[15] Ziegler, D. M., Stiennon, N., Wu, J., Brown, T. B., Radford, A., Amodei, D., … &amp; Irving, G. (2019). Fine-Tuning Language Models from Human Preferences. <em>arXiv preprint arXiv:1909.08593</em>.</p><h2 id="习题"><a href="#习题" class="headerlink" title="习题"></a>习题</h2><blockquote><p><strong>提示</strong>：部分习题没有标准答案，重点在于培养学习者对 Agentic RL 和智能体训练的综合理解和实践能力。</p></blockquote><ol><li><p>本章介绍了从 LLM 训练到 Agentic RL 的演进过程。请分析：</p><ul><li>在 11.1.3 节的表 11.1 中，对比了 PBRFT（基于偏好的强化微调）和 Agentic RL 在 MDP 框架下的差异。请深入解释：为什么 Agentic RL 的状态空间 $s_t &#x3D; (\text{prompt}, o_1, o_2, …, o_t)$ 包含历史观察，而 PBRFT 的状态 $s_0 &#x3D; \text{prompt}$ 只包含初始提示？这种差异对训练过程和最终效果有什么影响？</li><li>假设你要训练一个”智能代码调试助手”，它需要：（1）分析代码找出 bug；（2）查阅文档了解 API 用法；（3）修改代码；（4）运行测试验证修复效果。请将这个任务映射到强化学习框架，明确定义状态空间、行动空间、奖励函数和状态转移函数。</li><li>在 11.1.1 节中提到，传统监督学习存在”难以优化长期目标”的局限。请设计一个具体的多步推理任务（如数学证明、复杂问题求解），展示为什么监督学习难以优化中间步骤，而强化学习可以通过延迟奖励来解决这个问题。</li></ul></li><li><p>SFT（监督微调）和 GRPO（群组相对策略优化）是本章的两个核心训练方法。基于 11.2 节和 11.3 节的内容，请深入思考：</p><blockquote><p><strong>提示</strong>：这是一道动手实践题，建议实际操作</p></blockquote><ul><li>在 11.2.4 节的 SFT 训练代码中，我们使用了 LoRA（低秩适配）技术来减少训练参数。请分析：LoRA 的核心思想是什么？为什么它能够用少量参数（如 0.16%）实现接近全参数微调的效果？在什么情况下应该选择 LoRA 而不是全参数微调？</li><li>GRPO 算法（11.3 节）相比传统的 PPO 算法有什么优势？请对比两者的训练流程，分析 GRPO 如何通过”群组相对奖励”来简化训练过程并提升稳定性。如果要将 GRPO 应用到其他任务（如代码生成、对话优化），需要做哪些调整？</li><li>请基于 11.2.5 节的代码，扩展 SFT 训练流程，添加以下功能：（1）支持多轮对话数据的训练；（2）添加数据增强策略（如同义改写、难度调整）；（3）实现训练过程的可视化监控（如 loss 曲线、样本质量评估）。</li></ul></li><li><p>奖励函数设计是 Agentic RL 的核心挑战。基于 11.3.3 节的内容，请完成以下扩展实践：</p><blockquote><p><strong>提示</strong>：这是一道动手实践题，建议实际操作</p></blockquote><ul><li>在 11.3.3 节中，我们为 GSM8K 数学问题设计了简单的二元奖励（正确+1，错误 0）。请设计一个更精细的奖励函数，能够：（1）对部分正确的答案给予部分奖励；（2）对推理过程的合理性进行评分；（3）惩罚过于冗长或低效的解题路径。这个奖励函数应该如何实现？</li><li>奖励函数的设计往往需要领域知识。请为以下三个不同的智能体任务设计奖励函数：（1）代码生成助手（需要考虑代码正确性、可读性、效率）；（2）客服对话智能体（需要考虑问题解决率、用户满意度、响应时间）；（3）游戏 AI（需要考虑胜率、策略多样性、对抗鲁棒性）。</li><li>在实际应用中，奖励函数可能存在”奖励黑客”（reward hacking）问题：智能体找到了获得高奖励的捷径，但并没有真正完成任务。请举例说明这种现象，并设计防御机制来避免奖励黑客。</li></ul></li><li><p>在 11.4 节的”数学推理智能体训练”案例中，我们看到了完整的训练流程。请深入分析：</p><ul><li>案例中使用了 GSM8K 数据集进行训练和评估。请分析：这个数据集的特点是什么？它适合训练什么类型的推理能力？如果要训练一个能够处理更复杂数学问题（如高等数学、数学证明）的智能体，应该如何扩展数据集和训练方法？</li><li>在 11.4.3 节的训练结果中，我们观察到模型在训练集上的准确率提升，但可能存在过拟合风险。请设计一个”泛化能力评估”方案：如何测试模型是否真正学会了数学推理，而不是记住了训练数据？如何通过正则化、数据增强等技术提升泛化能力？</li><li>案例中的训练是离线的（使用预先收集的数据集）。请设计一个”在线学习”方案：智能体在实际使用过程中持续收集用户反馈，并自动更新模型。这个方案需要考虑哪些技术挑战（如数据质量控制、灾难性遗忘、安全性保障）？</li></ul></li><li><p>Agentic RL 的一个重要应用是让智能体学会使用工具。请思考：</p><ul><li>在 11.1.3 节中提到，Agentic RL 适合优化”需要多步推理、工具使用、长期规划”的任务。请设计一个”工具学习”训练方案：给定一组工具（如搜索引擎、计算器、代码执行器），如何训练智能体学会在合适的时机选择合适的工具？奖励函数应该如何设计？</li><li>工具使用往往涉及复杂的依赖关系（如”必须先调用工具 A 获取信息，才能调用工具 B”）。请设计一个”分层强化学习”方案：高层策略负责任务规划，低层策略负责工具调用。这种分层结构如何训练？如何协调高层和低层的优化目标？</li><li>在实际应用中，工具的数量可能非常多（如 50+个 API），直接训练可能面临”探索效率低”的问题。请设计一个”课程学习”（curriculum learning）方案：从简单任务（使用少量工具）开始训练，逐步增加任务难度和工具数量。这个方案应该如何设计课程顺序？如何评估智能体是否准备好进入下一阶段？</li></ul></li></ol>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC11%E7%AB%A0-Agentic-RL/</id>
    <link href="http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC11%E7%AB%A0-Agentic-RL/"/>
    <published>2026-03-02T00:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="第十一章-Agentic-RL"><a href="#第十一章-Agentic-RL" class="headerlink" title="第十一章 Agentic-RL"></a>第十一章 Agentic-RL</h1><h2 id="11-1-从-LLM-训练]]>
    </summary>
    <title>第十一章 Agentic-RL</title>
    <updated>2026-03-08T09:24:16.341Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="第十章-智能体通信协议"><a href="#第十章-智能体通信协议" class="headerlink" title="第十章 智能体通信协议"></a>第十章 智能体通信协议</h1><p>在前面的章节中，我们构建了功能完备的单体智能体，它们具备推理、工具调用和记忆能力。然而，当我们尝试构建更复杂的 AI 系统时，自然会有疑问：<strong>如何让智能体与外部世界高效交互？如何让多个智能体相互协作？</strong></p><p>这正是智能体通信协议要解决的核心问题。本章将为 HelloAgents 框架引入三种通信协议：<strong>MCP（Model Context Protocol）</strong>用于智能体与工具的标准化通信，<strong>A2A（Agent-to-Agent Protocol）</strong>用于智能体间的点对点协作，<strong>ANP（Agent Network Protocol）</strong>用于构建大规模智能体网络。这三种协议共同构成了智能体通信的基础设施层。</p><p>通过本章的学习，您将掌握智能体通信协议的设计理念和实践技能，理解三种主流协议的设计差异，学会如何选择合适的协议来解决实际问题。</p><h2 id="10-1-智能体通信协议基础"><a href="#10-1-智能体通信协议基础" class="headerlink" title="10.1 智能体通信协议基础"></a>10.1 智能体通信协议基础</h2><h3 id="10-1-1-为何需要通信协议"><a href="#10-1-1-为何需要通信协议" class="headerlink" title="10.1.1 为何需要通信协议"></a>10.1.1 为何需要通信协议</h3><p>回顾我们在第七章构建的 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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ReActAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> CalculatorTool, SearchTool<br><br>llm = HelloAgentsLLM()<br>agent = ReActAgent(name=<span class="hljs-string">&quot;AI助手&quot;</span>, llm=llm)<br>agent.add_tool(CalculatorTool())<br>agent.add_tool(SearchTool())<br><br><span class="hljs-comment"># 智能体可以独立完成任务</span><br>response = agent.run(<span class="hljs-string">&quot;搜索最新的AI新闻，并计算相关公司的市值总和&quot;</span>)<br></code></pre></td></tr></table></figure><p>这个智能体工作得很好，但它面临着三个根本性的限制。首先是<strong>工具集成的困境</strong>：每当需要访问新的外部服务（如 GitHub API、数据库、文件系统），我们都必须编写专门的 Tool 类。这不仅工作量大，而且不同开发者编写的工具无法互相兼容。其次是<strong>能力扩展的瓶颈</strong>：智能体的能力被限制在预先定义的工具集内，无法动态发现和使用新的服务。最后是<strong>协作的缺失</strong>：当任务复杂到需要多个专业智能体协作时（如研究员+撰写员+编辑），我们只能通过手动编排来协调它们的工作。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 传统方式：手动集成每个服务</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">GitHubTool</span>(<span class="hljs-title class_ inherited__">BaseTool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;需要手写GitHub API适配器&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, repo_url</span>):<br>        <span class="hljs-comment"># 大量的API调用代码...</span><br>        <span class="hljs-keyword">pass</span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">DatabaseTool</span>(<span class="hljs-title class_ inherited__">BaseTool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;需要手写数据库适配器&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, query</span>):<br>        <span class="hljs-comment"># 数据库连接和查询代码...</span><br>        <span class="hljs-keyword">pass</span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">WeatherTool</span>(<span class="hljs-title class_ inherited__">BaseTool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;需要手写天气API适配器&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, location</span>):<br>        <span class="hljs-comment"># 天气API调用代码...</span><br>        <span class="hljs-keyword">pass</span><br><br><span class="hljs-comment"># 每个新服务都需要重复这个过程</span><br>agent.add_tool(GitHubTool())<br>agent.add_tool(DatabaseTool())<br>agent.add_tool(WeatherTool())<br></code></pre></td></tr></table></figure><p>这种方式存在明显的问题：代码重复（每个工具都要处理 HTTP 请求、错误处理、认证等），难以维护（API 变更需要修改所有相关工具），无法复用（其他开发者的工具无法直接使用），扩展性差（添加新服务需要大量编码工作）。</p><p><strong>通信协议的核心价值</strong>正是解决这些问题。它提供了一套标准化的接口规范，让智能体能够以统一的方式访问各种外部服务，而无需为每个服务编写专门的适配器。这就像互联网的 TCP&#x2F;IP 协议，它让不同的设备能够相互通信，而不需要为每种设备编写专门的通信代码。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool<br><br><span class="hljs-comment"># 连接到MCP服务器，自动获得所有工具</span><br>mcp_tool = MCPTool()  <span class="hljs-comment"># 内置服务器提供基础工具</span><br><br><span class="hljs-comment"># 或者连接到专业的MCP服务器</span><br>github_mcp = MCPTool(server_command=[<span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-github&quot;</span>])<br>database_mcp = MCPTool(server_command=[<span class="hljs-string">&quot;python&quot;</span>, <span class="hljs-string">&quot;database_mcp_server.py&quot;</span>])<br><br><span class="hljs-comment"># 智能体自动获得所有能力，无需手写适配器</span><br>agent.add_tool(mcp_tool)<br>agent.add_tool(github_mcp)<br>agent.add_tool(database_mcp)<br></code></pre></td></tr></table></figure><p>通信协议带来的改变是根本性的：<strong>标准化接口</strong>让不同服务提供统一的访问方式，<strong>互操作性</strong>使得不同开发者的工具可以无缝集成，<strong>动态发现</strong>允许智能体在运行时发现新的服务和能力，<strong>可扩展性</strong>让系统能够轻松添加新的功能模块。</p><h3 id="10-1-2-三种协议设计理念比较"><a href="#10-1-2-三种协议设计理念比较" class="headerlink" title="10.1.2 三种协议设计理念比较"></a>10.1.2 三种协议设计理念比较</h3><p>智能体通信协议并非单一的解决方案，而是针对不同通信场景设计的一系列标准。在本章以目前业界主流的三种协议 MCP、A2A 和 ANP 为例进行实践，下面是一个总览的比较。</p><p><strong>（1）MCP：智能体与工具的桥梁</strong></p><p>MCP（Model Context Protocol）由 Anthropic 团队提出<sup>[1]</sup>，其核心设计理念是<strong>标准化智能体与外部工具&#x2F;资源的通信方式</strong>。想象一下，你的智能体需要访问文件系统、数据库、GitHub、Slack 等各种服务。传统做法是为每个服务编写专门的适配器，这不仅工作量大，而且难以维护。MCP 通过定义统一的协议规范，让所有服务都能以相同的方式被访问。</p><p>MCP 的设计哲学是”上下文共享”。它不仅仅是一个 RPC（远程过程调用）协议，更重要的是它允许智能体和工具之间共享丰富的上下文信息。如图 10.1 所示，当智能体访问一个代码仓库时，MCP 服务器不仅能提供文件内容，还能提供代码结构、依赖关系、提交历史等上下文信息，让智能体能够做出更智能的决策。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-1.png" alt="" width="85%"/>  <p>图 10.1 MCP 设计思想</p></div><p><strong>（2）A2A：智能体间的对话</strong></p><p>A2A（Agent-to-Agent Protocol）协议由 Google 团队提出<sup>2</sup>，其核心设计理念是<strong>实现智能体之间的点对点通信</strong>。与 MCP 关注智能体与工具的通信不同，A2A 关注的是智能体之间如何相互协作。这种设计让智能体能够像人类团队一样进行对话、协商和协作。</p><p>A2A 的设计哲学是”对等通信”。如图 10.2 所示，在 A2A 网络中，每个智能体既是服务提供者，也是服务消费者。智能体可以主动发起请求，也可以响应其他智能体的请求。这种对等的设计避免了中心化协调器的瓶颈，让智能体网络更加灵活和可扩展。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-2.png" alt="" width="85%"/>  <p>图 10.2 A2A 设计思想</p></div><p><strong>（3）ANP：智能体网络的基础设施</strong></p><p>ANP（Agent Network Protocol）是一个概念性的协议框架<sup>3</sup>，目前由开源社区维护，还没有成熟的生态，其核心设计理念是<strong>构建大规模智能体网络的基础设施</strong>。如果说 MCP 解决的是”如何访问工具”，A2A 解决的是”如何与其他智能体对话”，那么 ANP 解决的是”如何在大规模网络中发现和连接智能体”。</p><p>ANP 的设计哲学是”去中心化服务发现”。在一个包含成百上千个智能体的网络中，如何让智能体能够找到它需要的服务？如图 10.3 所示，ANP 提供了服务注册、发现和路由机制，让智能体能够动态地发现网络中的其他服务，而不需要预先配置所有的连接关系。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-3.png" alt="" width="85%"/>  <p>图 10.3 ANP 设计思想</p></div><p>最后在表 10.1 中，让我们通过一个对比表格来更清晰地理解这三种协议的差异：</p><div align="center">  <p>表 10.1 三种协议对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-table-1.png" alt="" width="85%"/></div><p><strong>（4）如何选择合适的协议？</strong></p><p>目前的协议还处于发展早期，MCP 的生态相对成熟，不过各种工具的时效性取决于维护者，更推荐选择大公司背书的 MCP 工具。</p><p>选择协议的关键在于理解你的需求：</p><ul><li>如果你的智能体需要访问外部服务（文件、数据库、API），选择<strong>MCP</strong></li><li>如果你需要多个智能体相互协作完成任务，选择<strong>A2A</strong></li><li>如果你要构建大规模的智能体生态系统，考虑<strong>ANP</strong></li></ul><h3 id="10-1-3-HelloAgents-通信协议架构设计"><a href="#10-1-3-HelloAgents-通信协议架构设计" class="headerlink" title="10.1.3 HelloAgents 通信协议架构设计"></a>10.1.3 HelloAgents 通信协议架构设计</h3><p>在理解了三种协议的设计理念后，让我们看看如何在 HelloAgents 框架中实现和使用它们。我们的设计目标是：<strong>让学习者能够以最简单的方式使用这些协议，同时保持足够的灵活性以应对复杂场景</strong>。</p><p>如图 10.4 所示，HelloAgents 的通信协议架构采用三层设计，从底层到上层分别是：协议实现层、工具封装层和智能体集成层。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-4.png" alt="" width="85%"/>  <p>图 10.4 HelloAgents 通信协议设计</p></div><p><strong>（1）协议实现层</strong>：这一层包含了三种协议的具体实现。MCP 基于 FastMCP 库实现，提供客户端和服务器功能；A2A 基于 Google 官方的 a2a-sdk 实现；ANP 是我们自研的轻量级实现，提供服务发现和网络管理功能，当然目前也有官方的<a href="https://github.com/agent-network-protocol/AgentConnect">实现</a>，考虑到后期的迭代，因此这里只做概念的模拟。</p><p><strong>（2）工具封装层</strong>：这一层将协议实现封装成统一的 Tool 接口。MCPTool、A2ATool 和 ANPTool 都继承自 BaseTool，提供一致的<code>run()</code>方法。这种设计让智能体能够以相同的方式使用不同的协议。</p><p><strong>（3）智能体集成层</strong>：这一层是智能体与协议的集成点。所有的智能体（ReActAgent、SimpleAgent 等）都通过 Tool System 来使用协议工具，无需关心底层的协议细节。</p><h3 id="10-1-4-本章学习目标与快速体验"><a href="#10-1-4-本章学习目标与快速体验" class="headerlink" title="10.1.4 本章学习目标与快速体验"></a>10.1.4 本章学习目标与快速体验</h3><p>让我们先看看第十章的学习内容：</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><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><code class="hljs awk">hello_agents/<br>├── protocols/                          <span class="hljs-comment"># 通信协议模块</span><br>│   ├── mcp/                            <span class="hljs-comment"># MCP协议实现（Model Context Protocol）</span><br>│   │   ├── client.py                   <span class="hljs-comment"># MCP客户端（支持5种传输方式）</span><br>│   │   ├── server.py                   <span class="hljs-comment"># MCP服务器（FastMCP封装）</span><br>│   │   └── utils.py                    <span class="hljs-comment"># 工具函数（create_context/parse_context）</span><br>│   ├── a2a/                            <span class="hljs-comment"># A2A协议实现（Agent-to-Agent Protocol）</span><br>│   │   └── implementation.py           <span class="hljs-comment"># A2A服务器/客户端（基于a2a-sdk，可选依赖）</span><br>│   └── anp/                            <span class="hljs-comment"># ANP协议实现（Agent Network Protocol）</span><br>│       └── implementation.py           <span class="hljs-comment"># ANP服务发现/注册（概念性实现）</span><br>└── tools<span class="hljs-regexp">/builtin/</span>                      <span class="hljs-comment"># 内置工具模块</span><br>    └── protocol_tools.py               <span class="hljs-comment"># 协议工具包装器（MCPTool/A2ATool/ANPTool）</span><br></code></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></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 安装HelloAgents框架（第10章版本）</span><br>pip install <span class="hljs-string">&quot;hello-agents[protocol]==0.2.2&quot;</span><br><br><span class="hljs-comment"># 安装NodeJS, 可以参考Additional-Chapter中的文档</span><br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool, A2ATool, ANPTool<br><br><span class="hljs-comment"># 1. MCP：访问工具</span><br>mcp_tool = MCPTool()<br>result = mcp_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;call_tool&quot;</span>,<br>    <span class="hljs-string">&quot;tool_name&quot;</span>: <span class="hljs-string">&quot;add&quot;</span>,<br>    <span class="hljs-string">&quot;arguments&quot;</span>: &#123;<span class="hljs-string">&quot;a&quot;</span>: <span class="hljs-number">10</span>, <span class="hljs-string">&quot;b&quot;</span>: <span class="hljs-number">20</span>&#125;<br>&#125;)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;MCP计算结果: <span class="hljs-subst">&#123;result&#125;</span>&quot;</span>)  <span class="hljs-comment"># 输出: 30.0</span><br><br><span class="hljs-comment"># 2. ANP：服务发现</span><br>anp_tool = ANPTool()<br>anp_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;register_service&quot;</span>,<br>    <span class="hljs-string">&quot;service_id&quot;</span>: <span class="hljs-string">&quot;calculator&quot;</span>,<br>    <span class="hljs-string">&quot;service_type&quot;</span>: <span class="hljs-string">&quot;math&quot;</span>,<br>    <span class="hljs-string">&quot;endpoint&quot;</span>: <span class="hljs-string">&quot;http://localhost:8080&quot;</span><br>&#125;)<br>services = anp_tool.run(&#123;<span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;discover_services&quot;</span>&#125;)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;发现的服务: <span class="hljs-subst">&#123;services&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 3. A2A：智能体通信</span><br>a2a_tool = A2ATool(<span class="hljs-string">&quot;http://localhost:5000&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;A2A工具创建成功&quot;</span>)<br></code></pre></td></tr></table></figure><p>这个简单的示例展示了三种协议的核心功能。在接下来的章节中，我们将深入学习每种协议的详细用法和最佳实践。</p><h2 id="10-2-MCP-协议实战"><a href="#10-2-MCP-协议实战" class="headerlink" title="10.2 MCP 协议实战"></a>10.2 MCP 协议实战</h2><p>现在，让我们深入学习 MCP，掌握如何让智能体访问外部工具和资源。</p><h3 id="10-2-1-MCP-协议概念介绍"><a href="#10-2-1-MCP-协议概念介绍" class="headerlink" title="10.2.1 MCP 协议概念介绍"></a>10.2.1 MCP 协议概念介绍</h3><p><strong>（1）MCP：智能体的”USB-C”</strong></p><p>想象一下，你的智能体可能需要同时做很多事情，例如：</p><ul><li>读取本地文件系统的文档</li><li>查询 PostgreSQL 数据库</li><li>搜索 GitHub 上的代码</li><li>发送 Slack 消息</li><li>访问 Google Drive</li></ul><p>传统方式下，你需要为每个服务编写适配器代码，处理不同的 API、认证方式、错误处理等。这不仅工作量大，而且难以维护。更重要的是，不同 LLM 平台的 function call 实现差异巨大，切换模型时需要重写大量代码。</p><p>MCP 的出现改变了这一切。它就像 USB-C 统一了各种设备的连接方式一样，<strong>MCP 统一了智能体与外部工具的交互方式</strong>。无论你使用 Claude、GPT 还是其他模型，只要它们支持 MCP 协议，就能无缝访问相同的工具和资源。</p><p><strong>（2）MCP 架构</strong></p><p>MCP 协议采用 Host、Client、Servers 三层架构设计，让我们通过图 10.5 的场景来理解这些组件如何协同工作。</p><p>假设你正在使用 Claude Desktop 询问：”我桌面上有哪些文档？”</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-5.png" alt="" width="85%"/>  <p>图 10.5 MCP 案例演示</p></div><p><strong>三层架构的职责：</strong></p><ol><li><p><strong>Host（宿主层）</strong>：Claude Desktop 作为 Host，负责接收用户提问并与 Claude 模型交互。Host 是用户直接交互的界面，它管理整个对话流程。</p></li><li><p><strong>Client（客户端层）</strong>：当 Claude 模型决定需要访问文件系统时，Host 中内置的 MCP Client 被激活。Client 负责与适当的 MCP Server 建立连接，发送请求并接收响应。</p></li><li><p><strong>Server（服务器层）</strong>：文件系统 MCP Server 被调用，执行实际的文件扫描操作，访问桌面目录，并返回找到的文档列表。</p></li></ol><p><strong>完整的交互流程：</strong>用户问题 → Claude Desktop(Host) → Claude 模型分析 → 需要文件信息 → MCP Client 连接 → 文件系统 MCP Server → 执行操作 → 返回结果 → Claude 生成回答 → 显示在 Claude Desktop 上</p><p>这种架构设计的优势在于<strong>关注点分离</strong>：Host 专注于用户体验，Client 专注于协议通信，Server 专注于具体功能实现。开发者只需专注于开发对应的 MCP Server，无需关心 Host 和 Client 的实现细节。</p><p><strong>（3）MCP 的核心能力</strong></p><p>如表 10.2 所示，MCP 协议提供了三大核心能力，构成完整的工具访问框架：</p><div align="center">  <p>表 10.2 MCP 核心能力</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-table-2.png" alt="" width="85%"/></div><p>这三种能力的区别在于：<strong>Tools 是主动的</strong>（执行操作），<strong>Resources 是被动的</strong>（提供数据），<strong>Prompts 是指导性的</strong>（提供模板）。</p><p><strong>（4）MCP 的工作流程</strong></p><p>让我们通过一个具体例子来理解 MCP 的完整工作流程，如图 10.6 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-6.png" alt="" width="85%"/>  <p>图 10.6 MCP 案例演示</p></div><p>一个关键问题是：<strong>Claude（或其他 LLM）是如何决定使用哪些工具的？</strong> </p><p>当用户提出问题时，完整的工具选择流程如下：</p><ol><li><p><strong>工具发现阶段</strong>：MCP Client 连接到 Server 后，首先调用<code>list_tools()</code>获取所有可用工具的描述信息（包括工具名称、功能说明、参数定义）</p></li><li><p><strong>上下文构建</strong>：Client 将工具列表转换为 LLM 能理解的格式，添加到系统提示词中。例如：</p><figure class="highlight reasonml"><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><code class="hljs reasonml">你可以使用以下工具：<br>- read<span class="hljs-constructor">_file(<span class="hljs-params">path</span>: <span class="hljs-params">str</span>)</span>: 读取指定路径的文件内容<br>- search<span class="hljs-constructor">_code(<span class="hljs-params">query</span>: <span class="hljs-params">str</span>, <span class="hljs-params">language</span>: <span class="hljs-params">str</span>)</span>: 在代码库中搜索<br></code></pre></td></tr></table></figure></li><li><p><strong>模型推理</strong>：LLM 分析用户问题和可用工具，决定是否需要调用工具以及调用哪个工具。这个决策基于工具的描述和当前对话上下文</p></li><li><p><strong>工具执行</strong>：如果 LLM 决定使用工具，Client 通过 MCP Server 执行所选工具，获取结果</p></li><li><p><strong>结果整合</strong>：工具执行结果被送回给 LLM，LLM 结合结果生成最终回答</p></li></ol><p>这个过程是<strong>完全自动化</strong>的，LLM 会根据工具描述的质量来决定是否使用以及如何使用工具。因此，编写清晰、准确的工具描述至关重要。</p><p><strong>（5）MCP 与 Function Calling 的差异</strong></p><p>很多开发者会问：<strong>我已经在用 Function Calling 了，为什么还需要 MCP？</strong> 让我们通过表 10.3 来理解它们的区别。</p><div align="center">  <p>表 10.3 Function Calling 与 MCP 对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-table-3.png" alt="" width="85%"/></div><p>这里我们以智能体需要访问 GitHub 仓库和本地文件系统为例子来详细对比同一个任务的两种实现</p><p><strong>方式 1：使用 Function Calling</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><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><code class="hljs python"><span class="hljs-comment"># 步骤1：为每个LLM提供商定义函数</span><br><span class="hljs-comment"># OpenAI格式</span><br>openai_tools = [<br>    &#123;<br>        <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;function&quot;</span>,<br>        <span class="hljs-string">&quot;function&quot;</span>: &#123;<br>            <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;search_github&quot;</span>,<br>            <span class="hljs-string">&quot;description&quot;</span>: <span class="hljs-string">&quot;搜索GitHub仓库&quot;</span>,<br>            <span class="hljs-string">&quot;parameters&quot;</span>: &#123;<br>                <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;object&quot;</span>,<br>                <span class="hljs-string">&quot;properties&quot;</span>: &#123;<br>                    <span class="hljs-string">&quot;query&quot;</span>: &#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, <span class="hljs-string">&quot;description&quot;</span>: <span class="hljs-string">&quot;搜索关键词&quot;</span>&#125;<br>                &#125;,<br>                <span class="hljs-string">&quot;required&quot;</span>: [<span class="hljs-string">&quot;query&quot;</span>]<br>            &#125;<br>        &#125;<br>    &#125;<br>]<br><br><span class="hljs-comment"># Claude格式</span><br>claude_tools = [<br>    &#123;<br>        <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;search_github&quot;</span>,<br>        <span class="hljs-string">&quot;description&quot;</span>: <span class="hljs-string">&quot;搜索GitHub仓库&quot;</span>,<br>        <span class="hljs-string">&quot;input_schema&quot;</span>: &#123;  <span class="hljs-comment"># 注意：不是parameters</span><br>            <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;object&quot;</span>,<br>            <span class="hljs-string">&quot;properties&quot;</span>: &#123;<br>                <span class="hljs-string">&quot;query&quot;</span>: &#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, <span class="hljs-string">&quot;description&quot;</span>: <span class="hljs-string">&quot;搜索关键词&quot;</span>&#125;<br>            &#125;,<br>            <span class="hljs-string">&quot;required&quot;</span>: [<span class="hljs-string">&quot;query&quot;</span>]<br>        &#125;<br>    &#125;<br>]<br><br><span class="hljs-comment"># 步骤2：自己实现工具函数</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">search_github</span>(<span class="hljs-params">query</span>):<br>    <span class="hljs-keyword">import</span> requests<br>    response = requests.get(<br>        <span class="hljs-string">&quot;https://api.github.com/search/repositories&quot;</span>,<br>        params=&#123;<span class="hljs-string">&quot;q&quot;</span>: query&#125;<br>    )<br>    <span class="hljs-keyword">return</span> response.json()<br><br><span class="hljs-comment"># 步骤3：处理不同模型的响应格式</span><br><span class="hljs-comment"># OpenAI的响应</span><br><span class="hljs-keyword">if</span> response.choices[<span class="hljs-number">0</span>].message.tool_calls:<br>    tool_call = response.choices[<span class="hljs-number">0</span>].message.tool_calls[<span class="hljs-number">0</span>]<br>    result = search_github(**json.loads(tool_call.function.arguments))<br><br><span class="hljs-comment"># Claude的响应</span><br><span class="hljs-keyword">if</span> response.content[<span class="hljs-number">0</span>].<span class="hljs-built_in">type</span> == <span class="hljs-string">&quot;tool_use&quot;</span>:<br>    tool_use = response.content[<span class="hljs-number">0</span>]<br>    result = search_github(**tool_use.<span class="hljs-built_in">input</span>)<br></code></pre></td></tr></table></figure><p><strong>方式 2：使用 MCP</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> MCPClient<br><br><span class="hljs-comment"># 步骤1：连接到社区提供的MCP服务器（无需自己实现）</span><br>github_client = MCPClient([<br>    <span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-github&quot;</span><br>])<br><br>fs_client = MCPClient([<br>    <span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-filesystem&quot;</span>, <span class="hljs-string">&quot;.&quot;</span><br>])<br><br><span class="hljs-comment"># 步骤2：统一的调用方式（与模型无关）</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> github_client:<br>    <span class="hljs-comment"># 自动发现工具</span><br>    tools = <span class="hljs-keyword">await</span> github_client.list_tools()<br><br>    <span class="hljs-comment"># 调用工具（标准化接口）</span><br>    result = <span class="hljs-keyword">await</span> github_client.call_tool(<br>        <span class="hljs-string">&quot;search_repositories&quot;</span>,<br>        &#123;<span class="hljs-string">&quot;query&quot;</span>: <span class="hljs-string">&quot;AI agents&quot;</span>&#125;<br>    )<br><br><span class="hljs-comment"># 步骤3：任何支持MCP的模型都能使用</span><br><span class="hljs-comment"># OpenAI、Claude、Llama等都使用相同的MCP客户端</span><br></code></pre></td></tr></table></figure><p>首先需要明确的是，Function Calling 与 MCP 并非竞争关系，而是相辅相成的。Function Calling 是大语言模型的一项核心能力，它体现了模型内在的智能，使模型能够理解何时需要调用函数，并精准生成相应的调用参数。相对地，MCP 则扮演着基础设施协议的角色，它在工程层面解决了工具与模型如何连接的问题，通过标准化的方式来描述和调用工具。</p><p>我们可以用一个简单的类比来理解：Function Calling 相当于你学会了“如何打电话”这项技能，包括何时拨号、如何与对方沟通、何时挂断。而 MCP 则是那个全球统一的“电话通信标准”，确保了任何一部电话都能顺利地拨通另一部。</p><p>了解了它们之间的互补关系后，我们接下来看看如何在 HelloAgents 中使用 MCP 协议。</p><h3 id="10-2-2-使用-MCP-客户端"><a href="#10-2-2-使用-MCP-客户端" class="headerlink" title="10.2.2 使用 MCP 客户端"></a>10.2.2 使用 MCP 客户端</h3><p>HelloAgents 基于 FastMCP 2.0 实现了完整的 MCP 客户端功能。我们提供了异步和同步两种 API，以适应不同的使用场景。对于大多数应用，推荐使用异步 API，它能更好地处理并发请求和长时间运行的操作。下面我们将提供一个拆解的操作演示。</p><p><strong>（1）连接到 MCP 服务器</strong></p><p>MCP 客户端支持多种连接方式，最常用的是 Stdio 模式（通过标准输入输出与本地进程通信）：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> asyncio<br><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> MCPClient<br><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">connect_to_server</span>():<br>    <span class="hljs-comment"># 方式1：连接到社区提供的文件系统服务器</span><br>    <span class="hljs-comment"># npx会自动下载并运行@modelcontextprotocol/server-filesystem包</span><br>    client = MCPClient([<br>        <span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>,<br>        <span class="hljs-string">&quot;@modelcontextprotocol/server-filesystem&quot;</span>,<br>        <span class="hljs-string">&quot;.&quot;</span>  <span class="hljs-comment"># 指定根目录</span><br>    ])<br><br>    <span class="hljs-comment"># 使用async with确保连接正确关闭</span><br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> client:<br>        <span class="hljs-comment"># 在这里使用client</span><br>        tools = <span class="hljs-keyword">await</span> client.list_tools()<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;可用工具: <span class="hljs-subst">&#123;[t[<span class="hljs-string">&#x27;name&#x27;</span>] <span class="hljs-keyword">for</span> t <span class="hljs-keyword">in</span> tools]&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 方式2：连接到自定义的Python MCP服务器</span><br>    client = MCPClient([<span class="hljs-string">&quot;python&quot;</span>, <span class="hljs-string">&quot;my_mcp_server.py&quot;</span>])<br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> client:<br>        <span class="hljs-comment"># 使用client...</span><br>        <span class="hljs-keyword">pass</span><br><br><span class="hljs-comment"># 运行异步函数</span><br>asyncio.run(connect_to_server())<br></code></pre></td></tr></table></figure><p><strong>（2）发现可用工具</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">discover_tools</span>():<br>    client = MCPClient([<span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-filesystem&quot;</span>, <span class="hljs-string">&quot;.&quot;</span>])<br><br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> client:<br>        <span class="hljs-comment"># 获取所有可用工具</span><br>        tools = <span class="hljs-keyword">await</span> client.list_tools()<br><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;服务器提供了 <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(tools)&#125;</span> 个工具：&quot;</span>)<br>        <span class="hljs-keyword">for</span> tool <span class="hljs-keyword">in</span> tools:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n工具名称: <span class="hljs-subst">&#123;tool[<span class="hljs-string">&#x27;name&#x27;</span>]&#125;</span>&quot;</span>)<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;描述: <span class="hljs-subst">&#123;tool.get(<span class="hljs-string">&#x27;description&#x27;</span>, <span class="hljs-string">&#x27;无描述&#x27;</span>)&#125;</span>&quot;</span>)<br><br>            <span class="hljs-comment"># 打印参数信息</span><br>            <span class="hljs-keyword">if</span> <span class="hljs-string">&#x27;inputSchema&#x27;</span> <span class="hljs-keyword">in</span> tool:<br>                schema = tool[<span class="hljs-string">&#x27;inputSchema&#x27;</span>]<br>                <span class="hljs-keyword">if</span> <span class="hljs-string">&#x27;properties&#x27;</span> <span class="hljs-keyword">in</span> schema:<br>                    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;参数:&quot;</span>)<br>                    <span class="hljs-keyword">for</span> param_name, param_info <span class="hljs-keyword">in</span> schema[<span class="hljs-string">&#x27;properties&#x27;</span>].items():<br>                        param_type = param_info.get(<span class="hljs-string">&#x27;type&#x27;</span>, <span class="hljs-string">&#x27;any&#x27;</span>)<br>                        param_desc = param_info.get(<span class="hljs-string">&#x27;description&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)<br>                        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  - <span class="hljs-subst">&#123;param_name&#125;</span> (<span class="hljs-subst">&#123;param_type&#125;</span>): <span class="hljs-subst">&#123;param_desc&#125;</span>&quot;</span>)<br><br>asyncio.run(discover_tools())<br><br><span class="hljs-comment"># 输出示例：</span><br><span class="hljs-comment"># 服务器提供了 5 个工具：</span><br><span class="hljs-comment">#</span><br><span class="hljs-comment"># 工具名称: read_file</span><br><span class="hljs-comment"># 描述: 读取文件内容</span><br><span class="hljs-comment"># 参数:</span><br><span class="hljs-comment">#   - path (string): 文件路径</span><br><span class="hljs-comment">#</span><br><span class="hljs-comment"># 工具名称: write_file</span><br><span class="hljs-comment"># 描述: 写入文件内容</span><br><span class="hljs-comment"># 参数:</span><br><span class="hljs-comment">#   - path (string): 文件路径</span><br><span class="hljs-comment">#   - content (string): 文件内容</span><br></code></pre></td></tr></table></figure><p><strong>（3）调用工具</strong></p><p>调用工具时，只需提供工具名称和符合 JSON Schema 的参数：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">use_tools</span>():<br>    client = MCPClient([<span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-filesystem&quot;</span>, <span class="hljs-string">&quot;.&quot;</span>])<br><br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> client:<br>        <span class="hljs-comment"># 读取文件</span><br>        result = <span class="hljs-keyword">await</span> client.call_tool(<span class="hljs-string">&quot;read_file&quot;</span>, &#123;<span class="hljs-string">&quot;path&quot;</span>: <span class="hljs-string">&quot;my_README.md&quot;</span>&#125;)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;文件内容：\n<span class="hljs-subst">&#123;result&#125;</span>&quot;</span>)<br><br>        <span class="hljs-comment"># 列出目录</span><br>        result = <span class="hljs-keyword">await</span> client.call_tool(<span class="hljs-string">&quot;list_directory&quot;</span>, &#123;<span class="hljs-string">&quot;path&quot;</span>: <span class="hljs-string">&quot;.&quot;</span>&#125;)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;当前目录文件：<span class="hljs-subst">&#123;result&#125;</span>&quot;</span>)<br><br>        <span class="hljs-comment"># 写入文件</span><br>        result = <span class="hljs-keyword">await</span> client.call_tool(<span class="hljs-string">&quot;write_file&quot;</span>, &#123;<br>            <span class="hljs-string">&quot;path&quot;</span>: <span class="hljs-string">&quot;output.txt&quot;</span>,<br>            <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;Hello from MCP!&quot;</span><br>        &#125;)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;写入结果：<span class="hljs-subst">&#123;result&#125;</span>&quot;</span>)<br><br>asyncio.run(use_tools())<br></code></pre></td></tr></table></figure><p>在这里提供一种更为安全的方式来调用 MCP 服务，可供参考：</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><code class="hljs python"><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">safe_tool_call</span>():<br>    client = MCPClient([<span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-filesystem&quot;</span>, <span class="hljs-string">&quot;.&quot;</span>])<br><br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> client:<br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-comment"># 尝试读取可能不存在的文件</span><br>            result = <span class="hljs-keyword">await</span> client.call_tool(<span class="hljs-string">&quot;read_file&quot;</span>, &#123;<span class="hljs-string">&quot;path&quot;</span>: <span class="hljs-string">&quot;nonexistent.txt&quot;</span>&#125;)<br>            <span class="hljs-built_in">print</span>(result)<br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;工具调用失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>            <span class="hljs-comment"># 可以选择重试、使用默认值或向用户报告错误</span><br><br>asyncio.run(safe_tool_call())<br></code></pre></td></tr></table></figure><p><strong>（4）访问资源</strong></p><p>除了工具，MCP 服务器还可以提供资源（Resources）：</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><code class="hljs python"><span class="hljs-comment"># 列出可用资源</span><br>resources = client.list_resources()<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;可用资源：<span class="hljs-subst">&#123;[r[<span class="hljs-string">&#x27;uri&#x27;</span>] <span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> resources]&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 读取资源</span><br>resource_content = client.read_resource(<span class="hljs-string">&quot;file:///path/to/resource&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;资源内容：<span class="hljs-subst">&#123;resource_content&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>（5）使用提示模板</strong></p><p>MCP 服务器可以提供预定义的提示模板（Prompts）：</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><code class="hljs python"><span class="hljs-comment"># 列出可用提示</span><br>prompts = client.list_prompts()<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;可用提示：<span class="hljs-subst">&#123;[p[<span class="hljs-string">&#x27;name&#x27;</span>] <span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> prompts]&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 获取提示内容</span><br>prompt = client.get_prompt(<span class="hljs-string">&quot;code_review&quot;</span>, &#123;<span class="hljs-string">&quot;language&quot;</span>: <span class="hljs-string">&quot;python&quot;</span>&#125;)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;提示内容：<span class="hljs-subst">&#123;prompt&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>（6）完整示例：使用 GitHub MCP 服务</strong></p><p>让我们通过一个完整的例子来看如何使用社区提供的 GitHub MCP 服务，我们将采用封装好的 MCP Tools 来：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">GitHub MCP 服务示例</span><br><span class="hljs-string"></span><br><span class="hljs-string">注意：需要设置环境变量</span><br><span class="hljs-string">    Windows: $env:GITHUB_PERSONAL_ACCESS_TOKEN=&quot;your_token_here&quot;</span><br><span class="hljs-string">    Linux/macOS: export GITHUB_PERSONAL_ACCESS_TOKEN=&quot;your_token_here&quot;</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool<br><br><span class="hljs-comment"># 创建 GitHub MCP 工具</span><br>github_tool = MCPTool(<br>    server_command=[<span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-github&quot;</span>]<br>)<br><br><span class="hljs-comment"># 1. 列出可用工具</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;📋 可用工具：&quot;</span>)<br>result = github_tool.run(&#123;<span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;list_tools&quot;</span>&#125;)<br><span class="hljs-built_in">print</span>(result)<br><br><span class="hljs-comment"># 2. 搜索仓库</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n🔍 搜索仓库：&quot;</span>)<br>result = github_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;call_tool&quot;</span>,<br>    <span class="hljs-string">&quot;tool_name&quot;</span>: <span class="hljs-string">&quot;search_repositories&quot;</span>,<br>    <span class="hljs-string">&quot;arguments&quot;</span>: &#123;<br>        <span class="hljs-string">&quot;query&quot;</span>: <span class="hljs-string">&quot;AI agents language:python&quot;</span>,<br>        <span class="hljs-string">&quot;page&quot;</span>: <span class="hljs-number">1</span>,<br>        <span class="hljs-string">&quot;perPage&quot;</span>: <span class="hljs-number">3</span><br>    &#125;<br>&#125;)<br><span class="hljs-built_in">print</span>(result)<br><br></code></pre></td></tr></table></figure><h3 id="10-2-3-MCP-传输方式详解"><a href="#10-2-3-MCP-传输方式详解" class="headerlink" title="10.2.3 MCP 传输方式详解"></a>10.2.3 MCP 传输方式详解</h3><p>MCP 协议的一个重要特性是<strong>传输层无关性</strong>（Transport Agnostic）。这意味着 MCP 协议本身不依赖于特定的传输方式，可以在不同的通信通道上运行。HelloAgents 基于 FastMCP 2.0，提供了完整的传输方式支持，让你可以根据实际场景选择最合适的传输模式。</p><p><strong>（1）传输方式概览</strong></p><p>HelloAgents 的<code>MCPClient</code>支持五种传输方式，每种都有不同的使用场景，如表 10.4 所示：</p><div align="center">  <p>表 10.4 MCP 传输方式对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-table-4.png" alt="" width="85%"/></div><p><strong>（2）传输方式使用示例</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool<br><br><span class="hljs-comment"># 1. Memory Transport - 内存传输（用于测试）</span><br><span class="hljs-comment"># 不指定任何参数，使用内置演示服务器</span><br>mcp_tool = MCPTool()<br><br><span class="hljs-comment"># 2. Stdio Transport - 标准输入输出传输（本地开发）</span><br><span class="hljs-comment"># 使用命令列表启动本地服务器</span><br>mcp_tool = MCPTool(server_command=[<span class="hljs-string">&quot;python&quot;</span>, <span class="hljs-string">&quot;examples/mcp_example_server.py&quot;</span>])<br><br><span class="hljs-comment"># 3. Stdio Transport with Args - 带参数的命令传输</span><br><span class="hljs-comment"># 可以传递额外参数</span><br>mcp_tool = MCPTool(server_command=[<span class="hljs-string">&quot;python&quot;</span>, <span class="hljs-string">&quot;examples/mcp_example_server.py&quot;</span>, <span class="hljs-string">&quot;--debug&quot;</span>])<br><br><span class="hljs-comment"># 4. Stdio Transport - 社区服务器（npx方式）</span><br><span class="hljs-comment"># 使用npx启动社区MCP服务器</span><br>mcp_tool = MCPTool(server_command=[<span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-filesystem&quot;</span>, <span class="hljs-string">&quot;.&quot;</span>])<br><br><span class="hljs-comment"># 5. HTTP/SSE/StreamableHTTP Transport</span><br><span class="hljs-comment"># 注意：MCPTool主要用于Stdio和Memory传输</span><br><span class="hljs-comment"># 对于HTTP/SSE等远程传输，建议直接使用MCPClient</span><br></code></pre></td></tr></table></figure><p><strong>（3）Memory Transport - 内存传输</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool<br><br><span class="hljs-comment"># 使用内置演示服务器（Memory传输）</span><br>mcp_tool = MCPTool()<br><br><span class="hljs-comment"># 列出可用工具</span><br>result = mcp_tool.run(&#123;<span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;list_tools&quot;</span>&#125;)<br><span class="hljs-built_in">print</span>(result)<br><br><span class="hljs-comment"># 调用工具</span><br>result = mcp_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;call_tool&quot;</span>,<br>    <span class="hljs-string">&quot;tool_name&quot;</span>: <span class="hljs-string">&quot;add&quot;</span>,<br>    <span class="hljs-string">&quot;arguments&quot;</span>: &#123;<span class="hljs-string">&quot;a&quot;</span>: <span class="hljs-number">10</span>, <span class="hljs-string">&quot;b&quot;</span>: <span class="hljs-number">20</span>&#125;<br>&#125;)<br><span class="hljs-built_in">print</span>(result)<br></code></pre></td></tr></table></figure><p><strong>（4）Stdio Transport - 标准输入输出传输</strong></p><p>适用场景：本地开发、调试、Python 脚本服务器</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool<br><br><span class="hljs-comment"># 方式1：使用自定义Python服务器</span><br>mcp_tool = MCPTool(server_command=[<span class="hljs-string">&quot;python&quot;</span>, <span class="hljs-string">&quot;my_mcp_server.py&quot;</span>])<br><br><span class="hljs-comment"># 方式2：使用社区服务器（文件系统）</span><br>mcp_tool = MCPTool(server_command=[<span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-filesystem&quot;</span>, <span class="hljs-string">&quot;.&quot;</span>])<br><br><span class="hljs-comment"># 列出工具</span><br>result = mcp_tool.run(&#123;<span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;list_tools&quot;</span>&#125;)<br><span class="hljs-built_in">print</span>(result)<br><br><span class="hljs-comment"># 调用工具</span><br>result = mcp_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;call_tool&quot;</span>,<br>    <span class="hljs-string">&quot;tool_name&quot;</span>: <span class="hljs-string">&quot;read_file&quot;</span>,<br>    <span class="hljs-string">&quot;arguments&quot;</span>: &#123;<span class="hljs-string">&quot;path&quot;</span>: <span class="hljs-string">&quot;README.md&quot;</span>&#125;<br>&#125;)<br><span class="hljs-built_in">print</span>(result)<br></code></pre></td></tr></table></figure><p><strong>（5）HTTP Transport - HTTP 传输</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 注意：MCPTool 主要用于 Stdio 和 Memory 传输</span><br><span class="hljs-comment"># 对于 HTTP/SSE 等远程传输，建议使用底层的 MCPClient</span><br><br><span class="hljs-keyword">import</span> asyncio<br><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> MCPClient<br><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_http_transport</span>():<br>    <span class="hljs-comment"># 连接到远程 HTTP MCP 服务器</span><br>    client = MCPClient(<span class="hljs-string">&quot;http://api.example.com/mcp&quot;</span>)<br><br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> client:<br>        <span class="hljs-comment"># 获取服务器信息</span><br>        tools = <span class="hljs-keyword">await</span> client.list_tools()<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;远程服务器工具: <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(tools)&#125;</span> 个&quot;</span>)<br><br>        <span class="hljs-comment"># 调用远程工具</span><br>        result = <span class="hljs-keyword">await</span> client.call_tool(<span class="hljs-string">&quot;process_data&quot;</span>, &#123;<br>            <span class="hljs-string">&quot;data&quot;</span>: <span class="hljs-string">&quot;Hello, World!&quot;</span>,<br>            <span class="hljs-string">&quot;operation&quot;</span>: <span class="hljs-string">&quot;uppercase&quot;</span><br>        &#125;)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;远程处理结果: <span class="hljs-subst">&#123;result&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 注意：需要实际的 HTTP MCP 服务器</span><br><span class="hljs-comment"># asyncio.run(test_http_transport())</span><br></code></pre></td></tr></table></figure><p><strong>（6）SSE Transport - Server-Sent Events 传输</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 注意：MCPTool 主要用于 Stdio 和 Memory 传输</span><br><span class="hljs-comment"># 对于 SSE 传输，建议使用底层的 MCPClient</span><br><br><span class="hljs-keyword">import</span> asyncio<br><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> MCPClient<br><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_sse_transport</span>():<br>    <span class="hljs-comment"># 连接到 SSE MCP 服务器</span><br>    client = MCPClient(<br>        <span class="hljs-string">&quot;http://localhost:8080/sse&quot;</span>,<br>        transport_type=<span class="hljs-string">&quot;sse&quot;</span><br>    )<br><br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> client:<br>        <span class="hljs-comment"># SSE 特别适合流式处理</span><br>        result = <span class="hljs-keyword">await</span> client.call_tool(<span class="hljs-string">&quot;stream_process&quot;</span>, &#123;<br>            <span class="hljs-string">&quot;input&quot;</span>: <span class="hljs-string">&quot;大量数据处理请求&quot;</span>,<br>            <span class="hljs-string">&quot;stream&quot;</span>: <span class="hljs-literal">True</span><br>        &#125;)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;流式处理结果: <span class="hljs-subst">&#123;result&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 注意：需要支持 SSE 的 MCP 服务器</span><br><span class="hljs-comment"># asyncio.run(test_sse_transport())</span><br></code></pre></td></tr></table></figure><p><strong>（7）StreamableHTTP Transport - 流式 HTTP 传输</strong></p><p>适用场景：需要双向流式通信的 HTTP 场景</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 注意：MCPTool 主要用于 Stdio 和 Memory 传输</span><br><span class="hljs-comment"># 对于 StreamableHTTP 传输，建议使用底层的 MCPClient</span><br><br><span class="hljs-keyword">import</span> asyncio<br><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> MCPClient<br><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_streamable_http_transport</span>():<br>    <span class="hljs-comment"># 连接到 StreamableHTTP MCP 服务器</span><br>    client = MCPClient(<br>        <span class="hljs-string">&quot;http://localhost:8080/mcp&quot;</span>,<br>        transport_type=<span class="hljs-string">&quot;streamable_http&quot;</span><br>    )<br><br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> client:<br>        <span class="hljs-comment"># 支持双向流式通信</span><br>        tools = <span class="hljs-keyword">await</span> client.list_tools()<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;StreamableHTTP 服务器工具: <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(tools)&#125;</span> 个&quot;</span>)<br><br><span class="hljs-comment"># 注意：需要支持 StreamableHTTP 的 MCP 服务器</span><br><span class="hljs-comment"># asyncio.run(test_streamable_http_transport())</span><br></code></pre></td></tr></table></figure><h3 id="10-2-4-在智能体中使用-MCP-工具"><a href="#10-2-4-在智能体中使用-MCP-工具" class="headerlink" title="10.2.4 在智能体中使用 MCP 工具"></a>10.2.4 在智能体中使用 MCP 工具</h3><p>前面我们学习了如何直接使用 MCP 客户端。但在实际应用中，我们更希望让智能体<strong>自动</strong>调用 MCP 工具，而不是手动编写调用代码。HelloAgents 提供了<code>MCPTool</code>包装器，让 MCP 服务器无缝集成到智能体的工具链中。</p><p><strong>（1）MCP 工具的自动展开机制</strong></p><p>HelloAgents 的<code>MCPTool</code>有一个特性：<strong>自动展开</strong>。当你添加一个 MCP 工具到 Agent 时，它会自动将 MCP 服务器提供的所有工具展开为独立的工具，让 Agent 可以像调用普通工具一样调用它们。</p><p><strong>方式 1：使用内置演示服务器</strong></p><p>我们在之前实现过计算器的工具函数，在这里将他转化为 MCP 的服务。这是最简单的使用方式。</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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool<br><br>agent = SimpleAgent(name=<span class="hljs-string">&quot;助手&quot;</span>, llm=HelloAgentsLLM())<br><br><span class="hljs-comment"># 无需任何配置，自动使用内置演示服务器</span><br>mcp_tool = MCPTool(name=<span class="hljs-string">&quot;calculator&quot;</span>)<br>agent.add_tool(mcp_tool)<br><span class="hljs-comment"># ✅ MCP工具 &#x27;calculator&#x27; 已展开为 6 个独立工具</span><br><br><span class="hljs-comment"># 智能体可以直接使用展开后的工具</span><br>response = agent.run(<span class="hljs-string">&quot;计算 25 乘以 16&quot;</span>)<br><span class="hljs-built_in">print</span>(response)  <span class="hljs-comment"># 输出：25 乘以 16 的结果是 400</span><br></code></pre></td></tr></table></figure><p><strong>自动展开后的工具</strong>：</p><ul><li><code>calculator_add</code> - 加法计算器</li><li><code>calculator_subtract</code> - 减法计算器</li><li><code>calculator_multiply</code> - 乘法计算器</li><li><code>calculator_divide</code> - 除法计算器</li><li><code>calculator_greet</code> - 友好问候</li><li><code>calculator_get_system_info</code> - 获取系统信息</li></ul><p>Agent 调用时只需提供参数，例如：<code>[TOOL_CALL:calculator_multiply:a=25,b=16]</code>，系统会自动处理类型转换和 MCP 调用。</p><p><strong>方式 2：连接外部 MCP 服务器</strong></p><p>在实际项目中，你需要连接到功能更强大的 MCP 服务器。这些服务器可以是：</p><ul><li><strong>社区提供的官方服务器</strong>（如文件系统、GitHub、数据库等）</li><li><strong>你自己编写的自定义服务器</strong>（封装业务逻辑）</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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool<br><br>agent = SimpleAgent(name=<span class="hljs-string">&quot;文件助手&quot;</span>, llm=HelloAgentsLLM())<br><br><span class="hljs-comment"># 示例1：连接到社区提供的文件系统服务器</span><br>fs_tool = MCPTool(<br>    name=<span class="hljs-string">&quot;filesystem&quot;</span>,  <span class="hljs-comment"># 指定唯一名称</span><br>    description=<span class="hljs-string">&quot;访问本地文件系统&quot;</span>,<br>    server_command=[<span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-filesystem&quot;</span>, <span class="hljs-string">&quot;.&quot;</span>]<br>)<br>agent.add_tool(fs_tool)<br><br><span class="hljs-comment"># 示例2：连接到自定义的 Python MCP 服务器</span><br><span class="hljs-comment"># 关于如何编写自定义MCP服务器，请参考10.5章节</span><br>custom_tool = MCPTool(<br>    name=<span class="hljs-string">&quot;custom_server&quot;</span>,  <span class="hljs-comment"># 使用不同的名称</span><br>    description=<span class="hljs-string">&quot;自定义业务逻辑服务器&quot;</span>,<br>    server_command=[<span class="hljs-string">&quot;python&quot;</span>, <span class="hljs-string">&quot;my_mcp_server.py&quot;</span>]<br>)<br>agent.add_tool(custom_tool)<br><br><span class="hljs-comment"># Agent现在可以自动使用这些工具！</span><br>response = agent.run(<span class="hljs-string">&quot;请读取my_README.md文件，并总结其中的主要内容&quot;</span>)<br><span class="hljs-built_in">print</span>(response)<br></code></pre></td></tr></table></figure><p>当使用多个 MCP 服务器时，务必为每个 MCPTool 指定不同的 name，这个 name 会作为前缀添加到展开的工具名前，避免冲突。例如：<code>name=&quot;fs&quot;</code> 会展开为 <code>fs_read_file</code>、<code>fs_write_file</code> 等。如果你需要编写自己的 MCP 服务器来封装特定的业务逻辑，请参考 10.5 节内容。</p><p><strong>（2）MCP 工具自动展开的工作原理</strong></p><p>理解自动展开机制有助于你更好地使用 MCP 工具。让我们深入了解它是如何工作的：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 用户代码</span><br>fs_tool = MCPTool(name=<span class="hljs-string">&quot;fs&quot;</span>, server_command=[...])<br>agent.add_tool(fs_tool)<br><br><span class="hljs-comment"># 内部发生的事情：</span><br><span class="hljs-comment"># 1. MCPTool连接到服务器，发现14个工具</span><br><span class="hljs-comment"># 2. 为每个工具创建包装器：</span><br><span class="hljs-comment">#    - fs_read_text_file (参数: path, tail, head)</span><br><span class="hljs-comment">#    - fs_write_file (参数: path, content)</span><br><span class="hljs-comment">#    - ...</span><br><span class="hljs-comment"># 3. 注册到Agent的工具注册表</span><br><br><span class="hljs-comment"># Agent调用</span><br>response = agent.run(<span class="hljs-string">&quot;读取README.md&quot;</span>)<br><br><span class="hljs-comment"># Agent内部：</span><br><span class="hljs-comment"># 1. 识别需要调用 fs_read_text_file</span><br><span class="hljs-comment"># 2. 生成参数：path=README.md</span><br><span class="hljs-comment"># 3. 包装器转换为MCP格式：</span><br><span class="hljs-comment">#    &#123;&quot;action&quot;: &quot;call_tool&quot;, &quot;tool_name&quot;: &quot;read_text_file&quot;, &quot;arguments&quot;: &#123;&quot;path&quot;: &quot;README.md&quot;&#125;&#125;</span><br><span class="hljs-comment"># 4. 调用MCP服务器</span><br><span class="hljs-comment"># 5. 返回文件内容</span><br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># Agent调用计算器</span><br>agent.run(<span class="hljs-string">&quot;计算 25 乘以 16&quot;</span>)<br><br><span class="hljs-comment"># Agent生成：a=25,b=16 (字符串)</span><br><span class="hljs-comment"># 系统自动转换为：&#123;&quot;a&quot;: 25.0, &quot;b&quot;: 16.0&#125; (数字)</span><br><span class="hljs-comment"># MCP服务器接收到正确的数字类型</span><br></code></pre></td></tr></table></figure><p><strong>（3）实战案例：智能文档助手</strong></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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">多Agent协作的智能文档助手</span><br><span class="hljs-string"></span><br><span class="hljs-string">使用两个SimpleAgent分工协作：</span><br><span class="hljs-string">- Agent1：GitHub搜索专家</span><br><span class="hljs-string">- Agent2：文档生成专家</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool<br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><br><span class="hljs-comment"># 加载.env文件中的环境变量</span><br>load_dotenv(dotenv_path=<span class="hljs-string">&quot;../HelloAgents/.env&quot;</span>)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span>*<span class="hljs-number">70</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;多Agent协作的智能文档助手&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span>*<span class="hljs-number">70</span>)<br><br><span class="hljs-comment"># ============================================================</span><br><span class="hljs-comment"># Agent 1: GitHub搜索专家</span><br><span class="hljs-comment"># ============================================================</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n【步骤1】创建GitHub搜索专家...&quot;</span>)<br><br>github_searcher = SimpleAgent(<br>    name=<span class="hljs-string">&quot;GitHub搜索专家&quot;</span>,<br>    llm=HelloAgentsLLM(),<br>    system_prompt=<span class="hljs-string">&quot;&quot;&quot;你是一个GitHub搜索专家。</span><br><span class="hljs-string">你的任务是搜索GitHub仓库并返回结果。</span><br><span class="hljs-string">请返回清晰、结构化的搜索结果，包括：</span><br><span class="hljs-string">- 仓库名称</span><br><span class="hljs-string">- 简短描述</span><br><span class="hljs-string"></span><br><span class="hljs-string">保持简洁，不要添加额外的解释。&quot;&quot;&quot;</span><br>)<br><br><span class="hljs-comment"># 添加GitHub工具</span><br>github_tool = MCPTool(<br>    name=<span class="hljs-string">&quot;gh&quot;</span>,<br>    server_command=[<span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-github&quot;</span>]<br>)<br>github_searcher.add_tool(github_tool)<br><br><span class="hljs-comment"># ============================================================</span><br><span class="hljs-comment"># Agent 2: 文档生成专家</span><br><span class="hljs-comment"># ============================================================</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n【步骤2】创建文档生成专家...&quot;</span>)<br><br>document_writer = SimpleAgent(<br>    name=<span class="hljs-string">&quot;文档生成专家&quot;</span>,<br>    llm=HelloAgentsLLM(),<br>    system_prompt=<span class="hljs-string">&quot;&quot;&quot;你是一个文档生成专家。</span><br><span class="hljs-string">你的任务是根据提供的信息生成结构化的Markdown报告。</span><br><span class="hljs-string"></span><br><span class="hljs-string">报告应该包括：</span><br><span class="hljs-string">- 标题</span><br><span class="hljs-string">- 简介</span><br><span class="hljs-string">- 主要内容（分点列出，包括项目名称、描述等）</span><br><span class="hljs-string">- 总结</span><br><span class="hljs-string"></span><br><span class="hljs-string">请直接输出完整的Markdown格式报告内容，不要使用工具保存。&quot;&quot;&quot;</span><br>)<br><br><span class="hljs-comment"># 添加文件系统工具</span><br>fs_tool = MCPTool(<br>    name=<span class="hljs-string">&quot;fs&quot;</span>,<br>    server_command=[<span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@modelcontextprotocol/server-filesystem&quot;</span>, <span class="hljs-string">&quot;.&quot;</span>]<br>)<br>document_writer.add_tool(fs_tool)<br><br><span class="hljs-comment"># ============================================================</span><br><span class="hljs-comment"># 执行任务</span><br><span class="hljs-comment"># ============================================================</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n&quot;</span> + <span class="hljs-string">&quot;=&quot;</span>*<span class="hljs-number">70</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;开始执行任务...&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span>*<span class="hljs-number">70</span>)<br><br><span class="hljs-keyword">try</span>:<br>    <span class="hljs-comment"># 步骤1：GitHub搜索</span><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n【步骤3】Agent1 搜索GitHub...&quot;</span>)<br>    search_task = <span class="hljs-string">&quot;搜索关于&#x27;AI agent&#x27;的GitHub仓库，返回前5个最相关的结果&quot;</span><br>    <br>    search_results = github_searcher.run(search_task)<br>    <br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n搜索结果:&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;-&quot;</span> * <span class="hljs-number">70</span>)<br>    <span class="hljs-built_in">print</span>(search_results)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;-&quot;</span> * <span class="hljs-number">70</span>)<br>    <br>    <span class="hljs-comment"># 步骤2：生成报告</span><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n【步骤4】Agent2 生成报告...&quot;</span>)<br>    report_task = <span class="hljs-string">f&quot;&quot;&quot;</span><br><span class="hljs-string">根据以下GitHub搜索结果，生成一份Markdown格式的研究报告：</span><br><span class="hljs-string"></span><br><span class="hljs-string"><span class="hljs-subst">&#123;search_results&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">报告要求：</span><br><span class="hljs-string">1. 标题：# AI Agent框架研究报告</span><br><span class="hljs-string">2. 简介：说明这是关于AI Agent的GitHub项目调研</span><br><span class="hljs-string">3. 主要发现：列出找到的项目及其特点（包括名称、描述等）</span><br><span class="hljs-string">4. 总结：总结这些项目的共同特点</span><br><span class="hljs-string"></span><br><span class="hljs-string">请直接输出完整的Markdown格式报告。</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br>    report_content = document_writer.run(report_task)<br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n报告内容:&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">70</span>)<br>    <span class="hljs-built_in">print</span>(report_content)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">70</span>)<br><br>    <span class="hljs-comment"># 步骤3：保存报告</span><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n【步骤5】保存报告到文件...&quot;</span>)<br>    <span class="hljs-keyword">import</span> os<br>    <span class="hljs-keyword">try</span>:<br>        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(<span class="hljs-string">&quot;report.md&quot;</span>, <span class="hljs-string">&quot;w&quot;</span>, encoding=<span class="hljs-string">&quot;utf-8&quot;</span>) <span class="hljs-keyword">as</span> f:<br>            f.write(report_content)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;✅ 报告已保存到 report.md&quot;</span>)<br><br>        <span class="hljs-comment"># 验证文件</span><br>        file_size = os.path.getsize(<span class="hljs-string">&quot;report.md&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 文件大小: <span class="hljs-subst">&#123;file_size&#125;</span> 字节&quot;</span>)<br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;❌ 保存失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>    <br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n&quot;</span> + <span class="hljs-string">&quot;=&quot;</span>*<span class="hljs-number">70</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;任务完成！&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span>*<span class="hljs-number">70</span>)<br>    <br><span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n❌ 错误: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>    <span class="hljs-keyword">import</span> traceback<br>    traceback.print_exc()<br><br></code></pre></td></tr></table></figure><p><code>github_searcher</code>会在这个过程中调用<code>gh_search_repositories</code>搜索 GitHub 项目。得到的结果会返回给<code>document_writer</code>当做输入，进一步指导报告的生成，最后保存报告到 report.md。</p><h3 id="10-2-5-MCP-社区生态"><a href="#10-2-5-MCP-社区生态" class="headerlink" title="10.2.5 MCP 社区生态"></a>10.2.5 MCP 社区生态</h3><p>MCP 协议的一个巨大优势是<strong>丰富的社区生态</strong>。Anthropic 和社区开发者已经创建了大量现成的 MCP 服务器，涵盖文件系统、数据库、API 服务等各种场景。这意味着你不需要从零开始编写工具适配器，可以直接使用这些经过验证的服务器。</p><p>这里给出 MCP 社区的三个资源库：</p><ol><li><p><strong>Awesome MCP Servers</strong> (<a href="https://github.com/punkpeye/awesome-mcp-servers">https://github.com/punkpeye/awesome-mcp-servers</a>)</p><ul><li>社区维护的 MCP 服务器精选列表</li><li>包含各种第三方服务器</li><li>按功能分类，易于查找</li></ul></li><li><p><strong>MCP Servers Website</strong> (<a href="https://mcpservers.org/">https://mcpservers.org/</a>)</p><ul><li>官方 MCP 服务器目录网站</li><li>提供搜索和筛选功能</li><li>包含使用说明和示例</li></ul></li><li><p><strong>Official MCP Servers</strong> (<a href="https://github.com/modelcontextprotocol/servers">https://github.com/modelcontextprotocol/servers</a>)</p><ul><li>Anthropic 官方维护的服务器</li><li>质量最高、文档最完善</li><li>包含常用服务的实现</li></ul></li></ol><p>表 10.5 和 10.6 给出常用的官方 MCP 服务器和社区热门 MCP 服务器：</p><div align="center">  <p>表 10.5 常用官方 MCP 服务器</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-table-5.png" alt="" width="85%"/></div><div align="center">  <p>表 10.6 社区热门 MCP 服务器</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-table-6.png" alt="" width="85%"/></div><p>以下是一些特别有趣的案例 TODO 可供参考：</p><ol><li><p><strong>自动化网页测试（Playwright）</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># Agent可以自动：</span><br><span class="hljs-comment"># - 打开浏览器访问网站</span><br><span class="hljs-comment"># - 填写表单并提交</span><br><span class="hljs-comment"># - 截图验证结果</span><br><span class="hljs-comment"># - 生成测试报告</span><br>playwright_tool = MCPTool(<br>    name=<span class="hljs-string">&quot;playwright&quot;</span>,<br>    server_command=[<span class="hljs-string">&quot;npx&quot;</span>, <span class="hljs-string">&quot;-y&quot;</span>, <span class="hljs-string">&quot;@playwright/mcp&quot;</span>]<br>)<br></code></pre></td></tr></table></figure></li><li><p><strong>智能笔记助手（Obsidian + Perplexity）</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># Agent可以：</span><br><span class="hljs-comment"># - 搜索最新技术资讯（Perplexity）</span><br><span class="hljs-comment"># - 整理成结构化笔记</span><br><span class="hljs-comment"># - 保存到Obsidian知识库</span><br><span class="hljs-comment"># - 自动建立笔记间的链接</span><br></code></pre></td></tr></table></figure></li><li><p><strong>项目管理自动化（Jira + GitHub）</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># Agent可以：</span><br><span class="hljs-comment"># - 从GitHub Issue创建Jira任务</span><br><span class="hljs-comment"># - 同步代码提交到Jira</span><br><span class="hljs-comment"># - 自动更新Sprint进度</span><br><span class="hljs-comment"># - 生成项目报告</span><br></code></pre></td></tr></table></figure></li><li><p><strong>内容创作工作流（YouTube + Notion + Spotify）</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># Agent可以：</span><br><span class="hljs-comment"># - 获取YouTube视频字幕</span><br><span class="hljs-comment"># - 生成内容摘要</span><br><span class="hljs-comment"># - 保存到Notion数据库</span><br><span class="hljs-comment"># - 播放背景音乐（Spotify）</span><br></code></pre></td></tr></table></figure></li></ol><p>通过这一节内容的讲解，希望你能探索更多 MCP 的实现案例，也欢迎投稿至 Helloagents！接下来，让我们学习 A2A 协议。</p><h2 id="10-3-A2A-协议实战"><a href="#10-3-A2A-协议实战" class="headerlink" title="10.3 A2A 协议实战"></a>10.3 A2A 协议实战</h2><p>A2A（Agent-to-Agent）是一种支持智能体之间直接通信与协作的协议。</p><h3 id="10-3-1-协议设计动机"><a href="#10-3-1-协议设计动机" class="headerlink" title="10.3.1 协议设计动机"></a>10.3.1 协议设计动机</h3><p>MCP 协议解决了智能体与工具的交互，而 A2A 协议则解决智能体之间的协作问题。在一个需要多智能体（如研究员、撰写员、编辑）协作的任务中，它们需要通信、委托任务、协商能力和同步状态。</p><p>传统的中央协调器（星型拓扑）方案存在三个主要问题：</p><ul><li><strong>单点故障</strong>：协调器失效导致系统整体瘫痪。</li><li><strong>性能瓶颈</strong>：所有通信都经过中心节点，限制了并发。</li><li><strong>扩展困难</strong>：增加或修改智能体需要改动中心逻辑。</li></ul><p>A2A 协议采用点对点（P2P）架构（网状拓拓），允许智能体直接通信，从根本上解决了上述问题。它的核心是<strong>任务（Task）</strong>和<strong>工件（Artifact）</strong>这两个抽象概念，这是它与 MCP 最大的区别，如表 10.7 所示。</p><div align="center">  <p>表 10.7 A2A 核心概念</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-table-7.png" alt="" width="85%"/></div><p>为实现对协作过程的管理，A2A 为任务定义了标准化的生命周期，包括创建、协商、代理、执行中、完成、失败等状态，可见图 10.7。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-7.png" alt="" width="85%"/>  <p>图 10.7 A2A 任务周期</p></div><p>该机制使智能体可以进行任务协商、进度跟踪和异常处理。</p><p>A2A 请求生命周期是一个序列，详细说明了请求遵循的四个主要步骤：代理发现、身份验证、发送消息 API 和发送消息流 API。下图 10.8 借鉴了官网的流程图，用来展示了操作流程，说明了客户端、A2A 服务器和身份验证服务器之间的交互。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-8.png" alt="" width="85%"/>  <p>图 10.8 A2A 请求生命周期</p></div><h3 id="10-3-2-使用-A2A-协议实战"><a href="#10-3-2-使用-A2A-协议实战" class="headerlink" title="10.3.2 使用 A2A 协议实战"></a>10.3.2 使用 A2A 协议实战</h3><p>A2A 现有实现大部分为<code>Sample Code</code>，并且即使有 Python 的实现也较为繁琐，因此这里我们只采用模拟协议思想的方式，通过 A2A-SDK 来继承部分功能实现。</p><p><strong>（2）创建简单的 A2A 智能体</strong></p><p>让我们创建一个 A2A 的智能体，同样是计算器案例作为演示：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.protocols.a2a.implementation <span class="hljs-keyword">import</span> A2AServer, A2A_AVAILABLE<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_calculator_agent</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;创建一个计算器智能体&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> A2A_AVAILABLE:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;❌ A2A SDK 未安装，请运行: pip install a2a-sdk&quot;</span>)<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span><br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;🧮 创建计算器智能体&quot;</span>)<br><br>    <span class="hljs-comment"># 创建 A2A 服务器</span><br>    calculator = A2AServer(<br>        name=<span class="hljs-string">&quot;calculator-agent&quot;</span>,<br>        description=<span class="hljs-string">&quot;专业的数学计算智能体&quot;</span>,<br>        version=<span class="hljs-string">&quot;1.0.0&quot;</span>,<br>        capabilities=&#123;<br>            <span class="hljs-string">&quot;math&quot;</span>: [<span class="hljs-string">&quot;addition&quot;</span>, <span class="hljs-string">&quot;subtraction&quot;</span>, <span class="hljs-string">&quot;multiplication&quot;</span>, <span class="hljs-string">&quot;division&quot;</span>],<br>            <span class="hljs-string">&quot;advanced&quot;</span>: [<span class="hljs-string">&quot;power&quot;</span>, <span class="hljs-string">&quot;sqrt&quot;</span>, <span class="hljs-string">&quot;factorial&quot;</span>]<br>        &#125;<br>    )<br><br>    <span class="hljs-comment"># 添加基础计算技能</span><br><span class="hljs-meta">    @calculator.skill(<span class="hljs-params"><span class="hljs-string">&quot;add&quot;</span></span>)</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">add_numbers</span>(<span class="hljs-params">query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;加法计算&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-comment"># 简单解析 &quot;计算 5 + 3&quot; 格式</span><br>            parts = query.replace(<span class="hljs-string">&quot;计算&quot;</span>, <span class="hljs-string">&quot;&quot;</span>).replace(<span class="hljs-string">&quot;加&quot;</span>, <span class="hljs-string">&quot;+&quot;</span>).replace(<span class="hljs-string">&quot;加上&quot;</span>, <span class="hljs-string">&quot;+&quot;</span>)<br>            <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;+&quot;</span> <span class="hljs-keyword">in</span> parts:<br>                numbers = [<span class="hljs-built_in">float</span>(x.strip()) <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> parts.split(<span class="hljs-string">&quot;+&quot;</span>)]<br>                result = <span class="hljs-built_in">sum</span>(numbers)<br>                <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;计算结果: <span class="hljs-subst">&#123;<span class="hljs-string">&#x27; + &#x27;</span>.join(<span class="hljs-built_in">map</span>(<span class="hljs-built_in">str</span>, numbers))&#125;</span> = <span class="hljs-subst">&#123;result&#125;</span>&quot;</span><br>            <span class="hljs-keyword">else</span>:<br>                <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;请使用格式: 计算 5 + 3&quot;</span><br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;计算错误: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span><br><br><span class="hljs-meta">    @calculator.skill(<span class="hljs-params"><span class="hljs-string">&quot;multiply&quot;</span></span>)</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">multiply_numbers</span>(<span class="hljs-params">query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;乘法计算&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">try</span>:<br>            parts = query.replace(<span class="hljs-string">&quot;计算&quot;</span>, <span class="hljs-string">&quot;&quot;</span>).replace(<span class="hljs-string">&quot;乘以&quot;</span>, <span class="hljs-string">&quot;*&quot;</span>).replace(<span class="hljs-string">&quot;×&quot;</span>, <span class="hljs-string">&quot;*&quot;</span>)<br>            <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;*&quot;</span> <span class="hljs-keyword">in</span> parts:<br>                numbers = [<span class="hljs-built_in">float</span>(x.strip()) <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> parts.split(<span class="hljs-string">&quot;*&quot;</span>)]<br>                result = <span class="hljs-number">1</span><br>                <span class="hljs-keyword">for</span> num <span class="hljs-keyword">in</span> numbers:<br>                    result *= num<br>                <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;计算结果: <span class="hljs-subst">&#123;<span class="hljs-string">&#x27; × &#x27;</span>.join(<span class="hljs-built_in">map</span>(<span class="hljs-built_in">str</span>, numbers))&#125;</span> = <span class="hljs-subst">&#123;result&#125;</span>&quot;</span><br>            <span class="hljs-keyword">else</span>:<br>                <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;请使用格式: 计算 5 * 3&quot;</span><br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;计算错误: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span><br><br><span class="hljs-meta">    @calculator.skill(<span class="hljs-params"><span class="hljs-string">&quot;info&quot;</span></span>)</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_info</span>(<span class="hljs-params">query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;获取智能体信息&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;我是 <span class="hljs-subst">&#123;calculator.name&#125;</span>，可以进行基础数学计算。支持的技能: <span class="hljs-subst">&#123;<span class="hljs-built_in">list</span>(calculator.skills.keys())&#125;</span>&quot;</span><br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 计算器智能体创建成功，支持技能: <span class="hljs-subst">&#123;<span class="hljs-built_in">list</span>(calculator.skills.keys())&#125;</span>&quot;</span>)<br>    <span class="hljs-keyword">return</span> calculator<br><br><span class="hljs-comment"># 创建智能体</span><br>calc_agent = create_calculator_agent()<br><span class="hljs-keyword">if</span> calc_agent:<br>    <span class="hljs-comment"># 测试技能</span><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n🧪 测试智能体技能:&quot;</span>)<br>    test_queries = [<br>        <span class="hljs-string">&quot;获取信息&quot;</span>,<br>        <span class="hljs-string">&quot;计算 10 + 5&quot;</span>,<br>        <span class="hljs-string">&quot;计算 6 * 7&quot;</span><br>    ]<br><br>    <span class="hljs-keyword">for</span> query <span class="hljs-keyword">in</span> test_queries:<br>        <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;信息&quot;</span> <span class="hljs-keyword">in</span> query:<br>            result = calc_agent.skills[<span class="hljs-string">&quot;info&quot;</span>](query)<br>        <span class="hljs-keyword">elif</span> <span class="hljs-string">&quot;+&quot;</span> <span class="hljs-keyword">in</span> query:<br>            result = calc_agent.skills[<span class="hljs-string">&quot;add&quot;</span>](query)<br>        <span class="hljs-keyword">elif</span> <span class="hljs-string">&quot;*&quot;</span> <span class="hljs-keyword">in</span> query <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;×&quot;</span> <span class="hljs-keyword">in</span> query:<br>            result = calc_agent.skills[<span class="hljs-string">&quot;multiply&quot;</span>](query)<br>        <span class="hljs-keyword">else</span>:<br>            result = <span class="hljs-string">&quot;未知查询类型&quot;</span><br><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  📝 查询: <span class="hljs-subst">&#123;query&#125;</span>&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  🤖 回复: <span class="hljs-subst">&#123;result&#125;</span>&quot;</span>)<br>        <span class="hljs-built_in">print</span>()<br></code></pre></td></tr></table></figure><p><strong>（2）自定义 A2A 智能体</strong></p><p>你也可以创建自己的 A2A 智能体，这里只是进行简单演示：</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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.protocols.a2a.implementation <span class="hljs-keyword">import</span> A2AServer, A2A_AVAILABLE<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_custom_agent</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;创建自定义智能体&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> A2A_AVAILABLE:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;请先安装 A2A SDK: pip install a2a-sdk&quot;</span>)<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span><br><br>    <span class="hljs-comment"># 创建智能体</span><br>    agent = A2AServer(<br>        name=<span class="hljs-string">&quot;my-custom-agent&quot;</span>,<br>        description=<span class="hljs-string">&quot;我的自定义智能体&quot;</span>,<br>        capabilities=&#123;<span class="hljs-string">&quot;custom&quot;</span>: [<span class="hljs-string">&quot;skill1&quot;</span>, <span class="hljs-string">&quot;skill2&quot;</span>]&#125;<br>    )<br><br>    <span class="hljs-comment"># 添加技能</span><br><span class="hljs-meta">    @agent.skill(<span class="hljs-params"><span class="hljs-string">&quot;greet&quot;</span></span>)</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">greet_user</span>(<span class="hljs-params">name: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;问候用户&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;你好，<span class="hljs-subst">&#123;name&#125;</span>！我是自定义智能体。&quot;</span><br><br><span class="hljs-meta">    @agent.skill(<span class="hljs-params"><span class="hljs-string">&quot;calculate&quot;</span></span>)</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">simple_calculate</span>(<span class="hljs-params">expression: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;简单计算&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-comment"># 安全的计算（仅支持基本运算）</span><br>            allowed_chars = <span class="hljs-built_in">set</span>(<span class="hljs-string">&#x27;0123456789+-*/(). &#x27;</span>)<br>            <span class="hljs-keyword">if</span> <span class="hljs-built_in">all</span>(c <span class="hljs-keyword">in</span> allowed_chars <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> expression):<br>                result = <span class="hljs-built_in">eval</span>(expression)<br>                <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;计算结果: <span class="hljs-subst">&#123;expression&#125;</span> = <span class="hljs-subst">&#123;result&#125;</span>&quot;</span><br>            <span class="hljs-keyword">else</span>:<br>                <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;错误: 只支持基本数学运算&quot;</span><br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;计算错误: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span><br><br>    <span class="hljs-keyword">return</span> agent<br><br><span class="hljs-comment"># 创建并测试自定义智能体</span><br>custom_agent = create_custom_agent()<br><span class="hljs-keyword">if</span> custom_agent:<br>    <span class="hljs-comment"># 测试技能</span><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;测试问候技能:&quot;</span>)<br>    result1 = custom_agent.skills[<span class="hljs-string">&quot;greet&quot;</span>](<span class="hljs-string">&quot;张三&quot;</span>)<br>    <span class="hljs-built_in">print</span>(result1)<br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n测试计算技能:&quot;</span>)<br>    result2 = custom_agent.skills[<span class="hljs-string">&quot;calculate&quot;</span>](<span class="hljs-string">&quot;10 + 5 * 2&quot;</span>)<br>    <span class="hljs-built_in">print</span>(result2)<br></code></pre></td></tr></table></figure><h3 id="10-3-3-使用-HelloAgents-A2A-工具"><a href="#10-3-3-使用-HelloAgents-A2A-工具" class="headerlink" title="10.3.3 使用 HelloAgents A2A 工具"></a>10.3.3 使用 HelloAgents A2A 工具</h3><p>HelloAgents 提供了统一的 A2A 工具接口。</p><p><strong>（1）创建 A2A Agent 服务端</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><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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> A2AServer<br><span class="hljs-keyword">import</span> threading<br><span class="hljs-keyword">import</span> time<br><br><span class="hljs-comment"># 创建研究员Agent服务</span><br>researcher = A2AServer(<br>    name=<span class="hljs-string">&quot;researcher&quot;</span>,<br>    description=<span class="hljs-string">&quot;负责搜索和分析资料的Agent&quot;</span>,<br>    version=<span class="hljs-string">&quot;1.0.0&quot;</span><br>)<br><br><span class="hljs-comment"># 定义技能</span><br><span class="hljs-meta">@researcher.skill(<span class="hljs-params"><span class="hljs-string">&quot;research&quot;</span></span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">handle_research</span>(<span class="hljs-params">text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;处理研究请求&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">import</span> re<br>    <span class="hljs-keyword">match</span> = re.search(<span class="hljs-string">r&#x27;research\s+(.+)&#x27;</span>, text, re.IGNORECASE)<br>    topic = <span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>).strip() <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span> <span class="hljs-keyword">else</span> text<br>    <br>    <span class="hljs-comment"># 实际的研究逻辑（这里简化）</span><br>    result = &#123;<br>        <span class="hljs-string">&quot;topic&quot;</span>: topic,<br>        <span class="hljs-string">&quot;findings&quot;</span>: <span class="hljs-string">f&quot;关于<span class="hljs-subst">&#123;topic&#125;</span>的研究结果...&quot;</span>,<br>        <span class="hljs-string">&quot;sources&quot;</span>: [<span class="hljs-string">&quot;来源1&quot;</span>, <span class="hljs-string">&quot;来源2&quot;</span>, <span class="hljs-string">&quot;来源3&quot;</span>]<br>    &#125;<br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(result)<br><br><span class="hljs-comment"># 在后台启动服务</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">start_server</span>():<br>    researcher.run(host=<span class="hljs-string">&quot;localhost&quot;</span>, port=<span class="hljs-number">5000</span>)<br><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:<br>    server_thread = threading.Thread(target=start_server, daemon=<span class="hljs-literal">True</span>)<br>    server_thread.start()<br>    <br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;✅ 研究员Agent服务已启动在 http://localhost:5000&quot;</span>)<br>    <br>    <span class="hljs-comment"># 保持程序运行</span><br>    <span class="hljs-keyword">try</span>:<br>        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:<br>            time.sleep(<span class="hljs-number">1</span>)<br>    <span class="hljs-keyword">except</span> KeyboardInterrupt:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n服务已停止&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>（2）创建 A2A Agent 客户端</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> A2AClient<br><br><span class="hljs-comment"># 创建客户端连接到研究员Agent</span><br>client = A2AClient(<span class="hljs-string">&quot;http://localhost:5000&quot;</span>)<br><br><span class="hljs-comment"># 发送研究请求</span><br>response = client.execute_skill(<span class="hljs-string">&quot;research&quot;</span>, <span class="hljs-string">&quot;research AI在医疗领域的应用&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;收到响应：<span class="hljs-subst">&#123;response.get(<span class="hljs-string">&#x27;result&#x27;</span>)&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 输出：</span><br><span class="hljs-comment"># 收到响应：&#123;&#x27;topic&#x27;: &#x27;AI在医疗领域的应用&#x27;, &#x27;findings&#x27;: &#x27;关于AI在医疗领域的应用的研究结果...&#x27;, &#x27;sources&#x27;: [&#x27;来源1&#x27;, &#x27;来源2&#x27;, &#x27;来源3&#x27;]&#125;</span><br></code></pre></td></tr></table></figure><p><strong>（3）创建 Agent 网络</strong></p><p>对于多个 Agent 的协作，我们可以让多个 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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> A2AServer, A2AClient<br><span class="hljs-keyword">import</span> threading<br><span class="hljs-keyword">import</span> time<br><br><span class="hljs-comment"># 1. 创建多个Agent服务</span><br>researcher = A2AServer(<br>    name=<span class="hljs-string">&quot;researcher&quot;</span>,<br>    description=<span class="hljs-string">&quot;研究员&quot;</span><br>)<br><br><span class="hljs-meta">@researcher.skill(<span class="hljs-params"><span class="hljs-string">&quot;research&quot;</span></span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">do_research</span>(<span class="hljs-params">text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-keyword">import</span> re<br>    <span class="hljs-keyword">match</span> = re.search(<span class="hljs-string">r&#x27;research\s+(.+)&#x27;</span>, text, re.IGNORECASE)<br>    topic = <span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>).strip() <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span> <span class="hljs-keyword">else</span> text<br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(&#123;<span class="hljs-string">&quot;topic&quot;</span>: topic, <span class="hljs-string">&quot;findings&quot;</span>: <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;topic&#125;</span>的研究结果&quot;</span>&#125;)<br><br>writer = A2AServer(<br>    name=<span class="hljs-string">&quot;writer&quot;</span>,<br>    description=<span class="hljs-string">&quot;撰写员&quot;</span><br>)<br><br><span class="hljs-meta">@writer.skill(<span class="hljs-params"><span class="hljs-string">&quot;write&quot;</span></span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">write_article</span>(<span class="hljs-params">text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-keyword">import</span> re<br>    <span class="hljs-keyword">match</span> = re.search(<span class="hljs-string">r&#x27;write\s+(.+)&#x27;</span>, text, re.IGNORECASE)<br>    content = <span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>).strip() <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span> <span class="hljs-keyword">else</span> text<br>    <br>    <span class="hljs-comment"># 尝试解析研究数据</span><br>    <span class="hljs-keyword">try</span>:<br>        data = <span class="hljs-built_in">eval</span>(content)<br>        topic = data.get(<span class="hljs-string">&quot;topic&quot;</span>, <span class="hljs-string">&quot;未知主题&quot;</span>)<br>        findings = data.get(<span class="hljs-string">&quot;findings&quot;</span>, <span class="hljs-string">&quot;无研究结果&quot;</span>)<br>    <span class="hljs-keyword">except</span>:<br>        topic = <span class="hljs-string">&quot;未知主题&quot;</span><br>        findings = content<br>    <br>    <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;# <span class="hljs-subst">&#123;topic&#125;</span>\n\n基于研究：<span class="hljs-subst">&#123;findings&#125;</span>\n\n文章内容...&quot;</span><br><br>editor = A2AServer(<br>    name=<span class="hljs-string">&quot;editor&quot;</span>,<br>    description=<span class="hljs-string">&quot;编辑&quot;</span><br>)<br><br><span class="hljs-meta">@editor.skill(<span class="hljs-params"><span class="hljs-string">&quot;edit&quot;</span></span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">edit_article</span>(<span class="hljs-params">text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-keyword">import</span> re<br>    <span class="hljs-keyword">match</span> = re.search(<span class="hljs-string">r&#x27;edit\s+(.+)&#x27;</span>, text, re.IGNORECASE)<br>    article = <span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>).strip() <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span> <span class="hljs-keyword">else</span> text<br>    <br>    result = &#123;<br>        <span class="hljs-string">&quot;article&quot;</span>: article + <span class="hljs-string">&quot;\n\n[已编辑优化]&quot;</span>,<br>        <span class="hljs-string">&quot;feedback&quot;</span>: <span class="hljs-string">&quot;文章质量良好&quot;</span>,<br>        <span class="hljs-string">&quot;approved&quot;</span>: <span class="hljs-literal">True</span><br>    &#125;<br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(result)<br><br><span class="hljs-comment"># 2. 启动所有服务</span><br>threading.Thread(target=<span class="hljs-keyword">lambda</span>: researcher.run(port=<span class="hljs-number">5000</span>), daemon=<span class="hljs-literal">True</span>).start()<br>threading.Thread(target=<span class="hljs-keyword">lambda</span>: writer.run(port=<span class="hljs-number">5001</span>), daemon=<span class="hljs-literal">True</span>).start()<br>threading.Thread(target=<span class="hljs-keyword">lambda</span>: editor.run(port=<span class="hljs-number">5002</span>), daemon=<span class="hljs-literal">True</span>).start()<br>time.sleep(<span class="hljs-number">2</span>)  <span class="hljs-comment"># 等待服务启动</span><br><br><span class="hljs-comment"># 3. 创建客户端连接到各个Agent</span><br>researcher_client = A2AClient(<span class="hljs-string">&quot;http://localhost:5000&quot;</span>)<br>writer_client = A2AClient(<span class="hljs-string">&quot;http://localhost:5001&quot;</span>)<br>editor_client = A2AClient(<span class="hljs-string">&quot;http://localhost:5002&quot;</span>)<br><br><span class="hljs-comment"># 4. 协作流程</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_content</span>(<span class="hljs-params">topic</span>):<br>    <span class="hljs-comment"># 步骤1：研究</span><br>    research = researcher_client.execute_skill(<span class="hljs-string">&quot;research&quot;</span>, <span class="hljs-string">f&quot;research <span class="hljs-subst">&#123;topic&#125;</span>&quot;</span>)<br>    research_data = research.get(<span class="hljs-string">&#x27;result&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)<br>    <br>    <span class="hljs-comment"># 步骤2：撰写</span><br>    article = writer_client.execute_skill(<span class="hljs-string">&quot;write&quot;</span>, <span class="hljs-string">f&quot;write <span class="hljs-subst">&#123;research_data&#125;</span>&quot;</span>)<br>    article_content = article.get(<span class="hljs-string">&#x27;result&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)<br>    <br>    <span class="hljs-comment"># 步骤3：编辑</span><br>    final = editor_client.execute_skill(<span class="hljs-string">&quot;edit&quot;</span>, <span class="hljs-string">f&quot;edit <span class="hljs-subst">&#123;article_content&#125;</span>&quot;</span>)<br>    <span class="hljs-keyword">return</span> final.get(<span class="hljs-string">&#x27;result&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)<br><br><span class="hljs-comment"># 使用</span><br>result = create_content(<span class="hljs-string">&quot;AI在医疗领域的应用&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n最终结果：\n<span class="hljs-subst">&#123;result&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><h3 id="10-3-4-在智能体中使用-A2A-工具"><a href="#10-3-4-在智能体中使用-A2A-工具" class="headerlink" title="10.3.4 在智能体中使用 A2A 工具"></a>10.3.4 在智能体中使用 A2A 工具</h3><p>现在让我们看看如何将 A2A 集成到 HelloAgents 的智能体中。</p><p><strong>（1）使用 A2ATool 包装器</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> A2ATool<br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><br>load_dotenv()<br>llm = HelloAgentsLLM()<br><br><span class="hljs-comment"># 假设已经有一个研究员Agent服务运行在 http://localhost:5000</span><br><br><span class="hljs-comment"># 创建协调者Agent</span><br>coordinator = SimpleAgent(name=<span class="hljs-string">&quot;协调者&quot;</span>, llm=llm)<br><br><span class="hljs-comment"># 添加A2A工具，连接到研究员Agent</span><br>researcher_tool = A2ATool(<br>    name=<span class="hljs-string">&quot;researcher&quot;</span>,<br>    description=<span class="hljs-string">&quot;研究员Agent，可以搜索和分析资料&quot;</span>,<br>    agent_url=<span class="hljs-string">&quot;http://localhost:5000&quot;</span><br>)<br>coordinator.add_tool(researcher_tool)<br><br><span class="hljs-comment"># 协调者可以调用研究员Agent</span><br>response = coordinator.run(<span class="hljs-string">&quot;请让研究员帮我研究AI在教育领域的应用&quot;</span>)<br><span class="hljs-built_in">print</span>(response)<br></code></pre></td></tr></table></figure><p><strong>（2）实战案例：智能客服系统</strong></p><p>让我们构建一个完整的智能客服系统，包含三个 Agent：</p><ul><li><strong>接待员</strong>：分析客户问题类型</li><li><strong>技术专家</strong>：回答技术问题</li><li><strong>销售顾问</strong>：回答销售问题</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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> A2ATool<br><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> A2AServer<br><span class="hljs-keyword">import</span> threading<br><span class="hljs-keyword">import</span> time<br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><br>load_dotenv()<br>llm = HelloAgentsLLM()<br><br><span class="hljs-comment"># 1. 创建技术专家Agent服务</span><br>tech_expert = A2AServer(<br>    name=<span class="hljs-string">&quot;tech_expert&quot;</span>,<br>    description=<span class="hljs-string">&quot;技术专家，回答技术问题&quot;</span><br>)<br><br><span class="hljs-meta">@tech_expert.skill(<span class="hljs-params"><span class="hljs-string">&quot;answer&quot;</span></span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">answer_tech_question</span>(<span class="hljs-params">text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-keyword">import</span> re<br>    <span class="hljs-keyword">match</span> = re.search(<span class="hljs-string">r&#x27;answer\s+(.+)&#x27;</span>, text, re.IGNORECASE)<br>    question = <span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>).strip() <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span> <span class="hljs-keyword">else</span> text<br>    <span class="hljs-comment"># 实际应用中，这里会调用LLM或知识库</span><br>    <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;技术回答：关于&#x27;<span class="hljs-subst">&#123;question&#125;</span>&#x27;，我建议您查看我们的技术文档...&quot;</span><br><br><span class="hljs-comment"># 2. 创建销售顾问Agent服务</span><br>sales_advisor = A2AServer(<br>    name=<span class="hljs-string">&quot;sales_advisor&quot;</span>,<br>    description=<span class="hljs-string">&quot;销售顾问，回答销售问题&quot;</span><br>)<br><br><span class="hljs-meta">@sales_advisor.skill(<span class="hljs-params"><span class="hljs-string">&quot;answer&quot;</span></span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">answer_sales_question</span>(<span class="hljs-params">text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-keyword">import</span> re<br>    <span class="hljs-keyword">match</span> = re.search(<span class="hljs-string">r&#x27;answer\s+(.+)&#x27;</span>, text, re.IGNORECASE)<br>    question = <span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>).strip() <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span> <span class="hljs-keyword">else</span> text<br>    <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;销售回答：关于&#x27;<span class="hljs-subst">&#123;question&#125;</span>&#x27;，我们有特别优惠...&quot;</span><br><br><span class="hljs-comment"># 3. 启动服务</span><br>threading.Thread(target=<span class="hljs-keyword">lambda</span>: tech_expert.run(port=<span class="hljs-number">6000</span>), daemon=<span class="hljs-literal">True</span>).start()<br>threading.Thread(target=<span class="hljs-keyword">lambda</span>: sales_advisor.run(port=<span class="hljs-number">6001</span>), daemon=<span class="hljs-literal">True</span>).start()<br>time.sleep(<span class="hljs-number">2</span>)<br><br><span class="hljs-comment"># 4. 创建接待员Agent（使用HelloAgents的SimpleAgent）</span><br>receptionist = SimpleAgent(<br>    name=<span class="hljs-string">&quot;接待员&quot;</span>,<br>    llm=llm,<br>    system_prompt=<span class="hljs-string">&quot;&quot;&quot;你是客服接待员，负责：</span><br><span class="hljs-string">1. 分析客户问题类型（技术问题 or 销售问题）</span><br><span class="hljs-string">2. 将问题转发给相应的专家</span><br><span class="hljs-string">3. 整理专家的回答并返回给客户</span><br><span class="hljs-string"></span><br><span class="hljs-string">请保持礼貌和专业。&quot;&quot;&quot;</span><br>)<br><br><span class="hljs-comment"># 添加技术专家工具</span><br>tech_tool = A2ATool(<br>    agent_url=<span class="hljs-string">&quot;http://localhost:6000&quot;</span>,<br>    name=<span class="hljs-string">&quot;tech_expert&quot;</span>,<br>    description=<span class="hljs-string">&quot;技术专家，回答技术相关问题&quot;</span><br>)<br>receptionist.add_tool(tech_tool)<br><br><span class="hljs-comment"># 添加销售顾问工具</span><br>sales_tool = A2ATool(<br>    agent_url=<span class="hljs-string">&quot;http://localhost:6001&quot;</span>,<br>    name=<span class="hljs-string">&quot;sales_advisor&quot;</span>,<br>    description=<span class="hljs-string">&quot;销售顾问，回答价格、购买相关问题&quot;</span><br>)<br>receptionist.add_tool(sales_tool)<br><br><span class="hljs-comment"># 5. 处理客户咨询</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">handle_customer_query</span>(<span class="hljs-params">query</span>):<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n客户咨询：<span class="hljs-subst">&#123;query&#125;</span>&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br>    response = receptionist.run(query)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n客服回复：<span class="hljs-subst">&#123;response&#125;</span>&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br><br><span class="hljs-comment"># 测试不同类型的问题</span><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:<br>    handle_customer_query(<span class="hljs-string">&quot;你们的API如何调用？&quot;</span>)<br>    handle_customer_query(<span class="hljs-string">&quot;企业版的价格是多少？&quot;</span>)<br>    handle_customer_query(<span class="hljs-string">&quot;如何集成到我的Python项目中？&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>（3）高级用法：Agent 间协商</strong></p><p>A2A 协议还支持 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><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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> A2AServer, A2AClient<br><span class="hljs-keyword">import</span> threading<br><span class="hljs-keyword">import</span> time<br><br><span class="hljs-comment"># 创建两个需要协商的Agent</span><br>agent1 = A2AServer(<br>    name=<span class="hljs-string">&quot;agent1&quot;</span>,<br>    description=<span class="hljs-string">&quot;Agent 1&quot;</span><br>)<br><br><span class="hljs-meta">@agent1.skill(<span class="hljs-params"><span class="hljs-string">&quot;propose&quot;</span></span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">handle_proposal</span>(<span class="hljs-params">text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;处理协商提案&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">import</span> re<br>    <br>    <span class="hljs-comment"># 解析提案</span><br>    <span class="hljs-keyword">match</span> = re.search(<span class="hljs-string">r&#x27;propose\s+(.+)&#x27;</span>, text, re.IGNORECASE)<br>    proposal_str = <span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>).strip() <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span> <span class="hljs-keyword">else</span> text<br>    <br>    <span class="hljs-keyword">try</span>:<br>        proposal = <span class="hljs-built_in">eval</span>(proposal_str)<br>        task = proposal.get(<span class="hljs-string">&quot;task&quot;</span>)<br>        deadline = proposal.get(<span class="hljs-string">&quot;deadline&quot;</span>)<br>        <br>        <span class="hljs-comment"># 评估提案</span><br>        <span class="hljs-keyword">if</span> deadline &gt;= <span class="hljs-number">7</span>:  <span class="hljs-comment"># 至少需要7天</span><br>            result = &#123;<span class="hljs-string">&quot;accepted&quot;</span>: <span class="hljs-literal">True</span>, <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">&quot;接受提案&quot;</span>&#125;<br>        <span class="hljs-keyword">else</span>:<br>            result = &#123;<br>                <span class="hljs-string">&quot;accepted&quot;</span>: <span class="hljs-literal">False</span>,<br>                <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">&quot;时间太紧&quot;</span>,<br>                <span class="hljs-string">&quot;counter_proposal&quot;</span>: &#123;<span class="hljs-string">&quot;deadline&quot;</span>: <span class="hljs-number">7</span>&#125;<br>            &#125;<br>        <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(result)<br>    <span class="hljs-keyword">except</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(&#123;<span class="hljs-string">&quot;accepted&quot;</span>: <span class="hljs-literal">False</span>, <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">&quot;无效的提案格式&quot;</span>&#125;)<br><br>agent2 = A2AServer(<br>    name=<span class="hljs-string">&quot;agent2&quot;</span>,<br>    description=<span class="hljs-string">&quot;Agent 2&quot;</span><br>)<br><br><span class="hljs-meta">@agent2.skill(<span class="hljs-params"><span class="hljs-string">&quot;negotiate&quot;</span></span>)</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">negotiate_task</span>(<span class="hljs-params">text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;发起协商&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">import</span> re<br>    <br>    <span class="hljs-comment"># 解析任务和截止日期</span><br>    <span class="hljs-keyword">match</span> = re.search(<span class="hljs-string">r&#x27;negotiate\s+task:(.+?)\s+deadline:(\d+)&#x27;</span>, text, re.IGNORECASE)<br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">match</span>:<br>        task = <span class="hljs-keyword">match</span>.group(<span class="hljs-number">1</span>).strip()<br>        deadline = <span class="hljs-built_in">int</span>(<span class="hljs-keyword">match</span>.group(<span class="hljs-number">2</span>))<br>        <br>        <span class="hljs-comment"># 向agent1发送提案</span><br>        proposal = &#123;<span class="hljs-string">&quot;task&quot;</span>: task, <span class="hljs-string">&quot;deadline&quot;</span>: deadline&#125;<br>        <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(&#123;<span class="hljs-string">&quot;status&quot;</span>: <span class="hljs-string">&quot;negotiating&quot;</span>, <span class="hljs-string">&quot;proposal&quot;</span>: proposal&#125;)<br>    <span class="hljs-keyword">else</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(&#123;<span class="hljs-string">&quot;status&quot;</span>: <span class="hljs-string">&quot;error&quot;</span>, <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">&quot;无效的协商请求&quot;</span>&#125;)<br><br><span class="hljs-comment"># 启动服务</span><br>threading.Thread(target=<span class="hljs-keyword">lambda</span>: agent1.run(port=<span class="hljs-number">7000</span>), daemon=<span class="hljs-literal">True</span>).start()<br>threading.Thread(target=<span class="hljs-keyword">lambda</span>: agent2.run(port=<span class="hljs-number">7001</span>), daemon=<span class="hljs-literal">True</span>).start()<br></code></pre></td></tr></table></figure><h2 id="10-4-ANP-协议实战"><a href="#10-4-ANP-协议实战" class="headerlink" title="10.4 ANP 协议实战"></a>10.4 ANP 协议实战</h2><p>在 MCP 协议解决了工具调用、A2A 协议解决点对点智能体协作之后，ANP 协议则专注于解决大规模、开放网络环境下的智能体管理问题。</p><p>在 10.2 和 10.3 节中，我们学习了 MCP（工具访问）和 A2A（智能体协作）。现在，让我们学习 ANP（Agent Network Protocol）协议，它专注于构建<strong>大规模、开放的智能体网络</strong>。</p><h3 id="10-4-1-协议目标"><a href="#10-4-1-协议目标" class="headerlink" title="10.4.1 协议目标"></a>10.4.1 协议目标</h3><p>当一个网络中存在大量功能各异的智能体（例如，自然语言处理、图像识别、数据分析等）时，系统会面临一系列挑战：</p><ul><li><strong>服务发现</strong>：当新任务到达时，如何快速找到能够处理该任务的智能体？</li><li><strong>智能路由</strong>：如果多个智能体都能处理同一任务，如何选择最合适的一个（如根据负载、成本等）并向其分派任务？</li><li><strong>动态扩展</strong>：如何让新加入网络的智能体被其他成员发现和调用？</li></ul><p>ANP 的设计目标就是提供一套标准化的机制，来解决上述的服务发现、路由选择和网络扩展性问题。</p><p>为实现其设计目标，ANP 定义了以下几个核心概念，如表 10.8 所示：</p><div align="center">  <p>表 10.8 ANP 核心概念</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-table-8.png" alt="" width="85%"/></div><p>我们同样借用官方的<a href="https://github.com/agent-network-protocol/AgentNetworkProtocol/blob/main/docs/chinese/ANP%E5%85%A5%E9%97%A8%E6%8C%87%E5%8D%97.md">入门指南</a>来介绍 ANP 的架构设计，如图 10.9 所示</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-9.png" alt="" width="85%"/>  <p>图 10.9 ANP 整体流程</p></div><p>在这个流程图里，主要包括以下几个步骤：</p><p><strong>1. 服务的发现与匹配：</strong>首先，智能体 A 通过一个公开的发现服务，基于语义或功能描述进行查询，以定位到符合其任务需求的智能体 B。该发现服务通过预先爬取各智能体对外暴露的标准端点（<code>.well-known/agent-descriptions</code>）来建立索引，从而实现服务需求方与提供方的动态匹配。</p><p><strong>2. 基于 DID 的身份验证：</strong>在交互开始时，智能体 A 使用其私钥对包含自身 DID 的请求进行签名。智能体 B 收到后，通过解析该 DID 获取对应的公钥，并以此验证签名的真实性与请求的完整性，从而建立起双方的可信通信。</p><p><strong>3. 标准化的服务执行：</strong>身份验证通过后，智能体 B 响应请求，双方依据预定义的标准接口和数据格式进行数据交换或服务调用（如预订、查询等）。标准化的交互流程是实现跨平台、跨系统互操作性的基础。</p><p>总而言之，该机制的核心是利用 DID 构建了一个去中心化的信任根基，并借助标准化的描述协议实现了服务的动态发现。这套方法使得智能体能够在无需中央协调的前提下，安全、高效地在互联网上形成协作网络。</p><h3 id="10-4-2-使用-ANP-服务发现"><a href="#10-4-2-使用-ANP-服务发现" class="headerlink" title="10.4.2 使用 ANP 服务发现"></a>10.4.2 使用 ANP 服务发现</h3><p><strong>（1）创建服务发现中心</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> ANPDiscovery, register_service<br><br><span class="hljs-comment"># 创建服务发现中心</span><br>discovery = ANPDiscovery()<br><br><span class="hljs-comment"># 注册Agent服务</span><br>register_service(<br>    discovery=discovery,<br>    service_id=<span class="hljs-string">&quot;nlp_agent_1&quot;</span>,<br>    service_name=<span class="hljs-string">&quot;NLP处理专家A&quot;</span>,<br>    service_type=<span class="hljs-string">&quot;nlp&quot;</span>,<br>    capabilities=[<span class="hljs-string">&quot;text_analysis&quot;</span>, <span class="hljs-string">&quot;sentiment_analysis&quot;</span>, <span class="hljs-string">&quot;ner&quot;</span>],<br>    endpoint=<span class="hljs-string">&quot;http://localhost:8001&quot;</span>,<br>    metadata=&#123;<span class="hljs-string">&quot;load&quot;</span>: <span class="hljs-number">0.3</span>, <span class="hljs-string">&quot;price&quot;</span>: <span class="hljs-number">0.01</span>, <span class="hljs-string">&quot;version&quot;</span>: <span class="hljs-string">&quot;1.0.0&quot;</span>&#125;<br>)<br><br>register_service(<br>    discovery=discovery,<br>    service_id=<span class="hljs-string">&quot;nlp_agent_2&quot;</span>,<br>    service_name=<span class="hljs-string">&quot;NLP处理专家B&quot;</span>,<br>    service_type=<span class="hljs-string">&quot;nlp&quot;</span>,<br>    capabilities=[<span class="hljs-string">&quot;text_analysis&quot;</span>, <span class="hljs-string">&quot;translation&quot;</span>],<br>    endpoint=<span class="hljs-string">&quot;http://localhost:8002&quot;</span>,<br>    metadata=&#123;<span class="hljs-string">&quot;load&quot;</span>: <span class="hljs-number">0.7</span>, <span class="hljs-string">&quot;price&quot;</span>: <span class="hljs-number">0.02</span>, <span class="hljs-string">&quot;version&quot;</span>: <span class="hljs-string">&quot;1.1.0&quot;</span>&#125;<br>)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;✅ 服务注册完成&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>（2）发现服务</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> discover_service<br><br><span class="hljs-comment"># 按类型查找</span><br>nlp_services = discover_service(discovery, service_type=<span class="hljs-string">&quot;nlp&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;找到 <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(nlp_services)&#125;</span> 个NLP服务&quot;</span>)<br><br><span class="hljs-comment"># 选择负载最低的服务</span><br>best_service = <span class="hljs-built_in">min</span>(nlp_services, key=<span class="hljs-keyword">lambda</span> s: s.metadata.get(<span class="hljs-string">&quot;load&quot;</span>, <span class="hljs-number">1.0</span>))<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;最佳服务：<span class="hljs-subst">&#123;best_service.service_name&#125;</span> (负载: <span class="hljs-subst">&#123;best_service.metadata[<span class="hljs-string">&#x27;load&#x27;</span>]&#125;</span>)&quot;</span>)<br></code></pre></td></tr></table></figure><p><strong>（3）构建 Agent 网络</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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> ANPNetwork<br><br><span class="hljs-comment"># 创建网络</span><br>network = ANPNetwork(network_id=<span class="hljs-string">&quot;ai_cluster&quot;</span>)<br><br><span class="hljs-comment"># 添加节点</span><br><span class="hljs-keyword">for</span> service <span class="hljs-keyword">in</span> discovery.list_all_services():<br>    network.add_node(service.service_id, service.endpoint)<br><br><span class="hljs-comment"># 建立连接（根据能力匹配）</span><br>network.connect_nodes(<span class="hljs-string">&quot;nlp_agent_1&quot;</span>, <span class="hljs-string">&quot;nlp_agent_2&quot;</span>)<br><br>stats = network.get_network_stats()<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 网络构建完成，共 <span class="hljs-subst">&#123;stats[<span class="hljs-string">&#x27;total_nodes&#x27;</span>]&#125;</span> 个节点&quot;</span>)<br></code></pre></td></tr></table></figure><h3 id="10-4-3-实战案例"><a href="#10-4-3-实战案例" class="headerlink" title="10.4.3 实战案例"></a>10.4.3 实战案例</h3><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> ANPDiscovery, register_service<br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools.builtin <span class="hljs-keyword">import</span> ANPTool<br><span class="hljs-keyword">import</span> random<br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><br>load_dotenv()<br>llm = HelloAgentsLLM()<br><br><span class="hljs-comment"># 1. 创建服务发现中心</span><br>discovery = ANPDiscovery()<br><br><span class="hljs-comment"># 2. 注册多个计算节点</span><br><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">10</span>):<br>    register_service(<br>        discovery=discovery,<br>        service_id=<span class="hljs-string">f&quot;compute_node_<span class="hljs-subst">&#123;i&#125;</span>&quot;</span>,<br>        service_name=<span class="hljs-string">f&quot;计算节点<span class="hljs-subst">&#123;i&#125;</span>&quot;</span>,<br>        service_type=<span class="hljs-string">&quot;compute&quot;</span>,<br>        capabilities=[<span class="hljs-string">&quot;data_processing&quot;</span>, <span class="hljs-string">&quot;ml_training&quot;</span>],<br>        endpoint=<span class="hljs-string">f&quot;http://node<span class="hljs-subst">&#123;i&#125;</span>:8000&quot;</span>,<br>        metadata=&#123;<br>            <span class="hljs-string">&quot;load&quot;</span>: random.uniform(<span class="hljs-number">0.1</span>, <span class="hljs-number">0.9</span>),<br>            <span class="hljs-string">&quot;cpu_cores&quot;</span>: random.choice([<span class="hljs-number">4</span>, <span class="hljs-number">8</span>, <span class="hljs-number">16</span>]),<br>            <span class="hljs-string">&quot;memory_gb&quot;</span>: random.choice([<span class="hljs-number">16</span>, <span class="hljs-number">32</span>, <span class="hljs-number">64</span>]),<br>            <span class="hljs-string">&quot;gpu&quot;</span>: random.choice([<span class="hljs-literal">True</span>, <span class="hljs-literal">False</span>])<br>        &#125;<br>    )<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 注册了 <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(discovery.list_all_services())&#125;</span> 个计算节点&quot;</span>)<br><br><span class="hljs-comment"># 3. 创建任务调度Agent</span><br>scheduler = SimpleAgent(<br>    name=<span class="hljs-string">&quot;任务调度器&quot;</span>,<br>    llm=llm,<br>    system_prompt=<span class="hljs-string">&quot;&quot;&quot;你是一个智能任务调度器，负责：</span><br><span class="hljs-string">1. 分析任务需求</span><br><span class="hljs-string">2. 选择最合适的计算节点</span><br><span class="hljs-string">3. 分配任务</span><br><span class="hljs-string"></span><br><span class="hljs-string">选择节点时考虑：负载、CPU核心数、内存、GPU等因素。&quot;&quot;&quot;</span><br>)<br><br><span class="hljs-comment"># 添加ANP工具</span><br>anp_tool = ANPTool(<br>    name=<span class="hljs-string">&quot;service_discovery&quot;</span>,<br>    description=<span class="hljs-string">&quot;服务发现工具，可以查找和选择计算节点&quot;</span>,<br>    discovery=discovery<br>)<br>scheduler.add_tool(anp_tool)<br><br><span class="hljs-comment"># 4. 智能任务分配</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">assign_task</span>(<span class="hljs-params">task_description</span>):<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n任务：<span class="hljs-subst">&#123;task_description&#125;</span>&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br><br>    <span class="hljs-comment"># 让Agent智能选择节点</span><br>    response = scheduler.run(<span class="hljs-string">f&quot;&quot;&quot;</span><br><span class="hljs-string">    请为以下任务选择最合适的计算节点：</span><br><span class="hljs-string">    <span class="hljs-subst">&#123;task_description&#125;</span></span><br><span class="hljs-string"></span><br><span class="hljs-string">    要求：</span><br><span class="hljs-string">    1. 列出所有可用节点</span><br><span class="hljs-string">    2. 分析每个节点的特点</span><br><span class="hljs-string">    3. 选择最合适的节点</span><br><span class="hljs-string">    4. 说明选择理由</span><br><span class="hljs-string">    &quot;&quot;&quot;</span>)<br><br>    <span class="hljs-built_in">print</span>(response)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">50</span>)<br><br><span class="hljs-comment"># 测试不同类型的任务</span><br>assign_task(<span class="hljs-string">&quot;训练一个大型深度学习模型，需要GPU支持&quot;</span>)<br>assign_task(<span class="hljs-string">&quot;处理大量文本数据，需要高内存&quot;</span>)<br>assign_task(<span class="hljs-string">&quot;运行轻量级数据分析任务&quot;</span>)<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> ANPDiscovery, register_service<br><span class="hljs-keyword">import</span> random<br><br><span class="hljs-comment"># 创建服务发现中心</span><br>discovery = ANPDiscovery()<br><br><span class="hljs-comment"># 注册多个相同类型的服务</span><br><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">5</span>):<br>    register_service(<br>        discovery=discovery,<br>        service_id=<span class="hljs-string">f&quot;api_server_<span class="hljs-subst">&#123;i&#125;</span>&quot;</span>,<br>        service_name=<span class="hljs-string">f&quot;API服务器<span class="hljs-subst">&#123;i&#125;</span>&quot;</span>,<br>        service_type=<span class="hljs-string">&quot;api&quot;</span>,<br>        capabilities=[<span class="hljs-string">&quot;rest_api&quot;</span>],<br>        endpoint=<span class="hljs-string">f&quot;http://api<span class="hljs-subst">&#123;i&#125;</span>:8000&quot;</span>,<br>        metadata=&#123;<span class="hljs-string">&quot;load&quot;</span>: random.uniform(<span class="hljs-number">0.1</span>, <span class="hljs-number">0.9</span>)&#125;<br>    )<br><br><span class="hljs-comment"># 负载均衡函数</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">get_best_server</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;选择负载最低的服务器&quot;&quot;&quot;</span><br>    servers = discovery.discover_services(service_type=<span class="hljs-string">&quot;api&quot;</span>)<br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> servers:<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span><br><br>    best = <span class="hljs-built_in">min</span>(servers, key=<span class="hljs-keyword">lambda</span> s: s.metadata.get(<span class="hljs-string">&quot;load&quot;</span>, <span class="hljs-number">1.0</span>))<br>    <span class="hljs-keyword">return</span> best<br><br><span class="hljs-comment"># 模拟请求分配</span><br><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">10</span>):<br>    server = get_best_server()<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;请求 <span class="hljs-subst">&#123;i+<span class="hljs-number">1</span>&#125;</span> -&gt; <span class="hljs-subst">&#123;server.service_name&#125;</span> (负载: <span class="hljs-subst">&#123;server.metadata[<span class="hljs-string">&#x27;load&#x27;</span>]:<span class="hljs-number">.2</span>f&#125;</span>)&quot;</span>)<br><br>    <span class="hljs-comment"># 更新负载（模拟）</span><br>    server.metadata[<span class="hljs-string">&quot;load&quot;</span>] += <span class="hljs-number">0.1</span><br></code></pre></td></tr></table></figure><h2 id="10-5-构建自定义-MCP-服务器"><a href="#10-5-构建自定义-MCP-服务器" class="headerlink" title="10.5 构建自定义 MCP 服务器"></a>10.5 构建自定义 MCP 服务器</h2><p>在前面的章节中，我们学习了如何使用现有的 MCP 服务。并且也了解到了不同协议的特点。现在，让我们学习如何构建自己的 MCP 服务器。</p><h3 id="10-5-1-创建你的第一个-MCP-服务器"><a href="#10-5-1-创建你的第一个-MCP-服务器" class="headerlink" title="10.5.1 创建你的第一个 MCP 服务器"></a>10.5.1 创建你的第一个 MCP 服务器</h3><p><strong>（1）为什么要构建自定义 MCP 服务器？</strong></p><p>虽然可以直接使用公开的 MCP 服务，但在许多实际应用场景中，需要构建自定义的 MCP 服务器以满足特定需求。</p><p>主要动机包括以下几点：</p><ul><li><strong>封装业务逻辑</strong>：将企业内部特有的业务流程或复杂操作封装为标准化的 MCP 工具，供智能体统一调用。</li><li><strong>访问私有数据</strong>：创建一个安全可控的接口或代理，用于访问内部数据库、API 或其他无法对公网暴露的私有数据源。</li><li><strong>性能专项优化</strong>：针对高频调用或对响应延迟有严苛要求的应用场景，进行深度优化。</li><li><strong>功能定制扩展</strong>：实现标准 MCP 服务未提供的特定功能，例如集成专有算法模型或连接特定的硬件设备。</li></ul><p><strong>（2）教学案例：天气查询 MCP 服务器</strong></p><p>让我们从一个简单的天气查询服务器开始，逐步学习 MCP 服务器开发：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment">#!/usr/bin/env python3</span><br><span class="hljs-string">&quot;&quot;&quot;天气查询 MCP 服务器&quot;&quot;&quot;</span><br><br><span class="hljs-keyword">import</span> json<br><span class="hljs-keyword">import</span> requests<br><span class="hljs-keyword">import</span> os<br><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Dict</span>, <span class="hljs-type">Any</span><br><span class="hljs-keyword">from</span> hello_agents.protocols <span class="hljs-keyword">import</span> MCPServer<br><br><span class="hljs-comment"># 创建 MCP 服务器</span><br>weather_server = MCPServer(name=<span class="hljs-string">&quot;weather-server&quot;</span>, description=<span class="hljs-string">&quot;真实天气查询服务&quot;</span>)<br><br>CITY_MAP = &#123;<br>    <span class="hljs-string">&quot;北京&quot;</span>: <span class="hljs-string">&quot;Beijing&quot;</span>, <span class="hljs-string">&quot;上海&quot;</span>: <span class="hljs-string">&quot;Shanghai&quot;</span>, <span class="hljs-string">&quot;广州&quot;</span>: <span class="hljs-string">&quot;Guangzhou&quot;</span>,<br>    <span class="hljs-string">&quot;深圳&quot;</span>: <span class="hljs-string">&quot;Shenzhen&quot;</span>, <span class="hljs-string">&quot;杭州&quot;</span>: <span class="hljs-string">&quot;Hangzhou&quot;</span>, <span class="hljs-string">&quot;成都&quot;</span>: <span class="hljs-string">&quot;Chengdu&quot;</span>,<br>    <span class="hljs-string">&quot;重庆&quot;</span>: <span class="hljs-string">&quot;Chongqing&quot;</span>, <span class="hljs-string">&quot;武汉&quot;</span>: <span class="hljs-string">&quot;Wuhan&quot;</span>, <span class="hljs-string">&quot;西安&quot;</span>: <span class="hljs-string">&quot;Xi&#x27;an&quot;</span>,<br>    <span class="hljs-string">&quot;南京&quot;</span>: <span class="hljs-string">&quot;Nanjing&quot;</span>, <span class="hljs-string">&quot;天津&quot;</span>: <span class="hljs-string">&quot;Tianjin&quot;</span>, <span class="hljs-string">&quot;苏州&quot;</span>: <span class="hljs-string">&quot;Suzhou&quot;</span><br>&#125;<br><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">get_weather_data</span>(<span class="hljs-params">city: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;从 wttr.in 获取天气数据&quot;&quot;&quot;</span><br>    city_en = CITY_MAP.get(city, city)<br>    url = <span class="hljs-string">f&quot;https://wttr.in/<span class="hljs-subst">&#123;city_en&#125;</span>?format=j1&quot;</span><br>    response = requests.get(url, timeout=<span class="hljs-number">10</span>)<br>    response.raise_for_status()<br>    data = response.json()<br>    current = data[<span class="hljs-string">&quot;current_condition&quot;</span>][<span class="hljs-number">0</span>]<br><br>    <span class="hljs-keyword">return</span> &#123;<br>        <span class="hljs-string">&quot;city&quot;</span>: city,<br>        <span class="hljs-string">&quot;temperature&quot;</span>: <span class="hljs-built_in">float</span>(current[<span class="hljs-string">&quot;temp_C&quot;</span>]),<br>        <span class="hljs-string">&quot;feels_like&quot;</span>: <span class="hljs-built_in">float</span>(current[<span class="hljs-string">&quot;FeelsLikeC&quot;</span>]),<br>        <span class="hljs-string">&quot;humidity&quot;</span>: <span class="hljs-built_in">int</span>(current[<span class="hljs-string">&quot;humidity&quot;</span>]),<br>        <span class="hljs-string">&quot;condition&quot;</span>: current[<span class="hljs-string">&quot;weatherDesc&quot;</span>][<span class="hljs-number">0</span>][<span class="hljs-string">&quot;value&quot;</span>],<br>        <span class="hljs-string">&quot;wind_speed&quot;</span>: <span class="hljs-built_in">round</span>(<span class="hljs-built_in">float</span>(current[<span class="hljs-string">&quot;windspeedKmph&quot;</span>]) / <span class="hljs-number">3.6</span>, <span class="hljs-number">1</span>),<br>        <span class="hljs-string">&quot;visibility&quot;</span>: <span class="hljs-built_in">float</span>(current[<span class="hljs-string">&quot;visibility&quot;</span>]),<br>        <span class="hljs-string">&quot;timestamp&quot;</span>: datetime.now().strftime(<span class="hljs-string">&quot;%Y-%m-%d %H:%M:%S&quot;</span>)<br>    &#125;<br><br><br><span class="hljs-comment"># 定义工具函数</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">get_weather</span>(<span class="hljs-params">city: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;获取指定城市的当前天气&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">try</span>:<br>        weather_data = get_weather_data(city)<br>        <span class="hljs-keyword">return</span> json.dumps(weather_data, ensure_ascii=<span class="hljs-literal">False</span>, indent=<span class="hljs-number">2</span>)<br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-keyword">return</span> json.dumps(&#123;<span class="hljs-string">&quot;error&quot;</span>: <span class="hljs-built_in">str</span>(e), <span class="hljs-string">&quot;city&quot;</span>: city&#125;, ensure_ascii=<span class="hljs-literal">False</span>)<br><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">list_supported_cities</span>() -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;列出所有支持的中文城市&quot;&quot;&quot;</span><br>    result = &#123;<span class="hljs-string">&quot;cities&quot;</span>: <span class="hljs-built_in">list</span>(CITY_MAP.keys()), <span class="hljs-string">&quot;count&quot;</span>: <span class="hljs-built_in">len</span>(CITY_MAP)&#125;<br>    <span class="hljs-keyword">return</span> json.dumps(result, ensure_ascii=<span class="hljs-literal">False</span>, indent=<span class="hljs-number">2</span>)<br><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">get_server_info</span>() -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;获取服务器信息&quot;&quot;&quot;</span><br>    info = &#123;<br>        <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;Weather MCP Server&quot;</span>,<br>        <span class="hljs-string">&quot;version&quot;</span>: <span class="hljs-string">&quot;1.0.0&quot;</span>,<br>        <span class="hljs-string">&quot;tools&quot;</span>: [<span class="hljs-string">&quot;get_weather&quot;</span>, <span class="hljs-string">&quot;list_supported_cities&quot;</span>, <span class="hljs-string">&quot;get_server_info&quot;</span>]<br>    &#125;<br>    <span class="hljs-keyword">return</span> json.dumps(info, ensure_ascii=<span class="hljs-literal">False</span>, indent=<span class="hljs-number">2</span>)<br><br><br><span class="hljs-comment"># 注册工具到服务器</span><br>weather_server.add_tool(get_weather)<br>weather_server.add_tool(list_supported_cities)<br>weather_server.add_tool(get_server_info)<br><br><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:<br>    weather_server.run()<br></code></pre></td></tr></table></figure><p><strong>（3）测试自定义 MCP 服务器</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment">#!/usr/bin/env python3</span><br><span class="hljs-string">&quot;&quot;&quot;测试天气查询 MCP 服务器&quot;&quot;&quot;</span><br><br><span class="hljs-keyword">import</span> asyncio<br><span class="hljs-keyword">import</span> json<br><span class="hljs-keyword">import</span> sys<br><span class="hljs-keyword">import</span> os<br><br>sys.path.insert(<span class="hljs-number">0</span>, os.path.join(os.path.dirname(__file__), <span class="hljs-string">&#x27;..&#x27;</span>, <span class="hljs-string">&#x27;HelloAgents&#x27;</span>))<br><span class="hljs-keyword">from</span> hello_agents.protocols.mcp.client <span class="hljs-keyword">import</span> MCPClient<br><br><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_weather_server</span>():<br>    server_script = os.path.join(os.path.dirname(__file__), <span class="hljs-string">&quot;14_weather_mcp_server.py&quot;</span>)<br>    client = MCPClient([<span class="hljs-string">&quot;python&quot;</span>, server_script])<br><br>    <span class="hljs-keyword">try</span>:<br>        <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> client:<br>            <span class="hljs-comment"># 测试1: 获取服务器信息</span><br>            info = json.loads(<span class="hljs-keyword">await</span> client.call_tool(<span class="hljs-string">&quot;get_server_info&quot;</span>, &#123;&#125;))<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;服务器: <span class="hljs-subst">&#123;info[<span class="hljs-string">&#x27;name&#x27;</span>]&#125;</span> v<span class="hljs-subst">&#123;info[<span class="hljs-string">&#x27;version&#x27;</span>]&#125;</span>&quot;</span>)<br><br>            <span class="hljs-comment"># 测试2: 列出支持的城市</span><br>            cities = json.loads(<span class="hljs-keyword">await</span> client.call_tool(<span class="hljs-string">&quot;list_supported_cities&quot;</span>, &#123;&#125;))<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;支持城市: <span class="hljs-subst">&#123;cities[<span class="hljs-string">&#x27;count&#x27;</span>]&#125;</span> 个&quot;</span>)<br><br>            <span class="hljs-comment"># 测试3: 查询北京天气</span><br>            weather = json.loads(<span class="hljs-keyword">await</span> client.call_tool(<span class="hljs-string">&quot;get_weather&quot;</span>, &#123;<span class="hljs-string">&quot;city&quot;</span>: <span class="hljs-string">&quot;北京&quot;</span>&#125;))<br>            <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;error&quot;</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> weather:<br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n北京天气: <span class="hljs-subst">&#123;weather[<span class="hljs-string">&#x27;temperature&#x27;</span>]&#125;</span>°C, <span class="hljs-subst">&#123;weather[<span class="hljs-string">&#x27;condition&#x27;</span>]&#125;</span>&quot;</span>)<br><br>            <span class="hljs-comment"># 测试4: 查询深圳天气</span><br>            weather = json.loads(<span class="hljs-keyword">await</span> client.call_tool(<span class="hljs-string">&quot;get_weather&quot;</span>, &#123;<span class="hljs-string">&quot;city&quot;</span>: <span class="hljs-string">&quot;深圳&quot;</span>&#125;))<br>            <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;error&quot;</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> weather:<br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;深圳天气: <span class="hljs-subst">&#123;weather[<span class="hljs-string">&#x27;temperature&#x27;</span>]&#125;</span>°C, <span class="hljs-subst">&#123;weather[<span class="hljs-string">&#x27;condition&#x27;</span>]&#125;</span>&quot;</span>)<br><br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n✅ 所有测试完成！&quot;</span>)<br><br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;❌ 测试失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br><br><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:<br>    asyncio.run(test_weather_server())<br></code></pre></td></tr></table></figure><p><strong>（4）在 Agent 中使用自定义 MCP 服务器</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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-string">&quot;&quot;&quot;在 Agent 中使用天气 MCP 服务器&quot;&quot;&quot;</span><br><br><span class="hljs-keyword">import</span> os<br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool<br><br>load_dotenv()<br><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_weather_assistant</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;创建天气助手&quot;&quot;&quot;</span><br>    llm = HelloAgentsLLM()<br><br>    assistant = SimpleAgent(<br>        name=<span class="hljs-string">&quot;天气助手&quot;</span>,<br>        llm=llm,<br>        system_prompt=<span class="hljs-string">&quot;&quot;&quot;你是天气助手，可以查询城市天气。</span><br><span class="hljs-string">使用 get_weather 工具查询天气，支持中文城市名。</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br>    )<br><br>    <span class="hljs-comment"># 添加天气 MCP 工具</span><br>    server_script = os.path.join(os.path.dirname(__file__), <span class="hljs-string">&quot;14_weather_mcp_server.py&quot;</span>)<br>    weather_tool = MCPTool(server_command=[<span class="hljs-string">&quot;python&quot;</span>, server_script])<br>    assistant.add_tool(weather_tool)<br><br>    <span class="hljs-keyword">return</span> assistant<br><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">demo</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;演示&quot;&quot;&quot;</span><br>    assistant = create_weather_assistant()<br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n查询北京天气：&quot;</span>)<br>    response = assistant.run(<span class="hljs-string">&quot;北京今天天气怎么样？&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;回答: <span class="hljs-subst">&#123;response&#125;</span>\n&quot;</span>)<br><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">interactive</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;交互模式&quot;&quot;&quot;</span><br>    assistant = create_weather_assistant()<br><br>    <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:<br>        user_input = <span class="hljs-built_in">input</span>(<span class="hljs-string">&quot;\n你: &quot;</span>).strip()<br>        <span class="hljs-keyword">if</span> user_input.lower() <span class="hljs-keyword">in</span> [<span class="hljs-string">&#x27;quit&#x27;</span>, <span class="hljs-string">&#x27;exit&#x27;</span>]:<br>            <span class="hljs-keyword">break</span><br>        response = assistant.run(user_input)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;助手: <span class="hljs-subst">&#123;response&#125;</span>&quot;</span>)<br><br><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:<br>    <span class="hljs-keyword">import</span> sys<br>    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(sys.argv) &gt; <span class="hljs-number">1</span> <span class="hljs-keyword">and</span> sys.argv[<span class="hljs-number">1</span>] == <span class="hljs-string">&quot;demo&quot;</span>:<br>        demo()<br>    <span class="hljs-keyword">else</span>:<br>        interactive()<br></code></pre></td></tr></table></figure><figure class="highlight nestedtext"><table><tr><td class="gutter"><pre><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><code class="hljs nestedtext"><span class="hljs-attribute">🔗 连接到 MCP 服务器...</span><br><span class="hljs-attribute">✅ 连接成功！</span><br><span class="hljs-attribute">🔌 连接已断开</span><br><span class="hljs-attribute">✅ 工具 &#x27;mcp_get_weather&#x27; 已注册。</span><br><span class="hljs-attribute">✅ 工具 &#x27;mcp_list_supported_cities&#x27; 已注册。</span><br><span class="hljs-attribute">✅ 工具 &#x27;mcp_get_server_info&#x27; 已注册。</span><br><span class="hljs-attribute">✅ MCP工具 &#x27;mcp&#x27; 已展开为 3 个独立工具</span><br><span class="hljs-attribute"></span><br><span class="hljs-attribute">你</span><span class="hljs-punctuation">:</span> <span class="hljs-string">我想查询北京的天气</span><br><span class="hljs-attribute">🔗 连接到 MCP 服务器...</span><br><span class="hljs-attribute">✅ 连接成功！</span><br><span class="hljs-attribute">🔌 连接已断开</span><br><span class="hljs-attribute">助手</span><span class="hljs-punctuation">:</span> <span class="hljs-string">当前北京的天气情况如下：</span><br><br><span class="hljs-bullet">-</span> <span class="hljs-string">温度：10.0°C</span><br><span class="hljs-bullet">-</span> <span class="hljs-string">体感温度：9.0°C</span><br><span class="hljs-bullet">-</span> <span class="hljs-string">湿度：94%</span><br><span class="hljs-bullet">-</span> <span class="hljs-string">天气状况：小雨</span><br><span class="hljs-bullet">-</span> <span class="hljs-string">风速：1.7米/秒</span><br><span class="hljs-bullet">-</span> <span class="hljs-string">能见度：10.0公里</span><br><span class="hljs-bullet">-</span> <span class="hljs-string">时间戳：2025年10月9日 13:46:40</span><br><br>请注意携带雨具，并根据天气变化适当调整着装。<br></code></pre></td></tr></table></figure><h3 id="10-5-2-上传-MCP-服务器"><a href="#10-5-2-上传-MCP-服务器" class="headerlink" title="10.5.2 上传 MCP 服务器"></a>10.5.2 上传 MCP 服务器</h3><p>我们创建了一个真实的天气查询 MCP 服务器。现在，让我们将它发布到 Smithery 平台，让全世界的开发者都能使用我们的服务。</p><p> （1）什么是 Smithery？</p><p><a href="https://smithery.ai/">Smithery</a> 是 MCP 服务器的官方发布平台，类似于 Python 的 PyPI 或 Node.js 的 npm。通过 Smithery，用户可以：</p><ul><li>🔍 发现和搜索 MCP 服务器</li><li>📦 一键安装 MCP 服务器</li><li>📊 查看服务器的使用统计和评价</li><li>🔄 自动获取服务器更新</li></ul><p>（2）准备发布<br>首先，我们需要将项目整理成标准的发布格式，这个文件夹已经在<code>code</code>目录下整理好，可供大家参考：</p><figure class="highlight axapta"><table><tr><td class="gutter"><pre><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><code class="hljs axapta">weather-mcp-<span class="hljs-keyword">server</span>/<br>├── README.md           <span class="hljs-meta"># 项目说明文档</span><br>├── LICENSE            <span class="hljs-meta"># 开源许可证</span><br>├── Dockerfile         <span class="hljs-meta"># Docker 构建配置（推荐）</span><br>├── pyproject.toml     <span class="hljs-meta"># Python 项目配置（必需）</span><br>├── requirements.txt   <span class="hljs-meta"># Python 依赖</span><br>├── smithery.yaml      <span class="hljs-meta"># Smithery 配置文件（必需）</span><br>└── <span class="hljs-keyword">server</span>.py          <span class="hljs-meta"># MCP 服务器主文件</span><br></code></pre></td></tr></table></figure><p>需要注意的是，<code>smithery.yaml</code>是 Smithery 平台的配置文件：</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></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">weather-mcp-server</span><br><span class="hljs-attr">displayName:</span> <span class="hljs-string">Weather</span> <span class="hljs-string">MCP</span> <span class="hljs-string">Server</span><br><span class="hljs-attr">description:</span> <span class="hljs-string">Real-time</span> <span class="hljs-string">weather</span> <span class="hljs-string">query</span> <span class="hljs-string">MCP</span> <span class="hljs-string">server</span> <span class="hljs-string">based</span> <span class="hljs-string">on</span> <span class="hljs-string">HelloAgents</span> <span class="hljs-string">framework</span><br><span class="hljs-attr">version:</span> <span class="hljs-number">1.0</span><span class="hljs-number">.0</span><br><span class="hljs-attr">author:</span> <span class="hljs-string">HelloAgents</span> <span class="hljs-string">Team</span><br><span class="hljs-attr">homepage:</span> <span class="hljs-string">https://github.com/yourusername/weather-mcp-server</span><br><span class="hljs-attr">license:</span> <span class="hljs-string">MIT</span><br><span class="hljs-attr">categories:</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">Hello</span> <span class="hljs-string">Agents</span> <span class="hljs-string">学习</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">data</span><br><span class="hljs-attr">tags:</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">Hello</span> <span class="hljs-string">Agents</span> <span class="hljs-string">学习</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">real-time</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">helloagents</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">wttr</span><br><span class="hljs-attr">runtime:</span> <span class="hljs-string">container</span><br><span class="hljs-attr">build:</span><br>  <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile</span><br>  <span class="hljs-attr">dockerBuildPath:</span> <span class="hljs-string">.</span><br><span class="hljs-attr">startCommand:</span><br>  <span class="hljs-attr">type:</span> <span class="hljs-string">http</span><br><span class="hljs-attr">tools:</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">get_weather</span><br>    <span class="hljs-attr">description:</span> <span class="hljs-string">Get</span> <span class="hljs-string">current</span> <span class="hljs-string">weather</span> <span class="hljs-string">for</span> <span class="hljs-string">a</span> <span class="hljs-string">city</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">list_supported_cities</span><br>    <span class="hljs-attr">description:</span> <span class="hljs-string">List</span> <span class="hljs-string">all</span> <span class="hljs-string">supported</span> <span class="hljs-string">cities</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">get_server_info</span><br>    <span class="hljs-attr">description:</span> <span class="hljs-string">Get</span> <span class="hljs-string">server</span> <span class="hljs-string">information</span><br></code></pre></td></tr></table></figure><p>配置说明：</p><ul><li><code>name</code>: 服务器的唯一标识符（小写，用连字符分隔）</li><li><code>displayName</code>: 显示名称</li><li><code>description</code>: 简短描述</li><li><code>version</code>: 版本号（遵循语义化版本）</li><li><code>runtime</code>: 运行时环境（python&#x2F;node）</li><li><code>entrypoint</code>: 入口文件</li><li><code>tools</code>: 工具列表</li></ul><p><code>pyproject.toml</code>是 Python 项目的标准配置文件，Smithery 要求必须包含此文件，因为后续会打包成一个 server：</p><figure class="highlight toml"><table><tr><td class="gutter"><pre><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><code class="hljs toml"><span class="hljs-section">[build-system]</span><br><span class="hljs-attr">requires</span> = [<span class="hljs-string">&quot;setuptools&gt;=61.0&quot;</span>, <span class="hljs-string">&quot;wheel&quot;</span>]<br><span class="hljs-attr">build-backend</span> = <span class="hljs-string">&quot;setuptools.build_meta&quot;</span><br><br><span class="hljs-section">[project]</span><br><span class="hljs-attr">name</span> = <span class="hljs-string">&quot;weather-mcp-server&quot;</span><br><span class="hljs-attr">version</span> = <span class="hljs-string">&quot;1.0.0&quot;</span><br><span class="hljs-attr">description</span> = <span class="hljs-string">&quot;Real-time weather query MCP server based on HelloAgents framework&quot;</span><br><span class="hljs-attr">readme</span> = <span class="hljs-string">&quot;README.md&quot;</span><br><span class="hljs-attr">license</span> = &#123;text = <span class="hljs-string">&quot;MIT&quot;</span>&#125;<br><span class="hljs-attr">authors</span> = [<br>    &#123;name = <span class="hljs-string">&quot;HelloAgents Team&quot;</span>, email = <span class="hljs-string">&quot;xxx&quot;</span>&#125;<br>]<br><span class="hljs-attr">requires-python</span> = <span class="hljs-string">&quot;&gt;=3.10&quot;</span><br><span class="hljs-attr">dependencies</span> = [<br>    <span class="hljs-string">&quot;hello-agents&gt;=0.2.1&quot;</span>,<br>    <span class="hljs-string">&quot;requests&gt;=2.31.0&quot;</span>,<br>]<br><br><span class="hljs-section">[project.urls]</span><br><span class="hljs-attr">Homepage</span> = <span class="hljs-string">&quot;https://github.com/yourusername/weather-mcp-server&quot;</span><br><span class="hljs-attr">Repository</span> = <span class="hljs-string">&quot;https://github.com/yourusername/weather-mcp-server&quot;</span><br><span class="hljs-attr">&quot;Bug Tracker&quot;</span> = <span class="hljs-string">&quot;https://github.com/yourusername/weather-mcp-server/issues&quot;</span><br><br><span class="hljs-section">[tool.setuptools]</span><br><span class="hljs-attr">py-modules</span> = [<span class="hljs-string">&quot;server&quot;</span>]<br></code></pre></td></tr></table></figure><p>配置说明：</p><ul><li><code>[build-system]</code>: 指定构建工具（setuptools）</li><li><code>[project]</code>: 项目元数据<ul><li><code>name</code>: 项目名称</li><li><code>version</code>: 版本号（遵循语义化版本）</li><li><code>dependencies</code>: 项目依赖列表</li><li><code>requires-python</code>: Python 版本要求</li></ul></li><li><code>[project.urls]</code>: 项目相关链接</li><li><code>[tool.setuptools]</code>: setuptools 配置</li></ul><p>虽然 Smithery 会自动生成 Dockerfile，但提供自定义 Dockerfile 可以确保部署成功：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><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><code class="hljs dockerfile"><span class="hljs-comment"># Multi-stage build for weather-mcp-server</span><br><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.12</span>-slim-bookworm as base<br><br><span class="hljs-comment"># Set working directory</span><br><span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span><br><br><span class="hljs-comment"># Install system dependencies</span><br><span class="hljs-keyword">RUN</span><span class="language-bash"> apt-get update &amp;&amp; apt-get install -y \</span><br><span class="language-bash">    --no-install-recommends \</span><br><span class="language-bash">    &amp;&amp; <span class="hljs-built_in">rm</span> -rf /var/lib/apt/lists/*</span><br><br><span class="hljs-comment"># Copy project files</span><br><span class="hljs-keyword">COPY</span><span class="language-bash"> pyproject.toml requirements.txt ./</span><br><span class="hljs-keyword">COPY</span><span class="language-bash"> server.py ./</span><br><br><span class="hljs-comment"># Install Python dependencies</span><br><span class="hljs-keyword">RUN</span><span class="language-bash"> pip install --no-cache-dir --upgrade pip &amp;&amp; \</span><br><span class="language-bash">    pip install --no-cache-dir -r requirements.txt</span><br><br><span class="hljs-comment"># Set environment variables</span><br><span class="hljs-keyword">ENV</span> PYTHONUNBUFFERED=<span class="hljs-number">1</span><br><span class="hljs-keyword">ENV</span> PORT=<span class="hljs-number">8081</span><br><br><span class="hljs-comment"># Expose port (Smithery uses 8081)</span><br><span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8081</span><br><br><span class="hljs-comment"># Health check</span><br><span class="hljs-keyword">HEALTHCHECK</span><span class="language-bash"> --interval=30s --<span class="hljs-built_in">timeout</span>=3s --start-period=5s --retries=3 \</span><br><span class="language-bash">    CMD python -c <span class="hljs-string">&quot;import sys; sys.exit(0)&quot;</span></span><br><br><span class="hljs-comment"># Run the MCP server</span><br><span class="hljs-keyword">CMD</span><span class="language-bash"> [<span class="hljs-string">&quot;python&quot;</span>, <span class="hljs-string">&quot;server.py&quot;</span>]</span><br></code></pre></td></tr></table></figure><p>Dockerfile 配置说明：</p><ul><li><strong>基础镜像</strong>: <code>python:3.12-slim-bookworm</code> - 轻量级 Python 镜像</li><li><strong>工作目录</strong>: <code>/app</code> - 应用程序根目录</li><li><strong>端口</strong>: <code>8081</code> - Smithery 平台标准端口</li><li><strong>启动命令</strong>: <code>python server.py</code> - 运行 MCP 服务器</li></ul><p>在这里，我们需要 Fork<code>hello-agents</code>仓库，得到<code>code</code>中的源码，并使用自己的 github 创建一个名为<code>weather-mcp-server</code>的仓库，将<code>yourusername</code>改为自己 github 的 Username。</p><p>（3）提交到 Smithery</p><p>打开浏览器，访问 <a href="https://smithery.ai/">https://smithery.ai/</a>。使用 GitHub 账号登录 Smithery。点击页面上的 “Publish Server” 按钮，输入你的 GitHub 仓库 URL：<code>https://github.com/yourusername/weather-mcp-server</code>，即可等待发布。</p><p>一旦发布完成，可以看到类似这样的页面，如图 10.10 所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-10.png" alt="" width="85%"/>  <p>图 10.10 Smithery 发布成功页面 </p></div><p>一旦服务器发布成功，用户可以通过以下方式使用：</p><p>方式 1：通过 Smithery 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></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 安装 Smithery CLI</span><br>npm install -g @smithery/cli<br><br><span class="hljs-comment"># 安装你的服务器</span><br>smithery install weather-mcp-server<br></code></pre></td></tr></table></figure><p>方式 2：在 Claude Desktop 中配置</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><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;mcpServers&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;weather&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>      <span class="hljs-attr">&quot;command&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;smithery&quot;</span><span class="hljs-punctuation">,</span><br>      <span class="hljs-attr">&quot;args&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;run&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;weather-mcp-server&quot;</span><span class="hljs-punctuation">]</span><br>    <span class="hljs-punctuation">&#125;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>方式 3：在 HelloAgents 中使用</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><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.tools.builtin.protocol_tools <span class="hljs-keyword">import</span> MCPTool<br><br>agent = SimpleAgent(name=<span class="hljs-string">&quot;天气助手&quot;</span>, llm=HelloAgentsLLM())<br><br><span class="hljs-comment"># 使用 Smithery 安装的服务器</span><br>weather_tool = MCPTool(<br>    server_command=[<span class="hljs-string">&quot;smithery&quot;</span>, <span class="hljs-string">&quot;run&quot;</span>, <span class="hljs-string">&quot;weather-mcp-server&quot;</span>]<br>)<br>agent.add_tool(weather_tool)<br><br>response = agent.run(<span class="hljs-string">&quot;北京今天天气怎么样？&quot;</span>)<br></code></pre></td></tr></table></figure><p>当然，这里只是举例，还有更多的用法可以自行探索，下图 10.11 展示了当 MCP 工具发布成功会包含的信息，显示服务的名称“天气”，其唯一标识符 <code>@jjyaoao/weather-mcp-server</code>，以及状态信息。Tools 区域就是我们刚刚实现的方法，Connect 区则提供了连接和使用此服务所需的技术信息，包括服务的<strong>接入 URL 地址</strong>和多种语言&#x2F;环境下的<strong>配置代码片段</strong>。如果想要更加深入了解可以点击这个<a href="https://smithery.ai/server/@jjyaoao/weather-mcp-server">链接</a>。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/10-figures/10-11.png" alt="" width="85%"/>  <p>图 10.11 Smithery 发布成功的 MCP 工具 </p></div><p>现在是时候去创造你的 MCP 服务器了！</p><h2 id="10-6-本章总结"><a href="#10-6-本章总结" class="headerlink" title="10.6 本章总结"></a>10.6 本章总结</h2><p>本章系统性地介绍了智能体通信的三种核心协议：MCP、A2A 与 ANP，并探讨了它们的设计理念、应用场景与实践方法。</p><p><strong>协议定位：</strong></p><ul><li><strong>MCP (Model Context Protocol)</strong>: 作为智能体与工具之间的桥梁，提供统一的工具访问接口，适用于增强单个智能体的能力。</li><li><strong>A2A (Agent-to-Agent Protocol)</strong>: 作为智能体之间的对话系统，支持直接通信与任务协商，适用于小规模团队的紧密协作。</li><li><strong>ANP (Agent Network Protocol)</strong>: 作为智能体的“互联网”，提供服务发现、路由与负载均衡机制，适用于构建大规模、开放的智能体网络。</li></ul><p><strong>HelloAgents 的集成方案</strong></p><p>在<code>HelloAgents</code>框架中，这三种协议被统一抽象为工具（Tool），实现了无缝集成，允许开发者灵活地为智能体添加不同层级的通信能力：</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><code class="hljs python"><span class="hljs-comment"># 统一的Tool接口</span><br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MCPTool, A2ATool, ANPTool<br><br><span class="hljs-comment"># 所有协议都可以作为Tool添加到Agent</span><br>agent.add_tool(MCPTool(...))<br>agent.add_tool(A2ATool(...))<br>agent.add_tool(ANPTool(...))<br></code></pre></td></tr></table></figure><p><strong>实战经验总结</strong></p><ul><li>优先利用成熟的社区 MCP 服务，以减少不必要的重复开发。</li><li>根据系统规模选择合适的协议：小规模协作场景推荐使用 A2A，大规模网络场景则应采用 ANP。</li></ul><p>完成本章后，建议你：</p><ol><li><strong>动手实践</strong>：<ul><li>构建自己的 MCP 服务器</li><li>利用协议创建多 Agent 协作系统</li><li>MCP、A2A 与 ANP 的组合应用策略</li></ul></li><li><strong>深入学习</strong>：<ul><li>阅读 MCP 官方文档：<a href="https://modelcontextprotocol.io/">https://modelcontextprotocol.io</a></li><li>阅读 A2A 官方文档：<a href="https://a2a-protocol.org/latest/">https://a2a-protocol.org/latest/</a></li><li>阅读 ANP 官方文档：<a href="https://agent-network-protocol.com/guide/">https://agent-network-protocol.com/guide/</a></li></ul></li><li><strong>参与社区</strong>：<ul><li>向社区贡献新的 MCP 服务</li><li>分享个人开发的智能体实现案例</li><li>参与相关协议的技术标准讨论，也可以在 Issue 提问或是直接帮助 Helloagents 支持新的 example 案例</li></ul></li></ol><p><strong>恭喜你完成第十章的学习！</strong></p><p>你现在已经掌握了智能体通信协议的核心知识。继续加油！🚀</p><h2 id="习题"><a href="#习题" class="headerlink" title="习题"></a>习题</h2><blockquote><p><strong>提示</strong>：部分习题没有标准答案，重点在于培养学习者对智能体通信协议的综合理解和实践能力。</p></blockquote><ol><li><p>本章介绍了三种智能体通信协议：MCP、A2A 和 ANP。请分析：</p><ul><li>在 10.1.2 节中对比了三种协议的设计理念。请深入分析：为什么 MCP 强调”上下文共享”，A2A 强调”对话式协作”，而 ANP 强调”网络拓扑”？这些设计理念分别解决了什么核心问题？</li><li>假设你要构建一个”智能客服系统”，需要以下功能：（1）访问客户数据库和订单系统；（2）多个专业客服智能体协作处理复杂问题；（3）支持大规模并发用户请求。请为每个功能选择最合适的协议，并说明理由。</li><li>三种协议是否可以组合使用？请设计一个实际应用场景，展示如何同时使用 MCP、A2A 和 ANP 来构建一个完整的智能体系统。画出系统架构图并说明各协议的职责。</li></ul></li><li><p>MCP（Model Context Protocol）是智能体与工具通信的标准协议。基于 10.2 节的内容，请深入思考：</p><blockquote><p><strong>提示</strong>：这是一道动手实践题，建议实际操作</p></blockquote><ul><li>在 10.2.3 节的 MCP 服务器实现中，我们定义了<code>list_tools</code>、<code>call_tool</code>等核心方法。请扩展这个实现，添加一个新的 MCP 服务器，提供以下工具：（1）数据库查询工具；（2）数据可视化工具；（3）报表生成工具。要求工具之间能够协作完成复杂的数据分析任务。</li><li>MCP 协议支持”资源”（Resources）和”提示”（Prompts）两个重要概念，但本章主要聚焦于”工具”（Tools）。请查阅 MCP 官方文档，了解 Resources 和 Prompts 的设计目的，并设计一个应用场景，展示如何利用这三个核心概念构建更强大的智能体系统。</li><li>MCP 使用 JSON-RPC 2.0 作为底层通信协议，通过 stdio 进行进程间通信。请分析：这种设计有什么优势和局限性？如果需要支持远程 MCP 服务器（通过 HTTP&#x2F;WebSocket 访问），应该如何扩展当前的实现？</li></ul></li><li><p>A2A（Agent-to-Agent Protocol）支持智能体间的对话式协作。基于 10.3 节的内容，请完成以下扩展实践：</p><blockquote><p><strong>提示</strong>：这是一道动手实践题，建议实际操作</p></blockquote><ul><li>在 10.3.4 节的”研究团队”案例中，研究员和撰写员通过 A2A 协议协作完成论文写作。请扩展这个案例，添加第三个智能体”审稿人”（Reviewer），它能够评审论文质量并提出修改建议。设计三个智能体之间的协作流程，并实现完整的代码。</li><li>A2A 协议定义了<code>task</code>、<code>task_result</code>等消息类型。请分析：如果协作过程中出现冲突（如两个智能体对同一问题有不同意见），应该如何设计冲突解决机制？请扩展 A2A 协议，添加”协商”（negotiation）和”投票”（voting）等消息类型。</li><li>对比 A2A 协议与第六章介绍的 AutoGen、CAMEL 等多智能体框架：A2A 作为标准协议，与这些框架的关系是什么？它们能否互相替代？请设计一个方案，让基于 A2A 协议的智能体能够与 AutoGen 框架中的智能体进行通信。</li></ul></li><li><p>ANP（Agent Network Protocol）支持大规模智能体网络。基于 10.4 节的内容，请深入分析：</p><ul><li>在 10.4.2 节中介绍了 ANP 的网络拓扑设计，包括星型、网状、分层等结构。请分析：在什么场景下应该选择哪种拓扑结构？如果网络规模从 10 个智能体扩展到 1000 个智能体，拓扑结构应该如何演进？</li><li>ANP 协议支持”路由”（routing）和”发现”（discovery）机制，让智能体能够动态找到合适的协作伙伴。请设计一个”智能路由算法”：根据任务类型、智能体能力、网络负载等因素，自动选择最优的消息路由路径。</li><li>在 10.4.4 节的”智能城市”案例中，多个智能体协作管理城市系统。请思考：如果某个关键智能体（如交通管理智能体）出现故障，整个系统应该如何应对？请设计一个”容错机制”，包括故障检测、备份切换、状态恢复等功能。</li></ul></li><li><p>智能体通信协议的安全性和隐私保护是实际应用中的关键问题。请思考：</p><ul><li>在 10.2.4 节的 MCP 客户端实现中，智能体可以调用 MCP 服务器提供的任何工具。请分析：这种设计存在什么安全风险？如果 MCP 服务器提供了危险操作（如删除文件、执行系统命令），应该如何设计权限控制机制？</li><li>A2A 和 ANP 协议涉及多个智能体之间的通信，可能包含敏感信息（如用户隐私数据、商业机密）。请设计一个”端到端加密”方案：确保消息在传输过程中不被窃听或篡改，同时支持智能体身份认证和访问控制。</li><li>在大规模智能体网络中，恶意智能体可能会发送虚假信息、发起拒绝服务攻击或窃取其他智能体的数据。请设计一个”信任评估系统”：根据智能体的历史行为、协作质量、社区评价等因素，动态评估每个智能体的可信度，并据此调整通信策略。</li></ul></li></ol><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>[1] Anthropic. (2024). <em>Model Context Protocol</em>. Retrieved October 7, 2025, from <a href="https://modelcontextprotocol.io/">https://modelcontextprotocol.io/</a></p><p>[2] The A2A Project. (2025). <em>A2A Protocol: An open protocol for agent-to-agent communication</em>. Retrieved October 7, 2025, from <a href="https://a2a-protocol.org/">https://a2a-protocol.org/</a></p><p>[3] Chang, G., Lin, E., Yuan, C., Cai, R., Chen, B., Xie, X., &amp; Zhang, Y. (2025). <em>Agent Network Protocol technical white paper</em>. arXiv. <a href="https://doi.org/10.48550/arXiv.2508.00007">https://doi.org/10.48550/arXiv.2508.00007</a></p>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC10%E7%AB%A0-%E6%99%BA%E8%83%BD%E4%BD%93%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AE/</id>
    <link href="http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC10%E7%AB%A0-%E6%99%BA%E8%83%BD%E4%BD%93%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AE/"/>
    <published>2026-03-01T22:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="第十章-智能体通信协议"><a href="#第十章-智能体通信协议" class="headerlink" title="第十章 智能体通信协议"></a>第十章 智能体通信协议</h1><p>在前面的章节中，我们构建了功能完备的单体智能体，它们具备推理、工具调]]>
    </summary>
    <title>第十章 智能体通信协议</title>
    <updated>2026-03-08T09:24:16.337Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="第九章-上下文工程"><a href="#第九章-上下文工程" class="headerlink" title="第九章 上下文工程"></a>第九章 上下文工程</h1><p>在前面的章节中，我们已经为智能体引入了记忆系统与RAG。然而，要让智能体在真实复杂场景中稳定地“思考”与“行动”，仅有记忆与检索还不够——我们需要一套工程化方法，持续、系统地为模型构造恰当的“上下文”。这就是本章的主题：上下文工程（Context Engineering）。它关注的是“在每一次模型调用前，如何以可复用、可度量、可演进的方式，拼装并优化输入上下文”，从而提升正确性、鲁棒性与效率<sup>[1][2]</sup>。</p><p>为了让读者能够快速体验本章的完整功能，我们提供了可直接安装的Python包。你可以通过以下命令安装本章对应的版本：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">pip install <span class="hljs-string">&quot;hello-agents[all]==0.2.8&quot;</span><br></code></pre></td></tr></table></figure><p>本章主要介绍上下文工程的核心概念与实践，并在HelloAgents框架中新增了上下文构建器和两个配套工具：</p><ul><li><strong>ContextBuilder</strong> (<code>hello_agents/context/builder.py</code>)：上下文构建器，实现 GSSC (Gather-Select-Structure-Compress) 流水线，提供统一的上下文管理接口</li><li><strong>NoteTool</strong> (<code>hello_agents/tools/builtin/note_tool.py</code>)：结构化笔记工具，支持智能体进行持久化记忆管理</li><li><strong>TerminalTool</strong> (<code>hello_agents/tools/builtin/terminal_tool.py</code>)：终端工具，支持智能体进行文件系统操作和即时上下文检索</li></ul><p>这些组件共同构成了完整的上下文工程解决方案，是实现长时程任务管理和智能体式搜索的关键，将在后续章节中详细介绍。</p><p>除了安装框架外，还需要在<code>.env</code>配置LLM的API。本章示例主要使用大语言模型进行上下文管理和智能决策。</p><p>配置完成后，即可开始本章的学习之旅！</p><h2 id="9-1-什么是上下文工程"><a href="#9-1-什么是上下文工程" class="headerlink" title="9.1 什么是上下文工程"></a>9.1 什么是上下文工程</h2><p>在经历了数年提示工程（Prompt Engineering）成为应用型AI的焦点之后，一个新的术语开始走到台前：<strong>上下文工程（Context Engineering）</strong>。如今，用语言模型构建系统不再只是找对提示词里的句式和措辞，而是要回答一个更宏观的问题：<strong>什么样的上下文配置，最有可能让模型产出我们期望的行为？</strong></p><p>所谓“上下文”，是指在对大语言模型（LLM）进行采样时所包含的那组 tokens。手头的工程问题，是在 LLM 的固有约束之下，<strong>优化这些 tokens 的效用</strong>，以便稳定地得到预期结果。想要有效驾驭 LLM，往往需要“在上下文中思考”——也就是说：在任何一次调用时，都要审视 LLM 可见的整体状态，并预判这种状态可能诱发的行为。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/9-figures/9-1.webp" alt="" width="85%"/>  <p>图 9.1 Prompt engineering vs Context engineering</p></div><p>本节将探讨正在兴起的上下文工程，并给出一个用于构建<strong>可调控、有效</strong>智能体的精炼心智模型。</p><p><strong>上下文工程 vs. 提示工程</strong></p><p>如图9.1所示，在现在前沿模型厂商的视角中，上下文工程是提示工程的自然演进。提示工程关注如何编写与组织 LLM 的指令以获得更优结果（例如系统提示的写法与结构化策略）；而上下文工程则是<strong>在推理阶段，如何策划与维护“最优的信息集合（tokens）”</strong>，其中不仅包含提示本身，还包含其他会进入上下文窗口的一切信息。</p><p>在 LLM 工程的早期阶段，提示往往是主要工作，因为大多数用例（除日常聊天外）都需要针对单轮分类或文本生成做精调式的提示优化。顾名思义，提示工程的核心是“如何写出有效提示”，尤其是系统提示。然而，随着我们开始工程化地构建更强的智能体，它们在更长的时间范围内、跨多次推理轮次地工作，我们就需要能管理<strong>整个上下文状态</strong>的策略——其中包括系统指令、工具、MCP（Model Context Protocol）、外部数据、消息历史等。</p><p>一个循环运行的智能体，会不断产生下一轮推理可能相关的数据，这些信息必须被<strong>周期性地提炼</strong>。因此，上下文工程的“艺与术”，在于从持续扩张的“候选信息宇宙”中，<strong>甄别哪些内容应当进入有限的上下文窗口</strong>。</p><h2 id="9-2-为什么上下文工程重要"><a href="#9-2-为什么上下文工程重要" class="headerlink" title="9.2 为什么上下文工程重要"></a>9.2 为什么上下文工程重要</h2><p>尽管模型的速度越来越快、可处理的数据规模越来越大，但我们观察到：LLM 和人类一样，在一定点上会“走神”或“混乱”。针堆找针（needle-in-a-haystack）类基准揭示了一个现象：<strong>上下文腐蚀（context rot）</strong>——随着上下文窗口中的 tokens 增加，模型从上下文中准确回忆信息的能力反而下降。</p><p>不同模型的退化曲线或许更平滑，但这一特征几乎在所有模型上都会出现。因此，<strong>上下文必须被视作一种有限资源，且具有边际收益递减</strong>。就像人类有有限的工作记忆容量一样，LLM 也有一笔“注意力预算”。每新增一个 token，都会消耗这笔预算的一部分，因此我们更需要谨慎地筛选哪些 tokens 应该被提供给 LLM。</p><p>这种稀缺并非偶然，而是源自 LLM 的架构约束。Transformer 让每个 token 能够与上下文中的<strong>所有</strong> token 建立关联，理论上形成 (n^2) 级别的两两注意力关系。随着上下文长度增长，模型对这些两两关系的建模能力会被“拉薄”，从而自然地产生“上下文规模”与“注意力集中度”的张力。此外，模型的注意力模式来源于训练数据分布——短序列通常比长序列更常见，因此模型对“全上下文依赖”的经验更少、专门参数也更少。</p><p>诸如位置编码插值（position encoding interpolation）等技术可以让模型在推理时“适配”比训练期更长的序列，但会牺牲部分对 token 位置的精确理解。总体上，这些因素共同形成的是一个<strong>性能梯度</strong>，而非“悬崖式”崩溃：模型在长上下文下依旧强大，但相较短上下文，在信息检索与长程推理上的精度会有所下降。</p><p>基于上述现实，<strong>有意识的上下文工程</strong>就成为构建强健智能体的必需品。</p><h3 id="9-2-1-有效上下文的“解剖学”"><a href="#9-2-1-有效上下文的“解剖学”" class="headerlink" title="9.2.1 有效上下文的“解剖学”"></a>9.2.1 有效上下文的“解剖学”</h3><p>在“有限注意力预算”的约束下，优秀的上下文工程目标是：<strong>用尽可能少、但高信号密度的 tokens，最大化获得期望结果的概率</strong>。落实到实践中，我们建议围绕以下组件开展工程化建设：</p><ul><li><p><strong>系统提示（System Prompt）</strong>：语言清晰、直白，信息层级把握在“刚刚好”的高度。常见两极误区：</p><ul><li>过度硬编码：在提示中写入复杂、脆弱的 if-else 逻辑，长期维护成本高、易碎。</li><li>过于空泛：只给出宏观目标与泛化指引，缺少对期望输出的<strong>具体信号</strong>或假定了错误的“共享上下文”。<br>建议将提示分区组织（如 <background_information>、<instructions>、工具指引、输出描述等），用 XML&#x2F;Markdown 分隔。无论格式如何，追求的是<strong>能完整勾勒期望行为的“最小必要信息集”</strong>（“最小”并不等于“最短”）。先用最好的模型在最小提示上试跑，再依据失败模式增补清晰的指令与示例。</li></ul></li><li><p><strong>工具（Tools）</strong>：工具定义了智能体与信息&#x2F;行动空间的契约，必须促进效率：既要返回<strong>token 友好</strong>的信息，又要鼓励高效的智能体行为。工具应当：</p><ul><li>职责单一、相互低重叠，接口语义清晰；</li><li>对错误鲁棒；</li><li>入参描述明确、无歧义，充分发挥模型擅长的表达与推理能力。<br>常见失败模式是“臃肿工具集”：功能边界模糊，导致“选哪个工具”这一决策本身就含混不清。<strong>如果人类工程师都说不准用哪个工具，别指望智能体做得更好</strong>。精心甄别一个“最小可行工具集（MVTS）”往往能显著提升长期交互中的稳定性与可维护性。</li></ul></li><li><p><strong>示例（Few-shot）</strong>：始终推荐提供示例，但不建议把“所有边界条件”的罗列一股脑塞进提示。请精挑细选一组<strong>多样且典型</strong>的示例，直接画像“期望行为”。对 LLM 而言，<strong>好的示例胜过千言万语</strong>。</p></li></ul><p>总的指导思想是：<strong>信息充分但紧致</strong>。如图9.2所示，是进入运行时的动态检索。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/9-figures/9-2.webp" alt="" width="85%"/>  <p>图 9.2 Calibrating the system prompt</p></div><h3 id="9-2-2-上下文检索与智能体式搜索"><a href="#9-2-2-上下文检索与智能体式搜索" class="headerlink" title="9.2.2 上下文检索与智能体式搜索"></a>9.2.2 上下文检索与智能体式搜索</h3><p>一个简洁的定义：<strong>智能体 &#x3D; 在循环中自主调用工具的 LLM</strong>。随着底层模型能力增强，智能体的自治水平便可提升：更能独立探索复杂问题空间，并从错误中恢复。</p><p>工程实践正在从“推理前一次性检索（embedding 检索）”逐步过渡到“<strong>及时（Just-in-time, JIT）上下文</strong>”。后者不再预先加载所有相关数据，而是维护<strong>轻量化引用</strong>（文件路径、存储查询、URL 等），在运行时通过工具动态加载所需数据。这样可让模型撰写针对性查询、缓存必要结果，并用诸如 <code>head</code>&#x2F;<code>tail</code> 之类的命令分析大体量数据——无需把整块数据一次性塞入上下文。其认知模式更贴近人类：我们不会死记硬背全部信息，而是用文件系统、收件箱、书签等外部索引按需提取。</p><p>除了存储效率，<strong>引用的元数据</strong>本身也能帮助精化行为：目录层级、命名约定、时间戳等都在隐含地传达“目的与时效”。例如，<code>tests/test_utils.py</code> 与 <code>src/core/test_utils.py</code> 的语义暗示就不同。</p><p>允许智能体自主导航与检索还能实现<strong>渐进式披露（progressive disclosure）</strong>：每一步交互都会产生新的上下文，反过来指导下一步决策——文件大小暗示复杂度、命名暗示用途、时间戳暗示相关性。智能体得以按层构建理解，只在工作记忆中保留“当前必要子集”，并用“记笔记”的方式做补充持久化，从而维持聚焦而非“被大而全拖垮”。</p><p>需要权衡的是：运行时探索往往比预计算检索更慢，并且需要有“主见”的工程设计来确保模型拥有正确的工具与启发式。如果缺少引导，智能体可能会误用工具、追逐死胡同或错过关键信息，造成上下文浪费。</p><p>在不少场景中，<strong>混合策略</strong>更有效：前置加载少量“高价值”上下文以保证速度，然后允许智能体按需继续自主探索。边界的选择取决于任务动态性与时效要求。在工程上，可以预先放入类似“项目约定说明（如 README&#x2F;指南）”的文件，同时提供 <code>glob</code>、<code>grep</code> 等原语，让智能体即时检索具体文件，从而绕开过时索引与复杂语法树的沉没成本。</p><h3 id="9-2-3-面向长时程任务的上下文工程"><a href="#9-2-3-面向长时程任务的上下文工程" class="headerlink" title="9.2.3 面向长时程任务的上下文工程"></a>9.2.3 面向长时程任务的上下文工程</h3><p>长时程任务要求智能体在超出上下文窗口的长序列行动中，仍能保持连贯性、上下文一致与目标导向。例如大型代码库迁移、跨数小时的系统性研究。指望无限增大上下文窗口并不能根治“上下文污染”与相关性退化的问题，因此需要直接面向这些约束的工程手段：<strong>压缩整合（Compaction）</strong>、<strong>结构化笔记（Structured note-taking）</strong>与<strong>子代理架构（Sub-agent architectures）</strong>。</p><ul><li><p><strong>压缩整合（Compaction）</strong></p><ul><li>定义：当对话接近上下文上限时，对其进行高保真总结，并用该摘要重启一个新的上下文窗口，以维持长程连贯性。</li><li>实践：让模型压缩并保留架构性决策、未解决缺陷、实现细节，丢弃重复的工具输出与噪声；新窗口携带压缩摘要 + 最近少量高相关工件（如“最近访问的若干文件”）。</li><li>调参建议：先优化<strong>召回</strong>（确保不遗漏关键信息），再优化<strong>精确度</strong>（剔除冗余内容）；一种安全的“轻触式”压缩是对“深历史中的工具调用与结果”进行清理。</li></ul></li><li><p><strong>结构化笔记（Structured note-taking）</strong></p><ul><li>定义：也称“智能体记忆”。智能体以固定频率将关键信息写入<strong>上下文外的持久化存储</strong>，在后续阶段按需拉回。</li><li>价值：以极低的上下文开销维持持久状态与依赖关系。例如维护 TODO 列表、项目 NOTES.md、关键结论&#x2F;依赖&#x2F;阻塞项的索引，跨数十次工具调用与多轮上下文重置仍能保持进度与一致性。</li><li>说明：在非编码场景中同样有效（如长期策略性任务、游戏&#x2F;仿真中的目标管理与统计计数）。结合第八章的 <code>MemoryTool</code>，可轻松实现文件式&#x2F;向量式的外部记忆并在运行时检索。</li></ul></li><li><p><strong>子代理架构（Sub-agent architectures）</strong></p><ul><li>思想：由主代理负责高层规划与综合，多个专长子代理在“干净的上下文窗口”中各自深挖、调用工具并探索，最后仅回传<strong>凝练摘要</strong>（常见 1,000–2,000 tokens）。</li><li>好处：实现关注点分离。庞杂的搜索上下文留在子代理内部，主代理专注于整合与推理；适合需要并行探索的复杂研究&#x2F;分析任务。</li><li>经验：公开的多智能体研究系统显示，该模式在复杂研究任务上相较单代理基线具有显著优势。</li></ul></li></ul><p>方法取舍可以遵循以下经验法则：</p><ul><li><strong>压缩整合</strong>：适合需要长对话连续性的任务，强调上下文的“接力”。</li><li><strong>结构化笔记</strong>：适合有里程碑&#x2F;阶段性成果的迭代式开发与研究。</li><li><strong>子代理架构</strong>：适合复杂研究与分析，能从并行探索中获益。</li></ul><p>即便模型能力持续提升，“在长交互中维持连贯性与聚焦”仍是构建强健智能体的核心挑战。谨慎而系统的上下文工程将长期保持其关键价值。</p><h2 id="9-3-在-Hello-Agents-中的实践：ContextBuilder"><a href="#9-3-在-Hello-Agents-中的实践：ContextBuilder" class="headerlink" title="9.3 在 Hello-Agents 中的实践：ContextBuilder"></a>9.3 在 Hello-Agents 中的实践：ContextBuilder</h2><p>本节将详细介绍 HelloAgents 框架中的上下文工程实践。我们将从设计动机、核心数据结构、实现细节到完整案例，逐步展示如何构建一个生产级的上下文管理系统。ContextBuilder 的设计理念是”简单高效”，去除不必要的复杂性，统一以”相关性+新近性”的分数进行选择，符合 Agent 模块化与可维护性的工程取向。</p><h3 id="9-3-1-设计动机与目标"><a href="#9-3-1-设计动机与目标" class="headerlink" title="9.3.1 设计动机与目标"></a>9.3.1 设计动机与目标</h3><p>在构建 ContextBuilder 之前，我们首先需要明确其设计目标和核心价值。一个优秀的上下文管理系统应该解决以下几个关键问题：</p><ol><li><p><strong>统一入口</strong>：将”获取(Gather)- 选择(Select)- 结构化(Structure)- 压缩(Compress)”抽象为可复用流水线，减少在 Agent 实现中的重复模板代码。这种统一的接口设计让开发者无需在每个 Agent 中重复编写上下文管理逻辑。</p></li><li><p><strong>稳定形态</strong>：输出固定骨架的上下文模板，便于调试、A&#x2F;B 测试与评估。我们采用了分区组织的模板结构：</p><ul><li><code>[Role &amp; Policies]</code>：明确 Agent 的角色定位和行为准则</li><li><code>[Task]</code>：当前需要完成的具体任务</li><li><code>[State]</code>：Agent 的当前状态和上下文信息</li><li><code>[Evidence]</code>：从外部知识库检索的证据信息</li><li><code>[Context]</code>：历史对话和相关记忆</li><li><code>[Output]</code>：期望的输出格式和要求</li></ul></li><li><p><strong>预算守护</strong>：在 token 预算内尽量保留高价值信息，对超限上下文提供兜底压缩策略。这确保了即使在信息量巨大的场景下，系统也能稳定运行。</p></li><li><p><strong>最小规则</strong>：不引入来源&#x2F;优先级等分类维度，避免复杂度增长。实践表明，基于相关性和新近性的简单评分机制，在大多数场景下已经足够有效。</p></li></ol><h3 id="9-3-2-核心数据结构"><a href="#9-3-2-核心数据结构" class="headerlink" title="9.3.2 核心数据结构"></a>9.3.2 核心数据结构</h3><p>ContextBuilder 的实现依赖两个核心数据结构，它们定义了系统的配置和信息单元。</p><p>（1）ContextPacket：候选信息包</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> dataclasses <span class="hljs-keyword">import</span> dataclass<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Optional</span>, <span class="hljs-type">Dict</span>, <span class="hljs-type">Any</span><br><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><br><span class="hljs-meta">@dataclass</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ContextPacket</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;候选信息包</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Attributes:</span><br><span class="hljs-string">        content: 信息内容</span><br><span class="hljs-string">        timestamp: 时间戳</span><br><span class="hljs-string">        token_count: Token 数量</span><br><span class="hljs-string">        relevance_score: 相关性分数(0.0-1.0)</span><br><span class="hljs-string">        metadata: 可选的元数据</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    content: <span class="hljs-built_in">str</span><br>    timestamp: datetime<br>    token_count: <span class="hljs-built_in">int</span><br>    relevance_score: <span class="hljs-built_in">float</span> = <span class="hljs-number">0.5</span><br>    metadata: <span class="hljs-type">Optional</span>[<span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]] = <span class="hljs-literal">None</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__post_init__</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;初始化后处理&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> self.metadata <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br>            self.metadata = &#123;&#125;<br>        <span class="hljs-comment"># 确保相关性分数在有效范围内</span><br>        self.relevance_score = <span class="hljs-built_in">max</span>(<span class="hljs-number">0.0</span>, <span class="hljs-built_in">min</span>(<span class="hljs-number">1.0</span>, self.relevance_score))<br></code></pre></td></tr></table></figure><p><code>ContextPacket</code> 是系统中信息的基本单元。每个候选信息都会被封装为一个 ContextPacket，包含内容、时间戳、token 数量和相关性分数等核心属性。这种统一的数据结构简化了后续的选择和排序逻辑。</p><p>（2）ContextConfig：配置管理</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-meta">@dataclass</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ContextConfig</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;上下文构建配置</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Attributes:</span><br><span class="hljs-string">        max_tokens: 最大 token 数量</span><br><span class="hljs-string">        reserve_ratio: 为系统指令预留的比例(0.0-1.0)</span><br><span class="hljs-string">        min_relevance: 最低相关性阈值</span><br><span class="hljs-string">        enable_compression: 是否启用压缩</span><br><span class="hljs-string">        recency_weight: 新近性权重(0.0-1.0)</span><br><span class="hljs-string">        relevance_weight: 相关性权重(0.0-1.0)</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    max_tokens: <span class="hljs-built_in">int</span> = <span class="hljs-number">3000</span><br>    reserve_ratio: <span class="hljs-built_in">float</span> = <span class="hljs-number">0.2</span><br>    min_relevance: <span class="hljs-built_in">float</span> = <span class="hljs-number">0.1</span><br>    enable_compression: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span><br>    recency_weight: <span class="hljs-built_in">float</span> = <span class="hljs-number">0.3</span><br>    relevance_weight: <span class="hljs-built_in">float</span> = <span class="hljs-number">0.7</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__post_init__</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;验证配置参数&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">assert</span> <span class="hljs-number">0.0</span> &lt;= self.reserve_ratio &lt;= <span class="hljs-number">1.0</span>, <span class="hljs-string">&quot;reserve_ratio 必须在 [0, 1] 范围内&quot;</span><br>        <span class="hljs-keyword">assert</span> <span class="hljs-number">0.0</span> &lt;= self.min_relevance &lt;= <span class="hljs-number">1.0</span>, <span class="hljs-string">&quot;min_relevance 必须在 [0, 1] 范围内&quot;</span><br>        <span class="hljs-keyword">assert</span> <span class="hljs-built_in">abs</span>(self.recency_weight + self.relevance_weight - <span class="hljs-number">1.0</span>) &lt; <span class="hljs-number">1e-6</span>, \<br>            <span class="hljs-string">&quot;recency_weight + relevance_weight 必须等于 1.0&quot;</span><br></code></pre></td></tr></table></figure><p><code>ContextConfig</code> 封装了所有可配置的参数，使得系统行为可以灵活调整。特别值得注意的是 <code>reserve_ratio</code> 参数，它确保系统指令等关键信息始终有足够的空间，不会被其他信息挤占。</p><h3 id="9-3-3-GSSC-流水线详解"><a href="#9-3-3-GSSC-流水线详解" class="headerlink" title="9.3.3 GSSC 流水线详解"></a>9.3.3 GSSC 流水线详解</h3><p>ContextBuilder 的核心是 GSSC(Gather-Select-Structure-Compress)流水线，它将上下文构建过程分解为四个清晰的阶段。让我们深入了解每个阶段的实现细节。</p><p>（1）Gather：多源信息汇集</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_gather</span>(<span class="hljs-params"></span><br><span class="hljs-params">    self,</span><br><span class="hljs-params">    user_query: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">    conversation_history: <span class="hljs-type">Optional</span>[<span class="hljs-type">List</span>[Message]] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    system_instructions: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    custom_packets: <span class="hljs-type">Optional</span>[<span class="hljs-type">List</span>[ContextPacket]] = <span class="hljs-literal">None</span></span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-type">List</span>[ContextPacket]:<br>    <span class="hljs-string">&quot;&quot;&quot;汇集所有候选信息</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        user_query: 用户查询</span><br><span class="hljs-string">        conversation_history: 对话历史</span><br><span class="hljs-string">        system_instructions: 系统指令</span><br><span class="hljs-string">        custom_packets: 自定义信息包</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        List[ContextPacket]: 候选信息列表</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    packets = []<br><br>    <span class="hljs-comment"># 1. 添加系统指令(最高优先级,不参与评分)</span><br>    <span class="hljs-keyword">if</span> system_instructions:<br>        packets.append(ContextPacket(<br>            content=system_instructions,<br>            timestamp=datetime.now(),<br>            token_count=self._count_tokens(system_instructions),<br>            relevance_score=<span class="hljs-number">1.0</span>,  <span class="hljs-comment"># 系统指令始终保留</span><br>            metadata=&#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;system_instruction&quot;</span>, <span class="hljs-string">&quot;priority&quot;</span>: <span class="hljs-string">&quot;high&quot;</span>&#125;<br>        ))<br><br>    <span class="hljs-comment"># 2. 从记忆系统检索相关记忆</span><br>    <span class="hljs-keyword">if</span> self.memory_tool:<br>        <span class="hljs-keyword">try</span>:<br>            memory_results = self.memory_tool.run(&#123;<br>                <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;search&quot;</span>,<br>                <span class="hljs-string">&quot;query&quot;</span>: user_query,<br>                <span class="hljs-string">&quot;limit&quot;</span>: <span class="hljs-number">10</span>,<br>                <span class="hljs-string">&quot;min_importance&quot;</span>: <span class="hljs-number">0.3</span><br>            &#125;)<br>            <span class="hljs-comment"># 解析记忆结果并转换为 ContextPacket</span><br>            memory_packets = self._parse_memory_results(memory_results, user_query)<br>            packets.extend(memory_packets)<br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[WARNING] 记忆检索失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 3. 从 RAG 系统检索相关知识</span><br>    <span class="hljs-keyword">if</span> self.rag_tool:<br>        <span class="hljs-keyword">try</span>:<br>            rag_results = self.rag_tool.run(&#123;<br>                <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;search&quot;</span>,<br>                <span class="hljs-string">&quot;query&quot;</span>: user_query,<br>                <span class="hljs-string">&quot;limit&quot;</span>: <span class="hljs-number">5</span>,<br>                <span class="hljs-string">&quot;min_score&quot;</span>: <span class="hljs-number">0.3</span><br>            &#125;)<br>            <span class="hljs-comment"># 解析 RAG 结果并转换为 ContextPacket</span><br>            rag_packets = self._parse_rag_results(rag_results, user_query)<br>            packets.extend(rag_packets)<br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[WARNING] RAG 检索失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 4. 添加对话历史(仅保留最近的 N 条)</span><br>    <span class="hljs-keyword">if</span> conversation_history:<br>        recent_history = conversation_history[-<span class="hljs-number">5</span>:]  <span class="hljs-comment"># 默认保留最近 5 条</span><br>        <span class="hljs-keyword">for</span> msg <span class="hljs-keyword">in</span> recent_history:<br>            packets.append(ContextPacket(<br>                content=<span class="hljs-string">f&quot;<span class="hljs-subst">&#123;msg.role&#125;</span>: <span class="hljs-subst">&#123;msg.content&#125;</span>&quot;</span>,<br>                timestamp=msg.timestamp <span class="hljs-keyword">if</span> <span class="hljs-built_in">hasattr</span>(msg, <span class="hljs-string">&#x27;timestamp&#x27;</span>) <span class="hljs-keyword">else</span> datetime.now(),<br>                token_count=self._count_tokens(msg.content),<br>                relevance_score=<span class="hljs-number">0.6</span>,  <span class="hljs-comment"># 历史消息的基础相关性</span><br>                metadata=&#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;conversation_history&quot;</span>, <span class="hljs-string">&quot;role&quot;</span>: msg.role&#125;<br>            ))<br><br>    <span class="hljs-comment"># 5. 添加自定义信息包</span><br>    <span class="hljs-keyword">if</span> custom_packets:<br>        packets.extend(custom_packets)<br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[ContextBuilder] 汇集了 <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(packets)&#125;</span> 个候选信息包&quot;</span>)<br>    <span class="hljs-keyword">return</span> packets<br></code></pre></td></tr></table></figure><p>这个实现展示了几个重要的设计考虑：</p><ul><li><strong>容错机制</strong>：每个外部数据源的调用都被 try-except 包裹，确保单个源的失败不会影响整体流程</li><li><strong>优先级处理</strong>：系统指令被标记为高优先级，确保始终被保留</li><li><strong>历史限制</strong>：对话历史只保留最近的几条，避免上下文窗口被历史信息占据</li></ul><p>（2）Select：智能信息选择</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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_select</span>(<span class="hljs-params"></span><br><span class="hljs-params">    self,</span><br><span class="hljs-params">    packets: <span class="hljs-type">List</span>[ContextPacket],</span><br><span class="hljs-params">    user_query: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">    available_tokens: <span class="hljs-built_in">int</span></span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-type">List</span>[ContextPacket]:<br>    <span class="hljs-string">&quot;&quot;&quot;选择最相关的信息包</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        packets: 候选信息包列表</span><br><span class="hljs-string">        user_query: 用户查询(用于计算相关性)</span><br><span class="hljs-string">        available_tokens: 可用的 token 数量</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        List[ContextPacket]: 选中的信息包列表</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 1. 分离系统指令和其他信息</span><br>    system_packets = [p <span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> packets <span class="hljs-keyword">if</span> p.metadata.get(<span class="hljs-string">&quot;type&quot;</span>) == <span class="hljs-string">&quot;system_instruction&quot;</span>]<br>    other_packets = [p <span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> packets <span class="hljs-keyword">if</span> p.metadata.get(<span class="hljs-string">&quot;type&quot;</span>) != <span class="hljs-string">&quot;system_instruction&quot;</span>]<br><br>    <span class="hljs-comment"># 2. 计算系统指令占用的 token</span><br>    system_tokens = <span class="hljs-built_in">sum</span>(p.token_count <span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> system_packets)<br>    remaining_tokens = available_tokens - system_tokens<br><br>    <span class="hljs-keyword">if</span> remaining_tokens &lt;= <span class="hljs-number">0</span>:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[WARNING] 系统指令已占满所有 token 预算&quot;</span>)<br>        <span class="hljs-keyword">return</span> system_packets<br><br>    <span class="hljs-comment"># 3. 为其他信息计算综合分数</span><br>    scored_packets = []<br>    <span class="hljs-keyword">for</span> packet <span class="hljs-keyword">in</span> other_packets:<br>        <span class="hljs-comment"># 计算相关性分数(如果尚未计算)</span><br>        <span class="hljs-keyword">if</span> packet.relevance_score == <span class="hljs-number">0.5</span>:  <span class="hljs-comment"># 默认值,需要重新计算</span><br>            relevance = self._calculate_relevance(packet.content, user_query)<br>            packet.relevance_score = relevance<br><br>        <span class="hljs-comment"># 计算新近性分数</span><br>        recency = self._calculate_recency(packet.timestamp)<br><br>        <span class="hljs-comment"># 综合分数 = 相关性权重 × 相关性 + 新近性权重 × 新近性</span><br>        combined_score = (<br>            self.config.relevance_weight * packet.relevance_score +<br>            self.config.recency_weight * recency<br>        )<br><br>        <span class="hljs-comment"># 过滤低于最小相关性阈值的信息</span><br>        <span class="hljs-keyword">if</span> packet.relevance_score &gt;= self.config.min_relevance:<br>            scored_packets.append((combined_score, packet))<br><br>    <span class="hljs-comment"># 4. 按分数降序排序</span><br>    scored_packets.sort(key=<span class="hljs-keyword">lambda</span> x: x[<span class="hljs-number">0</span>], reverse=<span class="hljs-literal">True</span>)<br><br>    <span class="hljs-comment"># 5. 贪心选择:按分数从高到低填充,直到达到 token 上限</span><br>    selected = system_packets.copy()<br>    current_tokens = system_tokens<br><br>    <span class="hljs-keyword">for</span> score, packet <span class="hljs-keyword">in</span> scored_packets:<br>        <span class="hljs-keyword">if</span> current_tokens + packet.token_count &lt;= available_tokens:<br>            selected.append(packet)<br>            current_tokens += packet.token_count<br>        <span class="hljs-keyword">else</span>:<br>            <span class="hljs-comment"># Token 预算已满,停止选择</span><br>            <span class="hljs-keyword">break</span><br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[ContextBuilder] 选择了 <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(selected)&#125;</span> 个信息包,共 <span class="hljs-subst">&#123;current_tokens&#125;</span> tokens&quot;</span>)<br>    <span class="hljs-keyword">return</span> selected<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">_calculate_relevance</span>(<span class="hljs-params">self, content: <span class="hljs-built_in">str</span>, query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">float</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;计算内容与查询的相关性</span><br><span class="hljs-string"></span><br><span class="hljs-string">    使用简单的关键词重叠算法。在生产环境中,可以替换为向量相似度计算。</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        content: 内容文本</span><br><span class="hljs-string">        query: 查询文本</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        float: 相关性分数(0.0-1.0)</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 分词(简单实现,可以使用更复杂的分词器)</span><br>    content_words = <span class="hljs-built_in">set</span>(content.lower().split())<br>    query_words = <span class="hljs-built_in">set</span>(query.lower().split())<br><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> query_words:<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">0.0</span><br><br>    <span class="hljs-comment"># Jaccard 相似度</span><br>    intersection = content_words &amp; query_words<br>    union = content_words | query_words<br><br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">len</span>(intersection) / <span class="hljs-built_in">len</span>(union) <span class="hljs-keyword">if</span> union <span class="hljs-keyword">else</span> <span class="hljs-number">0.0</span><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">_calculate_recency</span>(<span class="hljs-params">self, timestamp: datetime</span>) -&gt; <span class="hljs-built_in">float</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;计算时间近因性分数</span><br><span class="hljs-string"></span><br><span class="hljs-string">    使用指数衰减模型,24小时内保持高分,之后逐渐衰减。</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        timestamp: 信息的时间戳</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        float: 新近性分数(0.0-1.0)</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">import</span> math<br><br>    age_hours = (datetime.now() - timestamp).total_seconds() / <span class="hljs-number">3600</span><br><br>    <span class="hljs-comment"># 指数衰减:24小时内保持高分,之后逐渐衰减</span><br>    decay_factor = <span class="hljs-number">0.1</span>  <span class="hljs-comment"># 衰减系数</span><br>    recency_score = math.exp(-decay_factor * age_hours / <span class="hljs-number">24</span>)<br><br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">max</span>(<span class="hljs-number">0.1</span>, <span class="hljs-built_in">min</span>(<span class="hljs-number">1.0</span>, recency_score))  <span class="hljs-comment"># 限制在 [0.1, 1.0] 范围内</span><br></code></pre></td></tr></table></figure><p>选择阶段的核心算法体现了几个重要的工程考量：</p><ul><li><strong>评分机制</strong>：采用相关性和新近性的加权组合，权重可配置</li><li><strong>贪心算法</strong>：按分数从高到低填充，确保在有限预算内选择最有价值的信息</li><li><strong>过滤机制</strong>：通过 <code>min_relevance</code> 参数过滤低质量信息</li></ul><p>（3）Structure：结构化输出</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_structure</span>(<span class="hljs-params">self, selected_packets: <span class="hljs-type">List</span>[ContextPacket], user_query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;将选中的信息包组织成结构化的上下文模板</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        selected_packets: 选中的信息包列表</span><br><span class="hljs-string">        user_query: 用户查询</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        str: 结构化的上下文字符串</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 按类型分组</span><br>    system_instructions = []<br>    evidence = []<br>    context = []<br><br>    <span class="hljs-keyword">for</span> packet <span class="hljs-keyword">in</span> selected_packets:<br>        packet_type = packet.metadata.get(<span class="hljs-string">&quot;type&quot;</span>, <span class="hljs-string">&quot;general&quot;</span>)<br><br>        <span class="hljs-keyword">if</span> packet_type == <span class="hljs-string">&quot;system_instruction&quot;</span>:<br>            system_instructions.append(packet.content)<br>        <span class="hljs-keyword">elif</span> packet_type <span class="hljs-keyword">in</span> [<span class="hljs-string">&quot;rag_result&quot;</span>, <span class="hljs-string">&quot;knowledge&quot;</span>]:<br>            evidence.append(packet.content)<br>        <span class="hljs-keyword">else</span>:<br>            context.append(packet.content)<br><br>    <span class="hljs-comment"># 构建结构化模板</span><br>    sections = []<br><br>    <span class="hljs-comment"># [Role &amp; Policies]</span><br>    <span class="hljs-keyword">if</span> system_instructions:<br>        sections.append(<span class="hljs-string">&quot;[Role &amp; Policies]\n&quot;</span> + <span class="hljs-string">&quot;\n&quot;</span>.join(system_instructions))<br><br>    <span class="hljs-comment"># [Task]</span><br>    sections.append(<span class="hljs-string">f&quot;[Task]\n<span class="hljs-subst">&#123;user_query&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># [Evidence]</span><br>    <span class="hljs-keyword">if</span> evidence:<br>        sections.append(<span class="hljs-string">&quot;[Evidence]\n&quot;</span> + <span class="hljs-string">&quot;\n---\n&quot;</span>.join(evidence))<br><br>    <span class="hljs-comment"># [Context]</span><br>    <span class="hljs-keyword">if</span> context:<br>        sections.append(<span class="hljs-string">&quot;[Context]\n&quot;</span> + <span class="hljs-string">&quot;\n&quot;</span>.join(context))<br><br>    <span class="hljs-comment"># [Output]</span><br>    sections.append(<span class="hljs-string">&quot;[Output]\n请基于以上信息,提供准确、有据的回答。&quot;</span>)<br><br>    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;\n\n&quot;</span>.join(sections)<br></code></pre></td></tr></table></figure><p>结构化阶段将散乱的信息包组织成清晰的分区，这种设计有几个优势：</p><ul><li><strong>可读性</strong>：清晰的分区让人类和模型都更容易理解上下文结构</li><li><strong>可调试性</strong>：问题定位更容易，可以快速识别哪个区域的信息有问题</li><li><strong>可扩展性</strong>：添加新的信息源只需要创建新的分区</li></ul><p>（4）Compress：兜底压缩</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_compress</span>(<span class="hljs-params">self, context: <span class="hljs-built_in">str</span>, max_tokens: <span class="hljs-built_in">int</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;压缩超限的上下文</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        context: 原始上下文</span><br><span class="hljs-string">        max_tokens: 最大 token 限制</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        str: 压缩后的上下文</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    current_tokens = self._count_tokens(context)<br><br>    <span class="hljs-keyword">if</span> current_tokens &lt;= max_tokens:<br>        <span class="hljs-keyword">return</span> context  <span class="hljs-comment"># 无需压缩</span><br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[ContextBuilder] 上下文超限(<span class="hljs-subst">&#123;current_tokens&#125;</span> &gt; <span class="hljs-subst">&#123;max_tokens&#125;</span>),执行压缩&quot;</span>)<br><br>    <span class="hljs-comment"># 分区压缩:保持结构完整性</span><br>    sections = context.split(<span class="hljs-string">&quot;\n\n&quot;</span>)<br>    compressed_sections = []<br>    current_total = <span class="hljs-number">0</span><br><br>    <span class="hljs-keyword">for</span> section <span class="hljs-keyword">in</span> sections:<br>        section_tokens = self._count_tokens(section)<br><br>        <span class="hljs-keyword">if</span> current_total + section_tokens &lt;= max_tokens:<br>            <span class="hljs-comment"># 完整保留</span><br>            compressed_sections.append(section)<br>            current_total += section_tokens<br>        <span class="hljs-keyword">else</span>:<br>            <span class="hljs-comment"># 部分保留</span><br>            remaining_tokens = max_tokens - current_total<br>            <span class="hljs-keyword">if</span> remaining_tokens &gt; <span class="hljs-number">50</span>:  <span class="hljs-comment"># 至少保留 50 tokens</span><br>                <span class="hljs-comment"># 简单截断(生产环境中可以使用 LLM 摘要)</span><br>                truncated = self._truncate_text(section, remaining_tokens)<br>                compressed_sections.append(truncated + <span class="hljs-string">&quot;\n[... 内容已压缩 ...]&quot;</span>)<br>            <span class="hljs-keyword">break</span><br><br>    compressed_context = <span class="hljs-string">&quot;\n\n&quot;</span>.join(compressed_sections)<br>    final_tokens = self._count_tokens(compressed_context)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[ContextBuilder] 压缩完成: <span class="hljs-subst">&#123;current_tokens&#125;</span> -&gt; <span class="hljs-subst">&#123;final_tokens&#125;</span> tokens&quot;</span>)<br><br>    <span class="hljs-keyword">return</span> compressed_context<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">_truncate_text</span>(<span class="hljs-params">self, text: <span class="hljs-built_in">str</span>, max_tokens: <span class="hljs-built_in">int</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;截断文本到指定 token 数量</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        text: 原始文本</span><br><span class="hljs-string">        max_tokens: 最大 token 数量</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        str: 截断后的文本</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 简单实现:按字符比例估算</span><br>    <span class="hljs-comment"># 生产环境中应该使用精确的 tokenizer</span><br>    char_per_token = <span class="hljs-built_in">len</span>(text) / self._count_tokens(text) <span class="hljs-keyword">if</span> self._count_tokens(text) &gt; <span class="hljs-number">0</span> <span class="hljs-keyword">else</span> <span class="hljs-number">4</span><br>    max_chars = <span class="hljs-built_in">int</span>(max_tokens * char_per_token)<br><br>    <span class="hljs-keyword">return</span> text[:max_chars]<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">_count_tokens</span>(<span class="hljs-params">self, text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">int</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;估算文本的 token 数量</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        text: 文本内容</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        int: token 数量</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 简单估算:中文 1 字符 ≈ 1 token,英文 1 单词 ≈ 1.3 tokens</span><br>    <span class="hljs-comment"># 生产环境中应该使用实际的 tokenizer</span><br>    chinese_chars = <span class="hljs-built_in">sum</span>(<span class="hljs-number">1</span> <span class="hljs-keyword">for</span> ch <span class="hljs-keyword">in</span> text <span class="hljs-keyword">if</span> <span class="hljs-string">&#x27;\u4e00&#x27;</span> &lt;= ch &lt;= <span class="hljs-string">&#x27;\u9fff&#x27;</span>)<br>    english_words = <span class="hljs-built_in">len</span>([w <span class="hljs-keyword">for</span> w <span class="hljs-keyword">in</span> text.split() <span class="hljs-keyword">if</span> w])<br><br>    <span class="hljs-keyword">return</span> <span class="hljs-built_in">int</span>(chinese_chars + english_words * <span class="hljs-number">1.3</span>)<br></code></pre></td></tr></table></figure><p>压缩阶段的设计体现了”保持结构完整性”的原则，即使在 token 预算紧张的情况下，也要尽量保留每个分区的关键信息。</p><h3 id="9-3-4-完整使用示例"><a href="#9-3-4-完整使用示例" class="headerlink" title="9.3.4 完整使用示例"></a>9.3.4 完整使用示例</h3><p>现在让我们通过一个完整的示例，展示如何在实际项目中使用 ContextBuilder。</p><p>（1）基础使用</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.context <span class="hljs-keyword">import</span> ContextBuilder, ContextConfig<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MemoryTool, RAGTool<br><span class="hljs-keyword">from</span> hello_agents.core.message <span class="hljs-keyword">import</span> Message<br><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><br><span class="hljs-comment"># 1. 初始化工具</span><br>memory_tool = MemoryTool(user_id=<span class="hljs-string">&quot;user123&quot;</span>)<br>rag_tool = RAGTool(knowledge_base_path=<span class="hljs-string">&quot;./knowledge_base&quot;</span>)<br><br><span class="hljs-comment"># 2. 创建 ContextBuilder</span><br>config = ContextConfig(<br>    max_tokens=<span class="hljs-number">3000</span>,<br>    reserve_ratio=<span class="hljs-number">0.2</span>,<br>    min_relevance=<span class="hljs-number">0.2</span>,<br>    enable_compression=<span class="hljs-literal">True</span><br>)<br><br>builder = ContextBuilder(<br>    memory_tool=memory_tool,<br>    rag_tool=rag_tool,<br>    config=config<br>)<br><br><span class="hljs-comment"># 3. 准备对话历史</span><br>conversation_history = [<br>    Message(content=<span class="hljs-string">&quot;我正在开发一个数据分析工具&quot;</span>, role=<span class="hljs-string">&quot;user&quot;</span>, timestamp=datetime.now()),<br>    Message(content=<span class="hljs-string">&quot;很好!数据分析工具通常需要处理大量数据。您计划使用什么技术栈?&quot;</span>, role=<span class="hljs-string">&quot;assistant&quot;</span>, timestamp=datetime.now()),<br>    Message(content=<span class="hljs-string">&quot;我打算使用Python和Pandas,已经完成了CSV读取模块&quot;</span>, role=<span class="hljs-string">&quot;user&quot;</span>, timestamp=datetime.now()),<br>    Message(content=<span class="hljs-string">&quot;不错的选择!Pandas在数据处理方面非常强大。接下来您可能需要考虑数据清洗和转换。&quot;</span>, role=<span class="hljs-string">&quot;assistant&quot;</span>, timestamp=datetime.now()),<br>]<br><br><span class="hljs-comment"># 4. 添加一些记忆</span><br>memory_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;add&quot;</span>,<br>    <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;用户正在开发数据分析工具,使用Python和Pandas&quot;</span>,<br>    <span class="hljs-string">&quot;memory_type&quot;</span>: <span class="hljs-string">&quot;semantic&quot;</span>,<br>    <span class="hljs-string">&quot;importance&quot;</span>: <span class="hljs-number">0.8</span><br>&#125;)<br><br>memory_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;add&quot;</span>,<br>    <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;已完成CSV读取模块的开发&quot;</span>,<br>    <span class="hljs-string">&quot;memory_type&quot;</span>: <span class="hljs-string">&quot;episodic&quot;</span>,<br>    <span class="hljs-string">&quot;importance&quot;</span>: <span class="hljs-number">0.7</span><br>&#125;)<br><br><span class="hljs-comment"># 5. 构建上下文</span><br>context = builder.build(<br>    user_query=<span class="hljs-string">&quot;如何优化Pandas的内存占用?&quot;</span>,<br>    conversation_history=conversation_history,<br>    system_instructions=<span class="hljs-string">&quot;你是一位资深的Python数据工程顾问。你的回答需要:1) 提供具体可行的建议 2) 解释技术原理 3) 给出代码示例&quot;</span><br>)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">80</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;构建的上下文:&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">80</span>)<br><span class="hljs-built_in">print</span>(context)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span> * <span class="hljs-number">80</span>)<br></code></pre></td></tr></table></figure><p>（2）运行效果展示</p><p>运行上述代码后，您将看到如下结构化的上下文输出：</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><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><code class="hljs asciidoc">================================================================================<br>构建的上下文:<br>================================================================================<br><span class="hljs-meta">[Role &amp; Policies]</span><br>你是一位资深的Python数据工程顾问。你的回答需要:1) 提供具体可行的建议 2) 解释技术原理 3) 给出代码示例<br><br><span class="hljs-meta">[Task]</span><br>如何优化Pandas的内存占用?<br><br><span class="hljs-meta">[Evidence]</span><br>Pandas内存优化的核心策略包括:<br>1. 使用合适的数据类型(如category代替object)<br>2. 分块读取大文件<br><span class="hljs-section">3. 使用 chunksize 参数</span><br><span class="hljs-section">---</span><br>数据类型优化可以显著减少内存占用。例如,将int64降级为int32可以节省50%的内存。<br><br><span class="hljs-meta">[Context]</span><br>user: 我正在开发一个数据分析工具<br>assistant: 很好!数据分析工具通常需要处理大量数据。您计划使用什么技术栈?<br>user: 我打算使用Python和Pandas,已经完成了CSV读取模块<br>assistant: 不错的选择!Pandas在数据处理方面非常强大。接下来您可能需要考虑数据清洗和转换。<br>记忆: 用户正在开发数据分析工具,使用Python和Pandas<br>记忆: 已完成CSV读取模块的开发<br><br><span class="hljs-meta">[Output]</span><br><span class="hljs-section">请基于以上信息,提供准确、有据的回答。</span><br><span class="hljs-section">================================================================================</span><br></code></pre></td></tr></table></figure><p>这个结构化的上下文包含了所有必要的信息：</p><ul><li><strong>[Role &amp; Policies]</strong>：明确了 AI 的角色和回答要求</li><li><strong>[Task]</strong>：清晰地表达了用户的问题</li><li><strong>[Evidence]</strong>：从 RAG 系统检索的相关知识</li><li><strong>[Context]</strong>：对话历史和相关记忆，提供了充分的背景信息</li><li><strong>[Output]</strong>：指导 LLM 如何组织回答</li></ul><p>（3）与 Agent 集成</p><p>最后，让我们展示如何将 ContextBuilder 集成到 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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM, ToolRegistry<br><span class="hljs-keyword">from</span> hello_agents.context <span class="hljs-keyword">import</span> ContextBuilder, ContextConfig<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MemoryTool, RAGTool<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ContextAwareAgent</span>(<span class="hljs-title class_ inherited__">SimpleAgent</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;具有上下文感知能力的 Agent&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, name: <span class="hljs-built_in">str</span>, llm: HelloAgentsLLM, **kwargs</span>):<br>        <span class="hljs-built_in">super</span>().__init__(name=name, llm=llm, system_prompt=kwargs.get(<span class="hljs-string">&quot;system_prompt&quot;</span>, <span class="hljs-string">&quot;&quot;</span>))<br><br>        <span class="hljs-comment"># 初始化上下文构建器</span><br>        self.memory_tool = MemoryTool(user_id=kwargs.get(<span class="hljs-string">&quot;user_id&quot;</span>, <span class="hljs-string">&quot;default&quot;</span>))<br>        self.rag_tool = RAGTool(knowledge_base_path=kwargs.get(<span class="hljs-string">&quot;knowledge_base_path&quot;</span>, <span class="hljs-string">&quot;./kb&quot;</span>))<br><br>        self.context_builder = ContextBuilder(<br>            memory_tool=self.memory_tool,<br>            rag_tool=self.rag_tool,<br>            config=ContextConfig(max_tokens=<span class="hljs-number">4000</span>)<br>        )<br><br>        self.conversation_history = []<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, user_input: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;运行 Agent,自动构建优化的上下文&quot;&quot;&quot;</span><br><br>        <span class="hljs-comment"># 1. 使用 ContextBuilder 构建优化的上下文</span><br>        optimized_context = self.context_builder.build(<br>            user_query=user_input,<br>            conversation_history=self.conversation_history,<br>            system_instructions=self.system_prompt<br>        )<br><br>        <span class="hljs-comment"># 2. 使用优化后的上下文调用 LLM</span><br>        messages = [<br>            &#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;system&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: optimized_context&#125;,<br>            &#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: user_input&#125;<br>        ]<br>        response = self.llm.invoke(messages)<br><br>        <span class="hljs-comment"># 3. 更新对话历史</span><br>        <span class="hljs-keyword">from</span> hello_agents.core.message <span class="hljs-keyword">import</span> Message<br>        <span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><br>        self.conversation_history.append(<br>            Message(content=user_input, role=<span class="hljs-string">&quot;user&quot;</span>, timestamp=datetime.now())<br>        )<br>        self.conversation_history.append(<br>            Message(content=response, role=<span class="hljs-string">&quot;assistant&quot;</span>, timestamp=datetime.now())<br>        )<br><br>        <span class="hljs-comment"># 4. 将重要交互记录到记忆系统</span><br>        self.memory_tool.run(&#123;<br>            <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;add&quot;</span>,<br>            <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">f&quot;Q: <span class="hljs-subst">&#123;user_input&#125;</span>\nA: <span class="hljs-subst">&#123;response[:<span class="hljs-number">200</span>]&#125;</span>...&quot;</span>,  <span class="hljs-comment"># 摘要</span><br>            <span class="hljs-string">&quot;memory_type&quot;</span>: <span class="hljs-string">&quot;episodic&quot;</span>,<br>            <span class="hljs-string">&quot;importance&quot;</span>: <span class="hljs-number">0.6</span><br>        &#125;)<br><br>        <span class="hljs-keyword">return</span> response<br><br><span class="hljs-comment"># 使用示例</span><br>agent = ContextAwareAgent(<br>    name=<span class="hljs-string">&quot;数据分析顾问&quot;</span>,<br>    llm=HelloAgentsLLM(),<br>    system_prompt=<span class="hljs-string">&quot;你是一位资深的Python数据工程顾问。&quot;</span>,<br>    user_id=<span class="hljs-string">&quot;user123&quot;</span>,<br>    knowledge_base_path=<span class="hljs-string">&quot;./data_science_kb&quot;</span><br>)<br><br>response = agent.run(<span class="hljs-string">&quot;如何优化Pandas的内存占用?&quot;</span>)<br><span class="hljs-built_in">print</span>(response)<br></code></pre></td></tr></table></figure><p>通过这种方式，ContextBuilder 成为了 Agent 的”上下文管理大脑”，自动处理信息的收集、筛选和组织，让 Agent 始终能够在最优的上下文下进行推理和生成。</p><h3 id="9-3-5-最佳实践与优化建议"><a href="#9-3-5-最佳实践与优化建议" class="headerlink" title="9.3.5 最佳实践与优化建议"></a>9.3.5 最佳实践与优化建议</h3><p>在实际应用 ContextBuilder 时，以下几点最佳实践值得注意：</p><ol><li><p><strong>动态调整 token 预算</strong>：根据任务复杂度动态调整 <code>max_tokens</code>，简单任务使用较小预算，复杂任务增加预算。</p></li><li><p><strong>相关性计算优化</strong>：在生产环境中，将简单的关键词重叠替换为向量相似度计算，提升检索质量。</p></li><li><p><strong>缓存机制</strong>：对于不变的系统指令和知识库内容，可以实现缓存机制，避免重复计算。</p></li><li><p><strong>监控与日志</strong>：记录每次上下文构建的统计信息(选中信息数量、token 使用率等)，便于后续优化。</p></li><li><p><strong>A&#x2F;B 测试</strong>：对于关键参数(如相关性权重、新近性权重)，通过 A&#x2F;B 测试找到最优配置。</p></li></ol><h2 id="9-4-NoteTool：结构化笔记"><a href="#9-4-NoteTool：结构化笔记" class="headerlink" title="9.4 NoteTool：结构化笔记"></a>9.4 NoteTool：结构化笔记</h2><p>NoteTool 是为”长时程任务”提供的结构化外部记忆组件。它以 Markdown 文件作为载体，头部使用 YAML 前置元数据记录关键信息，正文用于记录状态、结论、阻塞与行动项等内容。这种设计结合了人类可读性、版本控制友好性和易于回注上下文的特性，是构建长时程智能体的重要工具。</p><h3 id="9-4-1-设计理念与应用场景"><a href="#9-4-1-设计理念与应用场景" class="headerlink" title="9.4.1 设计理念与应用场景"></a>9.4.1 设计理念与应用场景</h3><p>在深入实现细节之前，让我们首先理解 NoteTool 的设计理念和典型应用场景。</p><p>（1）为什么需要 NoteTool?</p><p>在第八章中，我们介绍了 MemoryTool，它提供了强大的记忆管理能力。然而，MemoryTool 主要关注<strong>对话式记忆</strong>——短期工作记忆、情景记忆和语义记忆。对于需要长期追踪、结构化管理的<strong>项目式任务</strong>，我们需要一种更轻量、更人类友好的记录方式。</p><p>NoteTool 填补了这个gap，它提供了：</p><ul><li><strong>结构化记录</strong>：使用 Markdown + YAML 格式，既适合机器解析，也方便人类阅读和编辑</li><li><strong>版本友好</strong>：纯文本格式，天然支持 Git 等版本控制系统</li><li><strong>低开销</strong>：无需复杂的数据库操作，适合轻量级的状态追踪</li><li><strong>灵活分类</strong>：通过 <code>type</code> 和 <code>tags</code> 灵活组织笔记，支持多维度检索</li></ul><p>（2）典型应用场景</p><p>NoteTool 特别适合以下场景：</p><p><strong>场景1：长期项目追踪</strong></p><p>想象一个智能体正在协助完成一个大型代码库的重构任务，这可能需要几天甚至几周。NoteTool 可以记录：</p><ul><li><code>task_state</code>：当前阶段的任务状态和进度</li><li><code>conclusion</code>：每个阶段结束后的关键结论</li><li><code>blocker</code>：遇到的问题和阻塞点</li><li><code>action</code>：下一步的行动计划</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 记录任务状态</span><br>notes.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create&quot;</span>,<br>    <span class="hljs-string">&quot;title&quot;</span>: <span class="hljs-string">&quot;重构项目 - 第一阶段&quot;</span>,<br>    <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;已完成数据模型层的重构,测试覆盖率达到85%。下一步将重构业务逻辑层。&quot;</span>,<br>    <span class="hljs-string">&quot;note_type&quot;</span>: <span class="hljs-string">&quot;task_state&quot;</span>,<br>    <span class="hljs-string">&quot;tags&quot;</span>: [<span class="hljs-string">&quot;refactoring&quot;</span>, <span class="hljs-string">&quot;phase1&quot;</span>]<br>&#125;)<br><br><span class="hljs-comment"># 记录阻塞点</span><br>notes.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create&quot;</span>,<br>    <span class="hljs-string">&quot;title&quot;</span>: <span class="hljs-string">&quot;依赖冲突问题&quot;</span>,<br>    <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;发现某些第三方库版本不兼容,需要解决。影响范围:业务逻辑层的3个模块。&quot;</span>,<br>    <span class="hljs-string">&quot;note_type&quot;</span>: <span class="hljs-string">&quot;blocker&quot;</span>,<br>    <span class="hljs-string">&quot;tags&quot;</span>: [<span class="hljs-string">&quot;dependency&quot;</span>, <span class="hljs-string">&quot;urgent&quot;</span>]<br>&#125;)<br></code></pre></td></tr></table></figure><p><strong>场景2：研究任务管理</strong></p><p>一个智能研究助手在进行文献综述时，可以使用 NoteTool 记录：</p><ul><li>每篇论文的核心观点(<code>conclusion</code>)</li><li>待深入调研的主题(<code>action</code>)</li><li>重要的参考文献(<code>reference</code>)</li></ul><p><strong>场景3：与 ContextBuilder 配合</strong></p><p>在每轮对话前，Agent 可以通过 <code>search</code> 或 <code>list</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><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><code class="hljs python"><span class="hljs-comment"># 在 Agent 的 run 方法中</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, user_input: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-comment"># 1. 检索相关笔记</span><br>    relevant_notes = self.note_tool.run(&#123;<br>        <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;search&quot;</span>,<br>        <span class="hljs-string">&quot;query&quot;</span>: user_input,<br>        <span class="hljs-string">&quot;limit&quot;</span>: <span class="hljs-number">3</span><br>    &#125;)<br><br>    <span class="hljs-comment"># 2. 将笔记内容转换为 ContextPacket</span><br>    note_packets = []<br>    <span class="hljs-keyword">for</span> note <span class="hljs-keyword">in</span> relevant_notes:<br>        note_packets.append(ContextPacket(<br>            content=note[<span class="hljs-string">&#x27;content&#x27;</span>],<br>            timestamp=note[<span class="hljs-string">&#x27;updated_at&#x27;</span>],<br>            token_count=self._count_tokens(note[<span class="hljs-string">&#x27;content&#x27;</span>]),<br>            relevance_score=<span class="hljs-number">0.7</span>,<br>            metadata=&#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;note&quot;</span>, <span class="hljs-string">&quot;note_type&quot;</span>: note[<span class="hljs-string">&#x27;type&#x27;</span>]&#125;<br>        ))<br><br>    <span class="hljs-comment"># 3. 构建上下文时传入笔记</span><br>    context = self.context_builder.build(<br>        user_query=user_input,<br>        custom_packets=note_packets,<br>        ...<br>    )<br></code></pre></td></tr></table></figure><h3 id="9-4-2-存储格式详解"><a href="#9-4-2-存储格式详解" class="headerlink" title="9.4.2 存储格式详解"></a>9.4.2 存储格式详解</h3><p>NoteTool 采用了 Markdown + YAML 的混合格式，这种设计兼顾了结构化和可读性。</p><p>（1）笔记文件格式</p><p>每个笔记都是一个独立的 <code>.md</code> 文件，格式如下：</p><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><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><code class="hljs markdown">---<br>id: note<span class="hljs-emphasis">_20250119_</span>153000<span class="hljs-emphasis">_0</span><br><span class="hljs-emphasis">title: 项目进展 - 第一阶段</span><br><span class="hljs-emphasis">type: task_</span>state<br>tags: [refactoring, phase1, backend]<br>created<span class="hljs-emphasis">_at: 2025-01-19T15:30:00</span><br><span class="hljs-emphasis">updated_</span>at: 2025-01-19T15:30:00<br>---<br><br><span class="hljs-section"># 项目进展 - 第一阶段</span><br><br><span class="hljs-section">## 完成情况</span><br><br>已完成数据模型层的重构,主要改动包括:<br><br><span class="hljs-bullet">1.</span> 统一了实体类的命名规范<br><span class="hljs-bullet">2.</span> 引入了类型提示,提升代码可维护性<br><span class="hljs-bullet">3.</span> 优化了数据库查询性能<br><br><span class="hljs-section">## 测试覆盖</span><br><br><span class="hljs-bullet">-</span> 单元测试覆盖率: 85%<br><span class="hljs-bullet">-</span> 集成测试覆盖率: 70%<br><br><span class="hljs-section">## 下一步计划</span><br><br><span class="hljs-bullet">1.</span> 重构业务逻辑层<br><span class="hljs-bullet">2.</span> 解决依赖冲突问题<br><span class="hljs-bullet">3.</span> 提升集成测试覆盖率至85%<br></code></pre></td></tr></table></figure><p>这种格式的优势：</p><ul><li><strong>YAML 元数据</strong>：机器可解析，支持精确的字段提取和检索</li><li><strong>Markdown 正文</strong>：人类可读，支持丰富的格式化(标题、列表、代码块等)</li><li><strong>文件名即 ID</strong>：简化管理，每个笔记的文件名就是其唯一标识</li></ul><p>（2）索引文件</p><p>NoteTool 维护一个 <code>notes_index.json</code> 文件，用于快速检索和管理笔记：</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;note_20250119_153000_0&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;note_20250119_153000_0&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;title&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;项目进展 - 第一阶段&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;task_state&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;tags&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;refactoring&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;phase1&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;backend&quot;</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;created_at&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-01-19T15:30:00&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;updated_at&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-01-19T15:30:00&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;file_path&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;./notes/note_20250119_153000_0.md&quot;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>这个索引文件的作用：</p><ul><li><strong>快速检索</strong>：无需打开每个文件，直接从索引中查找</li><li><strong>元数据管理</strong>：集中管理所有笔记的元数据</li><li><strong>完整性校验</strong>：可以检测文件缺失或损坏</li></ul><h3 id="9-4-3-核心操作详解"><a href="#9-4-3-核心操作详解" class="headerlink" title="9.4.3 核心操作详解"></a>9.4.3 核心操作详解</h3><p>NoteTool 提供了七个核心操作，覆盖了笔记的完整生命周期管理。</p><p>（1）create：创建笔记</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_create_note</span>(<span class="hljs-params"></span><br><span class="hljs-params">    self,</span><br><span class="hljs-params">    title: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">    content: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">    note_type: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;general&quot;</span>,</span><br><span class="hljs-params">    tags: <span class="hljs-type">Optional</span>[<span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>]] = <span class="hljs-literal">None</span></span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;创建笔记</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        title: 笔记标题</span><br><span class="hljs-string">        content: 笔记内容(Markdown格式)</span><br><span class="hljs-string">        note_type: 笔记类型(task_state/conclusion/blocker/action/reference/general)</span><br><span class="hljs-string">        tags: 标签列表</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        str: 笔记ID</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><br>    <span class="hljs-comment"># 1. 生成唯一ID</span><br>    timestamp = datetime.now().strftime(<span class="hljs-string">&#x27;%Y%m%d_%H%M%S&#x27;</span>)<br>    note_id = <span class="hljs-string">f&quot;note_<span class="hljs-subst">&#123;timestamp&#125;</span>_<span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(self.index)&#125;</span>&quot;</span><br><br>    <span class="hljs-comment"># 2. 构建元数据</span><br>    metadata = &#123;<br>        <span class="hljs-string">&quot;id&quot;</span>: note_id,<br>        <span class="hljs-string">&quot;title&quot;</span>: title,<br>        <span class="hljs-string">&quot;type&quot;</span>: note_type,<br>        <span class="hljs-string">&quot;tags&quot;</span>: tags <span class="hljs-keyword">or</span> [],<br>        <span class="hljs-string">&quot;created_at&quot;</span>: datetime.now().isoformat(),<br>        <span class="hljs-string">&quot;updated_at&quot;</span>: datetime.now().isoformat()<br>    &#125;<br><br>    <span class="hljs-comment"># 3. 构建完整的 Markdown 文件内容</span><br>    md_content = self._build_markdown(metadata, content)<br><br>    <span class="hljs-comment"># 4. 保存到文件</span><br>    file_path = os.path.join(self.workspace, <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;note_id&#125;</span>.md&quot;</span>)<br>    <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(file_path, <span class="hljs-string">&#x27;w&#x27;</span>, encoding=<span class="hljs-string">&#x27;utf-8&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>        f.write(md_content)<br><br>    <span class="hljs-comment"># 5. 更新索引</span><br>    metadata[<span class="hljs-string">&quot;file_path&quot;</span>] = file_path<br>    self.index[note_id] = metadata<br>    self._save_index()<br><br>    <span class="hljs-keyword">return</span> note_id<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">_build_markdown</span>(<span class="hljs-params">self, metadata: <span class="hljs-type">Dict</span>, content: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;构建 Markdown 文件内容(YAML + 正文)&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">import</span> yaml<br><br>    <span class="hljs-comment"># YAML 前置元数据</span><br>    yaml_header = yaml.dump(metadata, allow_unicode=<span class="hljs-literal">True</span>, sort_keys=<span class="hljs-literal">False</span>)<br><br>    <span class="hljs-comment"># 组合格式</span><br>    <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;---\n<span class="hljs-subst">&#123;yaml_header&#125;</span>---\n\n<span class="hljs-subst">&#123;content&#125;</span>&quot;</span><br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> NoteTool<br><br>notes = NoteTool(workspace=<span class="hljs-string">&quot;./project_notes&quot;</span>)<br><br>note_id = notes.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create&quot;</span>,<br>    <span class="hljs-string">&quot;title&quot;</span>: <span class="hljs-string">&quot;重构项目 - 第一阶段&quot;</span>,<br>    <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;&quot;&quot;## 完成情况</span><br><span class="hljs-string">已完成数据模型层的重构,测试覆盖率达到85%。</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 下一步</span><br><span class="hljs-string">重构业务逻辑层&quot;&quot;&quot;</span>,<br>    <span class="hljs-string">&quot;note_type&quot;</span>: <span class="hljs-string">&quot;task_state&quot;</span>,<br>    <span class="hljs-string">&quot;tags&quot;</span>: [<span class="hljs-string">&quot;refactoring&quot;</span>, <span class="hljs-string">&quot;phase1&quot;</span>]<br>&#125;)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 笔记创建成功,ID: <span class="hljs-subst">&#123;note_id&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>（2）read：读取笔记</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_read_note</span>(<span class="hljs-params">self, note_id: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-type">Dict</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;读取笔记内容</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        note_id: 笔记ID</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        Dict: 包含元数据和内容的字典</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> note_id <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.index:<br>        <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f&quot;笔记不存在: <span class="hljs-subst">&#123;note_id&#125;</span>&quot;</span>)<br><br>    file_path = self.index[note_id][<span class="hljs-string">&quot;file_path&quot;</span>]<br><br>    <span class="hljs-comment"># 读取文件</span><br>    <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(file_path, <span class="hljs-string">&#x27;r&#x27;</span>, encoding=<span class="hljs-string">&#x27;utf-8&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>        raw_content = f.read()<br><br>    <span class="hljs-comment"># 解析 YAML 元数据和 Markdown 正文</span><br>    metadata, content = self._parse_markdown(raw_content)<br><br>    <span class="hljs-keyword">return</span> &#123;<br>        <span class="hljs-string">&quot;metadata&quot;</span>: metadata,<br>        <span class="hljs-string">&quot;content&quot;</span>: content<br>    &#125;<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">_parse_markdown</span>(<span class="hljs-params">self, raw_content: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-type">Tuple</span>[<span class="hljs-type">Dict</span>, <span class="hljs-built_in">str</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;解析 Markdown 文件(分离 YAML 和正文)&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">import</span> yaml<br><br>    <span class="hljs-comment"># 查找 YAML 分隔符</span><br>    parts = raw_content.split(<span class="hljs-string">&#x27;---\n&#x27;</span>, <span class="hljs-number">2</span>)<br><br>    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(parts) &gt;= <span class="hljs-number">3</span>:<br>        <span class="hljs-comment"># 有 YAML 前置元数据</span><br>        yaml_str = parts[<span class="hljs-number">1</span>]<br>        content = parts[<span class="hljs-number">2</span>].strip()<br>        metadata = yaml.safe_load(yaml_str)<br>    <span class="hljs-keyword">else</span>:<br>        <span class="hljs-comment"># 无元数据,全部作为正文</span><br>        metadata = &#123;&#125;<br>        content = raw_content.strip()<br><br>    <span class="hljs-keyword">return</span> metadata, content<br></code></pre></td></tr></table></figure><p>（3）update：更新笔记</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_update_note</span>(<span class="hljs-params"></span><br><span class="hljs-params">    self,</span><br><span class="hljs-params">    note_id: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">    title: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    content: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    note_type: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    tags: <span class="hljs-type">Optional</span>[<span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>]] = <span class="hljs-literal">None</span></span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;更新笔记</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        note_id: 笔记ID</span><br><span class="hljs-string">        title: 新标题(可选)</span><br><span class="hljs-string">        content: 新内容(可选)</span><br><span class="hljs-string">        note_type: 新类型(可选)</span><br><span class="hljs-string">        tags: 新标签(可选)</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        str: 操作结果消息</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> note_id <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.index:<br>        <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f&quot;笔记不存在: <span class="hljs-subst">&#123;note_id&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 1. 读取现有笔记</span><br>    note = self._read_note(note_id)<br>    metadata = note[<span class="hljs-string">&quot;metadata&quot;</span>]<br>    old_content = note[<span class="hljs-string">&quot;content&quot;</span>]<br><br>    <span class="hljs-comment"># 2. 更新字段</span><br>    <span class="hljs-keyword">if</span> title:<br>        metadata[<span class="hljs-string">&quot;title&quot;</span>] = title<br>    <span class="hljs-keyword">if</span> note_type:<br>        metadata[<span class="hljs-string">&quot;type&quot;</span>] = note_type<br>    <span class="hljs-keyword">if</span> tags <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:<br>        metadata[<span class="hljs-string">&quot;tags&quot;</span>] = tags<br>    <span class="hljs-keyword">if</span> content <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:<br>        old_content = content<br><br>    <span class="hljs-comment"># 更新时间戳</span><br>    <span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br>    metadata[<span class="hljs-string">&quot;updated_at&quot;</span>] = datetime.now().isoformat()<br><br>    <span class="hljs-comment"># 3. 重新构建并保存</span><br>    md_content = self._build_markdown(metadata, old_content)<br>    file_path = metadata[<span class="hljs-string">&quot;file_path&quot;</span>]<br><br>    <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(file_path, <span class="hljs-string">&#x27;w&#x27;</span>, encoding=<span class="hljs-string">&#x27;utf-8&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>        f.write(md_content)<br><br>    <span class="hljs-comment"># 4. 更新索引</span><br>    self.index[note_id] = metadata<br>    self._save_index()<br><br>    <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;✅ 笔记已更新: <span class="hljs-subst">&#123;metadata[<span class="hljs-string">&#x27;title&#x27;</span>]&#125;</span>&quot;</span><br></code></pre></td></tr></table></figure><p>（4）search：搜索笔记</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_search_notes</span>(<span class="hljs-params"></span><br><span class="hljs-params">    self,</span><br><span class="hljs-params">    query: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">    limit: <span class="hljs-built_in">int</span> = <span class="hljs-number">10</span>,</span><br><span class="hljs-params">    note_type: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    tags: <span class="hljs-type">Optional</span>[<span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>]] = <span class="hljs-literal">None</span></span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;搜索笔记</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        query: 搜索关键词</span><br><span class="hljs-string">        limit: 返回数量限制</span><br><span class="hljs-string">        note_type: 按类型过滤(可选)</span><br><span class="hljs-string">        tags: 按标签过滤(可选)</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        List[Dict]: 匹配的笔记列表</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    results = []<br>    query_lower = query.lower()<br><br>    <span class="hljs-keyword">for</span> note_id, metadata <span class="hljs-keyword">in</span> self.index.items():<br>        <span class="hljs-comment"># 类型过滤</span><br>        <span class="hljs-keyword">if</span> note_type <span class="hljs-keyword">and</span> metadata.get(<span class="hljs-string">&quot;type&quot;</span>) != note_type:<br>            <span class="hljs-keyword">continue</span><br><br>        <span class="hljs-comment"># 标签过滤</span><br>        <span class="hljs-keyword">if</span> tags:<br>            note_tags = <span class="hljs-built_in">set</span>(metadata.get(<span class="hljs-string">&quot;tags&quot;</span>, []))<br>            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> note_tags.intersection(tags):<br>                <span class="hljs-keyword">continue</span><br><br>        <span class="hljs-comment"># 读取笔记内容</span><br>        <span class="hljs-keyword">try</span>:<br>            note = self._read_note(note_id)<br>            content = note[<span class="hljs-string">&quot;content&quot;</span>]<br>            title = metadata.get(<span class="hljs-string">&quot;title&quot;</span>, <span class="hljs-string">&quot;&quot;</span>)<br><br>            <span class="hljs-comment"># 在标题和内容中搜索</span><br>            <span class="hljs-keyword">if</span> query_lower <span class="hljs-keyword">in</span> title.lower() <span class="hljs-keyword">or</span> query_lower <span class="hljs-keyword">in</span> content.lower():<br>                results.append(&#123;<br>                    <span class="hljs-string">&quot;note_id&quot;</span>: note_id,<br>                    <span class="hljs-string">&quot;title&quot;</span>: title,<br>                    <span class="hljs-string">&quot;type&quot;</span>: metadata.get(<span class="hljs-string">&quot;type&quot;</span>),<br>                    <span class="hljs-string">&quot;tags&quot;</span>: metadata.get(<span class="hljs-string">&quot;tags&quot;</span>, []),<br>                    <span class="hljs-string">&quot;content&quot;</span>: content,<br>                    <span class="hljs-string">&quot;updated_at&quot;</span>: metadata.get(<span class="hljs-string">&quot;updated_at&quot;</span>)<br>                &#125;)<br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[WARNING] 读取笔记 <span class="hljs-subst">&#123;note_id&#125;</span> 失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>            <span class="hljs-keyword">continue</span><br><br>    <span class="hljs-comment"># 按更新时间排序</span><br>    results.sort(key=<span class="hljs-keyword">lambda</span> x: x[<span class="hljs-string">&quot;updated_at&quot;</span>], reverse=<span class="hljs-literal">True</span>)<br><br>    <span class="hljs-keyword">return</span> results[:limit]<br></code></pre></td></tr></table></figure><p>（5）list：列出笔记</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_list_notes</span>(<span class="hljs-params"></span><br><span class="hljs-params">    self,</span><br><span class="hljs-params">    note_type: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    tags: <span class="hljs-type">Optional</span>[<span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>]] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    limit: <span class="hljs-built_in">int</span> = <span class="hljs-number">20</span></span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;列出笔记(按更新时间倒序)</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        note_type: 按类型过滤(可选)</span><br><span class="hljs-string">        tags: 按标签过滤(可选)</span><br><span class="hljs-string">        limit: 返回数量限制</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        List[Dict]: 笔记元数据列表</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    results = []<br><br>    <span class="hljs-keyword">for</span> note_id, metadata <span class="hljs-keyword">in</span> self.index.items():<br>        <span class="hljs-comment"># 类型过滤</span><br>        <span class="hljs-keyword">if</span> note_type <span class="hljs-keyword">and</span> metadata.get(<span class="hljs-string">&quot;type&quot;</span>) != note_type:<br>            <span class="hljs-keyword">continue</span><br><br>        <span class="hljs-comment"># 标签过滤</span><br>        <span class="hljs-keyword">if</span> tags:<br>            note_tags = <span class="hljs-built_in">set</span>(metadata.get(<span class="hljs-string">&quot;tags&quot;</span>, []))<br>            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> note_tags.intersection(tags):<br>                <span class="hljs-keyword">continue</span><br><br>        results.append(metadata)<br><br>    <span class="hljs-comment"># 按更新时间排序</span><br>    results.sort(key=<span class="hljs-keyword">lambda</span> x: x.get(<span class="hljs-string">&quot;updated_at&quot;</span>, <span class="hljs-string">&quot;&quot;</span>), reverse=<span class="hljs-literal">True</span>)<br><br>    <span class="hljs-keyword">return</span> results[:limit]<br></code></pre></td></tr></table></figure><p>（6）summary：笔记摘要</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_summary</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;生成笔记摘要统计</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        Dict: 统计信息</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    total_count = <span class="hljs-built_in">len</span>(self.index)<br><br>    <span class="hljs-comment"># 按类型统计</span><br>    type_counts = &#123;&#125;<br>    <span class="hljs-keyword">for</span> metadata <span class="hljs-keyword">in</span> self.index.values():<br>        note_type = metadata.get(<span class="hljs-string">&quot;type&quot;</span>, <span class="hljs-string">&quot;general&quot;</span>)<br>        type_counts[note_type] = type_counts.get(note_type, <span class="hljs-number">0</span>) + <span class="hljs-number">1</span><br><br>    <span class="hljs-comment"># 最近更新的笔记</span><br>    recent_notes = <span class="hljs-built_in">sorted</span>(<br>        self.index.values(),<br>        key=<span class="hljs-keyword">lambda</span> x: x.get(<span class="hljs-string">&quot;updated_at&quot;</span>, <span class="hljs-string">&quot;&quot;</span>),<br>        reverse=<span class="hljs-literal">True</span><br>    )[:<span class="hljs-number">5</span>]<br><br>    <span class="hljs-keyword">return</span> &#123;<br>        <span class="hljs-string">&quot;total_notes&quot;</span>: total_count,<br>        <span class="hljs-string">&quot;type_distribution&quot;</span>: type_counts,<br>        <span class="hljs-string">&quot;recent_notes&quot;</span>: [<br>            &#123;<br>                <span class="hljs-string">&quot;id&quot;</span>: note[<span class="hljs-string">&quot;id&quot;</span>],<br>                <span class="hljs-string">&quot;title&quot;</span>: note.get(<span class="hljs-string">&quot;title&quot;</span>, <span class="hljs-string">&quot;&quot;</span>),<br>                <span class="hljs-string">&quot;type&quot;</span>: note.get(<span class="hljs-string">&quot;type&quot;</span>),<br>                <span class="hljs-string">&quot;updated_at&quot;</span>: note.get(<span class="hljs-string">&quot;updated_at&quot;</span>)<br>            &#125;<br>            <span class="hljs-keyword">for</span> note <span class="hljs-keyword">in</span> recent_notes<br>        ]<br>    &#125;<br></code></pre></td></tr></table></figure><p>（7）delete：删除笔记</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_delete_note</span>(<span class="hljs-params">self, note_id: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;删除笔记</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        note_id: 笔记ID</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        str: 操作结果消息</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> note_id <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.index:<br>        <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f&quot;笔记不存在: <span class="hljs-subst">&#123;note_id&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 1. 删除文件</span><br>    file_path = self.index[note_id][<span class="hljs-string">&quot;file_path&quot;</span>]<br>    <span class="hljs-keyword">if</span> os.path.exists(file_path):<br>        os.remove(file_path)<br><br>    <span class="hljs-comment"># 2. 从索引中移除</span><br>    title = self.index[note_id].get(<span class="hljs-string">&quot;title&quot;</span>, note_id)<br>    <span class="hljs-keyword">del</span> self.index[note_id]<br>    self._save_index()<br><br>    <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;✅ 笔记已删除: <span class="hljs-subst">&#123;title&#125;</span>&quot;</span><br></code></pre></td></tr></table></figure><h3 id="9-4-4-与-ContextBuilder-的深度集成"><a href="#9-4-4-与-ContextBuilder-的深度集成" class="headerlink" title="9.4.4 与 ContextBuilder 的深度集成"></a>9.4.4 与 ContextBuilder 的深度集成</h3><p>NoteTool 的真正威力在于与 ContextBuilder 的配合使用。让我们通过一个完整的案例来展示这种集成。</p><p>（1）场景设定</p><p>假设我们正在构建一个长期项目助手，它需要：</p><ol><li>记录项目的阶段性进展</li><li>追踪待解决的问题</li><li>在每次对话时，自动回顾相关笔记</li><li>基于历史笔记提供连贯的建议</li></ol><p>（2）实现示例</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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.context <span class="hljs-keyword">import</span> ContextBuilder, ContextConfig, ContextPacket<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MemoryTool, RAGTool, NoteTool<br><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ProjectAssistant</span>(<span class="hljs-title class_ inherited__">SimpleAgent</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;长期项目助手,集成 NoteTool 和 ContextBuilder&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, name: <span class="hljs-built_in">str</span>, project_name: <span class="hljs-built_in">str</span>, **kwargs</span>):<br>        <span class="hljs-built_in">super</span>().__init__(name=name, llm=HelloAgentsLLM(), **kwargs)<br><br>        self.project_name = project_name<br><br>        <span class="hljs-comment"># 初始化工具</span><br>        self.memory_tool = MemoryTool(user_id=project_name)<br>        self.rag_tool = RAGTool(knowledge_base_path=<span class="hljs-string">f&quot;./<span class="hljs-subst">&#123;project_name&#125;</span>_kb&quot;</span>)<br>        self.note_tool = NoteTool(workspace=<span class="hljs-string">f&quot;./<span class="hljs-subst">&#123;project_name&#125;</span>_notes&quot;</span>)<br><br>        <span class="hljs-comment"># 初始化上下文构建器</span><br>        self.context_builder = ContextBuilder(<br>            memory_tool=self.memory_tool,<br>            rag_tool=self.rag_tool,<br>            config=ContextConfig(max_tokens=<span class="hljs-number">4000</span>)<br>        )<br><br>        self.conversation_history = []<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, user_input: <span class="hljs-built_in">str</span>, note_as_action: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">False</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;运行助手,自动集成笔记&quot;&quot;&quot;</span><br><br>        <span class="hljs-comment"># 1. 从 NoteTool 检索相关笔记</span><br>        relevant_notes = self._retrieve_relevant_notes(user_input)<br><br>        <span class="hljs-comment"># 2. 将笔记转换为 ContextPacket</span><br>        note_packets = self._notes_to_packets(relevant_notes)<br><br>        <span class="hljs-comment"># 3. 构建优化的上下文</span><br>        context = self.context_builder.build(<br>            user_query=user_input,<br>            conversation_history=self.conversation_history,<br>            system_instructions=self._build_system_instructions(),<br>            custom_packets=note_packets<br>        )<br><br>        <span class="hljs-comment"># 4. 调用 LLM</span><br>        response = self.llm.invoke(context)<br><br>        <span class="hljs-comment"># 5. 如果需要,将交互记录为笔记</span><br>        <span class="hljs-keyword">if</span> note_as_action:<br>            self._save_as_note(user_input, response)<br><br>        <span class="hljs-comment"># 6. 更新对话历史</span><br>        self._update_history(user_input, response)<br><br>        <span class="hljs-keyword">return</span> response<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_retrieve_relevant_notes</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span>, limit: <span class="hljs-built_in">int</span> = <span class="hljs-number">3</span></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;检索相关笔记&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-comment"># 优先检索 blocker 和 action 类型的笔记</span><br>            blockers = self.note_tool.run(&#123;<br>                <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;list&quot;</span>,<br>                <span class="hljs-string">&quot;note_type&quot;</span>: <span class="hljs-string">&quot;blocker&quot;</span>,<br>                <span class="hljs-string">&quot;limit&quot;</span>: <span class="hljs-number">2</span><br>            &#125;)<br><br>            <span class="hljs-comment"># 通用搜索</span><br>            search_results = self.note_tool.run(&#123;<br>                <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;search&quot;</span>,<br>                <span class="hljs-string">&quot;query&quot;</span>: query,<br>                <span class="hljs-string">&quot;limit&quot;</span>: limit<br>            &#125;)<br><br>            <span class="hljs-comment"># 合并并去重</span><br>            all_notes = &#123;note[<span class="hljs-string">&#x27;note_id&#x27;</span>]: note <span class="hljs-keyword">for</span> note <span class="hljs-keyword">in</span> blockers + search_results&#125;<br>            <span class="hljs-keyword">return</span> <span class="hljs-built_in">list</span>(all_notes.values())[:limit]<br><br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[WARNING] 笔记检索失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>            <span class="hljs-keyword">return</span> []<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_notes_to_packets</span>(<span class="hljs-params">self, notes: <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>]</span>) -&gt; <span class="hljs-type">List</span>[ContextPacket]:<br>        <span class="hljs-string">&quot;&quot;&quot;将笔记转换为上下文包&quot;&quot;&quot;</span><br>        packets = []<br><br>        <span class="hljs-keyword">for</span> note <span class="hljs-keyword">in</span> notes:<br>            content = <span class="hljs-string">f&quot;[笔记:<span class="hljs-subst">&#123;note[<span class="hljs-string">&#x27;title&#x27;</span>]&#125;</span>]\n<span class="hljs-subst">&#123;note[<span class="hljs-string">&#x27;content&#x27;</span>]&#125;</span>&quot;</span><br><br>            packets.append(ContextPacket(<br>                content=content,<br>                timestamp=datetime.fromisoformat(note[<span class="hljs-string">&#x27;updated_at&#x27;</span>]),<br>                token_count=<span class="hljs-built_in">len</span>(content) // <span class="hljs-number">4</span>,  <span class="hljs-comment"># 简单估算</span><br>                relevance_score=<span class="hljs-number">0.75</span>,  <span class="hljs-comment"># 笔记具有较高相关性</span><br>                metadata=&#123;<br>                    <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;note&quot;</span>,<br>                    <span class="hljs-string">&quot;note_type&quot;</span>: note[<span class="hljs-string">&#x27;type&#x27;</span>],<br>                    <span class="hljs-string">&quot;note_id&quot;</span>: note[<span class="hljs-string">&#x27;note_id&#x27;</span>]<br>                &#125;<br>            ))<br><br>        <span class="hljs-keyword">return</span> packets<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_save_as_note</span>(<span class="hljs-params">self, user_input: <span class="hljs-built_in">str</span>, response: <span class="hljs-built_in">str</span></span>):<br>        <span class="hljs-string">&quot;&quot;&quot;将交互保存为笔记&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-comment"># 判断应该保存为什么类型的笔记</span><br>            <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;问题&quot;</span> <span class="hljs-keyword">in</span> user_input <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;阻塞&quot;</span> <span class="hljs-keyword">in</span> user_input:<br>                note_type = <span class="hljs-string">&quot;blocker&quot;</span><br>            <span class="hljs-keyword">elif</span> <span class="hljs-string">&quot;计划&quot;</span> <span class="hljs-keyword">in</span> user_input <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;下一步&quot;</span> <span class="hljs-keyword">in</span> user_input:<br>                note_type = <span class="hljs-string">&quot;action&quot;</span><br>            <span class="hljs-keyword">else</span>:<br>                note_type = <span class="hljs-string">&quot;conclusion&quot;</span><br><br>            self.note_tool.run(&#123;<br>                <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create&quot;</span>,<br>                <span class="hljs-string">&quot;title&quot;</span>: <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;user_input[:<span class="hljs-number">30</span>]&#125;</span>...&quot;</span>,<br>                <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">f&quot;## 问题\n<span class="hljs-subst">&#123;user_input&#125;</span>\n\n## 分析\n<span class="hljs-subst">&#123;response&#125;</span>&quot;</span>,<br>                <span class="hljs-string">&quot;note_type&quot;</span>: note_type,<br>                <span class="hljs-string">&quot;tags&quot;</span>: [self.project_name, <span class="hljs-string">&quot;auto_generated&quot;</span>]<br>            &#125;)<br><br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[WARNING] 保存笔记失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_build_system_instructions</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;构建系统指令&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;&quot;&quot;你是 <span class="hljs-subst">&#123;self.project_name&#125;</span> 项目的长期助手。</span><br><span class="hljs-string"></span><br><span class="hljs-string">你的职责:</span><br><span class="hljs-string">1. 基于历史笔记提供连贯的建议</span><br><span class="hljs-string">2. 追踪项目进展和待解决问题</span><br><span class="hljs-string">3. 在回答时引用相关的历史笔记</span><br><span class="hljs-string">4. 提供具体、可操作的下一步建议</span><br><span class="hljs-string"></span><br><span class="hljs-string">注意:</span><br><span class="hljs-string">- 优先关注标记为 blocker 的问题</span><br><span class="hljs-string">- 在建议中说明依据来源(笔记、记忆或知识库)</span><br><span class="hljs-string">- 保持对项目整体进度的认识&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_update_history</span>(<span class="hljs-params">self, user_input: <span class="hljs-built_in">str</span>, response: <span class="hljs-built_in">str</span></span>):<br>        <span class="hljs-string">&quot;&quot;&quot;更新对话历史&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">from</span> hello_agents.core.message <span class="hljs-keyword">import</span> Message<br><br>        self.conversation_history.append(<br>            Message(content=user_input, role=<span class="hljs-string">&quot;user&quot;</span>, timestamp=datetime.now())<br>        )<br>        self.conversation_history.append(<br>            Message(content=response, role=<span class="hljs-string">&quot;assistant&quot;</span>, timestamp=datetime.now())<br>        )<br><br>        <span class="hljs-comment"># 限制历史长度</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(self.conversation_history) &gt; <span class="hljs-number">10</span>:<br>            self.conversation_history = self.conversation_history[-<span class="hljs-number">10</span>:]<br><br><span class="hljs-comment"># 使用示例</span><br>assistant = ProjectAssistant(<br>    name=<span class="hljs-string">&quot;项目助手&quot;</span>,<br>    project_name=<span class="hljs-string">&quot;data_pipeline_refactoring&quot;</span><br>)<br><br><span class="hljs-comment"># 第一次交互:记录项目状态</span><br>response = assistant.run(<br>    <span class="hljs-string">&quot;我们已经完成了数据模型层的重构,测试覆盖率达到85%。下一步计划重构业务逻辑层。&quot;</span>,<br>    note_as_action=<span class="hljs-literal">True</span><br>)<br><br><span class="hljs-comment"># 第二次交互:提出问题</span><br>response = assistant.run(<br>    <span class="hljs-string">&quot;在重构业务逻辑层时,我遇到了依赖版本冲突的问题,该如何解决?&quot;</span><br>)<br><br><span class="hljs-comment"># 查看笔记摘要</span><br>summary = assistant.note_tool.run(&#123;<span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;summary&quot;</span>&#125;)<br><span class="hljs-built_in">print</span>(summary)<br></code></pre></td></tr></table></figure><p>（3）运行效果展示</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><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><code class="hljs bash">[ContextBuilder] 汇集了 8 个候选信息包<br>[ContextBuilder] 选择了 7 个信息包,共 3500 tokens<br><br>✅ 助手回答:<br><br>我注意到您之前记录的笔记中提到了这个问题。根据笔记[重构项目 - 第一阶段],您当前的测试覆盖率已经达到85%,这是一个很好的基础。<br><br>关于依赖版本冲突的问题,我建议:<br><br>1. **使用虚拟环境隔离**: 为业务逻辑层创建独立的虚拟环境,避免与其他模块的依赖冲突<br>2. **锁定版本**: 在 requirements.txt 中明确指定所有依赖的精确版本<br>3. **使用 pipdeptree**: 分析依赖树,找出冲突的根源<br><br>这个问题我会标记为 blocker,建议优先解决。<br><br>[依据来源: 笔记 note_20250119_153000_0, 项目知识库]<br><br>---<br><br>📋 笔记摘要:<br>&#123;<br>  <span class="hljs-string">&quot;total_notes&quot;</span>: 2,<br>  <span class="hljs-string">&quot;type_distribution&quot;</span>: &#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: 1,<br>    <span class="hljs-string">&quot;blocker&quot;</span>: 1<br>  &#125;,<br>  <span class="hljs-string">&quot;recent_notes&quot;</span>: [<br>    &#123;<br>      <span class="hljs-string">&quot;id&quot;</span>: <span class="hljs-string">&quot;note_20250119_154500_1&quot;</span>,<br>      <span class="hljs-string">&quot;title&quot;</span>: <span class="hljs-string">&quot;在重构业务逻辑层时,我遇到了依赖版本冲突的问题...&quot;</span>,<br>      <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;blocker&quot;</span>,<br>      <span class="hljs-string">&quot;updated_at&quot;</span>: <span class="hljs-string">&quot;2025-01-19T15:45:00&quot;</span><br>    &#125;,<br>    &#123;<br>      <span class="hljs-string">&quot;id&quot;</span>: <span class="hljs-string">&quot;note_20250119_153000_0&quot;</span>,<br>      <span class="hljs-string">&quot;title&quot;</span>: <span class="hljs-string">&quot;我们已经完成了数据模型层的重构...&quot;</span>,<br>      <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;action&quot;</span>,<br>      <span class="hljs-string">&quot;updated_at&quot;</span>: <span class="hljs-string">&quot;2025-01-19T15:30:00&quot;</span><br>    &#125;<br>  ]<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="9-4-5-最佳实践"><a href="#9-4-5-最佳实践" class="headerlink" title="9.4.5 最佳实践"></a>9.4.5 最佳实践</h3><p>在实际使用 NoteTool 时，以下最佳实践能帮助您构建更强大的长时程智能体：</p><ol><li><p><strong>合理的笔记分类</strong>：</p><ul><li><code>task_state</code>：记录阶段性进展和状态</li><li><code>conclusion</code>：记录重要的结论和发现</li><li><code>blocker</code>：记录阻塞问题，优先级最高</li><li><code>action</code>：记录下一步行动计划</li><li><code>reference</code>：记录重要的参考资料</li></ul></li><li><p><strong>定期清理和归档</strong>：</p><ul><li>对于已解决的 blocker，更新为 conclusion</li><li>对于过时的 action，及时删除或更新</li><li>使用 tags 进行版本管理，如 <code>[&quot;v1.0&quot;, &quot;completed&quot;]</code></li></ul></li><li><p><strong>与 ContextBuilder 的配合</strong>：</p><ul><li>在每轮对话前检索相关笔记</li><li>根据笔记类型设置不同的相关性分数(blocker &gt; action &gt; conclusion)</li><li>限制笔记数量，避免上下文过载</li></ul></li><li><p><strong>人机协作</strong>：</p><ul><li>笔记是人类可读的 Markdown 格式，支持手动编辑</li><li>使用 Git 进行版本控制，追踪笔记的演化</li><li>在关键阶段，人工审核 Agent 生成的笔记</li></ul></li><li><p><strong>自动化工作流</strong>：</p><ul><li>定期生成笔记摘要报告</li><li>基于笔记自动生成项目进度文档</li><li>将笔记内容同步到其他系统(如 Notion、Confluence)</li></ul></li></ol><h2 id="9-5-TerminalTool：即时文件系统访问"><a href="#9-5-TerminalTool：即时文件系统访问" class="headerlink" title="9.5 TerminalTool：即时文件系统访问"></a>9.5 TerminalTool：即时文件系统访问</h2><p>在前面的章节中，我们介绍了 MemoryTool 和 RAGTool，它们分别提供了对话记忆和知识检索能力。然而，在许多实际场景中，智能体需要<strong>即时访问和探索文件系统</strong>——查看日志文件、分析代码库结构、检索配置文件等。这就是 TerminalTool 的用武之地。</p><p>TerminalTool 为智能体提供了<strong>安全的命令行执行能力</strong>，支持常用的文件系统和文本处理命令，同时通过多层安全机制确保系统安全。这种设计实现了 9.2.2 节提到的”即时(Just-in-time, JIT)上下文”理念——智能体不需要预先加载所有文件，而是按需探索和检索。</p><h3 id="9-5-1-设计理念与安全机制"><a href="#9-5-1-设计理念与安全机制" class="headerlink" title="9.5.1 设计理念与安全机制"></a>9.5.1 设计理念与安全机制</h3><p>（1）为什么需要 TerminalTool?</p><p>在构建长程智能体时，我们经常遇到以下场景：</p><p><strong>场景1：代码库探索</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 传统方式:预先索引所有文件(成本高、可能过时)</span><br>rag_tool.add_document(<span class="hljs-string">&quot;./project/**/*.py&quot;</span>)  <span class="hljs-comment"># 耗时、占用大量存储</span><br><br><span class="hljs-comment"># TerminalTool 方式:即时探索</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;find . -name &#x27;*.py&#x27; -type f&quot;</span>&#125;)  <span class="hljs-comment"># 快速、实时</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;grep -r &#x27;class UserService&#x27; .&quot;</span>&#125;)  <span class="hljs-comment"># 精确定位</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;head -n 50 src/services/user.py&quot;</span>&#125;)  <span class="hljs-comment"># 按需查看</span><br></code></pre></td></tr></table></figure><p><strong>场景2：日志文件分析</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 检查日志文件大小</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;ls -lh /var/log/app.log&quot;</span>&#125;)<br><br><span class="hljs-comment"># 查看最新的错误日志</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;tail -n 100 /var/log/app.log | grep ERROR&quot;</span>&#125;)<br><br><span class="hljs-comment"># 统计错误类型分布</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;grep ERROR /var/log/app.log | cut -d&#x27;:&#x27; -f3 | sort | uniq -c&quot;</span>&#125;)<br></code></pre></td></tr></table></figure><p><strong>场景3：数据文件预览</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 查看 CSV 文件的前几行</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;head -n 5 data/sales.csv&quot;</span>&#125;)<br><br><span class="hljs-comment"># 统计行数</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;wc -l data/*.csv&quot;</span>&#125;)<br><br><span class="hljs-comment"># 查看列名</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;head -n 1 data/sales.csv | tr &#x27;,&#x27; &#x27;\n&#x27;&quot;</span>&#125;)<br></code></pre></td></tr></table></figure><p>这些场景的共同特点是：<strong>需要实时、轻量级的文件系统访问，而不是预先索引和向量化</strong>。TerminalTool 正是为这种”探索式”工作流设计的。</p><p>（2）安全机制详解</p><p>允许智能体执行命令是一个强大但危险的能力。TerminalTool 通过多层安全机制确保系统安全：</p><p><strong>第一层：命令白名单</strong></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></pre></td><td class="code"><pre><code class="hljs python">ALLOWED_COMMANDS = &#123;<br>    <span class="hljs-comment"># 文件列表与信息</span><br>    <span class="hljs-string">&#x27;ls&#x27;</span>, <span class="hljs-string">&#x27;dir&#x27;</span>, <span class="hljs-string">&#x27;tree&#x27;</span>,<br>    <span class="hljs-comment"># 文件内容查看</span><br>    <span class="hljs-string">&#x27;cat&#x27;</span>, <span class="hljs-string">&#x27;head&#x27;</span>, <span class="hljs-string">&#x27;tail&#x27;</span>, <span class="hljs-string">&#x27;less&#x27;</span>, <span class="hljs-string">&#x27;more&#x27;</span>,<br>    <span class="hljs-comment"># 文件搜索</span><br>    <span class="hljs-string">&#x27;find&#x27;</span>, <span class="hljs-string">&#x27;grep&#x27;</span>, <span class="hljs-string">&#x27;egrep&#x27;</span>, <span class="hljs-string">&#x27;fgrep&#x27;</span>,<br>    <span class="hljs-comment"># 文本处理</span><br>    <span class="hljs-string">&#x27;wc&#x27;</span>, <span class="hljs-string">&#x27;sort&#x27;</span>, <span class="hljs-string">&#x27;uniq&#x27;</span>, <span class="hljs-string">&#x27;cut&#x27;</span>, <span class="hljs-string">&#x27;awk&#x27;</span>, <span class="hljs-string">&#x27;sed&#x27;</span>,<br>    <span class="hljs-comment"># 目录操作</span><br>    <span class="hljs-string">&#x27;pwd&#x27;</span>, <span class="hljs-string">&#x27;cd&#x27;</span>,<br>    <span class="hljs-comment"># 文件信息</span><br>    <span class="hljs-string">&#x27;file&#x27;</span>, <span class="hljs-string">&#x27;stat&#x27;</span>, <span class="hljs-string">&#x27;du&#x27;</span>, <span class="hljs-string">&#x27;df&#x27;</span>,<br>    <span class="hljs-comment"># 其他</span><br>    <span class="hljs-string">&#x27;echo&#x27;</span>, <span class="hljs-string">&#x27;which&#x27;</span>, <span class="hljs-string">&#x27;whereis&#x27;</span>,<br>&#125;<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python">terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;rm -rf /&quot;</span>&#125;)<br><span class="hljs-comment"># ❌ 不允许的命令: rm</span><br><span class="hljs-comment"># 允许的命令: cat, cd, cut, dir, du, ...</span><br></code></pre></td></tr></table></figure><p><strong>第二层：工作目录限制(沙箱)</strong></p><p>TerminalTool 只能访问指定的工作目录及其子目录，无法访问系统其他部分：</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><code class="hljs python"><span class="hljs-comment"># 初始化时指定工作目录</span><br>terminal = TerminalTool(workspace=<span class="hljs-string">&quot;./project&quot;</span>)<br><br><span class="hljs-comment"># 允许:访问工作目录内的文件</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;cat ./src/main.py&quot;</span>&#125;)  <span class="hljs-comment"># ✅</span><br><br><span class="hljs-comment"># 禁止:访问工作目录外的文件</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;cat /etc/passwd&quot;</span>&#125;)  <span class="hljs-comment"># ❌ 不允许访问工作目录外的路径</span><br><br><span class="hljs-comment"># 禁止:通过 .. 逃逸</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;cd ../../../etc&quot;</span>&#125;)  <span class="hljs-comment"># ❌ 不允许访问工作目录外的路径</span><br></code></pre></td></tr></table></figure><p>这种沙箱机制确保了即使智能体的行为出现异常，也无法影响系统其他部分。</p><p><strong>第三层：超时控制</strong></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></pre></td><td class="code"><pre><code class="hljs python">terminal = TerminalTool(<br>    workspace=<span class="hljs-string">&quot;./project&quot;</span>,<br>    timeout=<span class="hljs-number">30</span>  <span class="hljs-comment"># 30秒超时</span><br>)<br><br><span class="hljs-comment"># 如果命令执行超过30秒</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;find / -name &#x27;*.log&#x27;&quot;</span>&#125;)<br><span class="hljs-comment"># ❌ 命令执行超时（超过 30 秒）</span><br></code></pre></td></tr></table></figure><p><strong>第四层：输出大小限制</strong></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></pre></td><td class="code"><pre><code class="hljs python">terminal = TerminalTool(<br>    workspace=<span class="hljs-string">&quot;./project&quot;</span>,<br>    max_output_size=<span class="hljs-number">10</span> * <span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>  <span class="hljs-comment"># 10MB</span><br>)<br><br><span class="hljs-comment"># 如果输出超过10MB</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;cat huge_file.log&quot;</span>&#125;)<br><span class="hljs-comment"># ... (前10MB的内容) ...</span><br><span class="hljs-comment"># ⚠️ 输出被截断（超过 10485760 字节）</span><br></code></pre></td></tr></table></figure><p>通过这四层安全机制，TerminalTool 在提供强大能力的同时，最大程度地保证了系统安全。</p><h3 id="9-5-2-核心功能详解"><a href="#9-5-2-核心功能详解" class="headerlink" title="9.5.2 核心功能详解"></a>9.5.2 核心功能详解</h3><p>TerminalTool 的实现聚焦于两个核心功能：命令执行和目录导航。</p><p>（1）命令执行</p><p>核心的 <code>_execute_command</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><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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_execute_command</span>(<span class="hljs-params">self, command: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;执行命令&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">try</span>:<br>        <span class="hljs-comment"># 在当前目录下执行命令</span><br>        result = subprocess.run(<br>            command,<br>            shell=<span class="hljs-literal">True</span>,<br>            cwd=<span class="hljs-built_in">str</span>(self.current_dir),  <span class="hljs-comment"># 在当前工作目录执行</span><br>            capture_output=<span class="hljs-literal">True</span>,<br>            text=<span class="hljs-literal">True</span>,<br>            timeout=self.timeout,<br>            env=os.environ.copy()<br>        )<br><br>        <span class="hljs-comment"># 合并标准输出和标准错误</span><br>        output = result.stdout<br>        <span class="hljs-keyword">if</span> result.stderr:<br>            output += <span class="hljs-string">f&quot;\n[stderr]\n<span class="hljs-subst">&#123;result.stderr&#125;</span>&quot;</span><br><br>        <span class="hljs-comment"># 检查输出大小</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(output) &gt; self.max_output_size:<br>            output = output[:self.max_output_size]<br>            output += <span class="hljs-string">f&quot;\n\n⚠️ 输出被截断（超过 <span class="hljs-subst">&#123;self.max_output_size&#125;</span> 字节）&quot;</span><br><br>        <span class="hljs-comment"># 添加返回码信息</span><br>        <span class="hljs-keyword">if</span> result.returncode != <span class="hljs-number">0</span>:<br>            output = <span class="hljs-string">f&quot;⚠️ 命令返回码: <span class="hljs-subst">&#123;result.returncode&#125;</span>\n\n<span class="hljs-subst">&#123;output&#125;</span>&quot;</span><br><br>        <span class="hljs-keyword">return</span> output <span class="hljs-keyword">if</span> output <span class="hljs-keyword">else</span> <span class="hljs-string">&quot;✅ 命令执行成功（无输出）&quot;</span><br><br>    <span class="hljs-keyword">except</span> subprocess.TimeoutExpired:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 命令执行超时（超过 <span class="hljs-subst">&#123;self.timeout&#125;</span> 秒）&quot;</span><br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 命令执行失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span><br></code></pre></td></tr></table></figure><p>这个实现的关键点：</p><ul><li><strong>当前目录感知</strong>：使用 <code>cwd</code> 参数在正确的目录下执行命令</li><li><strong>错误处理</strong>：捕获并合并标准错误，提供完整的诊断信息</li><li><strong>返回码检查</strong>：非零返回码会被标记为警告</li><li><strong>容错设计</strong>：超时和异常都会被妥善处理，不会导致智能体崩溃</li></ul><p>（2）目录导航</p><p><code>cd</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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_handle_cd</span>(<span class="hljs-params">self, parts: <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>]</span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;处理 cd 命令&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.allow_cd:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;❌ cd 命令已禁用&quot;</span><br><br>    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(parts) &lt; <span class="hljs-number">2</span>:<br>        <span class="hljs-comment"># cd 无参数，返回当前目录</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;当前目录: <span class="hljs-subst">&#123;self.current_dir&#125;</span>&quot;</span><br><br>    target_dir = parts[<span class="hljs-number">1</span>]<br><br>    <span class="hljs-comment"># 处理相对路径</span><br>    <span class="hljs-keyword">if</span> target_dir == <span class="hljs-string">&quot;..&quot;</span>:<br>        new_dir = self.current_dir.parent<br>    <span class="hljs-keyword">elif</span> target_dir == <span class="hljs-string">&quot;.&quot;</span>:<br>        new_dir = self.current_dir<br>    <span class="hljs-keyword">elif</span> target_dir == <span class="hljs-string">&quot;~&quot;</span>:<br>        new_dir = self.workspace<br>    <span class="hljs-keyword">else</span>:<br>        new_dir = (self.current_dir / target_dir).resolve()<br><br>    <span class="hljs-comment"># 检查是否在工作目录内</span><br>    <span class="hljs-keyword">try</span>:<br>        new_dir.relative_to(self.workspace)<br>    <span class="hljs-keyword">except</span> ValueError:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 不允许访问工作目录外的路径: <span class="hljs-subst">&#123;new_dir&#125;</span>&quot;</span><br><br>    <span class="hljs-comment"># 检查目录是否存在</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> new_dir.exists():<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 目录不存在: <span class="hljs-subst">&#123;new_dir&#125;</span>&quot;</span><br><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> new_dir.is_dir():<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 不是目录: <span class="hljs-subst">&#123;new_dir&#125;</span>&quot;</span><br><br>    <span class="hljs-comment"># 更新当前目录</span><br>    self.current_dir = new_dir<br>    <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;✅ 切换到目录: <span class="hljs-subst">&#123;self.current_dir&#125;</span>&quot;</span><br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 第一步:查看项目结构</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;ls -la&quot;</span>&#125;)<br><br><span class="hljs-comment"># 第二步:进入源代码目录</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;cd src&quot;</span>&#125;)<br><br><span class="hljs-comment"># 第三步:查找特定文件</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;find . -name &#x27;*service*.py&#x27;&quot;</span>&#125;)<br><br><span class="hljs-comment"># 第四步:查看文件内容</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;cat user_service.py&quot;</span>&#125;)<br></code></pre></td></tr></table></figure><h3 id="9-5-3-典型使用模式"><a href="#9-5-3-典型使用模式" class="headerlink" title="9.5.3 典型使用模式"></a>9.5.3 典型使用模式</h3><p>TerminalTool 支持多种常见的文件系统操作模式。</p><p>（1）探索式导航</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> TerminalTool<br><br>terminal = TerminalTool(workspace=<span class="hljs-string">&quot;./my_project&quot;</span>)<br><br><span class="hljs-comment"># 第一步:查看项目根目录</span><br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;ls -la&quot;</span>&#125;))<br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">total 24</span><br><span class="hljs-string">drwxr-xr-x  6 user  staff   192 Jan 19 16:00 .</span><br><span class="hljs-string">drwxr-xr-x  5 user  staff   160 Jan 19 15:30 ..</span><br><span class="hljs-string">-rw-r--r--  1 user  staff  1234 Jan 19 15:30 README.md</span><br><span class="hljs-string">drwxr-xr-x  4 user  staff   128 Jan 19 15:30 src</span><br><span class="hljs-string">drwxr-xr-x  3 user  staff    96 Jan 19 15:30 tests</span><br><span class="hljs-string">-rw-r--r--  1 user  staff   456 Jan 19 15:30 requirements.txt</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># 第二步:查看源代码目录结构</span><br>terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;cd src&quot;</span>&#125;)<br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;tree&quot;</span>&#125;))<br><br><span class="hljs-comment"># 第三步:搜索特定模式</span><br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;grep -r &#x27;def process&#x27; .&quot;</span>&#125;))<br></code></pre></td></tr></table></figure><p>（2）数据文件分析</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></pre></td><td class="code"><pre><code class="hljs python">terminal = TerminalTool(workspace=<span class="hljs-string">&quot;./data&quot;</span>)<br><br><span class="hljs-comment"># 查看 CSV 文件的前几行</span><br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;head -n 5 sales_2024.csv&quot;</span>&#125;))<br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">date,product,quantity,revenue</span><br><span class="hljs-string">2024-01-01,Widget A,150,4500.00</span><br><span class="hljs-string">2024-01-01,Widget B,200,8000.00</span><br><span class="hljs-string">2024-01-02,Widget A,180,5400.00</span><br><span class="hljs-string">2024-01-02,Widget C,120,3600.00</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># 统计总行数</span><br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;wc -l *.csv&quot;</span>&#125;))<br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">  10234 sales_2024.csv</span><br><span class="hljs-string">   8567 sales_2023.csv</span><br><span class="hljs-string">  18801 total</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># 提取和统计产品类别</span><br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;tail -n +2 sales_2024.csv | cut -d&#x27;,&#x27; -f2 | sort | uniq -c&quot;</span>&#125;))<br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">  3456 Widget A</span><br><span class="hljs-string">  4123 Widget B</span><br><span class="hljs-string">  2655 Widget C</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p>（3）日志文件分析</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></pre></td><td class="code"><pre><code class="hljs python">terminal = TerminalTool(workspace=<span class="hljs-string">&quot;/var/log&quot;</span>)<br><br><span class="hljs-comment"># 查看最新的错误日志</span><br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;tail -n 50 app.log | grep ERROR&quot;</span>&#125;))<br><br><span class="hljs-comment"># 统计错误类型分布</span><br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;grep ERROR app.log | awk &#x27;&#123;print $4&#125;&#x27; | sort | uniq -c | sort -rn&quot;</span>&#125;))<br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">  245 DatabaseConnectionError</span><br><span class="hljs-string">  123 TimeoutException</span><br><span class="hljs-string">   67 ValidationError</span><br><span class="hljs-string">   34 AuthenticationError</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># 查找特定时间段的日志</span><br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;grep &#x27;2024-01-19 15:&#x27; app.log | tail -n 20&quot;</span>&#125;))<br></code></pre></td></tr></table></figure><p>（4）代码库分析</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></pre></td><td class="code"><pre><code class="hljs python">terminal = TerminalTool(workspace=<span class="hljs-string">&quot;./codebase&quot;</span>)<br><br><span class="hljs-comment"># 统计代码行数</span><br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;find . -name &#x27;*.py&#x27; -exec wc -l &#123;&#125; + | tail -n 1&quot;</span>&#125;))<br><br><span class="hljs-comment"># 查找所有 TODO 注释</span><br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;grep -rn &#x27;TODO&#x27; --include=&#x27;*.py&#x27;&quot;</span>&#125;))<br><br><span class="hljs-comment"># 查找特定函数的定义</span><br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;grep -rn &#x27;def process_data&#x27; --include=&#x27;*.py&#x27;&quot;</span>&#125;))<br><br><span class="hljs-comment"># 查看函数实现</span><br><span class="hljs-built_in">print</span>(terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;sed -n &#x27;/def process_data/,/^def /p&#x27; src/processor.py | head -n -1&quot;</span>&#125;))<br></code></pre></td></tr></table></figure><h3 id="9-5-4-与其他工具的协同"><a href="#9-5-4-与其他工具的协同" class="headerlink" title="9.5.4 与其他工具的协同"></a>9.5.4 与其他工具的协同</h3><p>TerminalTool 的真正威力在于与 MemoryTool、NoteTool 和 ContextBuilder 的协同使用。</p><p>（1）与 MemoryTool 协同</p><p>TerminalTool 发现的信息可以存储到记忆系统中：</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><code class="hljs python"><span class="hljs-comment"># 使用 TerminalTool 发现项目结构</span><br>structure = terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;tree -L 2 src&quot;</span>&#125;)<br><br><span class="hljs-comment"># 存储到语义记忆</span><br>memory_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;add&quot;</span>,<br>    <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">f&quot;项目结构:\n<span class="hljs-subst">&#123;structure&#125;</span>&quot;</span>,<br>    <span class="hljs-string">&quot;memory_type&quot;</span>: <span class="hljs-string">&quot;semantic&quot;</span>,<br>    <span class="hljs-string">&quot;importance&quot;</span>: <span class="hljs-number">0.8</span>,<br>    <span class="hljs-string">&quot;metadata&quot;</span>: &#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;project_structure&quot;</span>&#125;<br>&#125;)<br></code></pre></td></tr></table></figure><p>（2）与 NoteTool 协同</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 发现一个性能瓶颈</span><br>log_analysis = terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;grep &#x27;slow query&#x27; app.log | tail -n 10&quot;</span>&#125;)<br><br><span class="hljs-comment"># 记录为 blocker 笔记</span><br>note_tool.run(&#123;<br>    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create&quot;</span>,<br>    <span class="hljs-string">&quot;title&quot;</span>: <span class="hljs-string">&quot;数据库慢查询问题&quot;</span>,<br>    <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">f&quot;## 问题描述\n发现多个慢查询,影响系统性能\n\n## 日志分析\n```\n<span class="hljs-subst">&#123;log_analysis&#125;</span>\n```\n\n## 下一步\n1. 分析慢查询SQL\n2. 添加索引\n3. 优化查询逻辑&quot;</span>,<br>    <span class="hljs-string">&quot;note_type&quot;</span>: <span class="hljs-string">&quot;blocker&quot;</span>,<br>    <span class="hljs-string">&quot;tags&quot;</span>: [<span class="hljs-string">&quot;performance&quot;</span>, <span class="hljs-string">&quot;database&quot;</span>]<br>&#125;)<br></code></pre></td></tr></table></figure><p>（3）与 ContextBuilder 协同</p><p>TerminalTool 的输出可以作为上下文的一部分：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 探索代码库</span><br>code_structure = terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;ls -R src&quot;</span>&#125;)<br>recent_changes = terminal.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;git log --oneline -10&quot;</span>&#125;)<br><br><span class="hljs-comment"># 转换为 ContextPacket</span><br><span class="hljs-keyword">from</span> hello_agents.context <span class="hljs-keyword">import</span> ContextPacket<br><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><br>packets = [<br>    ContextPacket(<br>        content=<span class="hljs-string">f&quot;代码库结构:\n<span class="hljs-subst">&#123;code_structure&#125;</span>&quot;</span>,<br>        timestamp=datetime.now(),<br>        token_count=<span class="hljs-built_in">len</span>(code_structure) // <span class="hljs-number">4</span>,<br>        relevance_score=<span class="hljs-number">0.7</span>,<br>        metadata=&#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;code_structure&quot;</span>, <span class="hljs-string">&quot;source&quot;</span>: <span class="hljs-string">&quot;terminal&quot;</span>&#125;<br>    ),<br>    ContextPacket(<br>        content=<span class="hljs-string">f&quot;最近提交:\n<span class="hljs-subst">&#123;recent_changes&#125;</span>&quot;</span>,<br>        timestamp=datetime.now(),<br>        token_count=<span class="hljs-built_in">len</span>(recent_changes) // <span class="hljs-number">4</span>,<br>        relevance_score=<span class="hljs-number">0.8</span>,<br>        metadata=&#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;git_history&quot;</span>, <span class="hljs-string">&quot;source&quot;</span>: <span class="hljs-string">&quot;terminal&quot;</span>&#125;<br>    )<br>]<br><br><span class="hljs-comment"># 在构建上下文时包含这些信息</span><br>context = context_builder.build(<br>    user_query=<span class="hljs-string">&quot;如何重构用户服务模块?&quot;</span>,<br>    custom_packets=packets<br>)<br></code></pre></td></tr></table></figure><h2 id="9-6-长程智能体实战：代码库维护助手"><a href="#9-6-长程智能体实战：代码库维护助手" class="headerlink" title="9.6 长程智能体实战：代码库维护助手"></a>9.6 长程智能体实战：代码库维护助手</h2><p>现在，让我们将 ContextBuilder、NoteTool 和 TerminalTool 整合起来，构建一个完整的长程智能体——<strong>代码库维护助手</strong>。这个助手能够：</p><ol><li>探索和理解代码库结构</li><li>记录发现的问题和改进点</li><li>追踪长期的重构任务</li><li>在上下文窗口限制下保持连贯性</li></ol><h3 id="9-6-1-场景设定与需求分析"><a href="#9-6-1-场景设定与需求分析" class="headerlink" title="9.6.1 场景设定与需求分析"></a>9.6.1 场景设定与需求分析</h3><p><strong>业务场景</strong></p><p>假设我们正在维护一个中型 Python Web 应用，这个代码库包含约 50 个 Python 文件，使用 Flask 框架构建，涵盖数据模型、业务逻辑、API 接口等多个模块，同时存在一些技术债务需要逐步清理。在这样的场景下，我们需要一个智能助手来帮助我们探索代码库，理解项目结构、依赖关系和代码风格；识别代码中的问题，比如代码重复、复杂度过高、缺少测试等；追踪任务进度，记录待办事项、已完成工作和遇到的阻塞；并基于历史上下文提供连贯的重构建议。</p><p><strong>挑战与解决方案</strong></p><p>这个场景面临几个典型的长程任务挑战。首先是信息量超出上下文窗口的问题，整个代码库可能包含数万行代码，无法一次性放入上下文窗口，我们通过使用 TerminalTool 进行即时、按需的代码探索来解决这个问题，只在需要时查看具体文件。其次是跨会话的状态管理挑战，重构任务可能持续数天，需要跨多个会话保持进度，我们使用 NoteTool 记录阶段性进展、待办事项和关键决策来应对。最后是上下文质量与相关性的问题，每次对话需要回顾相关的历史信息，但不能被无关信息淹没，我们通过 ContextBuilder 智能筛选和组织上下文，确保高信号密度。</p><h3 id="9-6-2-系统架构设计"><a href="#9-6-2-系统架构设计" class="headerlink" title="9.6.2 系统架构设计"></a>9.6.2 系统架构设计</h3><p>我们的代码库维护助手采用三层架构，如图9.3所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/9-figures/9-3.png" alt="" width="85%"/>  <p>图 9.3 代码库维护助手三层架构</p></div><h3 id="9-6-3-核心实现"><a href="#9-6-3-核心实现" class="headerlink" title="9.6.3 核心实现"></a>9.6.3 核心实现</h3><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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Dict</span>， <span class="hljs-type">Any</span>, <span class="hljs-type">List</span>, <span class="hljs-type">Optional</span><br><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><span class="hljs-keyword">import</span> json<br><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> hello_agents.context <span class="hljs-keyword">import</span> ContextBuilder, ContextConfig, ContextPacket<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MemoryTool, NoteTool, TerminalTool<br><span class="hljs-keyword">from</span> hello_agents.core.message <span class="hljs-keyword">import</span> Message<br><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">CodebaseMaintainer</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;代码库维护助手 - 长程智能体示例</span><br><span class="hljs-string"></span><br><span class="hljs-string">    整合 ContextBuilder + NoteTool + TerminalTool + MemoryTool</span><br><span class="hljs-string">    实现跨会话的代码库维护任务管理</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        project_name: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        codebase_path: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        llm: <span class="hljs-type">Optional</span>[HelloAgentsLLM] = <span class="hljs-literal">None</span></span><br><span class="hljs-params">    </span>):<br>        self.project_name = project_name<br>        self.codebase_path = codebase_path<br>        self.session_id = <span class="hljs-string">f&quot;session_<span class="hljs-subst">&#123;datetime.now().strftime(<span class="hljs-string">&#x27;%Y%m%d_%H%M%S&#x27;</span>)&#125;</span>&quot;</span><br><br>        <span class="hljs-comment"># 初始化 LLM</span><br>        self.llm = llm <span class="hljs-keyword">or</span> HelloAgentsLLM()<br><br>        <span class="hljs-comment"># 初始化工具</span><br>        self.memory_tool = MemoryTool(user_id=project_name)<br>        self.note_tool = NoteTool(workspace=<span class="hljs-string">f&quot;./<span class="hljs-subst">&#123;project_name&#125;</span>_notes&quot;</span>)<br>        self.terminal_tool = TerminalTool(workspace=codebase_path, timeout=<span class="hljs-number">60</span>)<br><br>        <span class="hljs-comment"># 初始化上下文构建器</span><br>        self.context_builder = ContextBuilder(<br>            memory_tool=self.memory_tool,<br>            rag_tool=<span class="hljs-literal">None</span>,  <span class="hljs-comment"># 本案例不使用 RAG</span><br>            config=ContextConfig(<br>                max_tokens=<span class="hljs-number">4000</span>,<br>                reserve_ratio=<span class="hljs-number">0.15</span>,<br>                min_relevance=<span class="hljs-number">0.2</span>,<br>                enable_compression=<span class="hljs-literal">True</span><br>            )<br>        )<br><br>        <span class="hljs-comment"># 对话历史</span><br>        self.conversation_history: <span class="hljs-type">List</span>[Message] = []<br><br>        <span class="hljs-comment"># 统计信息</span><br>        self.stats = &#123;<br>            <span class="hljs-string">&quot;session_start&quot;</span>: datetime.now(),<br>            <span class="hljs-string">&quot;commands_executed&quot;</span>: <span class="hljs-number">0</span>,<br>            <span class="hljs-string">&quot;notes_created&quot;</span>: <span class="hljs-number">0</span>,<br>            <span class="hljs-string">&quot;issues_found&quot;</span>: <span class="hljs-number">0</span><br>        &#125;<br><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 代码库维护助手已初始化: <span class="hljs-subst">&#123;project_name&#125;</span>&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;📁 工作目录: <span class="hljs-subst">&#123;codebase_path&#125;</span>&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;🆔 会话ID: <span class="hljs-subst">&#123;self.session_id&#125;</span>&quot;</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, user_input: <span class="hljs-built_in">str</span>, mode: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;auto&quot;</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;运行助手</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Args:</span><br><span class="hljs-string">            user_input: 用户输入</span><br><span class="hljs-string">            mode: 运行模式</span><br><span class="hljs-string">                - &quot;auto&quot;: 自动决策是否使用工具</span><br><span class="hljs-string">                - &quot;explore&quot;: 侧重代码探索</span><br><span class="hljs-string">                - &quot;analyze&quot;: 侧重问题分析</span><br><span class="hljs-string">                - &quot;plan&quot;: 侧重任务规划</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Returns:</span><br><span class="hljs-string">            str: 助手的回答</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n<span class="hljs-subst">&#123;<span class="hljs-string">&#x27;=&#x27;</span>*<span class="hljs-number">80</span>&#125;</span>&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;👤 用户: <span class="hljs-subst">&#123;user_input&#125;</span>&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;<span class="hljs-subst">&#123;<span class="hljs-string">&#x27;=&#x27;</span>*<span class="hljs-number">80</span>&#125;</span>\n&quot;</span>)<br><br>        <span class="hljs-comment"># 第一步:根据模式执行预处理</span><br>        pre_context = self._preprocess_by_mode(user_input, mode)<br><br>        <span class="hljs-comment"># 第二步:检索相关笔记</span><br>        relevant_notes = self._retrieve_relevant_notes(user_input)<br>        note_packets = self._notes_to_packets(relevant_notes)<br><br>        <span class="hljs-comment"># 第三步:构建优化的上下文</span><br>        context = self.context_builder.build(<br>            user_query=user_input,<br>            conversation_history=self.conversation_history,<br>            system_instructions=self._build_system_instructions(mode),<br>            custom_packets=note_packets + pre_context<br>        )<br><br>        <span class="hljs-comment"># 第四步:调用 LLM</span><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;🤖 正在思考...&quot;</span>)<br>        response = self.llm.invoke(context)<br><br>        <span class="hljs-comment"># 第五步:后处理</span><br>        self._postprocess_response(user_input, response)<br><br>        <span class="hljs-comment"># 第六步:更新对话历史</span><br>        self._update_history(user_input, response)<br><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n🤖 助手: <span class="hljs-subst">&#123;response&#125;</span>\n&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;<span class="hljs-subst">&#123;<span class="hljs-string">&#x27;=&#x27;</span>*<span class="hljs-number">80</span>&#125;</span>\n&quot;</span>)<br><br>        <span class="hljs-keyword">return</span> response<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_preprocess_by_mode</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        user_input: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        mode: <span class="hljs-built_in">str</span></span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-type">List</span>[ContextPacket]:<br>        <span class="hljs-string">&quot;&quot;&quot;根据模式执行预处理,收集相关信息&quot;&quot;&quot;</span><br>        packets = []<br><br>        <span class="hljs-keyword">if</span> mode == <span class="hljs-string">&quot;explore&quot;</span> <span class="hljs-keyword">or</span> mode == <span class="hljs-string">&quot;auto&quot;</span>:<br>            <span class="hljs-comment"># 探索模式:自动查看项目结构</span><br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;🔍 探索代码库结构...&quot;</span>)<br><br>            structure = self.terminal_tool.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;find . -type f -name &#x27;*.py&#x27; | head -n 20&quot;</span>&#125;)<br>            self.stats[<span class="hljs-string">&quot;commands_executed&quot;</span>] += <span class="hljs-number">1</span><br><br>            packets.append(ContextPacket(<br>                content=<span class="hljs-string">f&quot;[代码库结构]\n<span class="hljs-subst">&#123;structure&#125;</span>&quot;</span>,<br>                timestamp=datetime.now(),<br>                token_count=<span class="hljs-built_in">len</span>(structure) // <span class="hljs-number">4</span>,<br>                relevance_score=<span class="hljs-number">0.6</span>,<br>                metadata=&#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;code_structure&quot;</span>, <span class="hljs-string">&quot;source&quot;</span>: <span class="hljs-string">&quot;terminal&quot;</span>&#125;<br>            ))<br><br>        <span class="hljs-keyword">if</span> mode == <span class="hljs-string">&quot;analyze&quot;</span>:<br>            <span class="hljs-comment"># 分析模式:检查代码复杂度和问题</span><br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;📊 分析代码质量...&quot;</span>)<br><br>            <span class="hljs-comment"># 统计代码行数</span><br>            loc = self.terminal_tool.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;find . -name &#x27;*.py&#x27; -exec wc -l &#123;&#125; + | tail -n 1&quot;</span>&#125;)<br><br>            <span class="hljs-comment"># 查找 TODO 和 FIXME</span><br>            todos = self.terminal_tool.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: <span class="hljs-string">&quot;grep -rn &#x27;TODO\\|FIXME&#x27; --include=&#x27;*.py&#x27; | head -n 10&quot;</span>&#125;)<br><br>            self.stats[<span class="hljs-string">&quot;commands_executed&quot;</span>] += <span class="hljs-number">2</span><br><br>            packets.append(ContextPacket(<br>                content=<span class="hljs-string">f&quot;[代码统计]\n<span class="hljs-subst">&#123;loc&#125;</span>\n\n[待办事项]\n<span class="hljs-subst">&#123;todos&#125;</span>&quot;</span>,<br>                timestamp=datetime.now(),<br>                token_count=(<span class="hljs-built_in">len</span>(loc) + <span class="hljs-built_in">len</span>(todos)) // <span class="hljs-number">4</span>,<br>                relevance_score=<span class="hljs-number">0.7</span>,<br>                metadata=&#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;code_analysis&quot;</span>, <span class="hljs-string">&quot;source&quot;</span>: <span class="hljs-string">&quot;terminal&quot;</span>&#125;<br>            ))<br><br>        <span class="hljs-keyword">if</span> mode == <span class="hljs-string">&quot;plan&quot;</span>:<br>            <span class="hljs-comment"># 规划模式:加载最近的笔记</span><br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;📋 加载任务规划...&quot;</span>)<br><br>            task_notes = self.note_tool.run(&#123;<br>                <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;list&quot;</span>,<br>                <span class="hljs-string">&quot;note_type&quot;</span>: <span class="hljs-string">&quot;task_state&quot;</span>,<br>                <span class="hljs-string">&quot;limit&quot;</span>: <span class="hljs-number">3</span><br>            &#125;)<br><br>            <span class="hljs-keyword">if</span> task_notes:<br>                content = <span class="hljs-string">&quot;\n&quot;</span>.join([<span class="hljs-string">f&quot;- <span class="hljs-subst">&#123;note[<span class="hljs-string">&#x27;title&#x27;</span>]&#125;</span>&quot;</span> <span class="hljs-keyword">for</span> note <span class="hljs-keyword">in</span> task_notes])<br>                packets.append(ContextPacket(<br>                    content=<span class="hljs-string">f&quot;[当前任务]\n<span class="hljs-subst">&#123;content&#125;</span>&quot;</span>,<br>                    timestamp=datetime.now(),<br>                    token_count=<span class="hljs-built_in">len</span>(content) // <span class="hljs-number">4</span>,<br>                    relevance_score=<span class="hljs-number">0.8</span>,<br>                    metadata=&#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;task_plan&quot;</span>, <span class="hljs-string">&quot;source&quot;</span>: <span class="hljs-string">&quot;notes&quot;</span>&#125;<br>                ))<br><br>        <span class="hljs-keyword">return</span> packets<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_retrieve_relevant_notes</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span>, limit: <span class="hljs-built_in">int</span> = <span class="hljs-number">3</span></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;检索相关笔记&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-comment"># 优先检索 blocker</span><br>            blockers = self.note_tool.run(&#123;<br>                <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;list&quot;</span>,<br>                <span class="hljs-string">&quot;note_type&quot;</span>: <span class="hljs-string">&quot;blocker&quot;</span>,<br>                <span class="hljs-string">&quot;limit&quot;</span>: <span class="hljs-number">2</span><br>            &#125;)<br><br>            <span class="hljs-comment"># 搜索相关笔记</span><br>            search_results = self.note_tool.run(&#123;<br>                <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;search&quot;</span>,<br>                <span class="hljs-string">&quot;query&quot;</span>: query,<br>                <span class="hljs-string">&quot;limit&quot;</span>: limit<br>            &#125;)<br><br>            <span class="hljs-comment"># 合并去重</span><br>            all_notes = &#123;note.get(<span class="hljs-string">&#x27;note_id&#x27;</span>) <span class="hljs-keyword">or</span> note.get(<span class="hljs-string">&#x27;id&#x27;</span>): note <span class="hljs-keyword">for</span> note <span class="hljs-keyword">in</span> (blockers <span class="hljs-keyword">or</span> []) + (search_results <span class="hljs-keyword">or</span> [])&#125;<br>            <span class="hljs-keyword">return</span> <span class="hljs-built_in">list</span>(all_notes.values())[:limit]<br><br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[WARNING] 笔记检索失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>            <span class="hljs-keyword">return</span> []<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_notes_to_packets</span>(<span class="hljs-params">self, notes: <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>]</span>) -&gt; <span class="hljs-type">List</span>[ContextPacket]:<br>        <span class="hljs-string">&quot;&quot;&quot;将笔记转换为上下文包&quot;&quot;&quot;</span><br>        packets = []<br><br>        <span class="hljs-keyword">for</span> note <span class="hljs-keyword">in</span> notes:<br>            <span class="hljs-comment"># 根据笔记类型设置不同的相关性分数</span><br>            relevance_map = &#123;<br>                <span class="hljs-string">&quot;blocker&quot;</span>: <span class="hljs-number">0.9</span>,<br>                <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-number">0.8</span>,<br>                <span class="hljs-string">&quot;task_state&quot;</span>: <span class="hljs-number">0.75</span>,<br>                <span class="hljs-string">&quot;conclusion&quot;</span>: <span class="hljs-number">0.7</span><br>            &#125;<br><br>            note_type = note.get(<span class="hljs-string">&#x27;type&#x27;</span>, <span class="hljs-string">&#x27;general&#x27;</span>)<br>            relevance = relevance_map.get(note_type, <span class="hljs-number">0.6</span>)<br><br>            content = <span class="hljs-string">f&quot;[笔记:<span class="hljs-subst">&#123;note.get(<span class="hljs-string">&#x27;title&#x27;</span>, <span class="hljs-string">&#x27;Untitled&#x27;</span>)&#125;</span>]\n类型: <span class="hljs-subst">&#123;note_type&#125;</span>\n\n<span class="hljs-subst">&#123;note.get(<span class="hljs-string">&#x27;content&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)&#125;</span>&quot;</span><br><br>            packets.append(ContextPacket(<br>                content=content,<br>                timestamp=datetime.fromisoformat(note.get(<span class="hljs-string">&#x27;updated_at&#x27;</span>, datetime.now().isoformat())),<br>                token_count=<span class="hljs-built_in">len</span>(content) // <span class="hljs-number">4</span>,<br>                relevance_score=relevance,<br>                metadata=&#123;<br>                    <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;note&quot;</span>,<br>                    <span class="hljs-string">&quot;note_type&quot;</span>: note_type,<br>                    <span class="hljs-string">&quot;note_id&quot;</span>: note.get(<span class="hljs-string">&#x27;note_id&#x27;</span>) <span class="hljs-keyword">or</span> note.get(<span class="hljs-string">&#x27;id&#x27;</span>)<br>                &#125;<br>            ))<br><br>        <span class="hljs-keyword">return</span> packets<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_build_system_instructions</span>(<span class="hljs-params">self, mode: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;构建系统指令&quot;&quot;&quot;</span><br>        base_instructions = <span class="hljs-string">f&quot;&quot;&quot;你是 <span class="hljs-subst">&#123;self.project_name&#125;</span> 项目的代码库维护助手。</span><br><span class="hljs-string"></span><br><span class="hljs-string">你的核心能力:</span><br><span class="hljs-string">1. 使用 TerminalTool 探索代码库(ls, cat, grep, find等)</span><br><span class="hljs-string">2. 使用 NoteTool 记录发现和任务</span><br><span class="hljs-string">3. 基于历史笔记提供连贯的建议</span><br><span class="hljs-string"></span><br><span class="hljs-string">当前会话ID: <span class="hljs-subst">&#123;self.session_id&#125;</span></span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br>        mode_specific = &#123;<br>            <span class="hljs-string">&quot;explore&quot;</span>: <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">当前模式: 探索代码库</span><br><span class="hljs-string"></span><br><span class="hljs-string">你应该:</span><br><span class="hljs-string">- 主动使用 terminal 命令了解代码结构</span><br><span class="hljs-string">- 识别关键模块和文件</span><br><span class="hljs-string">- 记录项目架构到笔记</span><br><span class="hljs-string">&quot;&quot;&quot;</span>,<br>            <span class="hljs-string">&quot;analyze&quot;</span>: <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">当前模式: 分析代码质量</span><br><span class="hljs-string"></span><br><span class="hljs-string">你应该:</span><br><span class="hljs-string">- 查找代码问题(重复、复杂度、TODO等)</span><br><span class="hljs-string">- 评估代码质量</span><br><span class="hljs-string">- 将发现的问题记录为 blocker 或 action 笔记</span><br><span class="hljs-string">&quot;&quot;&quot;</span>,<br>            <span class="hljs-string">&quot;plan&quot;</span>: <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">当前模式: 任务规划</span><br><span class="hljs-string"></span><br><span class="hljs-string">你应该:</span><br><span class="hljs-string">- 回顾历史笔记和任务</span><br><span class="hljs-string">- 制定下一步行动计划</span><br><span class="hljs-string">- 更新任务状态笔记</span><br><span class="hljs-string">&quot;&quot;&quot;</span>,<br>            <span class="hljs-string">&quot;auto&quot;</span>: <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">当前模式: 自动决策</span><br><span class="hljs-string"></span><br><span class="hljs-string">你应该:</span><br><span class="hljs-string">- 根据用户需求灵活选择策略</span><br><span class="hljs-string">- 在需要时使用工具</span><br><span class="hljs-string">- 保持回答的专业性和实用性</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br>        &#125;<br><br>        <span class="hljs-keyword">return</span> base_instructions + mode_specific.get(mode, mode_specific[<span class="hljs-string">&quot;auto&quot;</span>])<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_postprocess_response</span>(<span class="hljs-params">self, user_input: <span class="hljs-built_in">str</span>, response: <span class="hljs-built_in">str</span></span>):<br>        <span class="hljs-string">&quot;&quot;&quot;后处理:分析回答,自动记录重要信息&quot;&quot;&quot;</span><br><br>        <span class="hljs-comment"># 如果发现问题,自动创建 blocker 笔记</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">any</span>(keyword <span class="hljs-keyword">in</span> response.lower() <span class="hljs-keyword">for</span> keyword <span class="hljs-keyword">in</span> [<span class="hljs-string">&quot;问题&quot;</span>, <span class="hljs-string">&quot;bug&quot;</span>, <span class="hljs-string">&quot;错误&quot;</span>, <span class="hljs-string">&quot;阻塞&quot;</span>]):<br>            <span class="hljs-keyword">try</span>:<br>                self.note_tool.run(&#123;<br>                    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create&quot;</span>,<br>                    <span class="hljs-string">&quot;title&quot;</span>: <span class="hljs-string">f&quot;发现问题: <span class="hljs-subst">&#123;user_input[:<span class="hljs-number">30</span>]&#125;</span>...&quot;</span>,<br>                    <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">f&quot;## 用户输入\n<span class="hljs-subst">&#123;user_input&#125;</span>\n\n## 问题分析\n<span class="hljs-subst">&#123;response[:<span class="hljs-number">500</span>]&#125;</span>...&quot;</span>,<br>                    <span class="hljs-string">&quot;note_type&quot;</span>: <span class="hljs-string">&quot;blocker&quot;</span>,<br>                    <span class="hljs-string">&quot;tags&quot;</span>: [self.project_name, <span class="hljs-string">&quot;auto_detected&quot;</span>, self.session_id]<br>                &#125;)<br>                self.stats[<span class="hljs-string">&quot;notes_created&quot;</span>] += <span class="hljs-number">1</span><br>                self.stats[<span class="hljs-string">&quot;issues_found&quot;</span>] += <span class="hljs-number">1</span><br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;📝 已自动创建问题笔记&quot;</span>)<br>            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[WARNING] 创建笔记失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br><br>        <span class="hljs-comment"># 如果是任务规划,自动创建 action 笔记</span><br>        <span class="hljs-keyword">elif</span> <span class="hljs-built_in">any</span>(keyword <span class="hljs-keyword">in</span> user_input.lower() <span class="hljs-keyword">for</span> keyword <span class="hljs-keyword">in</span> [<span class="hljs-string">&quot;计划&quot;</span>, <span class="hljs-string">&quot;下一步&quot;</span>, <span class="hljs-string">&quot;任务&quot;</span>, <span class="hljs-string">&quot;todo&quot;</span>]):<br>            <span class="hljs-keyword">try</span>:<br>                self.note_tool.run(&#123;<br>                    <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create&quot;</span>,<br>                    <span class="hljs-string">&quot;title&quot;</span>: <span class="hljs-string">f&quot;任务规划: <span class="hljs-subst">&#123;user_input[:<span class="hljs-number">30</span>]&#125;</span>...&quot;</span>,<br>                    <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">f&quot;## 讨论\n<span class="hljs-subst">&#123;user_input&#125;</span>\n\n## 行动计划\n<span class="hljs-subst">&#123;response[:<span class="hljs-number">500</span>]&#125;</span>...&quot;</span>,<br>                    <span class="hljs-string">&quot;note_type&quot;</span>: <span class="hljs-string">&quot;action&quot;</span>,<br>                    <span class="hljs-string">&quot;tags&quot;</span>: [self.project_name, <span class="hljs-string">&quot;planning&quot;</span>, self.session_id]<br>                &#125;)<br>                self.stats[<span class="hljs-string">&quot;notes_created&quot;</span>] += <span class="hljs-number">1</span><br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;📝 已自动创建行动计划笔记&quot;</span>)<br>            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[WARNING] 创建笔记失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_update_history</span>(<span class="hljs-params">self, user_input: <span class="hljs-built_in">str</span>, response: <span class="hljs-built_in">str</span></span>):<br>        <span class="hljs-string">&quot;&quot;&quot;更新对话历史&quot;&quot;&quot;</span><br>        self.conversation_history.append(<br>            Message(content=user_input, role=<span class="hljs-string">&quot;user&quot;</span>, timestamp=datetime.now())<br>        )<br>        self.conversation_history.append(<br>            Message(content=response, role=<span class="hljs-string">&quot;assistant&quot;</span>, timestamp=datetime.now())<br>        )<br><br>        <span class="hljs-comment"># 限制历史长度(保留最近10轮对话)</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(self.conversation_history) &gt; <span class="hljs-number">20</span>:<br>            self.conversation_history = self.conversation_history[-<span class="hljs-number">20</span>:]<br><br>    <span class="hljs-comment"># === 便捷方法 ===</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">explore</span>(<span class="hljs-params">self, target: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;.&quot;</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;探索代码库&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> self.run(<span class="hljs-string">f&quot;请探索 <span class="hljs-subst">&#123;target&#125;</span> 的代码结构&quot;</span>, mode=<span class="hljs-string">&quot;explore&quot;</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">analyze</span>(<span class="hljs-params">self, focus: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;&quot;</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;分析代码质量&quot;&quot;&quot;</span><br>        query = <span class="hljs-string">f&quot;请分析代码质量&quot;</span> + (<span class="hljs-string">f&quot;,重点关注<span class="hljs-subst">&#123;focus&#125;</span>&quot;</span> <span class="hljs-keyword">if</span> focus <span class="hljs-keyword">else</span> <span class="hljs-string">&quot;&quot;</span>)<br>        <span class="hljs-keyword">return</span> self.run(query, mode=<span class="hljs-string">&quot;analyze&quot;</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">plan_next_steps</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;规划下一步任务&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> self.run(<span class="hljs-string">&quot;根据当前进度,规划下一步任务&quot;</span>, mode=<span class="hljs-string">&quot;plan&quot;</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">execute_command</span>(<span class="hljs-params">self, command: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;执行终端命令&quot;&quot;&quot;</span><br>        result = self.terminal_tool.run(&#123;<span class="hljs-string">&quot;command&quot;</span>: command&#125;)<br>        self.stats[<span class="hljs-string">&quot;commands_executed&quot;</span>] += <span class="hljs-number">1</span><br>        <span class="hljs-keyword">return</span> result<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">create_note</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        title: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        content: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        note_type: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;general&quot;</span>,</span><br><span class="hljs-params">        tags: <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span></span><br><span class="hljs-params">    </span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;创建笔记&quot;&quot;&quot;</span><br>        result = self.note_tool.run(&#123;<br>            <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;create&quot;</span>,<br>            <span class="hljs-string">&quot;title&quot;</span>: title,<br>            <span class="hljs-string">&quot;content&quot;</span>: content,<br>            <span class="hljs-string">&quot;note_type&quot;</span>: note_type,<br>            <span class="hljs-string">&quot;tags&quot;</span>: tags <span class="hljs-keyword">or</span> [self.project_name]<br>        &#125;)<br>        self.stats[<span class="hljs-string">&quot;notes_created&quot;</span>] += <span class="hljs-number">1</span><br>        <span class="hljs-keyword">return</span> result<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_stats</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;获取统计信息&quot;&quot;&quot;</span><br>        duration = (datetime.now() - self.stats[<span class="hljs-string">&quot;session_start&quot;</span>]).total_seconds()<br><br>        <span class="hljs-comment"># 获取笔记摘要</span><br>        <span class="hljs-keyword">try</span>:<br>            note_summary = self.note_tool.run(&#123;<span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;summary&quot;</span>&#125;)<br>        <span class="hljs-keyword">except</span>:<br>            note_summary = &#123;&#125;<br><br>        <span class="hljs-keyword">return</span> &#123;<br>            <span class="hljs-string">&quot;session_info&quot;</span>: &#123;<br>                <span class="hljs-string">&quot;session_id&quot;</span>: self.session_id,<br>                <span class="hljs-string">&quot;project&quot;</span>: self.project_name,<br>                <span class="hljs-string">&quot;duration_seconds&quot;</span>: duration<br>            &#125;,<br>            <span class="hljs-string">&quot;activity&quot;</span>: &#123;<br>                <span class="hljs-string">&quot;commands_executed&quot;</span>: self.stats[<span class="hljs-string">&quot;commands_executed&quot;</span>],<br>                <span class="hljs-string">&quot;notes_created&quot;</span>: self.stats[<span class="hljs-string">&quot;notes_created&quot;</span>],<br>                <span class="hljs-string">&quot;issues_found&quot;</span>: self.stats[<span class="hljs-string">&quot;issues_found&quot;</span>]<br>            &#125;,<br>            <span class="hljs-string">&quot;notes&quot;</span>: note_summary<br>        &#125;<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">generate_report</span>(<span class="hljs-params">self, save_to_file: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span></span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;生成会话报告&quot;&quot;&quot;</span><br>        report = self.get_stats()<br><br>        <span class="hljs-keyword">if</span> save_to_file:<br>            report_file = <span class="hljs-string">f&quot;maintainer_report_<span class="hljs-subst">&#123;self.session_id&#125;</span>.json&quot;</span><br>            <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(report_file, <span class="hljs-string">&#x27;w&#x27;</span>, encoding=<span class="hljs-string">&#x27;utf-8&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>                json.dump(report, f, ensure_ascii=<span class="hljs-literal">False</span>, indent=<span class="hljs-number">2</span>, default=<span class="hljs-built_in">str</span>)<br>            report[<span class="hljs-string">&quot;report_file&quot;</span>] = report_file<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;📄 报告已保存: <span class="hljs-subst">&#123;report_file&#125;</span>&quot;</span>)<br><br>        <span class="hljs-keyword">return</span> report<br></code></pre></td></tr></table></figure><h3 id="9-6-4-完整使用示例"><a href="#9-6-4-完整使用示例" class="headerlink" title="9.6.4 完整使用示例"></a>9.6.4 完整使用示例</h3><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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># ========== 初始化助手 ==========</span><br><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> HelloAgentsLLM<br><br>maintainer = CodebaseMaintainer(<br>    project_name=<span class="hljs-string">&quot;my_flask_app&quot;</span>,<br>    codebase_path=<span class="hljs-string">&quot;./my_flask_app&quot;</span>,<br>    llm=HelloAgentsLLM()<br>)<br><br><br><span class="hljs-comment"># ========== 第一天:探索代码库 ==========</span><br><span class="hljs-comment"># 1. 初步探索</span><br>response = maintainer.explore()<br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">🔍 探索代码库结构...</span><br><span class="hljs-string">🤖 正在思考...</span><br><span class="hljs-string"></span><br><span class="hljs-string">🤖 助手: 我已经扫描了代码库,这是一个 Flask Web 应用,主要包含以下模块:</span><br><span class="hljs-string"></span><br><span class="hljs-string">📁 项目结构:</span><br><span class="hljs-string">- app/</span><br><span class="hljs-string">  ├── models/      # 数据模型(User, Product, Order等)</span><br><span class="hljs-string">  ├── routes/      # API路由</span><br><span class="hljs-string">  ├── services/    # 业务逻辑</span><br><span class="hljs-string">  └── utils/       # 工具函数</span><br><span class="hljs-string">- tests/           # 测试文件</span><br><span class="hljs-string">- migrations/      # 数据库迁移</span><br><span class="hljs-string">- config.py        # 配置文件</span><br><span class="hljs-string"></span><br><span class="hljs-string">总计约 3,500 行 Python 代码。</span><br><span class="hljs-string"></span><br><span class="hljs-string">建议下一步:</span><br><span class="hljs-string">1. 查看核心模型定义</span><br><span class="hljs-string">2. 检查路由和API设计</span><br><span class="hljs-string">3. 评估测试覆盖率</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># 2. 深入分析某个模块</span><br>response = maintainer.run(<span class="hljs-string">&quot;请分析 app/models/ 目录下的数据模型设计&quot;</span>)<br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">🤖 助手: 我查看了 app/models/ 目录,发现以下数据模型:</span><br><span class="hljs-string"></span><br><span class="hljs-string">1. User (user.py)</span><br><span class="hljs-string">   - 字段: id, username, email, password_hash, created_at</span><br><span class="hljs-string">   - 关系: 一对多 Order</span><br><span class="hljs-string">   - 问题: ⚠️ 缺少索引,email 字段未设置唯一约束</span><br><span class="hljs-string"></span><br><span class="hljs-string">2. Product (product.py)</span><br><span class="hljs-string">   - 字段: id, name, description, price, stock</span><br><span class="hljs-string">   - 问题: ✅ 设计合理</span><br><span class="hljs-string"></span><br><span class="hljs-string">3. Order (order.py)</span><br><span class="hljs-string">   - 字段: id, user_id, product_id, quantity, status</span><br><span class="hljs-string">   - 问题: ⚠️ 缺少创建时间字段,不利于数据分析</span><br><span class="hljs-string"></span><br><span class="hljs-string">建议:</span><br><span class="hljs-string">1. 为 User.email 添加唯一约束</span><br><span class="hljs-string">2. 为 Order 添加 created_at 和 updated_at 字段</span><br><span class="hljs-string">3. 考虑添加软删除支持</span><br><span class="hljs-string"></span><br><span class="hljs-string">我已将这些问题记录为 blocker 笔记。</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-comment"># [自动创建了 blocker 笔记]</span><br><br><br><span class="hljs-comment"># ========== 第二天:分析代码质量 ==========</span><br><br><span class="hljs-comment"># 1. 整体质量分析</span><br>response = maintainer.analyze()<br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">📊 分析代码质量...</span><br><span class="hljs-string">🤖 正在思考...</span><br><span class="hljs-string"></span><br><span class="hljs-string">🤖 助手: 代码质量分析报告:</span><br><span class="hljs-string"></span><br><span class="hljs-string">📊 代码统计:</span><br><span class="hljs-string">- 总行数: 3,542 行</span><br><span class="hljs-string">- 平均文件长度: 约 150 行</span><br><span class="hljs-string">- 最大文件: services/order_service.py (456 行)</span><br><span class="hljs-string"></span><br><span class="hljs-string">⚠️ 发现的问题:</span><br><span class="hljs-string"></span><br><span class="hljs-string">1. 代码重复 (HIGH):</span><br><span class="hljs-string">   - user_service.py 和 product_service.py 有相似的 CRUD 逻辑</span><br><span class="hljs-string">   - 建议: 提取基类 BaseService</span><br><span class="hljs-string"></span><br><span class="hljs-string">2. 复杂度过高 (MEDIUM):</span><br><span class="hljs-string">   - order_service.py::process_order 方法有 8 层嵌套</span><br><span class="hljs-string">   - 建议: 拆分为多个小方法</span><br><span class="hljs-string"></span><br><span class="hljs-string">3. 缺少测试 (HIGH):</span><br><span class="hljs-string">   - 测试覆盖率仅 45%</span><br><span class="hljs-string">   - services/ 目录完全没有测试</span><br><span class="hljs-string"></span><br><span class="hljs-string">4. TODO/FIXME (12 个):</span><br><span class="hljs-string">   - 大部分是&quot;稍后优化&quot;类的注释</span><br><span class="hljs-string">   - 建议: 清理或转化为正式任务</span><br><span class="hljs-string"></span><br><span class="hljs-string">我已将这些问题记录为笔记,并标记优先级。</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-comment"># [自动创建了多个 blocker 笔记]</span><br><br><span class="hljs-comment"># 2. 查看具体问题</span><br>response = maintainer.run(<span class="hljs-string">&quot;请查看 order_service.py 的 process_order 方法,给出重构建议&quot;</span>)<br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">🤖 助手: 我查看了 process_order 方法的代码:</span><br><span class="hljs-string"></span><br><span class="hljs-string">【原始代码】</span><br><span class="hljs-string">def process_order(self, order_id):</span><br><span class="hljs-string">    order = Order.query.get(order_id)</span><br><span class="hljs-string">    if order:</span><br><span class="hljs-string">        if order.status == &#x27;pending&#x27;:</span><br><span class="hljs-string">            product = Product.query.get(order.product_id)</span><br><span class="hljs-string">            if product:</span><br><span class="hljs-string">                if product.stock &gt;= order.quantity:</span><br><span class="hljs-string">                    # ... 8 层嵌套继续 ...</span><br><span class="hljs-string"></span><br><span class="hljs-string">【问题分析】</span><br><span class="hljs-string">1. 过深的嵌套导致可读性差</span><br><span class="hljs-string">2. 缺少错误处理</span><br><span class="hljs-string">3. 业务逻辑与数据访问混在一起</span><br><span class="hljs-string"></span><br><span class="hljs-string">【重构建议】</span><br><span class="hljs-string">def process_order(self, order_id):</span><br><span class="hljs-string">    # 1. 早返回模式,减少嵌套</span><br><span class="hljs-string">    order = self._get_order(order_id)</span><br><span class="hljs-string">    if not order or order.status != &#x27;pending&#x27;:</span><br><span class="hljs-string">        return False</span><br><span class="hljs-string"></span><br><span class="hljs-string">    # 2. 提取方法,单一职责</span><br><span class="hljs-string">    if not self._check_stock(order):</span><br><span class="hljs-string">        return False</span><br><span class="hljs-string"></span><br><span class="hljs-string">    # 3. 使用事务确保一致性</span><br><span class="hljs-string">    with db.transaction():</span><br><span class="hljs-string">        self._update_stock(order)</span><br><span class="hljs-string">        self._update_order_status(order, &#x27;processing&#x27;)</span><br><span class="hljs-string"></span><br><span class="hljs-string">    return True</span><br><span class="hljs-string"></span><br><span class="hljs-string">【下一步】</span><br><span class="hljs-string">1. 我已创建重构计划笔记</span><br><span class="hljs-string">2. 建议先编写测试用例</span><br><span class="hljs-string">3. 逐步重构,保持功能不变</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-comment"># [自动创建了 action 笔记]</span><br><br><br><span class="hljs-comment"># ========== 第三天:规划重构任务 ==========</span><br><br><span class="hljs-comment"># 1. 回顾进度</span><br>response = maintainer.plan_next_steps()<br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">📋 加载任务规划...</span><br><span class="hljs-string">🤖 正在思考...</span><br><span class="hljs-string"></span><br><span class="hljs-string">🤖 助手: 基于前两天的探索和分析,我整理了重构任务优先级:</span><br><span class="hljs-string"></span><br><span class="hljs-string">🔴 高优先级 (本周完成):</span><br><span class="hljs-string">1. [blocker] User.email 添加唯一约束</span><br><span class="hljs-string">   - 风险: 低,只是数据库迁移</span><br><span class="hljs-string">   - 工作量: 1小时</span><br><span class="hljs-string"></span><br><span class="hljs-string">2. [blocker] 提取 BaseService 减少代码重复</span><br><span class="hljs-string">   - 风险: 中,影响多个服务类</span><br><span class="hljs-string">   - 工作量: 半天</span><br><span class="hljs-string"></span><br><span class="hljs-string">3. [blocker] 重构 process_order 方法</span><br><span class="hljs-string">   - 风险: 高,核心业务逻辑</span><br><span class="hljs-string">   - 工作量: 1天(包括编写测试)</span><br><span class="hljs-string"></span><br><span class="hljs-string">🟡 中优先级 (下周):</span><br><span class="hljs-string">4. 为 services/ 添加单元测试</span><br><span class="hljs-string">5. 清理 TODO 注释</span><br><span class="hljs-string">6. 为 Order 添加时间字段</span><br><span class="hljs-string"></span><br><span class="hljs-string">🟢 低优先级 (待定):</span><br><span class="hljs-string">7. 性能优化</span><br><span class="hljs-string">8. 文档更新</span><br><span class="hljs-string"></span><br><span class="hljs-string">建议:</span><br><span class="hljs-string">- 本周先解决数据模型问题(任务1、6)</span><br><span class="hljs-string">- 下周进行代码重构(任务2、3)</span><br><span class="hljs-string">- 持续增加测试覆盖率(任务4)</span><br><span class="hljs-string"></span><br><span class="hljs-string">我已更新任务状态笔记,您可以随时查看进度。</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># 2. 手动创建详细的重构计划</span><br>maintainer.create_note(<br>    title=<span class="hljs-string">&quot;本周重构计划 - Week 1&quot;</span>,<br>    content=<span class="hljs-string">&quot;&quot;&quot;## 目标</span><br><span class="hljs-string">完成数据模型层的优化</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 任务清单</span><br><span class="hljs-string">- [ ] 为 User.email 添加唯一约束</span><br><span class="hljs-string">- [ ] 为 Order 添加 created_at, updated_at 字段</span><br><span class="hljs-string">- [ ] 编写数据库迁移脚本</span><br><span class="hljs-string">- [ ] 更新相关测试用例</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 时间安排</span><br><span class="hljs-string">- 周一: 设计迁移脚本</span><br><span class="hljs-string">- 周二-周三: 执行迁移并测试</span><br><span class="hljs-string">- 周四: 更新测试用例</span><br><span class="hljs-string">- 周五: Code Review</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 风险</span><br><span class="hljs-string">- 数据库迁移可能影响线上环境,需要在非高峰期执行</span><br><span class="hljs-string">- 现有数据中可能存在重复email,需要先清理</span><br><span class="hljs-string">&quot;&quot;&quot;</span>,<br>    note_type=<span class="hljs-string">&quot;task_state&quot;</span>,<br>    tags=[<span class="hljs-string">&quot;refactoring&quot;</span>, <span class="hljs-string">&quot;week1&quot;</span>, <span class="hljs-string">&quot;high_priority&quot;</span>]<br>)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;✅ 已创建详细的重构计划&quot;</span>)<br><br><br><span class="hljs-comment"># ========== 一周后:检查进度 ==========</span><br><br><span class="hljs-comment"># 查看笔记摘要</span><br>summary = maintainer.note_tool.run(&#123;<span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;summary&quot;</span>&#125;)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;📊 笔记摘要:&quot;</span>)<br><span class="hljs-built_in">print</span>(json.dumps(summary, indent=<span class="hljs-number">2</span>, ensure_ascii=<span class="hljs-literal">False</span>))<br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">&#123;</span><br><span class="hljs-string">  &quot;total_notes&quot;: 8,</span><br><span class="hljs-string">  &quot;type_distribution&quot;: &#123;</span><br><span class="hljs-string">    &quot;blocker&quot;: 3,</span><br><span class="hljs-string">    &quot;action&quot;: 2,</span><br><span class="hljs-string">    &quot;task_state&quot;: 2,</span><br><span class="hljs-string">    &quot;conclusion&quot;: 1</span><br><span class="hljs-string">  &#125;,</span><br><span class="hljs-string">  &quot;recent_notes&quot;: [</span><br><span class="hljs-string">    &#123;</span><br><span class="hljs-string">      &quot;id&quot;: &quot;note_20250119_160000_7&quot;,</span><br><span class="hljs-string">      &quot;title&quot;: &quot;本周重构计划 - Week 1&quot;,</span><br><span class="hljs-string">      &quot;type&quot;: &quot;task_state&quot;,</span><br><span class="hljs-string">      &quot;updated_at&quot;: &quot;2025-01-19T16:00:00&quot;</span><br><span class="hljs-string">    &#125;,</span><br><span class="hljs-string">    ...</span><br><span class="hljs-string">  ]</span><br><span class="hljs-string">&#125;</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-comment"># 生成完整报告</span><br>report = maintainer.generate_report()<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n📄 会话报告:&quot;</span>)<br><span class="hljs-built_in">print</span>(json.dumps(report, indent=<span class="hljs-number">2</span>, ensure_ascii=<span class="hljs-literal">False</span>))<br><span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">&#123;</span><br><span class="hljs-string">  &quot;session_info&quot;: &#123;</span><br><span class="hljs-string">    &quot;session_id&quot;: &quot;session_20250119_150000&quot;,</span><br><span class="hljs-string">    &quot;project&quot;: &quot;my_flask_app&quot;,</span><br><span class="hljs-string">    &quot;duration_seconds&quot;: 172800  # 2天</span><br><span class="hljs-string">  &#125;,</span><br><span class="hljs-string">  &quot;activity&quot;: &#123;</span><br><span class="hljs-string">    &quot;commands_executed&quot;: 24,</span><br><span class="hljs-string">    &quot;notes_created&quot;: 8,</span><br><span class="hljs-string">    &quot;issues_found&quot;: 3</span><br><span class="hljs-string">  &#125;,</span><br><span class="hljs-string">  &quot;notes&quot;: &#123; ... &#125;</span><br><span class="hljs-string">&#125;</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><h3 id="9-6-5-运行效果分析"><a href="#9-6-5-运行效果分析" class="headerlink" title="9.6.5 运行效果分析"></a>9.6.5 运行效果分析</h3><p>通过这个完整的案例，我们可以看到长程智能体的几个关键特性。首先是跨会话的连贯性，智能体通过 NoteTool 保持了跨多天、多个会话的任务连贯性，第一天探索的问题在第二天分析时被自动考虑，第三天规划时能够综合前两天的所有发现，一周后检查时完整的历史都被保留。其次是智能的上下文管理，ContextBuilder 确保每次对话都有高质量的上下文，自动汇集相关笔记(特别是 blocker 类型)，根据对话模式动态调整预处理策略，在 token 预算内选择最相关的信息。</p><p>第三个特性是即时的文件系统访问，TerminalTool 支持灵活的代码探索，无需预先索引整个代码库，可以即时查看具体文件内容，支持复杂的文本处理(grep、awk等)。第四是自动化的知识管理，系统自动化地管理发现的知识，发现问题时自动创建 blocker 笔记，讨论计划时自动创建 action 笔记，关键信息自动存储到记忆系统。最后是人机协作，这个系统支持灵活的人机协作模式，智能体可以自动化地完成探索和分析，人类可以通过笔记系统进行干预和指导，支持手动创建详细的计划笔记。</p><p>这个基础框架可以进一步扩展，比如集成 RAGTool 为代码库建立向量索引结合语义检索，拆分为专门的探索者、分析者、规划者实现多智能体协作，集成测试工具自动验证重构结果，通过 TerminalTool 执行 git 命令追踪代码变更，或者使用 Gradio&#x2F;Streamlit 构建可视化界面。</p><h2 id="9-7-本章总结"><a href="#9-7-本章总结" class="headerlink" title="9.7 本章总结"></a>9.7 本章总结</h2><p>在本章中，我们深入探讨了上下文工程的理论基础和工程实践：</p><h3 id="理论层面"><a href="#理论层面" class="headerlink" title="理论层面"></a>理论层面</h3><ol><li><strong>上下文工程的本质</strong>：从”提示工程”到”上下文工程”的演进，核心是管理有限的注意力预算</li><li><strong>上下文腐蚀</strong>：理解长上下文带来的性能下降，认识到上下文是稀缺资源</li><li><strong>三大策略</strong>：压缩整合、结构化笔记、子代理架构</li></ol><h3 id="工程实践"><a href="#工程实践" class="headerlink" title="工程实践"></a>工程实践</h3><ol><li><strong>ContextBuilder</strong>：实现了 GSSC 流水线，提供统一的上下文管理接口</li><li><strong>NoteTool</strong>：Markdown+YAML 的混合格式，支持结构化的长期记忆</li><li><strong>TerminalTool</strong>：安全的命令行工具，支持即时的文件系统访问</li><li><strong>长程智能体</strong>：整合三大工具，构建了跨会话的代码库维护助手</li></ol><h3 id="核心收获"><a href="#核心收获" class="headerlink" title="核心收获"></a>核心收获</h3><ul><li><strong>分层设计</strong>：即时访问(TerminalTool) + 会话记忆(MemoryTool) + 持久笔记(NoteTool)</li><li><strong>智能筛选</strong>：基于相关性和新近性的评分机制</li><li><strong>安全第一</strong>：多层安全机制确保系统稳定</li><li><strong>人机协作</strong>：自动化与可控性的平衡</li></ul><p>通过这一章的学习，您不仅掌握了上下文工程的核心技术，更重要的是理解了如何构建能够在长时间跨度内保持连贯性和有效性的智能体系统。这些技能将成为您构建生产级智能体应用的重要基础。</p><p>在下一章中，我们将探讨智能体通信协议，学习如何让智能体与外部世界进行更广泛的交互。</p><h2 id="习题"><a href="#习题" class="headerlink" title="习题"></a>习题</h2><blockquote><p><strong>提示</strong>：部分习题没有标准答案，重点在于培养学习者对上下文工程和长时程任务管理的综合理解和实践能力。</p></blockquote><ol><li><p>本章介绍了上下文工程与提示工程的区别。请分析：</p><ul><li>在9.1节中提到”上下文必须被视作一种有限资源，且具有边际收益递减”。请解释什么是”上下文腐蚀”（context rot）现象？为什么即使模型支持100K甚至200K的上下文窗口，我们仍然需要谨慎管理上下文？</li><li>假设你要构建一个”代码审查助手”，需要分析一个包含50个文件的代码库。请对比两种策略：（1）一次性将所有文件内容加载到上下文中；（2）使用JIT（Just-in-time）上下文，通过工具按需检索文件。分析各自的优缺点和适用场景。</li><li>在9.2.1节中提到系统提示的两个极端误区：”过度硬编码”和”过于空泛”。请各举一个实际例子，并说明如何找到合适的平衡点。</li></ul></li><li><p>GSSC（Gather-Select-Structure-Compress）流水线是本章的核心技术。请深入思考：</p><blockquote><p><strong>提示</strong>：这是一道动手实践题，建议实际操作</p></blockquote><ul><li>在9.3节的ContextBuilder实现中，四个阶段各有不同的职责。请分析：如果某个阶段失效（如Select阶段选择了不相关的信息，或Compress阶段过度压缩导致信息丢失），会对最终的智能体表现产生什么影响？</li><li>请基于9.3.4节的代码，为ContextBuilder添加一个”上下文质量评估”功能：在每次构建上下文后，自动评估上下文的信息密度、相关性和完整性，并给出优化建议。</li><li>GSSC流水线中的”压缩”阶段使用了LLM进行智能摘要。请思考：在什么情况下，简单的截断（truncation）或滑动窗口（sliding window）策略可能比LLM摘要更合适？设计一个混合压缩策略，结合多种压缩方法的优势。</li></ul></li><li><p>NoteTool和TerminalTool是支持长时程任务的关键工具。基于9.4节和9.5节的内容，请完成以下扩展实践：</p><blockquote><p><strong>提示</strong>：这是一道动手实践题，建议实际操作</p></blockquote><ul><li>NoteTool使用了分层笔记系统（项目笔记、任务笔记、临时笔记）。请设计一个”笔记自动整理”机制：当临时笔记积累到一定数量时，智能体能够自动分析这些笔记，将重要信息提升为任务笔记或项目笔记，并清理冗余内容。</li><li>TerminalTool提供了文件系统操作能力，但在9.5.2节中强调了安全性设计。请分析：当前的安全机制（路径验证、命令白名单、权限检查）是否足够？如果智能体需要访问敏感文件或执行危险操作，应该如何设计一个”人机协作审批”流程？</li><li>结合NoteTool和TerminalTool，设计一个”智能代码重构助手”：能够分析代码库结构、记录重构计划、逐步执行重构操作，并在笔记中追踪进度和遇到的问题。请画出完整的工作流程图。</li></ul></li><li><p>在9.6节的”长时程任务管理”案例中，我们看到了上下文工程在实际应用中的价值。请深入分析：</p><ul><li>案例中使用了”分层上下文管理”策略：即时访问（TerminalTool）+ 会话记忆（MemoryTool）+ 持久笔记（NoteTool）。请分析：这三层之间应该如何协调？什么信息应该放在哪一层？如何避免信息冗余和不一致？</li><li>假设任务执行过程中发生了中断（如系统崩溃、网络断开），智能体需要从笔记中恢复状态并继续执行。请设计一个”断点续传”机制：如何在笔记中记录足够的状态信息？如何验证恢复后的状态是否正确？</li><li>长时程任务往往涉及多个子任务的并行或串行执行。请设计一个”任务依赖管理”系统：能够表达任务之间的依赖关系（如”任务B必须在任务A完成后执行”），并自动调度任务执行顺序。这个系统应该如何与NoteTool集成？</li></ul></li><li><p>本章多次提到”渐进式披露”（progressive disclosure）的概念。请思考：</p><ul><li>在9.2.2节中，渐进式披露被描述为”每一步交互都会产生新的上下文，反过来指导下一步决策”。请设计一个具体的应用场景（如学术论文写作、复杂问题调试），展示渐进式披露如何帮助智能体更高效地完成任务。</li><li>渐进式披露的一个潜在风险是”探索效率低下”：智能体可能会在不重要的细节上浪费时间，或者错过关键信息。请设计一个”探索引导”机制：通过启发式规则或元认知策略，帮助智能体更聪明地决定”下一步应该探索什么”。</li><li>对比”渐进式披露”与传统的”一次性加载所有上下文”：在什么类型的任务中，前者有明显优势？在什么类型的任务中，后者可能更合适？请给出至少3个不同类型的任务示例。</li></ul></li></ol><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>[1] Anthropic. Effective Context Engineering for AI Agents. <code>https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents</code></p><p>[2] David Kim. Context-Engineering (GitHub). <code>https://github.com/davidkimai/Context-Engineering</code></p>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC9%E7%AB%A0-%E4%B8%8A%E4%B8%8B%E6%96%87%E5%B7%A5%E7%A8%8B/</id>
    <link href="http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC9%E7%AB%A0-%E4%B8%8A%E4%B8%8B%E6%96%87%E5%B7%A5%E7%A8%8B/"/>
    <published>2026-03-01T20:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="第九章-上下文工程"><a href="#第九章-上下文工程" class="headerlink" title="第九章 上下文工程"></a>第九章 上下文工程</h1><p>在前面的章节中，我们已经为智能体引入了记忆系统与RAG。然而，要让智能体在真实复杂场]]>
    </summary>
    <title>第九章 上下文工程</title>
    <updated>2026-03-08T09:24:16.333Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="第八章-记忆与检索"><a href="#第八章-记忆与检索" class="headerlink" title="第八章 记忆与检索"></a>第八章 记忆与检索</h1><p>在前面的章节中，我们构建了HelloAgents框架的基础架构，实现了多种智能体范式和工具系统。不过，我们的框架还缺少一个关键能力：<strong>记忆</strong>。如果智能体无法记住之前的交互内容，也无法从历史经验中学习，那么在连续对话或复杂任务中，其表现将受到极大限制。</p><p>本章将在第七章构建的框架基础上，为HelloAgents增加两个核心能力：<strong>记忆系统（Memory System）</strong>和<strong>检索增强生成（Retrieval-Augmented Generation, RAG）</strong>。我们将采用”框架扩展 + 知识科普”的方式，在构建过程中深入理解Memory和RAG的理论基础，最终实现一个具有完整记忆和知识检索能力的智能体系统。</p><h2 id="8-1-从认知科学到智能体记忆"><a href="#8-1-从认知科学到智能体记忆" class="headerlink" title="8.1 从认知科学到智能体记忆"></a>8.1 从认知科学到智能体记忆</h2><h3 id="8-1-1-人类记忆系统的启发"><a href="#8-1-1-人类记忆系统的启发" class="headerlink" title="8.1.1 人类记忆系统的启发"></a>8.1.1 人类记忆系统的启发</h3><p>在构建智能体的记忆系统之前，让我们先从认知科学的角度理解人类是如何处理和存储信息的。人类记忆是一个多层级的认知系统，它不仅能存储信息，还能根据重要性、时间和上下文对信息进行分类和整理。认知心理学为理解记忆的结构和过程提供了经典的理论框架<sup>[1]</sup>，如图8.1所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/8-figures/8-1.png" alt="人类记忆系统结构图" width="85%"/>  <p>图 8.1 人类记忆系统的层次结构</p></div><p>根据认知心理学的研究，人类记忆可以分为以下几个层次：</p><ol><li><strong>感觉记忆（Sensory Memory）</strong>：持续时间极短（0.5-3秒），容量巨大，负责暂时保存感官接收到的所有信息</li><li><strong>工作记忆（Working Memory）</strong>：持续时间短（15-30秒），容量有限（7±2个项目），负责当前任务的信息处理</li><li><strong>长期记忆（Long-term Memory）</strong>：持续时间长（可达终生），容量几乎无限，进一步分为：<ul><li><strong>程序性记忆</strong>：技能和习惯（如骑自行车）</li><li><strong>陈述性记忆</strong>：可以用语言表达的知识，又分为：<ul><li><strong>语义记忆</strong>：一般知识和概念（如”巴黎是法国首都”）</li><li><strong>情景记忆</strong>：个人经历和事件（如”昨天的会议内容”）</li></ul></li></ul></li></ol><h3 id="8-1-2-为何智能体需要记忆与RAG"><a href="#8-1-2-为何智能体需要记忆与RAG" class="headerlink" title="8.1.2 为何智能体需要记忆与RAG"></a>8.1.2 为何智能体需要记忆与RAG</h3><p>借鉴人类记忆系统的设计，我们可以理解为什么智能体也需要类似的记忆能力。人类智能的一个重要特征就是能够记住过去的经历，从中学习，并将这些经验应用到新的情况中。同样，一个真正智能的智能体也需要具备记忆能力。对于基于LLM的智能体而言，通常面临两个根本性局限：<strong>对话状态的遗忘</strong>和<strong>内置知识的局限</strong>。</p><p>（1）局限一：无状态导致的对话遗忘</p><p>当前的大语言模型虽然强大，但设计上是<strong>无状态的</strong>。这意味着，每一次用户请求（或API调用）都是一次独立的、无关联的计算。模型本身不会自动“记住”上一次对话的内容。这带来了几个问题：</p><ol><li><strong>上下文丢失</strong>：在长对话中，早期的重要信息可能会因为上下文窗口限制而丢失</li><li><strong>个性化缺失</strong>：Agent无法记住用户的偏好、习惯或特定需求</li><li><strong>学习能力受限</strong>：无法从过往的成功或失败经验中学习改进</li><li><strong>一致性问题</strong>：在多轮对话中可能出现前后矛盾的回答</li></ol><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 第七章的Agent使用方式</span><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><br>agent = SimpleAgent(name=<span class="hljs-string">&quot;学习助手&quot;</span>, llm=HelloAgentsLLM())<br><br><span class="hljs-comment"># 第一次对话</span><br>response1 = agent.run(<span class="hljs-string">&quot;我叫张三，正在学习Python，目前掌握了基础语法&quot;</span>)<br><span class="hljs-built_in">print</span>(response1)  <span class="hljs-comment"># &quot;很好！Python基础语法是编程的重要基础...&quot;</span><br> <br><span class="hljs-comment"># 第二次对话（新的会话）</span><br>response2 = agent.run(<span class="hljs-string">&quot;你还记得我的学习进度吗？&quot;</span>)<br><span class="hljs-built_in">print</span>(response2)  <span class="hljs-comment"># &quot;抱歉，我不知道您的学习进度...&quot;</span><br></code></pre></td></tr></table></figure><p>要解决这个问题，我们的框架需要引入记忆系统。</p><p>（2）局限二：模型内置知识的局限性</p><p>除了遗忘对话历史，LLM 的另一个核心局限在于其知识是<strong>静态的、有限的</strong>。这些知识完全来自于它的训练数据，并因此带来一系列问题：</p><ol><li><strong>知识时效性</strong>：大模型的训练数据有时间截止点，无法获取最新信息</li><li><strong>专业领域知识</strong>：通用模型在特定领域的深度知识可能不足</li><li><strong>事实准确性</strong>：通过检索验证，减少模型的幻觉问题</li><li><strong>可解释性</strong>：提供信息来源，增强回答的可信度</li></ol><p>为了克服这一局限，RAG技术应运而生。它的核心思想是在模型生成回答之前，先从一个外部知识库（如文档、数据库、API）中检索出最相关的信息，并将这些信息作为上下文一同提供给模型。</p><h3 id="8-1-3-记忆与RAG系统架构设计"><a href="#8-1-3-记忆与RAG系统架构设计" class="headerlink" title="8.1.3 记忆与RAG系统架构设计"></a>8.1.3 记忆与RAG系统架构设计</h3><p>基于第七章建立的框架基础和认知科学的启发，我们设计了一个分层的记忆与RAG系统架构，如图8.2所示。这个架构不仅借鉴了人类记忆系统的层次结构，还充分考虑了工程实现的可扩展性。在实现上，我们将记忆和RAG设计为两个独立的工具：<code>memory_tool</code>负责存储和维护对话过程中的交互信息，<code>rag_tool</code>则负责从用户提供的知识库中检索相关信息作为上下文，并可将重要的检索结果自动存储到记忆系统中。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/8-figures/8-2.png" alt="HelloAgents记忆与RAG系统架构图" width="95%"/>  <p>图 8.2 HelloAgents记忆与RAG系统整体架构</p></div><p>记忆系统采用了四层架构设计：</p><figure class="highlight scss"><table><tr><td class="gutter"><pre><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><code class="hljs scss">HelloAgents记忆系统<br>├── 基础设施层 (Infrastructure Layer)<br>│   ├── MemoryManager - 记忆管理器（统一调度和协调）<br>│   ├── MemoryItem - 记忆数据结构（标准化记忆项）<br>│   ├── MemoryConfig - 配置管理（系统参数设置）<br>│   └── BaseMemory - 记忆基类（通用接口定义）<br>├── 记忆类型层 (Memory Types Layer)<br>│   ├── WorkingMemory - 工作记忆（临时信息，TTL管理）<br>│   ├── EpisodicMemory - 情景记忆（具体事件，时间序列）<br>│   ├── SemanticMemory - 语义记忆（抽象知识，图谱关系）<br>│   └── PerceptualMemory - 感知记忆（多模态数据）<br>├── 存储后端层 (Storage Backend Layer)<br>│   ├── QdrantVectorStore - 向量存储（高性能语义检索）<br>│   ├── Neo4jGraphStore - 图存储（知识图谱管理）<br>│   └── SQLiteDocumentStore - 文档存储（结构化持久化）<br>└── 嵌入服务层 (Embedding Service Layer)<br>    ├── DashScopeEmbedding - 通义千问嵌入（云端API）<br>    ├── LocalTransformerEmbedding - 本地嵌入（离线部署）<br>    └── TFIDFEmbedding - TFIDF嵌入（轻量级兜底）<br></code></pre></td></tr></table></figure><p>RAG系统专注于外部知识的获取和利用：</p><figure class="highlight scss"><table><tr><td class="gutter"><pre><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><code class="hljs scss">HelloAgents RAG系统<br>├── 文档处理层 (Document Processing Layer)<br>│   ├── DocumentProcessor - 文档处理器（多格式解析）<br>│   ├── Document - 文档对象（元数据管理）<br>│   └── Pipeline - RAG管道（端到端处理）<br>├── 嵌入表示层 (Embedding Layer)<br>│   └── 统一嵌入接口 - 复用记忆系统的嵌入服务<br>├── 向量存储层 (Vector Storage Layer)<br>│   └── QdrantVectorStore - 向量数据库（命名空间隔离）<br>└── 智能问答层 (Intelligent Q&amp;A Layer)<br>    ├── 多策略检索 - 向量检索 + MQE + HyDE<br>    ├── 上下文构建 - 智能片段合并与截断<br>    └── LLM增强生成 - 基于上下文的准确问答<br></code></pre></td></tr></table></figure><h3 id="8-1-4-本章学习目标与快速体验"><a href="#8-1-4-本章学习目标与快速体验" class="headerlink" title="8.1.4 本章学习目标与快速体验"></a>8.1.4 本章学习目标与快速体验</h3><p>让我们先看看第八章的核心学习内容：</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><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><code class="hljs awk">hello-agents/<br>├── hello_agents/<br>│   ├── memory/                   <span class="hljs-comment"># 记忆系统模块</span><br>│   │   ├── base.py               <span class="hljs-comment"># 基础数据结构（MemoryItem, MemoryConfig, BaseMemory）</span><br>│   │   ├── manager.py            <span class="hljs-comment"># 记忆管理器（统一协调调度）</span><br>│   │   ├── embedding.py          <span class="hljs-comment"># 统一嵌入服务（DashScope/Local/TFIDF）</span><br>│   │   ├── types/                <span class="hljs-comment"># 记忆类型实现</span><br>│   │   │   ├── working.py        <span class="hljs-comment"># 工作记忆（TTL管理，纯内存）</span><br>│   │   │   ├── episodic.py       <span class="hljs-comment"># 情景记忆（事件序列，SQLite+Qdrant）</span><br>│   │   │   ├── semantic.py       <span class="hljs-comment"># 语义记忆（知识图谱，Qdrant+Neo4j）</span><br>│   │   │   └── perceptual.py     <span class="hljs-comment"># 感知记忆（多模态，SQLite+Qdrant）</span><br>│   │   ├── storage/              <span class="hljs-comment"># 存储后端实现</span><br>│   │   │   ├── qdrant_store.py   <span class="hljs-comment"># Qdrant向量存储（高性能向量检索）</span><br>│   │   │   ├── neo4j_store.py    <span class="hljs-comment"># Neo4j图存储（知识图谱管理）</span><br>│   │   │   └── document_store.py <span class="hljs-comment"># SQLite文档存储（结构化持久化）</span><br>│   │   └── rag/                  <span class="hljs-comment"># RAG系统</span><br>│   │       ├── pipeline.py       <span class="hljs-comment"># RAG管道（端到端处理）</span><br>│   │       └── document.py       <span class="hljs-comment"># 文档处理器（多格式解析）</span><br>│   └── tools<span class="hljs-regexp">/builtin/</span>            <span class="hljs-comment"># 扩展内置工具</span><br>│       ├── memory_tool.py        <span class="hljs-comment"># 记忆工具（Agent记忆能力）</span><br>│       └── rag_tool.py           <span class="hljs-comment"># RAG工具（智能问答能力）</span><br>└──<br></code></pre></td></tr></table></figure><p><strong>快速开始：安装HelloAgents框架</strong></p><p>为了让读者能够快速体验本章的完整功能，我们提供了可直接安装的Python包。你可以通过以下命令安装本章对应的版本：</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></pre></td><td class="code"><pre><code class="hljs bash">pip install <span class="hljs-string">&quot;hello-agents[all]==0.2.0&quot;</span><br>python -m spacy download zh_core_web_sm<br>python -m spacy download en_core_web_sm<br></code></pre></td></tr></table></figure><p>除此之外，还需要在<code>.env</code>配置图数据库，向量数据库，LLM以及Embedding方案的API。在教程中向量数据库采用Qdrant，图数据库采用Neo4J，Embedding首选百炼平台，若没有API可切换为本地部署模型方案。</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><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></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># ================================</span><br><span class="hljs-comment"># Qdrant 向量数据库配置 - 获取API密钥：https://cloud.qdrant.io/</span><br><span class="hljs-comment"># ================================</span><br><span class="hljs-comment"># 使用Qdrant云服务 (推荐)</span><br>QDRANT_URL=https://your-cluster.qdrant.tech:6333<br>QDRANT_API_KEY=your_qdrant_api_key_here<br><br><span class="hljs-comment"># 或使用本地Qdrant (需要Docker)</span><br><span class="hljs-comment"># QDRANT_URL=http://localhost:6333</span><br><span class="hljs-comment"># QDRANT_API_KEY=</span><br><br><span class="hljs-comment"># Qdrant集合配置</span><br>QDRANT_COLLECTION=hello_agents_vectors<br>QDRANT_VECTOR_SIZE=384<br>QDRANT_DISTANCE=cosine<br>QDRANT_TIMEOUT=30<br><br><span class="hljs-comment"># ================================</span><br><span class="hljs-comment"># Neo4j 图数据库配置 - 获取API密钥：https://neo4j.com/cloud/aura/</span><br><span class="hljs-comment"># ================================</span><br><span class="hljs-comment"># 使用Neo4j Aura云服务 (推荐)</span><br>NEO4J_URI=neo4j+s://your-instance.databases.neo4j.io<br>NEO4J_USERNAME=neo4j<br>NEO4J_PASSWORD=your_neo4j_password_here<br><br><span class="hljs-comment"># 或使用本地Neo4j (需要Docker)</span><br><span class="hljs-comment"># NEO4J_URI=bolt://localhost:7687</span><br><span class="hljs-comment"># NEO4J_USERNAME=neo4j</span><br><span class="hljs-comment"># NEO4J_PASSWORD=hello-agents-password</span><br><br><span class="hljs-comment"># Neo4j连接配置</span><br>NEO4J_DATABASE=neo4j<br>NEO4J_MAX_CONNECTION_LIFETIME=3600<br>NEO4J_MAX_CONNECTION_POOL_SIZE=50<br>NEO4J_CONNECTION_TIMEOUT=60<br><br><span class="hljs-comment"># ==========================</span><br><span class="hljs-comment"># 嵌入（Embedding）配置示例 - 可从阿里云控制台获取：https://dashscope.aliyun.com/</span><br><span class="hljs-comment"># ==========================</span><br><span class="hljs-comment"># - 若为空，dashscope 默认 text-embedding-v3；local 默认 sentence-transformers/all-MiniLM-L6-v2</span><br>EMBED_MODEL_TYPE=dashscope<br>EMBED_MODEL_NAME=<br>EMBED_API_KEY=<br>EMBED_BASE_URL=<br></code></pre></td></tr></table></figure><p>本章的学习可以采用两种方式：</p><ol><li><strong>体验式学习</strong>：直接使用<code>pip</code>安装框架，运行示例代码，快速体验各种功能</li><li><strong>深度学习</strong>：跟随本章内容，从零开始实现每个组件，深入理解框架的设计思想和实现细节</li></ol><p>我们建议采用”先体验，后实现”的学习路径。在本章中，我们提供了完整的测试文件，你可以重写核心函数并运行测试，以检验你的实现是否正确。</p><p>遵循第七章确立的设计原则，我们将记忆和RAG能力封装为标准工具，而不是创建新的Agent类。在开始之前，让我们用30秒体验使用Hello-agents构建具有记忆和RAG能力的智能体！</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><code class="hljs python"><span class="hljs-comment"># 配置好同级文件夹下.env中的大模型API</span><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM, ToolRegistry<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MemoryTool, RAGTool<br><br><span class="hljs-comment"># 创建LLM实例</span><br>llm = HelloAgentsLLM()<br><br><span class="hljs-comment"># 创建Agent</span><br>agent = SimpleAgent(<br>    name=<span class="hljs-string">&quot;智能助手&quot;</span>,<br>    llm=llm,<br>    system_prompt=<span class="hljs-string">&quot;你是一个有记忆和知识检索能力的AI助手&quot;</span><br>)<br><br><span class="hljs-comment"># 创建工具注册表</span><br>tool_registry = ToolRegistry()<br><br><span class="hljs-comment"># 添加记忆工具</span><br>memory_tool = MemoryTool(user_id=<span class="hljs-string">&quot;user123&quot;</span>)<br>tool_registry.register_tool(memory_tool)<br><br><span class="hljs-comment"># 添加RAG工具</span><br>rag_tool = RAGTool(knowledge_base_path=<span class="hljs-string">&quot;./knowledge_base&quot;</span>)<br>tool_registry.register_tool(rag_tool)<br><br><span class="hljs-comment"># 为Agent配置工具</span><br>agent.tool_registry = tool_registry<br><br><span class="hljs-comment"># 开始对话</span><br>response = agent.run(<span class="hljs-string">&quot;你好！请记住我叫张三，我是一名Python开发者&quot;</span>)<br><span class="hljs-built_in">print</span>(response)<br></code></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><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><code class="hljs bash">[OK] SQLite 数据库表和索引创建完成<br>[OK] SQLite 文档存储初始化完成: ./memory_data\memory.db<br>INFO:hello_agents.memory.storage.qdrant_store:✅ 成功连接到Qdrant云服务: https://0c517275-2ad0-4442-8309-11c36dc7e811.us-east-1-1.aws.cloud.qdrant.io:6333<br>INFO:hello_agents.memory.storage.qdrant_store:✅ 使用现有Qdrant集合: hello_agents_vectors<br>INFO:hello_agents.memory.types.semantic:✅ 嵌入模型就绪，维度: 1024<br>INFO:hello_agents.memory.types.semantic:✅ Qdrant向量数据库初始化完成<br>INFO:hello_agents.memory.storage.neo4j_store:✅ 成功连接到Neo4j云服务: neo4j+s://851b3a28.databases.neo4j.io      NFO:hello_agents.memory.types.semantic:✅ Neo4j图数据库初始化完成<br>INFO:hello_agents.memory.storage.neo4j_store:✅ Neo4j索引创建完成<br>INFO:hello_agents.memory.types.semantic:✅ Neo4j图数据库初始化完成<br>INFO:hello_agents.memory.types.semantic:🏥 数据库健康状态: Qdrant=✅, Neo4j=✅<br>INFO:hello_agents.memory.types.semantic:✅ 加载中文spaCy模型: zh_core_web_sm<br>INFO:hello_agents.memory.types.semantic:✅ 加载英文spaCy模型: en_core_web_sm<br>INFO:hello_agents.memory.types.semantic:📚 可用语言模型: 中文, 英文<br>INFO:hello_agents.memory.types.semantic:增强语义记忆初始化完成（使用Qdrant+Neo4j专业数据库）<br>INFO:hello_agents.memory.manager:MemoryManager初始化完成，启用记忆类型: [<span class="hljs-string">&#x27;working&#x27;</span>, <span class="hljs-string">&#x27;episodic&#x27;</span>, <span class="hljs-string">&#x27;semantic&#x27;</span>]      <br>✅ 工具 <span class="hljs-string">&#x27;memory&#x27;</span> 已注册。<br>INFO:hello_agents.memory.storage.qdrant_store:✅ 成功连接到Qdrant云服务: https://0c517275-2ad0-4442-8309-11c36dc7eNFO:hello_agents.memory.storage.qdrant_store:✅ 使用现有Qdrant集合: rag_knowledge_base<br>811.us-east-1-1.aws.cloud.qdrant.io:6333<br>INFO:hello_agents.memory.storage.qdrant_store:✅ 使用现有Qdrant集合: rag_knowledge_base<br>✅ RAG工具初始化成功: namespace=default, collection=rag_knowledge_base<br>✅ 工具 <span class="hljs-string">&#x27;rag&#x27;</span> 已注册。<br>你好，张三！很高兴认识你。作为一名Python开发者，你一定对编程很有热情。如果你有任何技术问题或者需要讨论Python相关 <br>的话题，随时可以找我。我会尽力帮助你。有什么我现在就能帮到你的吗？<br></code></pre></td></tr></table></figure><h2 id="8-2-记忆系统：让智能体拥有记忆"><a href="#8-2-记忆系统：让智能体拥有记忆" class="headerlink" title="8.2 记忆系统：让智能体拥有记忆"></a>8.2 记忆系统：让智能体拥有记忆</h2><h3 id="8-2-1-记忆系统的工作流程"><a href="#8-2-1-记忆系统的工作流程" class="headerlink" title="8.2.1 记忆系统的工作流程"></a>8.2.1 记忆系统的工作流程</h3><p>在进入代码实现阶段前，我们需要先定义记忆系统的工作流程。该流程参考了认知科学中的记忆模型，并将每个认知阶段映射为具体的技术组件和操作。理解这一映射关系，有助于我们后续的代码实现。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/8-figures/8-3.png" alt="记忆形成过程" width="90%"/>  <p>图 8.3 记忆形成的认知过程</p></div><p>如图8.3所示，根据认知科学的研究，人类记忆的形成经历以下几个阶段：</p><ol><li><strong>编码（Encoding）</strong>：将感知到的信息转换为可存储的形式</li><li><strong>存储（Storage）</strong>：将编码后的信息保存在记忆系统中</li><li><strong>检索（Retrieval）</strong>：根据需要从记忆中提取相关信息</li><li><strong>整合（Consolidation）</strong>：将短期记忆转化为长期记忆</li><li><strong>遗忘（Forgetting）</strong>：删除不重要或过时的信息</li></ol><p>基于该启发，我们为 HelloAgents 设计了一套完整的记忆系统。其核心思想是模仿人类大脑处理不同类型信息的方式，将记忆划分为多个专门的模块，并建立一套智能化的管理机制。图8.4详细展示了这套系统的工作流程，包括记忆的添加、检索、整合和遗忘等关键环节。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/8-figures/8-4.png" alt="记忆系统工作流程" width="95%"/>  <p>图 8.4 HelloAgents记忆系统的完整工作流程</p></div><p>我们的记忆系统由四种不同类型的记忆模块构成，每种模块都针对特定的应用场景和生命周期进行了优化：</p><p>首先是<strong>工作记忆 (Working Memory)</strong>，它扮演着智能体“短期记忆”的角色，主要用于存储当前对话的上下文信息。为确保高速访问和响应，其容量被有意限制（例如，默认50条），并且生命周期与单个会话绑定，会话结束后便会自动清理。</p><p>其次是<strong>情景记忆 (Episodic Memory)</strong>，它负责长期存储具体的交互事件和智能体的学习经历。与工作记忆不同，情景记忆包含了丰富的上下文信息，并支持按时间序列或主题进行回顾式检索，是智能体“复盘”和学习过往经验的基础。</p><p>与具体事件相对应的是<strong>语义记忆 (Semantic Memory)</strong>，它存储的是更为抽象的知识、概念和规则。例如，通过对话了解到的用户偏好、需要长期遵守的指令或领域知识点，都适合存放在这里。这部分记忆具有高度的持久性和重要性，是智能体形成“知识体系”和进行关联推理的核心。</p><p>最后，为了与日益丰富的多媒体交互，我们引入了<strong>感知记忆 (Perceptual Memory)</strong>。该模块专门处理图像、音频等多模态信息，并支持跨模态检索。其生命周期会根据信息的重要性和可用存储空间进行动态管理。</p><h3 id="8-2-2-快速体验：30秒上手记忆功能"><a href="#8-2-2-快速体验：30秒上手记忆功能" class="headerlink" title="8.2.2 快速体验：30秒上手记忆功能"></a>8.2.2 快速体验：30秒上手记忆功能</h3><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM, ToolRegistry<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> MemoryTool<br><br><span class="hljs-comment"># 创建具有记忆能力的Agent</span><br>llm = HelloAgentsLLM()<br>agent = SimpleAgent(name=<span class="hljs-string">&quot;记忆助手&quot;</span>, llm=llm)<br><br><span class="hljs-comment"># 创建记忆工具</span><br>memory_tool = MemoryTool(user_id=<span class="hljs-string">&quot;user123&quot;</span>)<br>tool_registry = ToolRegistry()<br>tool_registry.register_tool(memory_tool)<br>agent.tool_registry = tool_registry<br> <br><span class="hljs-comment"># 体验记忆功能</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=== 添加多个记忆 ===&quot;</span>)<br><br><span class="hljs-comment"># 添加第一个记忆</span><br>result1 = memory_tool.execute(<span class="hljs-string">&quot;add&quot;</span>, content=<span class="hljs-string">&quot;用户张三是一名Python开发者，专注于机器学习和数据分析&quot;</span>, memory_type=<span class="hljs-string">&quot;semantic&quot;</span>, importance=<span class="hljs-number">0.8</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;记忆1: <span class="hljs-subst">&#123;result1&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 添加第二个记忆</span><br>result2 = memory_tool.execute(<span class="hljs-string">&quot;add&quot;</span>, content=<span class="hljs-string">&quot;李四是前端工程师，擅长React和Vue.js开发&quot;</span>, memory_type=<span class="hljs-string">&quot;semantic&quot;</span>, importance=<span class="hljs-number">0.7</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;记忆2: <span class="hljs-subst">&#123;result2&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 添加第三个记忆</span><br>result3 = memory_tool.execute(<span class="hljs-string">&quot;add&quot;</span>, content=<span class="hljs-string">&quot;王五是产品经理，负责用户体验设计和需求分析&quot;</span>, memory_type=<span class="hljs-string">&quot;semantic&quot;</span>, importance=<span class="hljs-number">0.6</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;记忆3: <span class="hljs-subst">&#123;result3&#125;</span>&quot;</span>)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n=== 搜索特定记忆 ===&quot;</span>)<br><span class="hljs-comment"># 搜索前端相关的记忆</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;🔍 搜索 &#x27;前端工程师&#x27;:&quot;</span>)<br>result = memory_tool.execute(<span class="hljs-string">&quot;search&quot;</span>, query=<span class="hljs-string">&quot;前端工程师&quot;</span>, limit=<span class="hljs-number">3</span>)<br><span class="hljs-built_in">print</span>(result)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n=== 记忆摘要 ===&quot;</span>)<br>result = memory_tool.execute(<span class="hljs-string">&quot;summary&quot;</span>)<br><span class="hljs-built_in">print</span>(result)<br></code></pre></td></tr></table></figure><h3 id="8-2-3-MemoryTool详解"><a href="#8-2-3-MemoryTool详解" class="headerlink" title="8.2.3 MemoryTool详解"></a>8.2.3 MemoryTool详解</h3><p>现在让我们采用自顶向下的方式，从MemoryTool支持的具体操作开始，逐步深入到底层实现。MemoryTool作为记忆系统的统一接口，其设计遵循了”统一入口，分发处理”的架构模式：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">execute</span>(<span class="hljs-params">self, action: <span class="hljs-built_in">str</span>, **kwargs</span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;执行记忆操作</span><br><span class="hljs-string"></span><br><span class="hljs-string">    支持的操作：</span><br><span class="hljs-string">    - add: 添加记忆（支持4种类型: working/episodic/semantic/perceptual）</span><br><span class="hljs-string">    - search: 搜索记忆</span><br><span class="hljs-string">    - summary: 获取记忆摘要</span><br><span class="hljs-string">    - stats: 获取统计信息</span><br><span class="hljs-string">    - update: 更新记忆</span><br><span class="hljs-string">    - remove: 删除记忆</span><br><span class="hljs-string">    - forget: 遗忘记忆（多种策略）</span><br><span class="hljs-string">    - consolidate: 整合记忆（短期→长期）</span><br><span class="hljs-string">    - clear_all: 清空所有记忆</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">if</span> action == <span class="hljs-string">&quot;add&quot;</span>:<br>        <span class="hljs-keyword">return</span> self._add_memory(**kwargs)<br>    <span class="hljs-keyword">elif</span> action == <span class="hljs-string">&quot;search&quot;</span>:<br>        <span class="hljs-keyword">return</span> self._search_memory(**kwargs)<br>    <span class="hljs-keyword">elif</span> action == <span class="hljs-string">&quot;summary&quot;</span>:<br>        <span class="hljs-keyword">return</span> self._get_summary(**kwargs)<br>    <span class="hljs-comment"># ... 其他操作</span><br></code></pre></td></tr></table></figure><p>这种统一的<code>execute</code>接口设计简化了Agent的调用方式，通过<code>action</code>参数指定具体操作，使用<code>**kwargs</code>允许每个操作有不同的参数需求。在这里我们会将比较重要的几个操作罗列出来：</p><p>（1）操作1：add</p><p><code>add</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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_add_memory</span>(<span class="hljs-params"></span><br><span class="hljs-params">    self,</span><br><span class="hljs-params">    content: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;&quot;</span>,</span><br><span class="hljs-params">    memory_type: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;working&quot;</span>,</span><br><span class="hljs-params">    importance: <span class="hljs-built_in">float</span> = <span class="hljs-number">0.5</span>,</span><br><span class="hljs-params">    file_path: <span class="hljs-built_in">str</span> = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    modality: <span class="hljs-built_in">str</span> = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    **metadata</span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;添加记忆&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">try</span>:<br>        <span class="hljs-comment"># 确保会话ID存在</span><br>        <span class="hljs-keyword">if</span> self.current_session_id <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br>            self.current_session_id = <span class="hljs-string">f&quot;session_<span class="hljs-subst">&#123;datetime.now().strftime(<span class="hljs-string">&#x27;%Y%m%d_%H%M%S&#x27;</span>)&#125;</span>&quot;</span><br><br>        <span class="hljs-comment"># 感知记忆文件支持</span><br>        <span class="hljs-keyword">if</span> memory_type == <span class="hljs-string">&quot;perceptual&quot;</span> <span class="hljs-keyword">and</span> file_path:<br>            inferred = modality <span class="hljs-keyword">or</span> self._infer_modality(file_path)<br>            metadata.setdefault(<span class="hljs-string">&quot;modality&quot;</span>, inferred)<br>            metadata.setdefault(<span class="hljs-string">&quot;raw_data&quot;</span>, file_path)<br><br>        <span class="hljs-comment"># 添加会话信息到元数据</span><br>        metadata.update(&#123;<br>            <span class="hljs-string">&quot;session_id&quot;</span>: self.current_session_id,<br>            <span class="hljs-string">&quot;timestamp&quot;</span>: datetime.now().isoformat()<br>        &#125;)<br><br>        memory_id = self.memory_manager.add_memory(<br>            content=content,<br>            memory_type=memory_type,<br>            importance=importance,<br>            metadata=metadata,<br>            auto_classify=<span class="hljs-literal">False</span><br>        )<br><br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;✅ 记忆已添加 (ID: <span class="hljs-subst">&#123;memory_id[:<span class="hljs-number">8</span>]&#125;</span>...)&quot;</span><br><br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 添加记忆失败: <span class="hljs-subst">&#123;<span class="hljs-built_in">str</span>(e)&#125;</span>&quot;</span><br></code></pre></td></tr></table></figure><p>这里主要实现了三个关键任务：会话ID的自动管理（确保每个记忆都有明确的会话归属）、多模态数据的智能处理（自动推断文件类型并保存相关元数据）、以及上下文信息的自动补充（为每个记忆添加时间戳和会话信息）。其中，<code>importance</code>参数（默认0.5）用于标记记忆的重要程度，取值范围0.0-1.0，这个机制模拟了人类大脑对不同信息重要性的评估。这种设计让Agent能够自动区分不同时间段的对话，并为后续的检索和管理提供丰富的上下文信息。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 1. 工作记忆 - 临时信息，容量有限</span><br>memory_tool.execute(<span class="hljs-string">&quot;add&quot;</span>,<br>    content=<span class="hljs-string">&quot;用户刚才问了关于Python函数的问题&quot;</span>,<br>    memory_type=<span class="hljs-string">&quot;working&quot;</span>,<br>    importance=<span class="hljs-number">0.6</span><br>)<br><br><span class="hljs-comment"># 2. 情景记忆 - 具体事件和经历</span><br>memory_tool.execute(<span class="hljs-string">&quot;add&quot;</span>,<br>    content=<span class="hljs-string">&quot;2024年3月15日，用户张三完成了第一个Python项目&quot;</span>,<br>    memory_type=<span class="hljs-string">&quot;episodic&quot;</span>,<br>    importance=<span class="hljs-number">0.8</span>,<br>    event_type=<span class="hljs-string">&quot;milestone&quot;</span>,<br>    location=<span class="hljs-string">&quot;在线学习平台&quot;</span><br>)<br><br><span class="hljs-comment"># 3. 语义记忆 - 抽象知识和概念</span><br>memory_tool.execute(<span class="hljs-string">&quot;add&quot;</span>,<br>    content=<span class="hljs-string">&quot;Python是一种解释型、面向对象的编程语言&quot;</span>,<br>    memory_type=<span class="hljs-string">&quot;semantic&quot;</span>,<br>    importance=<span class="hljs-number">0.9</span>,<br>    knowledge_type=<span class="hljs-string">&quot;factual&quot;</span><br>)<br><br><span class="hljs-comment"># 4. 感知记忆 - 多模态信息</span><br>memory_tool.execute(<span class="hljs-string">&quot;add&quot;</span>,<br>    content=<span class="hljs-string">&quot;用户上传了一张Python代码截图，包含函数定义&quot;</span>,<br>    memory_type=<span class="hljs-string">&quot;perceptual&quot;</span>,<br>    importance=<span class="hljs-number">0.7</span>,<br>    modality=<span class="hljs-string">&quot;image&quot;</span>,<br>    file_path=<span class="hljs-string">&quot;./uploads/code_screenshot.png&quot;</span><br>)<br></code></pre></td></tr></table></figure><p>（2）操作2：search</p><p><code>search</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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_search_memory</span>(<span class="hljs-params"></span><br><span class="hljs-params">    self,</span><br><span class="hljs-params">    query: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">    limit: <span class="hljs-built_in">int</span> = <span class="hljs-number">5</span>,</span><br><span class="hljs-params">    memory_types: <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    memory_type: <span class="hljs-built_in">str</span> = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    min_importance: <span class="hljs-built_in">float</span> = <span class="hljs-number">0.1</span></span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;搜索记忆&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">try</span>:<br>        <span class="hljs-comment"># 参数标准化处理</span><br>        <span class="hljs-keyword">if</span> memory_type <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> memory_types:<br>            memory_types = [memory_type]<br><br>        results = self.memory_manager.retrieve_memories(<br>            query=query,<br>            limit=limit,<br>            memory_types=memory_types,<br>            min_importance=min_importance<br>        )<br><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> results:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;🔍 未找到与 &#x27;<span class="hljs-subst">&#123;query&#125;</span>&#x27; 相关的记忆&quot;</span><br><br>        <span class="hljs-comment"># 格式化结果</span><br>        formatted_results = []<br>        formatted_results.append(<span class="hljs-string">f&quot;🔍 找到 <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(results)&#125;</span> 条相关记忆:&quot;</span>)<br><br>        <span class="hljs-keyword">for</span> i, memory <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(results, <span class="hljs-number">1</span>):<br>            memory_type_label = &#123;<br>                <span class="hljs-string">&quot;working&quot;</span>: <span class="hljs-string">&quot;工作记忆&quot;</span>,<br>                <span class="hljs-string">&quot;episodic&quot;</span>: <span class="hljs-string">&quot;情景记忆&quot;</span>, <br>                <span class="hljs-string">&quot;semantic&quot;</span>: <span class="hljs-string">&quot;语义记忆&quot;</span>,<br>                <span class="hljs-string">&quot;perceptual&quot;</span>: <span class="hljs-string">&quot;感知记忆&quot;</span><br>            &#125;.get(memory.memory_type, memory.memory_type)<br><br>            content_preview = memory.content[:<span class="hljs-number">80</span>] + <span class="hljs-string">&quot;...&quot;</span> <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(memory.content) &gt; <span class="hljs-number">80</span> <span class="hljs-keyword">else</span> memory.content<br>            formatted_results.append(<br>                <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;i&#125;</span>. [<span class="hljs-subst">&#123;memory_type_label&#125;</span>] <span class="hljs-subst">&#123;content_preview&#125;</span> (重要性: <span class="hljs-subst">&#123;memory.importance:<span class="hljs-number">.2</span>f&#125;</span>)&quot;</span><br>            )<br><br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;\n&quot;</span>.join(formatted_results)<br><br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 搜索记忆失败: <span class="hljs-subst">&#123;<span class="hljs-built_in">str</span>(e)&#125;</span>&quot;</span><br></code></pre></td></tr></table></figure><p>搜索操作在设计上支持单数和复数两种参数形式（<code>memory_type</code>和<code>memory_types</code>），让用户以最自然的方式表达需求。其中，<code>min_importance</code>参数（默认0.1）用于过滤低质量记忆。对于搜索功能的使用，可以参考这个示例。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 基础搜索</span><br>result = memory_tool.execute(<span class="hljs-string">&quot;search&quot;</span>, query=<span class="hljs-string">&quot;Python编程&quot;</span>, limit=<span class="hljs-number">5</span>)<br><br><span class="hljs-comment"># 指定记忆类型搜索</span><br>result = memory_tool.execute(<span class="hljs-string">&quot;search&quot;</span>,<br>    query=<span class="hljs-string">&quot;学习进度&quot;</span>,<br>    memory_type=<span class="hljs-string">&quot;episodic&quot;</span>,<br>    limit=<span class="hljs-number">3</span><br>)<br><br><span class="hljs-comment"># 多类型搜索</span><br>result = memory_tool.execute(<span class="hljs-string">&quot;search&quot;</span>,<br>    query=<span class="hljs-string">&quot;函数定义&quot;</span>,<br>    memory_types=[<span class="hljs-string">&quot;semantic&quot;</span>, <span class="hljs-string">&quot;episodic&quot;</span>],<br>    min_importance=<span class="hljs-number">0.5</span><br>)<br></code></pre></td></tr></table></figure><p>（3）操作3：forget</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_forget</span>(<span class="hljs-params">self, strategy: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;importance_based&quot;</span>, threshold: <span class="hljs-built_in">float</span> = <span class="hljs-number">0.1</span>, max_age_days: <span class="hljs-built_in">int</span> = <span class="hljs-number">30</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;遗忘记忆（支持多种策略）&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">try</span>:<br>        count = self.memory_manager.forget_memories(<br>            strategy=strategy,<br>            threshold=threshold,<br>            max_age_days=max_age_days<br>        )<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;🧹 已遗忘 <span class="hljs-subst">&#123;count&#125;</span> 条记忆（策略: <span class="hljs-subst">&#123;strategy&#125;</span>）&quot;</span><br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 遗忘记忆失败: <span class="hljs-subst">&#123;<span class="hljs-built_in">str</span>(e)&#125;</span>&quot;</span><br></code></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 1. 基于重要性的遗忘 - 删除重要性低于阈值的记忆</span><br>memory_tool.execute(<span class="hljs-string">&quot;forget&quot;</span>,<br>    strategy=<span class="hljs-string">&quot;importance_based&quot;</span>,<br>    threshold=<span class="hljs-number">0.2</span><br>)<br><br><span class="hljs-comment"># 2. 基于时间的遗忘 - 删除超过指定天数的记忆</span><br>memory_tool.execute(<span class="hljs-string">&quot;forget&quot;</span>,<br>    strategy=<span class="hljs-string">&quot;time_based&quot;</span>,<br>    max_age_days=<span class="hljs-number">30</span><br>)<br><br><span class="hljs-comment"># 3. 基于容量的遗忘 - 当记忆数量超限时删除最不重要的</span><br>memory_tool.execute(<span class="hljs-string">&quot;forget&quot;</span>,<br>    strategy=<span class="hljs-string">&quot;capacity_based&quot;</span>,<br>    threshold=<span class="hljs-number">0.3</span><br>)<br></code></pre></td></tr></table></figure><p>（4）操作4：consolidate</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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_consolidate</span>(<span class="hljs-params">self, from_type: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;working&quot;</span>, to_type: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;episodic&quot;</span>, importance_threshold: <span class="hljs-built_in">float</span> = <span class="hljs-number">0.7</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;整合记忆（将重要的短期记忆提升为长期记忆）&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">try</span>:<br>        count = self.memory_manager.consolidate_memories(<br>            from_type=from_type,<br>            to_type=to_type,<br>            importance_threshold=importance_threshold,<br>        )<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;🔄 已整合 <span class="hljs-subst">&#123;count&#125;</span> 条记忆为长期记忆（<span class="hljs-subst">&#123;from_type&#125;</span> → <span class="hljs-subst">&#123;to_type&#125;</span>，阈值=<span class="hljs-subst">&#123;importance_threshold&#125;</span>）&quot;</span><br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 整合记忆失败: <span class="hljs-subst">&#123;<span class="hljs-built_in">str</span>(e)&#125;</span>&quot;</span><br></code></pre></td></tr></table></figure><p>consolidate操作借鉴了神经科学中的记忆固化概念，模拟人类大脑将短期记忆转化为长期记忆的过程。默认设置是将重要性超过0.7的工作记忆转换为情景记忆，这个阈值确保只有真正重要的信息才会被长期保存。整个过程是自动化的，用户无需手动选择具体的记忆，系统会智能地识别符合条件的记忆并执行类型转换。</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><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 将重要的工作记忆转为情景记忆</span><br>memory_tool.execute(<span class="hljs-string">&quot;consolidate&quot;</span>,<br>    from_type=<span class="hljs-string">&quot;working&quot;</span>,<br>    to_type=<span class="hljs-string">&quot;episodic&quot;</span>,<br>    importance_threshold=<span class="hljs-number">0.7</span><br>)<br><br><span class="hljs-comment"># 将重要的情景记忆转为语义记忆</span><br>memory_tool.execute(<span class="hljs-string">&quot;consolidate&quot;</span>,<br>    from_type=<span class="hljs-string">&quot;episodic&quot;</span>,<br>    to_type=<span class="hljs-string">&quot;semantic&quot;</span>,<br>    importance_threshold=<span class="hljs-number">0.8</span><br>)<br></code></pre></td></tr></table></figure><p>通过以上几个核心操作协作，MemoryTool构建了一个完整的记忆生命周期管理体系。从记忆的创建、检索、摘要到遗忘、整合和管理，形成了一个闭环的智能记忆管理系统，让Agent真正具备了类人的记忆能力。</p><h3 id="8-2-4-MemoryManager详解"><a href="#8-2-4-MemoryManager详解" class="headerlink" title="8.2.4 MemoryManager详解"></a>8.2.4 MemoryManager详解</h3><p>理解了MemoryTool的接口设计后，让我们深入到底层实现，看看MemoryTool是如何与MemoryManager协作的。这种分层设计体现了软件工程中的关注点分离原则，MemoryTool专注于用户接口和参数处理，而MemoryManager则负责核心的记忆管理逻辑。</p><p>MemoryTool在初始化时会创建一个MemoryManager实例，并根据配置启用不同类型的记忆模块。这种设计让用户可以根据具体需求选择启用哪些记忆类型，既保证了功能的完整性，又避免了不必要的资源消耗。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MemoryTool</span>(<span class="hljs-title class_ inherited__">Tool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;记忆工具 - 为Agent提供记忆功能&quot;&quot;&quot;</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        user_id: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;default_user&quot;</span>,</span><br><span class="hljs-params">        memory_config: MemoryConfig = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        memory_types: <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span></span><br><span class="hljs-params">    </span>):<br>        <span class="hljs-built_in">super</span>().__init__(<br>            name=<span class="hljs-string">&quot;memory&quot;</span>,<br>            description=<span class="hljs-string">&quot;记忆工具 - 可以存储和检索对话历史、知识和经验&quot;</span><br>        )<br>        <br>        <span class="hljs-comment"># 初始化记忆管理器</span><br>        self.memory_config = memory_config <span class="hljs-keyword">or</span> MemoryConfig()<br>        self.memory_types = memory_types <span class="hljs-keyword">or</span> [<span class="hljs-string">&quot;working&quot;</span>, <span class="hljs-string">&quot;episodic&quot;</span>, <span class="hljs-string">&quot;semantic&quot;</span>]<br>        <br>        self.memory_manager = MemoryManager(<br>            config=self.memory_config,<br>            user_id=user_id,<br>            enable_working=<span class="hljs-string">&quot;working&quot;</span> <span class="hljs-keyword">in</span> self.memory_types,<br>            enable_episodic=<span class="hljs-string">&quot;episodic&quot;</span> <span class="hljs-keyword">in</span> self.memory_types,<br>            enable_semantic=<span class="hljs-string">&quot;semantic&quot;</span> <span class="hljs-keyword">in</span> self.memory_types,<br>            enable_perceptual=<span class="hljs-string">&quot;perceptual&quot;</span> <span class="hljs-keyword">in</span> self.memory_types<br>        )<br></code></pre></td></tr></table></figure><p>MemoryManager作为记忆系统的核心协调者，负责管理不同类型的记忆模块，并提供统一的操作接口。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MemoryManager</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;记忆管理器 - 统一的记忆操作接口&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        config: <span class="hljs-type">Optional</span>[MemoryConfig] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        user_id: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;default_user&quot;</span>,</span><br><span class="hljs-params">        enable_working: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span>,</span><br><span class="hljs-params">        enable_episodic: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span>,</span><br><span class="hljs-params">        enable_semantic: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span>,</span><br><span class="hljs-params">        enable_perceptual: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">False</span></span><br><span class="hljs-params">    </span>):<br>        self.config = config <span class="hljs-keyword">or</span> MemoryConfig()<br>        self.user_id = user_id<br><br>        <span class="hljs-comment"># 初始化存储和检索组件</span><br>        self.store = MemoryStore(self.config)<br>        self.retriever = MemoryRetriever(self.store, self.config)<br><br>        <span class="hljs-comment"># 初始化各类型记忆</span><br>        self.memory_types = &#123;&#125;<br><br>        <span class="hljs-keyword">if</span> enable_working:<br>            self.memory_types[<span class="hljs-string">&#x27;working&#x27;</span>] = WorkingMemory(self.config, self.store)<br><br>        <span class="hljs-keyword">if</span> enable_episodic:<br>            self.memory_types[<span class="hljs-string">&#x27;episodic&#x27;</span>] = EpisodicMemory(self.config, self.store)<br><br>        <span class="hljs-keyword">if</span> enable_semantic:<br>            self.memory_types[<span class="hljs-string">&#x27;semantic&#x27;</span>] = SemanticMemory(self.config, self.store)<br><br>        <span class="hljs-keyword">if</span> enable_perceptual:<br>            self.memory_types[<span class="hljs-string">&#x27;perceptual&#x27;</span>] = PerceptualMemory(self.config, self.store)<br></code></pre></td></tr></table></figure><h3 id="8-2-5-四种记忆类型"><a href="#8-2-5-四种记忆类型" class="headerlink" title="8.2.5 四种记忆类型"></a>8.2.5 四种记忆类型</h3><p>现在让我们深入了解四种记忆类型的具体实现，每种记忆类型都有其独特的特点和应用场景：</p><p>（1）工作记忆（WorkingMemory）</p><p>工作记忆是记忆系统中最活跃的部分，它负责存储当前对话会话中的临时信息。工作记忆的设计重点在于快速访问和自动清理，这种设计确保了系统的响应速度和资源效率。</p><p>工作记忆采用了纯内存存储方案，配合TTL（Time To Live）机制进行自动清理。这种设计的优势在于访问速度极快，但也意味着工作记忆的内容在系统重启后会丢失。这种特性正好符合工作记忆的定位，存储临时的、易变的信息。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">WorkingMemory</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;工作记忆实现</span><br><span class="hljs-string">    特点：</span><br><span class="hljs-string">    - 容量有限（默认50条）+ TTL自动清理</span><br><span class="hljs-string">    - 纯内存存储，访问速度极快</span><br><span class="hljs-string">    - 混合检索：TF-IDF向量化 + 关键词匹配</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, config: MemoryConfig</span>):<br>        self.max_capacity = config.working_memory_capacity <span class="hljs-keyword">or</span> <span class="hljs-number">50</span><br>        self.max_age_minutes = config.working_memory_ttl <span class="hljs-keyword">or</span> <span class="hljs-number">60</span><br>        self.memories = []<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">add</span>(<span class="hljs-params">self, memory_item: MemoryItem</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;添加工作记忆&quot;&quot;&quot;</span><br>        self._expire_old_memories()  <span class="hljs-comment"># 过期清理</span><br>        <br>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(self.memories) &gt;= self.max_capacity:<br>            self._remove_lowest_priority_memory()  <span class="hljs-comment"># 容量管理</span><br>        <br>        self.memories.append(memory_item)<br>        <span class="hljs-keyword">return</span> memory_item.<span class="hljs-built_in">id</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">retrieve</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span>, limit: <span class="hljs-built_in">int</span> = <span class="hljs-number">5</span>, **kwargs</span>) -&gt; <span class="hljs-type">List</span>[MemoryItem]:<br>        <span class="hljs-string">&quot;&quot;&quot;混合检索：TF-IDF向量化 + 关键词匹配&quot;&quot;&quot;</span><br>        self._expire_old_memories()<br>        <br>        <span class="hljs-comment"># 尝试TF-IDF向量检索</span><br>        vector_scores = self._try_tfidf_search(query)<br>        <br>        <span class="hljs-comment"># 计算综合分数</span><br>        scored_memories = []<br>        <span class="hljs-keyword">for</span> memory <span class="hljs-keyword">in</span> self.memories:<br>            vector_score = vector_scores.get(memory.<span class="hljs-built_in">id</span>, <span class="hljs-number">0.0</span>)<br>            keyword_score = self._calculate_keyword_score(query, memory.content)<br>            <br>            <span class="hljs-comment"># 混合评分</span><br>            base_relevance = vector_score * <span class="hljs-number">0.7</span> + keyword_score * <span class="hljs-number">0.3</span> <span class="hljs-keyword">if</span> vector_score &gt; <span class="hljs-number">0</span> <span class="hljs-keyword">else</span> keyword_score<br>            time_decay = self._calculate_time_decay(memory.timestamp)<br>            importance_weight = <span class="hljs-number">0.8</span> + (memory.importance * <span class="hljs-number">0.4</span>)<br>            <br>            final_score = base_relevance * time_decay * importance_weight<br>            <span class="hljs-keyword">if</span> final_score &gt; <span class="hljs-number">0</span>:<br>                scored_memories.append((final_score, memory))<br>        <br>        scored_memories.sort(key=<span class="hljs-keyword">lambda</span> x: x[<span class="hljs-number">0</span>], reverse=<span class="hljs-literal">True</span>)<br>        <span class="hljs-keyword">return</span> [memory <span class="hljs-keyword">for</span> _, memory <span class="hljs-keyword">in</span> scored_memories[:limit]]<br></code></pre></td></tr></table></figure><p>工作记忆的检索采用了混合检索策略，首先尝试使用TF-IDF向量化进行语义检索，如果失败则回退到关键词匹配。这种设计确保了在各种环境下都能提供可靠的检索服务。评分算法结合了语义相似度、时间衰减和重要性权重，最终得分公式为：<code>(相似度 × 时间衰减) × (0.8 + 重要性 × 0.4)</code>。</p><p>（2）情景记忆（EpisodicMemory）</p><p>情景记忆负责存储具体的事件和经历，它的设计重点在于保持事件的完整性和时间序列关系。情景记忆采用了SQLite+Qdrant的混合存储方案，SQLite负责结构化数据的存储和复杂查询，Qdrant负责高效的向量检索。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">EpisodicMemory</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;情景记忆实现</span><br><span class="hljs-string">    特点：</span><br><span class="hljs-string">    - SQLite+Qdrant混合存储架构</span><br><span class="hljs-string">    - 支持时间序列和会话级检索</span><br><span class="hljs-string">    - 结构化过滤 + 语义向量检索</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, config: MemoryConfig</span>):<br>        self.doc_store = SQLiteDocumentStore(config.database_path)<br>        self.vector_store = QdrantVectorStore(config.qdrant_url, config.qdrant_api_key)<br>        self.embedder = create_embedding_model_with_fallback()<br>        self.sessions = &#123;&#125;  <span class="hljs-comment"># 会话索引</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">add</span>(<span class="hljs-params">self, memory_item: MemoryItem</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;添加情景记忆&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 创建情景对象</span><br>        episode = Episode(<br>            episode_id=memory_item.<span class="hljs-built_in">id</span>,<br>            session_id=memory_item.metadata.get(<span class="hljs-string">&quot;session_id&quot;</span>, <span class="hljs-string">&quot;default&quot;</span>),<br>            timestamp=memory_item.timestamp,<br>            content=memory_item.content,<br>            context=memory_item.metadata<br>        )<br>        <br>        <span class="hljs-comment"># 更新会话索引</span><br>        session_id = episode.session_id<br>        <span class="hljs-keyword">if</span> session_id <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.sessions:<br>            self.sessions[session_id] = []<br>        self.sessions[session_id].append(episode.episode_id)<br>        <br>        <span class="hljs-comment"># 持久化存储（SQLite + Qdrant）</span><br>        self._persist_episode(episode)<br>        <span class="hljs-keyword">return</span> memory_item.<span class="hljs-built_in">id</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">retrieve</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span>, limit: <span class="hljs-built_in">int</span> = <span class="hljs-number">5</span>, **kwargs</span>) -&gt; <span class="hljs-type">List</span>[MemoryItem]:<br>        <span class="hljs-string">&quot;&quot;&quot;混合检索：结构化过滤 + 语义向量检索&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 1. 结构化预过滤（时间范围、重要性等）</span><br>        candidate_ids = self._structured_filter(**kwargs)<br>        <br>        <span class="hljs-comment"># 2. 向量语义检索</span><br>        hits = self._vector_search(query, limit * <span class="hljs-number">5</span>, kwargs.get(<span class="hljs-string">&quot;user_id&quot;</span>))<br>        <br>        <span class="hljs-comment"># 3. 综合评分与排序</span><br>        results = []<br>        <span class="hljs-keyword">for</span> hit <span class="hljs-keyword">in</span> hits:<br>            <span class="hljs-keyword">if</span> self._should_include(hit, candidate_ids, kwargs):<br>                score = self._calculate_episode_score(hit)<br>                memory_item = self._create_memory_item(hit)<br>                results.append((score, memory_item))<br>        <br>        results.sort(key=<span class="hljs-keyword">lambda</span> x: x[<span class="hljs-number">0</span>], reverse=<span class="hljs-literal">True</span>)<br>        <span class="hljs-keyword">return</span> [item <span class="hljs-keyword">for</span> _, item <span class="hljs-keyword">in</span> results[:limit]]<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_calculate_episode_score</span>(<span class="hljs-params">self, hit</span>) -&gt; <span class="hljs-built_in">float</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;情景记忆评分算法&quot;&quot;&quot;</span><br>        vec_score = <span class="hljs-built_in">float</span>(hit.get(<span class="hljs-string">&quot;score&quot;</span>, <span class="hljs-number">0.0</span>))<br>        recency_score = self._calculate_recency(hit[<span class="hljs-string">&quot;metadata&quot;</span>][<span class="hljs-string">&quot;timestamp&quot;</span>])<br>        importance = hit[<span class="hljs-string">&quot;metadata&quot;</span>].get(<span class="hljs-string">&quot;importance&quot;</span>, <span class="hljs-number">0.5</span>)<br>        <br>        <span class="hljs-comment"># 评分公式：(向量相似度 × 0.8 + 时间近因性 × 0.2) × 重要性权重</span><br>        base_relevance = vec_score * <span class="hljs-number">0.8</span> + recency_score * <span class="hljs-number">0.2</span><br>        importance_weight = <span class="hljs-number">0.8</span> + (importance * <span class="hljs-number">0.4</span>)<br>        <br>        <span class="hljs-keyword">return</span> base_relevance * importance_weight<br></code></pre></td></tr></table></figure><p>情景记忆的检索实现展现了复杂的多因素评分机制。它不仅考虑了语义相似度，还加入了时间近因性的考量，最终通过重要性权重进行调节。评分公式为：<code>(向量相似度 × 0.8 + 时间近因性 × 0.2) × (0.8 + 重要性 × 0.4)</code>，确保检索结果既语义相关又时间相关。</p><p>（3）语义记忆（SemanticMemory）</p><p>语义记忆是记忆系统中最复杂的部分，它负责存储抽象的概念、规则和知识。语义记忆的设计重点在于知识的结构化表示和智能推理能力。语义记忆采用了Neo4j图数据库和Qdrant向量数据库的混合架构，这种设计让系统既能进行快速的语义检索，又能利用知识图谱进行复杂的关系推理。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">SemanticMemory</span>(<span class="hljs-title class_ inherited__">BaseMemory</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;语义记忆实现</span><br><span class="hljs-string">    </span><br><span class="hljs-string">    特点：</span><br><span class="hljs-string">    - 使用HuggingFace中文预训练模型进行文本嵌入</span><br><span class="hljs-string">    - 向量检索进行快速相似度匹配</span><br><span class="hljs-string">    - 知识图谱存储实体和关系</span><br><span class="hljs-string">    - 混合检索策略：向量+图+语义推理</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, config: MemoryConfig, storage_backend=<span class="hljs-literal">None</span></span>):<br>        <span class="hljs-built_in">super</span>().__init__(config, storage_backend)<br>        <br>        <span class="hljs-comment"># 嵌入模型（统一提供）</span><br>        self.embedding_model = get_text_embedder()<br>        <br>        <span class="hljs-comment"># 专业数据库存储</span><br>        self.vector_store = QdrantConnectionManager.get_instance(**qdrant_config)<br>        self.graph_store = Neo4jGraphStore(**neo4j_config)<br>        <br>        <span class="hljs-comment"># 实体和关系缓存</span><br>        self.entities: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, Entity] = &#123;&#125;<br>        self.relations: <span class="hljs-type">List</span>[Relation] = []<br>        <br>        <span class="hljs-comment"># NLP处理器（支持中英文）</span><br>        self.nlp = self._init_nlp()<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">add</span>(<span class="hljs-params">self, memory_item: MemoryItem</span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;添加语义记忆&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 1. 生成文本嵌入</span><br>    embedding = self.embedding_model.encode(memory_item.content)<br>    <br>    <span class="hljs-comment"># 2. 提取实体和关系</span><br>    entities = self._extract_entities(memory_item.content)<br>    relations = self._extract_relations(memory_item.content, entities)<br>    <br>    <span class="hljs-comment"># 3. 存储到Neo4j图数据库</span><br>    <span class="hljs-keyword">for</span> entity <span class="hljs-keyword">in</span> entities:<br>        self._add_entity_to_graph(entity, memory_item)<br>    <br>    <span class="hljs-keyword">for</span> relation <span class="hljs-keyword">in</span> relations:<br>        self._add_relation_to_graph(relation, memory_item)<br>    <br>    <span class="hljs-comment"># 4. 存储到Qdrant向量数据库</span><br>    metadata = &#123;<br>        <span class="hljs-string">&quot;memory_id&quot;</span>: memory_item.<span class="hljs-built_in">id</span>,<br>        <span class="hljs-string">&quot;entities&quot;</span>: [e.entity_id <span class="hljs-keyword">for</span> e <span class="hljs-keyword">in</span> entities],<br>        <span class="hljs-string">&quot;entity_count&quot;</span>: <span class="hljs-built_in">len</span>(entities),<br>        <span class="hljs-string">&quot;relation_count&quot;</span>: <span class="hljs-built_in">len</span>(relations)<br>    &#125;<br>    <br>    self.vector_store.add_vectors(<br>        vectors=[embedding.tolist()],<br>        metadata=[metadata],<br>        ids=[memory_item.<span class="hljs-built_in">id</span>]<br>    )<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">retrieve</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span>, limit: <span class="hljs-built_in">int</span> = <span class="hljs-number">5</span>, **kwargs</span>) -&gt; <span class="hljs-type">List</span>[MemoryItem]:<br>    <span class="hljs-string">&quot;&quot;&quot;检索语义记忆&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 1. 向量检索</span><br>    vector_results = self._vector_search(query, limit * <span class="hljs-number">2</span>, user_id)<br>    <br>    <span class="hljs-comment"># 2. 图检索</span><br>    graph_results = self._graph_search(query, limit * <span class="hljs-number">2</span>, user_id)<br>    <br>    <span class="hljs-comment"># 3. 混合排序</span><br>    combined_results = self._combine_and_rank_results(<br>        vector_results, graph_results, query, limit<br>    )<br>    <br>    <span class="hljs-keyword">return</span> combined_results[:limit]<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_combine_and_rank_results</span>(<span class="hljs-params">self, vector_results, graph_results, query, limit</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;混合排序结果&quot;&quot;&quot;</span><br>    combined = &#123;&#125;<br>    <br>    <span class="hljs-comment"># 合并向量和图检索结果</span><br>    <span class="hljs-keyword">for</span> result <span class="hljs-keyword">in</span> vector_results:<br>        combined[result[<span class="hljs-string">&quot;memory_id&quot;</span>]] = &#123;<br>            **result,<br>            <span class="hljs-string">&quot;vector_score&quot;</span>: result.get(<span class="hljs-string">&quot;score&quot;</span>, <span class="hljs-number">0.0</span>),<br>            <span class="hljs-string">&quot;graph_score&quot;</span>: <span class="hljs-number">0.0</span><br>        &#125;<br>    <br>    <span class="hljs-keyword">for</span> result <span class="hljs-keyword">in</span> graph_results:<br>        memory_id = result[<span class="hljs-string">&quot;memory_id&quot;</span>]<br>        <span class="hljs-keyword">if</span> memory_id <span class="hljs-keyword">in</span> combined:<br>            combined[memory_id][<span class="hljs-string">&quot;graph_score&quot;</span>] = result.get(<span class="hljs-string">&quot;similarity&quot;</span>, <span class="hljs-number">0.0</span>)<br>        <span class="hljs-keyword">else</span>:<br>            combined[memory_id] = &#123;<br>                **result,<br>                <span class="hljs-string">&quot;vector_score&quot;</span>: <span class="hljs-number">0.0</span>,<br>                <span class="hljs-string">&quot;graph_score&quot;</span>: result.get(<span class="hljs-string">&quot;similarity&quot;</span>, <span class="hljs-number">0.0</span>)<br>            &#125;<br>    <br>    <span class="hljs-comment"># 计算混合分数</span><br>    <span class="hljs-keyword">for</span> memory_id, result <span class="hljs-keyword">in</span> combined.items():<br>        vector_score = result[<span class="hljs-string">&quot;vector_score&quot;</span>]<br>        graph_score = result[<span class="hljs-string">&quot;graph_score&quot;</span>]<br>        importance = result.get(<span class="hljs-string">&quot;importance&quot;</span>, <span class="hljs-number">0.5</span>)<br>        <br>        <span class="hljs-comment"># 基础相似度得分</span><br>        base_relevance = vector_score * <span class="hljs-number">0.7</span> + graph_score * <span class="hljs-number">0.3</span><br>        <br>        <span class="hljs-comment"># 重要性权重 [0.8, 1.2]</span><br>        importance_weight = <span class="hljs-number">0.8</span> + (importance * <span class="hljs-number">0.4</span>)<br>        <br>        <span class="hljs-comment"># 最终得分：相似度 * 重要性权重</span><br>        combined_score = base_relevance * importance_weight<br>        result[<span class="hljs-string">&quot;combined_score&quot;</span>] = combined_score<br>    <br>    <span class="hljs-comment"># 排序并返回</span><br>    sorted_results = <span class="hljs-built_in">sorted</span>(<br>        combined.values(),<br>        key=<span class="hljs-keyword">lambda</span> x: x[<span class="hljs-string">&quot;combined_score&quot;</span>],<br>        reverse=<span class="hljs-literal">True</span><br>    )<br>    <br>    <span class="hljs-keyword">return</span> sorted_results[:limit]<br></code></pre></td></tr></table></figure><p>语义记忆的评分公式为：<code>(向量相似度 × 0.7 + 图相似度 × 0.3) × (0.8 + 重要性 × 0.4)</code>。这种设计的核心思想是：</p><ul><li><strong>向量检索权重（0.7）</strong>：语义相似度是主要因素，确保检索结果与查询语义相关</li><li><strong>图检索权重（0.3）</strong>：关系推理作为补充，发现概念间的隐含关联</li><li><strong>重要性权重范围[0.8, 1.2]</strong>：避免重要性过度影响相似度排序，保持检索的准确性</li></ul><p>（4）感知记忆（PerceptualMemory）</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">PerceptualMemory</span>(<span class="hljs-title class_ inherited__">BaseMemory</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;感知记忆实现</span><br><span class="hljs-string">    </span><br><span class="hljs-string">    特点：</span><br><span class="hljs-string">    - 支持多模态数据（文本、图像、音频等）</span><br><span class="hljs-string">    - 跨模态相似性搜索</span><br><span class="hljs-string">    - 感知数据的语义理解</span><br><span class="hljs-string">    - 支持内容生成和检索</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, config: MemoryConfig, storage_backend=<span class="hljs-literal">None</span></span>):<br>        <span class="hljs-built_in">super</span>().__init__(config, storage_backend)<br>        <br>        <span class="hljs-comment"># 多模态编码器</span><br>        self.text_embedder = get_text_embedder()<br>        self._clip_model = self._init_clip_model()  <span class="hljs-comment"># 图像编码</span><br>        self._clap_model = self._init_clap_model()  <span class="hljs-comment"># 音频编码</span><br>        <br>        <span class="hljs-comment"># 按模态分离的向量存储</span><br>        self.vector_stores = &#123;<br>            <span class="hljs-string">&quot;text&quot;</span>: QdrantConnectionManager.get_instance(<br>                collection_name=<span class="hljs-string">&quot;perceptual_text&quot;</span>,<br>                vector_size=self.vector_dim<br>            ),<br>            <span class="hljs-string">&quot;image&quot;</span>: QdrantConnectionManager.get_instance(<br>                collection_name=<span class="hljs-string">&quot;perceptual_image&quot;</span>, <br>                vector_size=self._image_dim<br>            ),<br>            <span class="hljs-string">&quot;audio&quot;</span>: QdrantConnectionManager.get_instance(<br>                collection_name=<span class="hljs-string">&quot;perceptual_audio&quot;</span>,<br>                vector_size=self._audio_dim<br>            )<br>        &#125;<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">retrieve</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span>, limit: <span class="hljs-built_in">int</span> = <span class="hljs-number">5</span>, **kwargs</span>) -&gt; <span class="hljs-type">List</span>[MemoryItem]:<br>    <span class="hljs-string">&quot;&quot;&quot;检索感知记忆（可筛模态；同模态向量检索+时间/重要性融合）&quot;&quot;&quot;</span><br>    user_id = kwargs.get(<span class="hljs-string">&quot;user_id&quot;</span>)<br>    target_modality = kwargs.get(<span class="hljs-string">&quot;target_modality&quot;</span>)<br>    query_modality = kwargs.get(<span class="hljs-string">&quot;query_modality&quot;</span>, target_modality <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;text&quot;</span>)<br>    <br>    <span class="hljs-comment"># 同模态向量检索</span><br>    <span class="hljs-keyword">try</span>:<br>        query_vector = self._encode_data(query, query_modality)<br>        store = self._get_vector_store_for_modality(target_modality <span class="hljs-keyword">or</span> query_modality)<br>        <br>        where = &#123;<span class="hljs-string">&quot;memory_type&quot;</span>: <span class="hljs-string">&quot;perceptual&quot;</span>&#125;<br>        <span class="hljs-keyword">if</span> user_id:<br>            where[<span class="hljs-string">&quot;user_id&quot;</span>] = user_id<br>        <span class="hljs-keyword">if</span> target_modality:<br>            where[<span class="hljs-string">&quot;modality&quot;</span>] = target_modality<br>        <br>        hits = store.search_similar(<br>            query_vector=query_vector,<br>            limit=<span class="hljs-built_in">max</span>(limit * <span class="hljs-number">5</span>, <span class="hljs-number">20</span>),<br>            where=where<br>        )<br>    <span class="hljs-keyword">except</span> Exception:<br>        hits = []<br>    <br>    <span class="hljs-comment"># 融合排序（向量相似度 + 时间近因性 + 重要性权重）</span><br>    results = []<br>    <span class="hljs-keyword">for</span> hit <span class="hljs-keyword">in</span> hits:<br>        vector_score = <span class="hljs-built_in">float</span>(hit.get(<span class="hljs-string">&quot;score&quot;</span>, <span class="hljs-number">0.0</span>))<br>        recency_score = self._calculate_recency_score(hit[<span class="hljs-string">&quot;metadata&quot;</span>][<span class="hljs-string">&quot;timestamp&quot;</span>])<br>        importance = hit[<span class="hljs-string">&quot;metadata&quot;</span>].get(<span class="hljs-string">&quot;importance&quot;</span>, <span class="hljs-number">0.5</span>)<br>        <br>        <span class="hljs-comment"># 评分算法</span><br>        base_relevance = vector_score * <span class="hljs-number">0.8</span> + recency_score * <span class="hljs-number">0.2</span><br>        importance_weight = <span class="hljs-number">0.8</span> + (importance * <span class="hljs-number">0.4</span>)<br>        combined_score = base_relevance * importance_weight<br>        <br>        results.append((combined_score, self._create_memory_item(hit)))<br>    <br>    results.sort(key=<span class="hljs-keyword">lambda</span> x: x[<span class="hljs-number">0</span>], reverse=<span class="hljs-literal">True</span>)<br>    <span class="hljs-keyword">return</span> [item <span class="hljs-keyword">for</span> _, item <span class="hljs-keyword">in</span> results[:limit]]<br></code></pre></td></tr></table></figure><p>感知记忆的评分公式为：<code>(向量相似度 × 0.8 + 时间近因性 × 0.2) × (0.8 + 重要性 × 0.4)</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><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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_calculate_recency_score</span>(<span class="hljs-params">self, timestamp: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">float</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;计算时间近因性得分&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">try</span>:<br>        memory_time = datetime.fromisoformat(timestamp)<br>        current_time = datetime.now()<br>        age_hours = (current_time - memory_time).total_seconds() / <span class="hljs-number">3600</span><br>        <br>        <span class="hljs-comment"># 指数衰减：24小时内保持高分，之后逐渐衰减</span><br>        decay_factor = <span class="hljs-number">0.1</span>  <span class="hljs-comment"># 衰减系数</span><br>        recency_score = math.exp(-decay_factor * age_hours / <span class="hljs-number">24</span>)<br>        <br>        <span class="hljs-keyword">return</span> <span class="hljs-built_in">max</span>(<span class="hljs-number">0.1</span>, recency_score)  <span class="hljs-comment"># 最低保持0.1的基础分数</span><br>    <span class="hljs-keyword">except</span> Exception:<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span>  <span class="hljs-comment"># 默认中等分数</span><br></code></pre></td></tr></table></figure><p>这种时间衰减模型模拟了人类记忆中的遗忘曲线，确保了感知记忆系统能够优先检索到时间上更相关的记忆内容。</p><h2 id="8-3-RAG系统：知识检索增强"><a href="#8-3-RAG系统：知识检索增强" class="headerlink" title="8.3 RAG系统：知识检索增强"></a>8.3 RAG系统：知识检索增强</h2><h3 id="8-3-1-RAG的基础知识"><a href="#8-3-1-RAG的基础知识" class="headerlink" title="8.3.1 RAG的基础知识"></a>8.3.1 RAG的基础知识</h3><p>在深入HelloAgents的RAG系统实现之前，让我们先了解RAG技术的基础概念、发展历程和核心原理。由于本文内容不是以RAG为基础进行创作，为此这里只帮读者快速梳理相关概念，以便更好地理解系统设计的技术选择和创新点。</p><p>（1）什么是RAG？</p><p>检索增强生成（Retrieval-Augmented Generation，RAG）是一种结合了信息检索和文本生成的技术。它的核心思想是：在生成回答之前，先从外部知识库中检索相关信息，然后将检索到的信息作为上下文提供给大语言模型，从而生成更准确、更可靠的回答。</p><p>因此，检索增强生成可以拆分为三个词汇。<strong>检索</strong>是指从知识库中查询相关内容；<strong>增强</strong>是将检索结果融入提示词，辅助模型生成；<strong>生成</strong>则输出兼具准确性与透明度的答案。</p><p>（2）基本工作流程</p><p>一个完整的RAG应用流程主要分为两大核心环节。在<strong>数据准备阶段</strong>，系统通过<strong>数据提取</strong>、<strong>文本分割</strong>和<strong>向量化</strong>，将外部知识构建成一个可检索的数据库。随后在<strong>应用阶段</strong>，系统会响应用户的<strong>提问</strong>，从数据库中<strong>检索</strong>相关信息，将其<strong>注入Prompt</strong>，并最终驱动大语言模型<strong>生成答案</strong>。</p><p>（3）发展历程</p><p>第一阶段：朴素RAG（Naive RAG, 2020-2021）。这是RAG技术的萌芽阶段，其流程直接而简单，通常被称为“检索-读取”（Retrieve-Read）模式。<strong>检索方式</strong>：主要依赖传统的关键词匹配算法，如<code>TF-IDF</code>或<code>BM25</code>。这些方法计算词频和文档频率来评估相关性，对字面匹配效果好，但难以理解语义上的相似性。<strong>生成模式</strong>：将检索到的文档内容不加处理地直接拼接到提示词的上下文中，然后送给生成模型。</p><p>第二阶段：高级RAG（Advanced RAG, 2022-2023）。随着向量数据库和文本嵌入技术的成熟，RAG进入了快速发展阶段。研究者和开发者们在“检索”和“生成”的各个环节引入了大量优化技术。<strong>检索方式</strong>：转向基于<strong>稠密嵌入（Dense Embedding）</strong>的语义检索。通过将文本转换为高维向量，模型能够理解和匹配语义上的相似性，而不仅仅是关键词。<strong>生成模式</strong>：引入了很多优化技术，例如查询重写，文档分块，重排序等。</p><p>第三阶段：模块化RAG（Modular RAG, 2023-至今）。在高级RAG的基础上，现代RAG系统进一步向着模块化、自动化和智能化的方向发展。系统的各个部分被设计成可插拔、可组合的独立模块，以适应更多样化和复杂的应用场景。<strong>检索方式</strong>：如混合检索，多查询扩展，假设性文档嵌入等。<strong>生成模式</strong>：思维链推理，自我反思与修正等。</p><h3 id="8-3-2-RAG系统工作原理"><a href="#8-3-2-RAG系统工作原理" class="headerlink" title="8.3.2 RAG系统工作原理"></a>8.3.2 RAG系统工作原理</h3><p>在深入实现细节之前，可以通过流程图来梳理Helloagents的RAG系统完整工作流程：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/8-figures/8-5.png" alt="RAG系统核心原理" width="85%"/>  <p>图 8.5 RAG系统的核心工作原理</p></div><p>如图8.5所示，展示了RAG系统的两个主要工作模式：</p><ol><li><strong>数据处理流程</strong>：处理和存储知识文档，在这里我们采取工具<code>Markitdown</code>，设计思路是将传入的一切外部知识源统一转化为Markdown格式进行处理。</li><li><strong>查询与生成流程</strong>：根据查询检索相关信息并生成回答。</li></ol><h3 id="8-3-3-快速体验：30秒上手RAG功能"><a href="#8-3-3-快速体验：30秒上手RAG功能" class="headerlink" title="8.3.3 快速体验：30秒上手RAG功能"></a>8.3.3 快速体验：30秒上手RAG功能</h3><p>让我们先快速体验一下RAG系统的基本功能：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM, ToolRegistry<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> RAGTool<br><br><span class="hljs-comment"># 创建具有RAG能力的Agent</span><br>llm = HelloAgentsLLM()<br>agent = SimpleAgent(name=<span class="hljs-string">&quot;知识助手&quot;</span>, llm=llm)<br><br><span class="hljs-comment"># 创建RAG工具</span><br>rag_tool = RAGTool(<br>    knowledge_base_path=<span class="hljs-string">&quot;./knowledge_base&quot;</span>,<br>    collection_name=<span class="hljs-string">&quot;test_collection&quot;</span>,<br>    rag_namespace=<span class="hljs-string">&quot;test&quot;</span><br>)<br><br>tool_registry = ToolRegistry()<br>tool_registry.register_tool(rag_tool)<br>agent.tool_registry = tool_registry<br><br><span class="hljs-comment"># 体验RAG功能</span><br><span class="hljs-comment"># 添加第一个知识</span><br>result1 = rag_tool.execute(<span class="hljs-string">&quot;add_text&quot;</span>, <br>    text=<span class="hljs-string">&quot;Python是一种高级编程语言，由Guido van Rossum于1991年首次发布。Python的设计哲学强调代码的可读性和简洁的语法。&quot;</span>,<br>    document_id=<span class="hljs-string">&quot;python_intro&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;知识1: <span class="hljs-subst">&#123;result1&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 添加第二个知识  </span><br>result2 = rag_tool.execute(<span class="hljs-string">&quot;add_text&quot;</span>,<br>    text=<span class="hljs-string">&quot;机器学习是人工智能的一个分支，通过算法让计算机从数据中学习模式。主要包括监督学习、无监督学习和强化学习三种类型。&quot;</span>,<br>    document_id=<span class="hljs-string">&quot;ml_basics&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;知识2: <span class="hljs-subst">&#123;result2&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 添加第三个知识</span><br>result3 = rag_tool.execute(<span class="hljs-string">&quot;add_text&quot;</span>,<br>    text=<span class="hljs-string">&quot;RAG（检索增强生成）是一种结合信息检索和文本生成的AI技术。它通过检索相关知识来增强大语言模型的生成能力。&quot;</span>,<br>    document_id=<span class="hljs-string">&quot;rag_concept&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;知识3: <span class="hljs-subst">&#123;result3&#125;</span>&quot;</span>)<br><br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n=== 搜索知识 ===&quot;</span>)<br>result = rag_tool.execute(<span class="hljs-string">&quot;search&quot;</span>,<br>    query=<span class="hljs-string">&quot;Python编程语言的历史&quot;</span>,<br>    limit=<span class="hljs-number">3</span>,<br>    min_score=<span class="hljs-number">0.1</span><br>)<br><span class="hljs-built_in">print</span>(result)<br><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n=== 知识库统计 ===&quot;</span>)<br>result = rag_tool.execute(<span class="hljs-string">&quot;stats&quot;</span>)<br><span class="hljs-built_in">print</span>(result)<br></code></pre></td></tr></table></figure><p>接下来，我们将深入探讨HelloAgents RAG系统的具体实现。</p><h3 id="8-3-4-RAG系统架构设计"><a href="#8-3-4-RAG系统架构设计" class="headerlink" title="8.3.4 RAG系统架构设计"></a>8.3.4 RAG系统架构设计</h3><p>在这一节中，我们采取与记忆系统不同的方式讲解。因为<code>Memory_tool</code>是系统性的实现，而RAG在我们的设计中被定义为一种工具，可以梳理为一条pipeline。我们的RAG系统的核心架构可以概括为”五层七步”的设计模式：</p><figure class="highlight"><table><tr><td class="gutter"><pre><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><code class="hljs">用户层：RAGTool统一接口<br>  ↓<br>应用层：智能问答、搜索、管理<br>  ↓  <br>处理层：文档解析、分块、向量化<br>  ↓<br>存储层：向量数据库、文档存储<br>  ↓<br>基础层：嵌入模型、LLM、数据库<br></code></pre></td></tr></table></figure><p>这种分层设计的优势在于每一层都可以独立优化和替换，同时保持整体系统的稳定性。例如，可以轻松地将嵌入模型从sentence-transformers切换到百炼API，而不影响上层的业务逻辑。同样的，这些处理的流程代码是完全可复用的，也可以选取自己需要的部分放进自己的项目中。RAGTool作为RAG系统的统一入口，提供了简洁的API接口。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">RAGTool</span>(<span class="hljs-title class_ inherited__">Tool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;RAG工具</span><br><span class="hljs-string">    </span><br><span class="hljs-string">    提供完整的 RAG 能力：</span><br><span class="hljs-string">    - 添加多格式文档（PDF、Office、图片、音频等）</span><br><span class="hljs-string">    - 智能检索与召回</span><br><span class="hljs-string">    - LLM 增强问答</span><br><span class="hljs-string">    - 知识库管理</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        knowledge_base_path: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;./knowledge_base&quot;</span>,</span><br><span class="hljs-params">        qdrant_url: <span class="hljs-built_in">str</span> = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        qdrant_api_key: <span class="hljs-built_in">str</span> = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        collection_name: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;rag_knowledge_base&quot;</span>,</span><br><span class="hljs-params">        rag_namespace: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;default&quot;</span></span><br><span class="hljs-params">    </span>):<br>        <span class="hljs-comment"># 初始化RAG管道</span><br>        self._pipelines: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]] = &#123;&#125;<br>        self.llm = HelloAgentsLLM()<br>        <br>        <span class="hljs-comment"># 创建默认管道</span><br>        default_pipeline = create_rag_pipeline(<br>            qdrant_url=self.qdrant_url,<br>            qdrant_api_key=self.qdrant_api_key,<br>            collection_name=self.collection_name,<br>            rag_namespace=self.rag_namespace<br>        )<br>        self._pipelines[self.rag_namespace] = default_pipeline<br></code></pre></td></tr></table></figure><p>整个处理流程如下所示：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs">任意格式文档 → MarkItDown转换 → Markdown文本 → 智能分块 → 向量化 → 存储检索<br></code></pre></td></tr></table></figure><p>（1）多模态文档载入</p><p>RAG系统的核心优势之一是其强大的多模态文档处理能力。系统使用MarkItDown作为统一的文档转换引擎，支持几乎所有常见的文档格式。MarkItDown是微软开源的通用文档转换工具，它是HelloAgents RAG系统的核心组件，负责将任意格式的文档统一转换为结构化的Markdown文本。无论输入是PDF、Word、Excel、图片还是音频，最终都会转换为标准的Markdown格式，然后进入统一的分块、向量化和存储流程。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_convert_to_markdown</span>(<span class="hljs-params">path: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">    Universal document reader using MarkItDown with enhanced PDF processing.</span><br><span class="hljs-string">    核心功能：将任意格式文档转换为Markdown文本</span><br><span class="hljs-string">    </span><br><span class="hljs-string">    支持格式：</span><br><span class="hljs-string">    - 文档：PDF、Word、Excel、PowerPoint</span><br><span class="hljs-string">    - 图像：JPG、PNG、GIF（通过OCR）</span><br><span class="hljs-string">    - 音频：MP3、WAV、M4A（通过转录）</span><br><span class="hljs-string">    - 文本：TXT、CSV、JSON、XML、HTML</span><br><span class="hljs-string">    - 代码：Python、JavaScript、Java等</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> os.path.exists(path):<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;</span><br>    <br>    <span class="hljs-comment"># 对PDF文件使用增强处理</span><br>    ext = (os.path.splitext(path)[<span class="hljs-number">1</span>] <span class="hljs-keyword">or</span> <span class="hljs-string">&#x27;&#x27;</span>).lower()<br>    <span class="hljs-keyword">if</span> ext == <span class="hljs-string">&#x27;.pdf&#x27;</span>:<br>        <span class="hljs-keyword">return</span> _enhanced_pdf_processing(path)<br>    <br>    <span class="hljs-comment"># 其他格式使用MarkItDown统一转换</span><br>    md_instance = _get_markitdown_instance()<br>    <span class="hljs-keyword">if</span> md_instance <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br>        <span class="hljs-keyword">return</span> _fallback_text_reader(path)<br>    <br>    <span class="hljs-keyword">try</span>:<br>        result = md_instance.convert(path)<br>        markdown_text = <span class="hljs-built_in">getattr</span>(result, <span class="hljs-string">&quot;text_content&quot;</span>, <span class="hljs-literal">None</span>)<br>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">isinstance</span>(markdown_text, <span class="hljs-built_in">str</span>) <span class="hljs-keyword">and</span> markdown_text.strip():<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[RAG] MarkItDown转换成功: <span class="hljs-subst">&#123;path&#125;</span> -&gt; <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(markdown_text)&#125;</span> chars Markdown&quot;</span>)<br>            <span class="hljs-keyword">return</span> markdown_text<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;</span><br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[WARNING] MarkItDown转换失败 <span class="hljs-subst">&#123;path&#125;</span>: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>        <span class="hljs-keyword">return</span> _fallback_text_reader(path)<br></code></pre></td></tr></table></figure><p>（2）智能分块策略</p><p>经过MarkItDown转换后，所有文档都统一为标准的Markdown格式。这为后续的智能分块提供了结构化的基础。HelloAgents实现了专门针对Markdown格式的智能分块策略，充分利用Markdown的结构化特性进行精确分割。</p><p>Markdown结构感知的分块流程：</p><figure class="highlight clean"><table><tr><td class="gutter"><pre><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><code class="hljs clean">标准Markdown文本 → 标题层次解析 → 段落语义分割 → Token计算分块 → 重叠策略优化 → 向量化准备<br>       ↓                ↓              ↓            ↓           ↓            ↓<br>   统一格式          #/##/###        语义边界      大小控制     信息连续性    嵌入向量<br>   结构清晰          层次识别        完整性保证    检索优化     上下文保持    相似度匹配<br></code></pre></td></tr></table></figure><p>由于所有文档都已转换为Markdown格式，系统可以利用Markdown的标题结构（#、##、###等）进行精确的语义分割：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_split_paragraphs_with_headings</span>(<span class="hljs-params">text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;根据标题层次分割段落，保持语义完整性&quot;&quot;&quot;</span><br>    lines = text.splitlines()<br>    heading_stack: <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>] = []<br>    paragraphs: <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>] = []<br>    buf: <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>] = []<br>    char_pos = <span class="hljs-number">0</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">flush_buf</span>(<span class="hljs-params">end_pos: <span class="hljs-built_in">int</span></span>):<br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> buf:<br>            <span class="hljs-keyword">return</span><br>        content = <span class="hljs-string">&quot;\n&quot;</span>.join(buf).strip()<br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> content:<br>            <span class="hljs-keyword">return</span><br>        paragraphs.append(&#123;<br>            <span class="hljs-string">&quot;content&quot;</span>: content,<br>            <span class="hljs-string">&quot;heading_path&quot;</span>: <span class="hljs-string">&quot; &gt; &quot;</span>.join(heading_stack) <span class="hljs-keyword">if</span> heading_stack <span class="hljs-keyword">else</span> <span class="hljs-literal">None</span>,<br>            <span class="hljs-string">&quot;start&quot;</span>: <span class="hljs-built_in">max</span>(<span class="hljs-number">0</span>, end_pos - <span class="hljs-built_in">len</span>(content)),<br>            <span class="hljs-string">&quot;end&quot;</span>: end_pos,<br>        &#125;)<br>    <br>    <span class="hljs-keyword">for</span> ln <span class="hljs-keyword">in</span> lines:<br>        raw = ln<br>        <span class="hljs-keyword">if</span> raw.strip().startswith(<span class="hljs-string">&quot;#&quot;</span>):<br>            <span class="hljs-comment"># 处理标题行</span><br>            flush_buf(char_pos)<br>            level = <span class="hljs-built_in">len</span>(raw) - <span class="hljs-built_in">len</span>(raw.lstrip(<span class="hljs-string">&#x27;#&#x27;</span>))<br>            title = raw.lstrip(<span class="hljs-string">&#x27;#&#x27;</span>).strip()<br>            <br>            <span class="hljs-keyword">if</span> level &lt;= <span class="hljs-number">0</span>:<br>                level = <span class="hljs-number">1</span><br>            <span class="hljs-keyword">if</span> level &lt;= <span class="hljs-built_in">len</span>(heading_stack):<br>                heading_stack = heading_stack[:level-<span class="hljs-number">1</span>]<br>            heading_stack.append(title)<br>            <br>            char_pos += <span class="hljs-built_in">len</span>(raw) + <span class="hljs-number">1</span><br>            <span class="hljs-keyword">continue</span><br>        <br>        <span class="hljs-comment"># 段落内容累积</span><br>        <span class="hljs-keyword">if</span> raw.strip() == <span class="hljs-string">&quot;&quot;</span>:<br>            flush_buf(char_pos)<br>            buf = []<br>        <span class="hljs-keyword">else</span>:<br>            buf.append(raw)<br>        char_pos += <span class="hljs-built_in">len</span>(raw) + <span class="hljs-number">1</span><br>    <br>    flush_buf(char_pos)<br>    <br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> paragraphs:<br>        paragraphs = [&#123;<span class="hljs-string">&quot;content&quot;</span>: text, <span class="hljs-string">&quot;heading_path&quot;</span>: <span class="hljs-literal">None</span>, <span class="hljs-string">&quot;start&quot;</span>: <span class="hljs-number">0</span>, <span class="hljs-string">&quot;end&quot;</span>: <span class="hljs-built_in">len</span>(text)&#125;]<br>    <br>    <span class="hljs-keyword">return</span> paragraphs<br></code></pre></td></tr></table></figure><p>在Markdown段落分割的基础上，系统进一步根据Token数量进行智能分块。由于输入已经是结构化的Markdown文本，系统可以更精确地控制分块边界，确保每个分块既适合向量化处理，又保持Markdown结构的完整性：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_chunk_paragraphs</span>(<span class="hljs-params">paragraphs: <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>], chunk_tokens: <span class="hljs-built_in">int</span>, overlap_tokens: <span class="hljs-built_in">int</span></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;基于Token数量的智能分块&quot;&quot;&quot;</span><br>    chunks: <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>] = []<br>    cur: <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>] = []<br>    cur_tokens = <span class="hljs-number">0</span><br>    i = <span class="hljs-number">0</span><br>    <br>    <span class="hljs-keyword">while</span> i &lt; <span class="hljs-built_in">len</span>(paragraphs):<br>        p = paragraphs[i]<br>        p_tokens = _approx_token_len(p[<span class="hljs-string">&quot;content&quot;</span>]) <span class="hljs-keyword">or</span> <span class="hljs-number">1</span><br>        <br>        <span class="hljs-keyword">if</span> cur_tokens + p_tokens &lt;= chunk_tokens <span class="hljs-keyword">or</span> <span class="hljs-keyword">not</span> cur:<br>            cur.append(p)<br>            cur_tokens += p_tokens<br>            i += <span class="hljs-number">1</span><br>        <span class="hljs-keyword">else</span>:<br>            <span class="hljs-comment"># 生成当前分块</span><br>            content = <span class="hljs-string">&quot;\n\n&quot;</span>.join(x[<span class="hljs-string">&quot;content&quot;</span>] <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> cur)<br>            start = cur[<span class="hljs-number">0</span>][<span class="hljs-string">&quot;start&quot;</span>]<br>            end = cur[-<span class="hljs-number">1</span>][<span class="hljs-string">&quot;end&quot;</span>]<br>            heading_path = <span class="hljs-built_in">next</span>((x[<span class="hljs-string">&quot;heading_path&quot;</span>] <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> <span class="hljs-built_in">reversed</span>(cur) <span class="hljs-keyword">if</span> x.get(<span class="hljs-string">&quot;heading_path&quot;</span>)), <span class="hljs-literal">None</span>)<br>            <br>            chunks.append(&#123;<br>                <span class="hljs-string">&quot;content&quot;</span>: content,<br>                <span class="hljs-string">&quot;start&quot;</span>: start,<br>                <span class="hljs-string">&quot;end&quot;</span>: end,<br>                <span class="hljs-string">&quot;heading_path&quot;</span>: heading_path,<br>            &#125;)<br>            <br>            <span class="hljs-comment"># 构建重叠部分</span><br>            <span class="hljs-keyword">if</span> overlap_tokens &gt; <span class="hljs-number">0</span> <span class="hljs-keyword">and</span> cur:<br>                kept: <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>] = []<br>                kept_tokens = <span class="hljs-number">0</span><br>                <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> <span class="hljs-built_in">reversed</span>(cur):<br>                    t = _approx_token_len(x[<span class="hljs-string">&quot;content&quot;</span>]) <span class="hljs-keyword">or</span> <span class="hljs-number">1</span><br>                    <span class="hljs-keyword">if</span> kept_tokens + t &gt; overlap_tokens:<br>                        <span class="hljs-keyword">break</span><br>                    kept.append(x)<br>                    kept_tokens += t<br>                cur = <span class="hljs-built_in">list</span>(<span class="hljs-built_in">reversed</span>(kept))<br>                cur_tokens = kept_tokens<br>            <span class="hljs-keyword">else</span>:<br>                cur = []<br>                cur_tokens = <span class="hljs-number">0</span><br>    <br>    <span class="hljs-comment"># 处理最后一个分块</span><br>    <span class="hljs-keyword">if</span> cur:<br>        content = <span class="hljs-string">&quot;\n\n&quot;</span>.join(x[<span class="hljs-string">&quot;content&quot;</span>] <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> cur)<br>        start = cur[<span class="hljs-number">0</span>][<span class="hljs-string">&quot;start&quot;</span>]<br>        end = cur[-<span class="hljs-number">1</span>][<span class="hljs-string">&quot;end&quot;</span>]<br>        heading_path = <span class="hljs-built_in">next</span>((x[<span class="hljs-string">&quot;heading_path&quot;</span>] <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> <span class="hljs-built_in">reversed</span>(cur) <span class="hljs-keyword">if</span> x.get(<span class="hljs-string">&quot;heading_path&quot;</span>)), <span class="hljs-literal">None</span>)<br>        <br>        chunks.append(&#123;<br>            <span class="hljs-string">&quot;content&quot;</span>: content,<br>            <span class="hljs-string">&quot;start&quot;</span>: start,<br>            <span class="hljs-string">&quot;end&quot;</span>: end,<br>            <span class="hljs-string">&quot;heading_path&quot;</span>: heading_path,<br>        &#125;)<br>    <br>    <span class="hljs-keyword">return</span> chunks<br></code></pre></td></tr></table></figure><p>同时为了兼容不同语言，系统实现了针对中英文混合文本的Token估算算法，这对于准确控制分块大小至关重要：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_approx_token_len</span>(<span class="hljs-params">text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">int</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;近似估计Token长度，支持中英文混合&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># CJK字符按1 token计算</span><br>    cjk = <span class="hljs-built_in">sum</span>(<span class="hljs-number">1</span> <span class="hljs-keyword">for</span> ch <span class="hljs-keyword">in</span> text <span class="hljs-keyword">if</span> _is_cjk(ch))<br>    <span class="hljs-comment"># 其他字符按空白分词计算</span><br>    non_cjk_tokens = <span class="hljs-built_in">len</span>([t <span class="hljs-keyword">for</span> t <span class="hljs-keyword">in</span> text.split() <span class="hljs-keyword">if</span> t])<br>    <span class="hljs-keyword">return</span> cjk + non_cjk_tokens<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">_is_cjk</span>(<span class="hljs-params">ch: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">bool</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;判断是否为CJK字符&quot;&quot;&quot;</span><br>    code = <span class="hljs-built_in">ord</span>(ch)<br>    <span class="hljs-keyword">return</span> (<br>        <span class="hljs-number">0x4E00</span> &lt;= code &lt;= <span class="hljs-number">0x9FFF</span> <span class="hljs-keyword">or</span>  <span class="hljs-comment"># CJK统一汉字</span><br>        <span class="hljs-number">0x3400</span> &lt;= code &lt;= <span class="hljs-number">0x4DBF</span> <span class="hljs-keyword">or</span>  <span class="hljs-comment"># CJK扩展A</span><br>        <span class="hljs-number">0x20000</span> &lt;= code &lt;= <span class="hljs-number">0x2A6DF</span> <span class="hljs-keyword">or</span> <span class="hljs-comment"># CJK扩展B</span><br>        <span class="hljs-number">0x2A700</span> &lt;= code &lt;= <span class="hljs-number">0x2B73F</span> <span class="hljs-keyword">or</span> <span class="hljs-comment"># CJK扩展C</span><br>        <span class="hljs-number">0x2B740</span> &lt;= code &lt;= <span class="hljs-number">0x2B81F</span> <span class="hljs-keyword">or</span> <span class="hljs-comment"># CJK扩展D</span><br>        <span class="hljs-number">0x2B820</span> &lt;= code &lt;= <span class="hljs-number">0x2CEAF</span> <span class="hljs-keyword">or</span> <span class="hljs-comment"># CJK扩展E</span><br>        <span class="hljs-number">0xF900</span> &lt;= code &lt;= <span class="hljs-number">0xFAFF</span>      <span class="hljs-comment"># CJK兼容汉字</span><br>    )<br></code></pre></td></tr></table></figure><p>（3）统一嵌入与向量存储</p><p>嵌入模型是RAG系统的核心，它负责将文本转换为高维向量，使得计算机能够理解和比较文本的语义相似性。RAG系统的检索能力很大程度上取决于嵌入模型的质量和向量存储的效率。HelloAgents实现了统一的嵌入接口。在这里为了演示，使用百炼API，如果尚未配置可以切换为本地的<code>all-MiniLM-L6-v2</code>模型，如果两种方案都不支持，也配置了TF-IDF算法来兜底。实际使用可以替换为自己想要的模型或者API，也可以尝试去扩展框架内容~</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">index_chunks</span>(<span class="hljs-params"></span><br><span class="hljs-params">    store = <span class="hljs-literal">None</span>, </span><br><span class="hljs-params">    chunks: <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>] = <span class="hljs-literal">None</span>, </span><br><span class="hljs-params">    cache_db: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>, </span><br><span class="hljs-params">    batch_size: <span class="hljs-built_in">int</span> = <span class="hljs-number">64</span>,</span><br><span class="hljs-params">    rag_namespace: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;default&quot;</span></span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-literal">None</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">    Index markdown chunks with unified embedding and Qdrant storage.</span><br><span class="hljs-string">    Uses百炼 API with fallback to sentence-transformers.</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> chunks:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;[RAG] No chunks to index&quot;</span>)<br>        <span class="hljs-keyword">return</span><br>    <br>    <span class="hljs-comment"># 使用统一嵌入模型</span><br>    embedder = get_text_embedder()<br>    dimension = get_dimension(<span class="hljs-number">384</span>)<br>    <br>    <span class="hljs-comment"># 创建默认Qdrant存储</span><br>    <span class="hljs-keyword">if</span> store <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br>        store = _create_default_vector_store(dimension)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[RAG] Created default Qdrant store with dimension <span class="hljs-subst">&#123;dimension&#125;</span>&quot;</span>)<br>    <br>    <span class="hljs-comment"># 预处理Markdown文本以获得更好的嵌入质量</span><br>    processed_texts = []<br>    <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> chunks:<br>        raw_content = c[<span class="hljs-string">&quot;content&quot;</span>]<br>        processed_content = _preprocess_markdown_for_embedding(raw_content)<br>        processed_texts.append(processed_content)<br>    <br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[RAG] Embedding start: total_texts=<span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(processed_texts)&#125;</span> batch_size=<span class="hljs-subst">&#123;batch_size&#125;</span>&quot;</span>)<br>    <br>    <span class="hljs-comment"># 批量编码</span><br>    vecs: <span class="hljs-type">List</span>[<span class="hljs-type">List</span>[<span class="hljs-built_in">float</span>]] = []<br>    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">0</span>, <span class="hljs-built_in">len</span>(processed_texts), batch_size):<br>        part = processed_texts[i:i+batch_size]<br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-comment"># 使用统一嵌入器（内部处理缓存）</span><br>            part_vecs = embedder.encode(part)<br>            <br>            <span class="hljs-comment"># 标准化为List[List[float]]格式</span><br>            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> <span class="hljs-built_in">isinstance</span>(part_vecs, <span class="hljs-built_in">list</span>):<br>                <span class="hljs-keyword">if</span> <span class="hljs-built_in">hasattr</span>(part_vecs, <span class="hljs-string">&quot;tolist&quot;</span>):<br>                    part_vecs = [part_vecs.tolist()]<br>                <span class="hljs-keyword">else</span>:<br>                    part_vecs = [<span class="hljs-built_in">list</span>(part_vecs)]<br>            <br>            <span class="hljs-comment"># 处理向量格式和维度</span><br>            <span class="hljs-keyword">for</span> v <span class="hljs-keyword">in</span> part_vecs:<br>                <span class="hljs-keyword">try</span>:<br>                    <span class="hljs-keyword">if</span> <span class="hljs-built_in">hasattr</span>(v, <span class="hljs-string">&quot;tolist&quot;</span>):<br>                        v = v.tolist()<br>                    v_norm = [<span class="hljs-built_in">float</span>(x) <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> v]<br>                    <br>                    <span class="hljs-comment"># 维度检查和调整</span><br>                    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(v_norm) != dimension:<br>                        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[WARNING] 向量维度异常: 期望<span class="hljs-subst">&#123;dimension&#125;</span>, 实际<span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(v_norm)&#125;</span>&quot;</span>)<br>                        <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(v_norm) &lt; dimension:<br>                            v_norm.extend([<span class="hljs-number">0.0</span>] * (dimension - <span class="hljs-built_in">len</span>(v_norm)))<br>                        <span class="hljs-keyword">else</span>:<br>                            v_norm = v_norm[:dimension]<br>                    <br>                    vecs.append(v_norm)<br>                <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>                    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[WARNING] 向量转换失败: <span class="hljs-subst">&#123;e&#125;</span>, 使用零向量&quot;</span>)<br>                    vecs.append([<span class="hljs-number">0.0</span>] * dimension)<br>                    <br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[WARNING] Batch <span class="hljs-subst">&#123;i&#125;</span> encoding failed: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>            <span class="hljs-comment"># 实现重试机制</span><br>            <span class="hljs-comment"># ... 重试逻辑 ...</span><br>        <br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;[RAG] Embedding progress: <span class="hljs-subst">&#123;<span class="hljs-built_in">min</span>(i+batch_size, <span class="hljs-built_in">len</span>(processed_texts))&#125;</span>/<span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(processed_texts)&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><h3 id="8-3-5-高级检索策略"><a href="#8-3-5-高级检索策略" class="headerlink" title="8.3.5 高级检索策略"></a>8.3.5 高级检索策略</h3><p>RAG系统的检索能力是其核心竞争力。在实际应用中，用户的查询表述与文档中的实际内容可能存在用词差异，导致相关文档无法被检索到。为了解决这个问题，HelloAgents实现了三种互补的高级检索策略：多查询扩展（MQE）、假设文档嵌入（HyDE）和统一的扩展检索框架。</p><p>（1）多查询扩展（MQE）</p><p>多查询扩展（Multi-Query Expansion）是一种通过生成语义等价的多样化查询来提高检索召回率的技术。这种方法的核心洞察是：同一个问题可以有多种不同的表述方式，而不同的表述可能匹配到不同的相关文档。例如，”如何学习Python”可以扩展为”Python入门教程”、”Python学习方法”、”Python编程指南”等多个查询。通过并行执行这些扩展查询并合并结果，系统能够覆盖更广泛的相关文档，避免因用词差异而遗漏重要信息。</p><p>MQE的优势在于它能够自动理解用户查询的多种可能含义，特别是对于模糊查询或专业术语查询效果显著。系统使用LLM生成扩展查询，确保扩展的多样性和语义相关性：</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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_prompt_mqe</span>(<span class="hljs-params">query: <span class="hljs-built_in">str</span>, n: <span class="hljs-built_in">int</span></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;使用LLM生成多样化的查询扩展&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">try</span>:<br>        <span class="hljs-keyword">from</span> ...core.llm <span class="hljs-keyword">import</span> HelloAgentsLLM<br>        llm = HelloAgentsLLM()<br>        prompt = [<br>            &#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;system&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;你是检索查询扩展助手。生成语义等价或互补的多样化查询。使用中文，简短，避免标点。&quot;</span>&#125;,<br>            &#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">f&quot;原始查询：<span class="hljs-subst">&#123;query&#125;</span>\n请给出<span class="hljs-subst">&#123;n&#125;</span>个不同表述的查询，每行一个。&quot;</span>&#125;<br>        ]<br>        text = llm.invoke(prompt)<br>        lines = [ln.strip(<span class="hljs-string">&quot;- \t&quot;</span>) <span class="hljs-keyword">for</span> ln <span class="hljs-keyword">in</span> (text <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;&quot;</span>).splitlines()]<br>        outs = [ln <span class="hljs-keyword">for</span> ln <span class="hljs-keyword">in</span> lines <span class="hljs-keyword">if</span> ln]<br>        <span class="hljs-keyword">return</span> outs[:n] <span class="hljs-keyword">or</span> [query]<br>    <span class="hljs-keyword">except</span> Exception:<br>        <span class="hljs-keyword">return</span> [query]<br></code></pre></td></tr></table></figure><p>（2）假设文档嵌入（HyDE）</p><p>假设文档嵌入（Hypothetical Document Embeddings，HyDE）是一种创新的检索技术，它的核心思想是”用答案找答案”。传统的检索方法是用问题去匹配文档，但问题和答案在语义空间中的分布往往存在差异——问题通常是疑问句，而文档内容是陈述句。HyDE通过让LLM先生成一个假设性的答案段落，然后用这个答案段落去检索真实文档，从而缩小了查询和文档之间的语义鸿沟。</p><p>这种方法的优势在于，假设答案与真实答案在语义空间中更加接近，因此能够更准确地匹配到相关文档。即使假设答案的内容不完全正确，它所包含的关键术语、概念和表述风格也能有效引导检索系统找到正确的文档。特别是对于专业领域的查询，HyDE能够生成包含领域术语的假设文档，显著提升检索精度：</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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_prompt_hyde</span>(<span class="hljs-params">query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;生成假设性文档用于改善检索&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">try</span>:<br>        <span class="hljs-keyword">from</span> ...core.llm <span class="hljs-keyword">import</span> HelloAgentsLLM<br>        llm = HelloAgentsLLM()<br>        prompt = [<br>            &#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;system&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;根据用户问题，先写一段可能的答案性段落，用于向量检索的查询文档（不要分析过程）。&quot;</span>&#125;,<br>            &#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">f&quot;问题：<span class="hljs-subst">&#123;query&#125;</span>\n请直接写一段中等长度、客观、包含关键术语的段落。&quot;</span>&#125;<br>        ]<br>        <span class="hljs-keyword">return</span> llm.invoke(prompt)<br>    <span class="hljs-keyword">except</span> Exception:<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span><br></code></pre></td></tr></table></figure><p>（3）扩展检索框架</p><p>HelloAgents将MQE和HyDE两种策略整合到统一的扩展检索框架中。系统通过<code>enable_mqe</code>和<code>enable_hyde</code>参数让用户可以根据具体场景选择启用哪些策略：对于需要高召回率的场景可以同时启用两种策略，对于性能敏感的场景可以只使用基础检索。</p><p>扩展检索的核心机制是”扩展-检索-合并”三步流程。首先，系统根据原始查询生成多个扩展查询（包括MQE生成的多样化查询和HyDE生成的假设文档）；然后，对每个扩展查询并行执行向量检索，获取候选文档池；最后，通过去重和分数排序合并所有结果，返回最相关的top-k文档。这种设计的巧妙之处在于，它通过<code>candidate_pool_multiplier</code>参数（默认为4）扩大候选池，确保有足够的候选文档进行筛选，同时通过智能去重避免返回重复内容。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">search_vectors_expanded</span>(<span class="hljs-params"></span><br><span class="hljs-params">    store = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    query: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;&quot;</span>,</span><br><span class="hljs-params">    top_k: <span class="hljs-built_in">int</span> = <span class="hljs-number">8</span>,</span><br><span class="hljs-params">    rag_namespace: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    only_rag_data: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span>,</span><br><span class="hljs-params">    score_threshold: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">float</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">    enable_mqe: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">False</span>,</span><br><span class="hljs-params">    mqe_expansions: <span class="hljs-built_in">int</span> = <span class="hljs-number">2</span>,</span><br><span class="hljs-params">    enable_hyde: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">False</span>,</span><br><span class="hljs-params">    candidate_pool_multiplier: <span class="hljs-built_in">int</span> = <span class="hljs-number">4</span>,</span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">    Search with query expansion using unified embedding and Qdrant.</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> query:<br>        <span class="hljs-keyword">return</span> []<br>    <br>    <span class="hljs-comment"># 创建默认存储</span><br>    <span class="hljs-keyword">if</span> store <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br>        store = _create_default_vector_store()<br>    <br>    <span class="hljs-comment"># 查询扩展</span><br>    expansions: <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>] = [query]<br>    <br>    <span class="hljs-keyword">if</span> enable_mqe <span class="hljs-keyword">and</span> mqe_expansions &gt; <span class="hljs-number">0</span>:<br>        expansions.extend(_prompt_mqe(query, mqe_expansions))<br>    <span class="hljs-keyword">if</span> enable_hyde:<br>        hyde_text = _prompt_hyde(query)<br>        <span class="hljs-keyword">if</span> hyde_text:<br>            expansions.append(hyde_text)<br><br>    <span class="hljs-comment"># 去重和修剪</span><br>    uniq: <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>] = []<br>    <span class="hljs-keyword">for</span> e <span class="hljs-keyword">in</span> expansions:<br>        <span class="hljs-keyword">if</span> e <span class="hljs-keyword">and</span> e <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> uniq:<br>            uniq.append(e)<br>    expansions = uniq[: <span class="hljs-built_in">max</span>(<span class="hljs-number">1</span>, <span class="hljs-built_in">len</span>(uniq))]<br><br>    <span class="hljs-comment"># 分配候选池</span><br>    pool = <span class="hljs-built_in">max</span>(top_k * candidate_pool_multiplier, <span class="hljs-number">20</span>)<br>    per = <span class="hljs-built_in">max</span>(<span class="hljs-number">1</span>, pool // <span class="hljs-built_in">max</span>(<span class="hljs-number">1</span>, <span class="hljs-built_in">len</span>(expansions)))<br><br>    <span class="hljs-comment"># 构建RAG数据过滤器</span><br>    where = &#123;<span class="hljs-string">&quot;memory_type&quot;</span>: <span class="hljs-string">&quot;rag_chunk&quot;</span>&#125;<br>    <span class="hljs-keyword">if</span> only_rag_data:<br>        where[<span class="hljs-string">&quot;is_rag_data&quot;</span>] = <span class="hljs-literal">True</span><br>        where[<span class="hljs-string">&quot;data_source&quot;</span>] = <span class="hljs-string">&quot;rag_pipeline&quot;</span><br>    <span class="hljs-keyword">if</span> rag_namespace:<br>        where[<span class="hljs-string">&quot;rag_namespace&quot;</span>] = rag_namespace<br><br>    <span class="hljs-comment"># 收集所有扩展查询的结果</span><br>    agg: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Dict</span>] = &#123;&#125;<br>    <span class="hljs-keyword">for</span> q <span class="hljs-keyword">in</span> expansions:<br>        qv = embed_query(q)<br>        hits = store.search_similar(<br>            query_vector=qv, <br>            limit=per, <br>            score_threshold=score_threshold, <br>            where=where<br>        )<br>        <span class="hljs-keyword">for</span> h <span class="hljs-keyword">in</span> hits:<br>            mid = h.get(<span class="hljs-string">&quot;metadata&quot;</span>, &#123;&#125;).get(<span class="hljs-string">&quot;memory_id&quot;</span>, h.get(<span class="hljs-string">&quot;id&quot;</span>))<br>            s = <span class="hljs-built_in">float</span>(h.get(<span class="hljs-string">&quot;score&quot;</span>, <span class="hljs-number">0.0</span>))<br>            <span class="hljs-keyword">if</span> mid <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> agg <span class="hljs-keyword">or</span> s &gt; <span class="hljs-built_in">float</span>(agg[mid].get(<span class="hljs-string">&quot;score&quot;</span>, <span class="hljs-number">0.0</span>)):<br>                agg[mid] = h<br>    <br>    <span class="hljs-comment"># 按分数排序返回</span><br>    merged = <span class="hljs-built_in">list</span>(agg.values())<br>    merged.sort(key=<span class="hljs-keyword">lambda</span> x: <span class="hljs-built_in">float</span>(x.get(<span class="hljs-string">&quot;score&quot;</span>, <span class="hljs-number">0.0</span>)), reverse=<span class="hljs-literal">True</span>)<br>    <span class="hljs-keyword">return</span> merged[:top_k]<br></code></pre></td></tr></table></figure><p>实际应用中，这三种策略的组合使用效果最佳。MQE擅长处理用词多样性问题，HyDE擅长处理语义鸿沟问题，而统一框架则确保了结果的质量和多样性。对于一般查询，建议启用MQE；对于专业领域查询，建议同时启用MQE和HyDE；对于性能敏感场景，可以只使用基础检索或仅启用MQE。</p><p>当然还有很多有趣的方法，这里只是为大家适当的扩展介绍，在实际的使用场景里也需要去尝试寻找适合问题的解决方案。</p><h2 id="8-4-构建智能文档问答助手"><a href="#8-4-构建智能文档问答助手" class="headerlink" title="8.4 构建智能文档问答助手"></a>8.4 构建智能文档问答助手</h2><p>在前面的章节中，我们详细介绍了HelloAgents的记忆系统和RAG系统的设计与实现。现在，让我们通过一个完整的实战案例，展示如何将这两个系统有机结合，构建一个智能文档问答助手。</p><h3 id="8-4-1-案例背景与目标"><a href="#8-4-1-案例背景与目标" class="headerlink" title="8.4.1 案例背景与目标"></a>8.4.1 案例背景与目标</h3><p>在实际工作中，我们经常需要处理大量的技术文档、研究论文、产品手册等PDF文件。传统的文档阅读方式效率低下，难以快速定位关键信息，更无法建立知识间的关联。</p><p>本案例将基于Datawhale另外一门动手学大模型教程Happy-LLM的公测PDF文档<code>Happy-LLM-0727.pdf</code>为例，构建一个<strong>基于Gradio的Web应用</strong>，展示如何使用RAGTool和MemoryTool构建完整的交互式学习助手。PDF可在这个<a href="https://github.com/datawhalechina/happy-llm/releases/download/v1.0.1/Happy-LLM-0727.pdf">链接</a>获取。</p><p>我们希望实现以下功能：</p><ol><li><p><strong>智能文档处理</strong>：使用MarkItDown实现PDF到Markdown的统一转换，基于Markdown结构的智能分块策略，高效的向量化和索引构建</p></li><li><p><strong>高级检索问答</strong>：多查询扩展（MQE）提升召回率，假设文档嵌入（HyDE）改善检索精度，上下文感知的智能问答</p></li><li><p><strong>多层次记忆管理</strong>：工作记忆管理当前学习任务和上下文，情景记忆记录学习事件和查询历史，语义记忆存储概念知识和理解，感知记忆处理文档特征和多模态信息</p></li><li><p><strong>个性化学习支持</strong>：基于学习历史的个性化推荐，记忆整合和选择性遗忘，学习报告生成和进度追踪</p></li></ol><p>为了更清晰地展示整个系统的工作流程，图8.6展示了五个步骤之间的关系和数据流动。五个步骤形成了一个完整的闭环：步骤1将PDF文档处理后的信息记录到记忆系统，步骤2的检索结果也会记录到记忆系统，步骤3展示记忆系统的完整功能（添加、检索、整合、遗忘），步骤4整合RAG和Memory提供智能路由，步骤5收集所有统计信息生成学习报告。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/8-figures/8-6.png" alt="" width="85%"/>  <p>图 8.6 智能问答助手的五步执行流程</p></div><p>接下来，我们将展示如何实现这个Web应用。整个应用分为三个核心部分：</p><ol><li><strong>核心助手类（PDFLearningAssistant）</strong>：封装RAGTool和MemoryTool的调用逻辑</li><li><strong>Gradio Web界面</strong>：提供友好的用户交互界面，这个部分可以参考示例代码学习</li><li><strong>其他核心功能</strong>：笔记记录、学习回顾、统计查看和报告生成</li></ol><h3 id="8-4-2-核心助手类的实现"><a href="#8-4-2-核心助手类的实现" class="headerlink" title="8.4.2 核心助手类的实现"></a>8.4.2 核心助手类的实现</h3><p>首先，我们实现核心的助手类<code>PDFLearningAssistant</code>，它封装了RAGTool和MemoryTool的调用逻辑。</p><p>（1）类的初始化</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">PDFLearningAssistant</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;智能文档问答助手&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, user_id: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;default_user&quot;</span></span>):<br>        <span class="hljs-string">&quot;&quot;&quot;初始化学习助手</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Args:</span><br><span class="hljs-string">            user_id: 用户ID，用于隔离不同用户的数据</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        self.user_id = user_id<br>        self.session_id = <span class="hljs-string">f&quot;session_<span class="hljs-subst">&#123;datetime.now().strftime(<span class="hljs-string">&#x27;%Y%m%d_%H%M%S&#x27;</span>)&#125;</span>&quot;</span><br><br>        <span class="hljs-comment"># 初始化工具</span><br>        self.memory_tool = MemoryTool(user_id=user_id)<br>        self.rag_tool = RAGTool(rag_namespace=<span class="hljs-string">f&quot;pdf_<span class="hljs-subst">&#123;user_id&#125;</span>&quot;</span>)<br><br>        <span class="hljs-comment"># 学习统计</span><br>        self.stats = &#123;<br>            <span class="hljs-string">&quot;session_start&quot;</span>: datetime.now(),<br>            <span class="hljs-string">&quot;documents_loaded&quot;</span>: <span class="hljs-number">0</span>,<br>            <span class="hljs-string">&quot;questions_asked&quot;</span>: <span class="hljs-number">0</span>,<br>            <span class="hljs-string">&quot;concepts_learned&quot;</span>: <span class="hljs-number">0</span><br>        &#125;<br><br>        <span class="hljs-comment"># 当前加载的文档</span><br>        self.current_document = <span class="hljs-literal">None</span><br></code></pre></td></tr></table></figure><p>在这个初始化过程中，我们做了几个关键的设计决策：</p><p><strong>MemoryTool的初始化</strong>：通过<code>user_id</code>参数实现用户级别的记忆隔离。不同用户的学习记忆是完全独立的，每个用户都有自己的工作记忆、情景记忆、语义记忆和感知记忆空间。</p><p><strong>RAGTool的初始化</strong>：通过<code>rag_namespace</code>参数实现知识库的命名空间隔离。使用<code>f&quot;pdf_&#123;user_id&#125;&quot;</code>作为命名空间，每个用户都有自己独立的PDF知识库。</p><p><strong>会话管理</strong>：<code>session_id</code>用于追踪单次学习会话的完整过程，便于后续的学习历程回顾和分析。</p><p><strong>统计信息</strong>：<code>stats</code>字典记录关键的学习指标，用于生成学习报告。</p><p>（2）加载PDF文档</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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">load_document</span>(<span class="hljs-params">self, pdf_path: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;加载PDF文档到知识库</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        pdf_path: PDF文件路径</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        Dict: 包含success和message的结果</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> os.path.exists(pdf_path):<br>        <span class="hljs-keyword">return</span> &#123;<span class="hljs-string">&quot;success&quot;</span>: <span class="hljs-literal">False</span>, <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">f&quot;文件不存在: <span class="hljs-subst">&#123;pdf_path&#125;</span>&quot;</span>&#125;<br><br>    start_time = time.time()<br><br>    <span class="hljs-comment"># 【RAGTool】处理PDF: MarkItDown转换 → 智能分块 → 向量化</span><br>    result = self.rag_tool.execute(<br>        <span class="hljs-string">&quot;add_document&quot;</span>,<br>        file_path=pdf_path,<br>        chunk_size=<span class="hljs-number">1000</span>,<br>        chunk_overlap=<span class="hljs-number">200</span><br>    )<br><br>    process_time = time.time() - start_time<br><br>    <span class="hljs-keyword">if</span> result.get(<span class="hljs-string">&quot;success&quot;</span>, <span class="hljs-literal">False</span>):<br>        self.current_document = os.path.basename(pdf_path)<br>        self.stats[<span class="hljs-string">&quot;documents_loaded&quot;</span>] += <span class="hljs-number">1</span><br><br>        <span class="hljs-comment"># 【MemoryTool】记录到学习记忆</span><br>        self.memory_tool.execute(<br>            <span class="hljs-string">&quot;add&quot;</span>,<br>            content=<span class="hljs-string">f&quot;加载了文档《<span class="hljs-subst">&#123;self.current_document&#125;</span>》&quot;</span>,<br>            memory_type=<span class="hljs-string">&quot;episodic&quot;</span>,<br>            importance=<span class="hljs-number">0.9</span>,<br>            event_type=<span class="hljs-string">&quot;document_loaded&quot;</span>,<br>            session_id=self.session_id<br>        )<br><br>        <span class="hljs-keyword">return</span> &#123;<br>            <span class="hljs-string">&quot;success&quot;</span>: <span class="hljs-literal">True</span>,<br>            <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">f&quot;加载成功！(耗时: <span class="hljs-subst">&#123;process_time:<span class="hljs-number">.1</span>f&#125;</span>秒)&quot;</span>,<br>            <span class="hljs-string">&quot;document&quot;</span>: self.current_document<br>        &#125;<br>    <span class="hljs-keyword">else</span>:<br>        <span class="hljs-keyword">return</span> &#123;<br>            <span class="hljs-string">&quot;success&quot;</span>: <span class="hljs-literal">False</span>,<br>            <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">f&quot;加载失败: <span class="hljs-subst">&#123;result.get(<span class="hljs-string">&#x27;error&#x27;</span>, <span class="hljs-string">&#x27;未知错误&#x27;</span>)&#125;</span>&quot;</span><br>        &#125;<br></code></pre></td></tr></table></figure><p>我们通过一行代码就能完成PDF的处理：</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><code class="hljs python">result = self.rag_tool.execute(<br>    <span class="hljs-string">&quot;add_document&quot;</span>,<br>    file_path=pdf_path,<br>    chunk_size=<span class="hljs-number">1000</span>,<br>    chunk_overlap=<span class="hljs-number">200</span><br>)<br></code></pre></td></tr></table></figure><p>这个调用会触发RAGTool的完整处理流程（MarkItDown转换、增强处理、智能分块、向量化存储），这些内部细节在8.3节已经详细介绍过。我们只需要关注：</p><ul><li><strong>操作类型</strong>：<code>&quot;add_document&quot;</code> - 添加文档到知识库</li><li><strong>文件路径</strong>：<code>file_path</code> - PDF文件的路径</li><li><strong>分块参数</strong>：<code>chunk_size=1000, chunk_overlap=200</code> - 控制文本分块</li><li><strong>返回结果</strong>：包含处理状态和统计信息的字典</li></ul><p>文档加载成功后，我们使用MemoryTool记录到情景记忆：</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></pre></td><td class="code"><pre><code class="hljs python">self.memory_tool.execute(<br>    <span class="hljs-string">&quot;add&quot;</span>,<br>    content=<span class="hljs-string">f&quot;加载了文档《<span class="hljs-subst">&#123;self.current_document&#125;</span>》&quot;</span>,<br>    memory_type=<span class="hljs-string">&quot;episodic&quot;</span>,<br>    importance=<span class="hljs-number">0.9</span>,<br>    event_type=<span class="hljs-string">&quot;document_loaded&quot;</span>,<br>    session_id=self.session_id<br>)<br></code></pre></td></tr></table></figure><p><strong>为什么用情景记忆？</strong> 因为这是一个具体的、有时间戳的事件，适合用情景记忆记录。<code>session_id</code>参数将这个事件关联到当前学习会话，便于后续回顾学习历程。</p><p>这个记忆记录为后续的个性化服务奠定了基础：</p><ul><li>用户询问”我之前加载过哪些文档？” → 从情景记忆中检索</li><li>系统可以追踪用户的学习历程和文档使用情况</li></ul><h3 id="8-4-3-智能问答功能"><a href="#8-4-3-智能问答功能" class="headerlink" title="8.4.3 智能问答功能"></a>8.4.3 智能问答功能</h3><p>文档加载完成后，用户就可以向文档提问了。我们实现一个<code>ask</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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">ask</span>(<span class="hljs-params">self, question: <span class="hljs-built_in">str</span>, use_advanced_search: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;向文档提问</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Args:</span><br><span class="hljs-string">        question: 用户问题</span><br><span class="hljs-string">        use_advanced_search: 是否使用高级检索（MQE + HyDE）</span><br><span class="hljs-string"></span><br><span class="hljs-string">    Returns:</span><br><span class="hljs-string">        str: 答案</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.current_document:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;⚠️ 请先加载文档！&quot;</span><br><br>    <span class="hljs-comment"># 【MemoryTool】记录问题到工作记忆</span><br>    self.memory_tool.execute(<br>        <span class="hljs-string">&quot;add&quot;</span>,<br>        content=<span class="hljs-string">f&quot;提问: <span class="hljs-subst">&#123;question&#125;</span>&quot;</span>,<br>        memory_type=<span class="hljs-string">&quot;working&quot;</span>,<br>        importance=<span class="hljs-number">0.6</span>,<br>        session_id=self.session_id<br>    )<br><br>    <span class="hljs-comment"># 【RAGTool】使用高级检索获取答案</span><br>    answer = self.rag_tool.execute(<br>        <span class="hljs-string">&quot;ask&quot;</span>,<br>        question=question,<br>        limit=<span class="hljs-number">5</span>,<br>        enable_advanced_search=use_advanced_search,<br>        enable_mqe=use_advanced_search,<br>        enable_hyde=use_advanced_search<br>    )<br><br>    <span class="hljs-comment"># 【MemoryTool】记录到情景记忆</span><br>    self.memory_tool.execute(<br>        <span class="hljs-string">&quot;add&quot;</span>,<br>        content=<span class="hljs-string">f&quot;关于&#x27;<span class="hljs-subst">&#123;question&#125;</span>&#x27;的学习&quot;</span>,<br>        memory_type=<span class="hljs-string">&quot;episodic&quot;</span>,<br>        importance=<span class="hljs-number">0.7</span>,<br>        event_type=<span class="hljs-string">&quot;qa_interaction&quot;</span>,<br>        session_id=self.session_id<br>    )<br><br>    self.stats[<span class="hljs-string">&quot;questions_asked&quot;</span>] += <span class="hljs-number">1</span><br><br>    <span class="hljs-keyword">return</span> answer<br></code></pre></td></tr></table></figure><p>当我们调用<code>self.rag_tool.execute(&quot;ask&quot;, ...)</code>时，RAGTool内部执行了以下高级检索流程：</p><ol><li><p><strong>多查询扩展（MQE）</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><code class="hljs python"><span class="hljs-comment"># 生成多样化查询</span><br>expanded_queries = self._generate_multi_queries(question)<br><span class="hljs-comment"># 例如，对于&quot;什么是大语言模型？&quot;，可能生成：</span><br><span class="hljs-comment"># - &quot;大语言模型的定义是什么？&quot;</span><br><span class="hljs-comment"># - &quot;请解释一下大语言模型&quot;</span><br><span class="hljs-comment"># - &quot;LLM是什么意思？&quot;</span><br></code></pre></td></tr></table></figure><p>MQE通过LLM生成语义等价但表述不同的查询，从多个角度理解用户意图，提升召回率30%-50%。</p></li><li><p><strong>假设文档嵌入（HyDE）</strong>：</p><ul><li>生成假设答案文档，桥接查询和文档的语义鸿沟</li><li>使用假设答案的向量进行检索</li></ul></li></ol><p>这些高级检索技术的内部实现在8.3.5节已经详细介绍过。</p><h3 id="8-4-4-其他核心功能"><a href="#8-4-4-其他核心功能" class="headerlink" title="8.4.4 其他核心功能"></a>8.4.4 其他核心功能</h3><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">add_note</span>(<span class="hljs-params">self, content: <span class="hljs-built_in">str</span>, concept: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span></span>):<br>    <span class="hljs-string">&quot;&quot;&quot;添加学习笔记&quot;&quot;&quot;</span><br>    self.memory_tool.execute(<br>        <span class="hljs-string">&quot;add&quot;</span>,<br>        content=content,<br>        memory_type=<span class="hljs-string">&quot;semantic&quot;</span>,<br>        importance=<span class="hljs-number">0.8</span>,<br>        concept=concept <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;general&quot;</span>,<br>        session_id=self.session_id<br>    )<br>    self.stats[<span class="hljs-string">&quot;concepts_learned&quot;</span>] += <span class="hljs-number">1</span><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">recall</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span>, limit: <span class="hljs-built_in">int</span> = <span class="hljs-number">5</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;回顾学习历程&quot;&quot;&quot;</span><br>    result = self.memory_tool.execute(<br>        <span class="hljs-string">&quot;search&quot;</span>,<br>        query=query,<br>        limit=limit<br>    )<br>    <span class="hljs-keyword">return</span> result<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">get_stats</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;获取学习统计&quot;&quot;&quot;</span><br>    duration = (datetime.now() - self.stats[<span class="hljs-string">&quot;session_start&quot;</span>]).total_seconds()<br>    <span class="hljs-keyword">return</span> &#123;<br>        <span class="hljs-string">&quot;会话时长&quot;</span>: <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;duration:<span class="hljs-number">.0</span>f&#125;</span>秒&quot;</span>,<br>        <span class="hljs-string">&quot;加载文档&quot;</span>: self.stats[<span class="hljs-string">&quot;documents_loaded&quot;</span>],<br>        <span class="hljs-string">&quot;提问次数&quot;</span>: self.stats[<span class="hljs-string">&quot;questions_asked&quot;</span>],<br>        <span class="hljs-string">&quot;学习笔记&quot;</span>: self.stats[<span class="hljs-string">&quot;concepts_learned&quot;</span>],<br>        <span class="hljs-string">&quot;当前文档&quot;</span>: self.current_document <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;未加载&quot;</span><br>    &#125;<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">generate_report</span>(<span class="hljs-params">self, save_to_file: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span></span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;生成学习报告&quot;&quot;&quot;</span><br>    memory_summary = self.memory_tool.execute(<span class="hljs-string">&quot;summary&quot;</span>, limit=<span class="hljs-number">10</span>)<br>    rag_stats = self.rag_tool.execute(<span class="hljs-string">&quot;stats&quot;</span>)<br><br>    duration = (datetime.now() - self.stats[<span class="hljs-string">&quot;session_start&quot;</span>]).total_seconds()<br>    report = &#123;<br>        <span class="hljs-string">&quot;session_info&quot;</span>: &#123;<br>            <span class="hljs-string">&quot;session_id&quot;</span>: self.session_id,<br>            <span class="hljs-string">&quot;user_id&quot;</span>: self.user_id,<br>            <span class="hljs-string">&quot;start_time&quot;</span>: self.stats[<span class="hljs-string">&quot;session_start&quot;</span>].isoformat(),<br>            <span class="hljs-string">&quot;duration_seconds&quot;</span>: duration<br>        &#125;,<br>        <span class="hljs-string">&quot;learning_metrics&quot;</span>: &#123;<br>            <span class="hljs-string">&quot;documents_loaded&quot;</span>: self.stats[<span class="hljs-string">&quot;documents_loaded&quot;</span>],<br>            <span class="hljs-string">&quot;questions_asked&quot;</span>: self.stats[<span class="hljs-string">&quot;questions_asked&quot;</span>],<br>            <span class="hljs-string">&quot;concepts_learned&quot;</span>: self.stats[<span class="hljs-string">&quot;concepts_learned&quot;</span>]<br>        &#125;,<br>        <span class="hljs-string">&quot;memory_summary&quot;</span>: memory_summary,<br>        <span class="hljs-string">&quot;rag_status&quot;</span>: rag_stats<br>    &#125;<br><br>    <span class="hljs-keyword">if</span> save_to_file:<br>        report_file = <span class="hljs-string">f&quot;learning_report_<span class="hljs-subst">&#123;self.session_id&#125;</span>.json&quot;</span><br>        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(report_file, <span class="hljs-string">&#x27;w&#x27;</span>, encoding=<span class="hljs-string">&#x27;utf-8&#x27;</span>) <span class="hljs-keyword">as</span> f:<br>            json.dump(report, f, ensure_ascii=<span class="hljs-literal">False</span>, indent=<span class="hljs-number">2</span>, default=<span class="hljs-built_in">str</span>)<br>        report[<span class="hljs-string">&quot;report_file&quot;</span>] = report_file<br><br>    <span class="hljs-keyword">return</span> report<br></code></pre></td></tr></table></figure><p>这些方法分别实现了：</p><ul><li><strong>add_note</strong>：将学习笔记保存到语义记忆</li><li><strong>recall</strong>：从记忆系统中检索学习历程</li><li><strong>get_stats</strong>：获取当前会话的统计信息</li><li><strong>generate_report</strong>：生成详细的学习报告并保存为JSON文件</li></ul><h3 id="8-4-5-运行效果展示"><a href="#8-4-5-运行效果展示" class="headerlink" title="8.4.5 运行效果展示"></a>8.4.5 运行效果展示</h3><p>接下来是运行效果展示，如图8.7所示，进入主页面后需要先初始化助手，也就是加载我们的数据库，模型，API之类的载入操作。后传入PDF文档，并点击加载文档。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/8-figures/8-7.png" alt="" width="85%"/>  <p>图 8.7 问答助手主页面</p></div><p>第一个功能是智能问答，将可以基于上传的文档进行检索，并返回参考来源和相关资料的相似度计算，这是RAG tool能力的体现，如图8.8所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/8-figures/8-8.png" alt="" width="85%"/>  <p>图 8.8 问答助手主页面</p></div><p>第二个功能是学习笔记，如图8.9所示，可以对于相关概念进行勾选，以及撰写笔记内容，这一部分运用到Memory tool，将会存放你的个人笔记在数据库内，方便统计和后续返回整体的学习报告。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/8-figures/8-9.png" alt="" width="85%"/>  <p>图 8.9 问答助手主页面</p></div><p>最后是学习进度的统计和报告的生成，如图8.10所示，我们将可以看到使用助手期间加载的文档数量，提问次数，和笔记数量，最终将我们的问答结果和笔记整理为一个JSON文档返回。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/8-figures/8-10.png" alt="" width="85%"/>  <p>图 8.10 问答助手主页面</p></div><p>通过这个问答助手的案例，我们展示了如何使用RAGTool和MemoryTool构建一个完整的<strong>基于Web的智能文档问答系统</strong>。完整的代码可以在<code>code/chapter8/11_Q&amp;A_Assistant.py</code>中找到。启动后访问 <code>http://localhost:7860</code> 即可使用这个智能学习助手。</p><p>建议读者亲自运行这个案例，体验RAG和Memory的能力，并在此基础上进行扩展和定制，构建符合自己需求的智能应用！</p><h2 id="8-5-本章总结与展望"><a href="#8-5-本章总结与展望" class="headerlink" title="8.5 本章总结与展望"></a>8.5 本章总结与展望</h2><p>在本章中，我们成功地为HelloAgents框架增加了两个核心能力：记忆系统和RAG系统。</p><p>对于希望深入学习和应用本章内容的读者，我们提供以下建议：</p><ol><li><p>从零到一，亲手设计一个基础记忆模块，并逐步迭代，为其增添更复杂的特性。</p></li><li><p>在项目中尝试并评估不同的嵌入模型与检索策略，寻找特定任务下的最优解。</p></li><li><p>将所学的记忆与 RAG 系统应用于一个真实的个人项目，在实战中检验和提升能力。</p></li></ol><p>进阶探索</p><ol><li>跟踪并研究前沿memory，rag仓库，学习优秀实现。</li><li>探索将 RAG 架构应用于多模态（文本+图像）或跨模态场景的可能性。</li><li>参与HelloAgents开源项目，贡献自己的想法和代码</li></ol><p>通过本章的学习，您不仅掌握了Memory和RAG系统的实现技术，更重要的是理解了如何将认知科学理论转化为实际的工程解决方案。这种跨学科的思维方式，将为您在AI领域的进一步发展奠定坚实的基础。</p><p>最后，让我们通过一个思维导图来总结本章的完整知识体系，如图8.11所示：</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/8-figures/8-11.png" alt="" width="85%"/>  <p>图 8.11 Hello-agents第八章知识总结</p></div><p>本章展示了HelloAgents框架记忆系统和RAG技术的能力，我们成功构建了一个具有真正”智能”的学习助手。这种架构可以轻松扩展到其他应用场景，如客户服务、技术支持、个人助理等领域。</p><p>在下一章中，我们将继续探索如何通过上下文工程进一步提升智能体的对话质量和用户体验，敬请期待！</p><h2 id="习题"><a href="#习题" class="headerlink" title="习题"></a>习题</h2><blockquote><p><strong>提示</strong>：部分习题没有标准答案，重点在于培养学习者对记忆系统和RAG技术的综合理解和实践能力。</p></blockquote><ol><li><p>本章介绍了四种记忆类型：工作记忆、情景记忆、语义记忆和感知记忆。请分析：</p><ul><li>在8.2.5节中，每种记忆类型都有独特的评分公式。请对比情景记忆和语义记忆的评分机制，解释为什么情景记忆更强调”时间近因性”（权重0.2），而语义记忆更强调”图检索”（权重0.3）？</li><li>如果要设计一个”个人健康管理助手”（需要记录用户的饮食、运动、睡眠数据，并提供健康建议），你会如何组合使用这四种记忆类型？请为每种记忆类型设计具体的应用场景。</li><li>工作记忆采用TTL（Time To Live）机制自动清理过期数据。请思考：在什么情况下，重要的工作记忆应该被”整合”（consolidate）为长期记忆？如何设计一个自动整合的触发条件？</li></ul></li><li><p>在8.3节的RAG系统中，我们使用MarkItDown将各种格式文档统一转换为Markdown。请深入思考：</p><blockquote><p><strong>提示</strong>：这是一道动手实践题，建议实际操作</p></blockquote><ul><li>当前的智能分块策略基于Markdown的标题层次（#、##、###）进行分割。如果处理的是没有明确标题结构的文档（如小说、法律条文），应该如何优化分块策略？请尝试实现一个基于”语义边界”的分块算法。</li><li>在8.3.5节中介绍了MQE（多查询扩展）和HyDE（假设文档嵌入）两种高级检索策略。请选择一个实际场景（如技术文档问答、医疗知识检索），对比基础检索、MQE和HyDE三种方法的效果差异，并分析各自的适用场景。</li><li>RAG系统的检索质量很大程度上取决于嵌入模型的选择。请对比本章提到的三种嵌入方案（百炼API、本地Transformer、TF-IDF），从准确性、速度、成本、离线部署等维度进行评估，并给出选型建议。</li></ul></li><li><p>记忆系统的”遗忘”机制是模拟人类认知的重要设计。基于8.2.3节的MemoryTool，请完成以下扩展实践：</p><blockquote><p><strong>提示</strong>：这是一道动手实践题，建议实际操作</p></blockquote><ul><li>当前提供了三种遗忘策略：基于重要性、基于时间、基于容量。请设计并实现一个”智能遗忘”策略，综合考虑重要性、访问频率、时间衰减等多个因素，使用加权评分来决定哪些记忆应该被遗忘。</li><li>在长期运行的智能体系统中，记忆数据库可能会积累大量数据。请设计一个”记忆归档”机制：将长期不用但可能有价值的记忆转移到冷存储，需要时再恢复。这个机制应该如何与现有的四种记忆类型集成？</li><li>思考：如果智能体需要”忘记”某些敏感信息（如用户隐私数据），仅仅从数据库删除是否足够？在使用向量数据库和图数据库的情况下，如何确保数据被彻底清除？</li></ul></li><li><p>在8.4节的”智能学习助手”案例中，我们结合了MemoryTool和RAGTool。请深入分析：</p><ul><li>案例中的<code>ask_question()</code>方法同时使用了RAG检索和记忆检索。请分析：在什么情况下应该优先使用RAG？在什么情况下应该优先使用Memory？如何设计一个”智能路由”机制来自动选择最合适的检索方式？</li><li>当前的学习报告（<code>generate_report()</code>）只包含统计信息。请扩展这个功能，设计一个更智能的学习报告生成器：能够分析用户的学习轨迹、识别知识盲点、推荐下一步学习内容。这需要用到哪些记忆类型和检索策略？</li><li>假设你要将这个学习助手部署为多用户的Web服务，每个用户都有独立的记忆和知识库。请设计数据隔离方案：如何在Qdrant和Neo4j中实现用户级别的数据隔离？如何优化多用户场景下的检索性能？</li></ul></li><li><p>语义记忆使用了Neo4j图数据库来存储知识图谱。请思考：</p><ul><li>在8.2.5节的语义记忆实现中，系统会自动提取实体和关系构建知识图谱。请分析：这种自动提取的准确性如何？在什么情况下可能会提取出错误的实体或关系？如何设计一个”知识图谱质量评估”机制？</li><li>知识图谱的一个重要优势是支持复杂的关系推理。请设计一个查询场景，充分利用Neo4j的图查询能力（如多跳关系、路径查找），实现纯向量检索无法完成的任务。</li><li>对比语义记忆的”向量检索+图检索”混合策略与纯向量检索：在什么类型的查询中，图检索能够带来显著的性能提升？请用具体例子说明。</li></ul></li></ol><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>[1] Atkinson, R. C., &amp; Shiffrin, R. M. (1968). Human memory: A proposed system and its control processes. In <em>Psychology of learning and motivation</em> (Vol. 2, pp. 89-195). Academic press.</p>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC8%E7%AB%A0-%E8%AE%B0%E5%BF%86%E4%B8%8E%E6%A3%80%E7%B4%A2/</id>
    <link href="http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC8%E7%AB%A0-%E8%AE%B0%E5%BF%86%E4%B8%8E%E6%A3%80%E7%B4%A2/"/>
    <published>2026-03-01T18:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="第八章-记忆与检索"><a href="#第八章-记忆与检索" class="headerlink" title="第八章 记忆与检索"></a>第八章 记忆与检索</h1><p>在前面的章节中，我们构建了HelloAgents框架的基础架构，实现了多种智能体范式]]>
    </summary>
    <title>第八章 记忆与检索</title>
    <updated>2026-03-08T09:24:16.329Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="第七章-构建你的智能体框架"><a href="#第七章-构建你的智能体框架" class="headerlink" title="第七章 构建你的智能体框架"></a>第七章 构建你的智能体框架</h1><p>在前面的章节中，我们讲解了智能体的基础知识，并体验了主流框架带来的开发便利。从本章开始，我们将进入一个更具挑战也更有价值的阶段：<strong>从零开始，逐步构建一个智能体框架——HelloAgents</strong>。</p><p>为确保学习过程的连贯性与可复现性，HelloAgents 将以版本迭代的方式推进开发。每一章都会在前一章的基础上增加新的功能模块，并将智能体相关的知识点进行串讲与实现。最终，我们将利用这个自建框架，来高效地实现本书后续章节中的高级应用案例。</p><h2 id="7-1-框架整体架构设计"><a href="#7-1-框架整体架构设计" class="headerlink" title="7.1 框架整体架构设计"></a>7.1 框架整体架构设计</h2><h3 id="7-1-1-为何需要自建Agent框架"><a href="#7-1-1-为何需要自建Agent框架" class="headerlink" title="7.1.1 为何需要自建Agent框架"></a>7.1.1 为何需要自建Agent框架</h3><p>在智能体技术快速发展的今天，市面上已经存在众多成熟的Agent框架。那么，为什么我们还要从零开始构建一个新的框架呢？</p><p>（1）市面框架的快速迭代与局限性</p><p>智能体领域是一个快速发展的领域，随时会有新的概念产生，对于智能体的设计每个框架都有自己的定位和理解，不过智能体的核心知识点是一致的。</p><ul><li><strong>过度抽象的复杂性</strong>：许多框架为了追求通用性，引入了大量抽象层和配置选项。以LangChain为例，其链式调用机制虽然灵活，但对初学者而言学习曲线陡峭，往往需要理解大量概念才能完成简单任务。</li><li><strong>快速迭代带来的不稳定性</strong>：商业化框架为了抢占市场，API接口变更频繁。开发者经常面临版本升级后代码无法运行的困扰，维护成本居高不下。</li><li><strong>黑盒化的实现逻辑</strong>：许多框架将核心逻辑封装得过于严密，开发者难以理解Agent的内部工作机制，缺乏深度定制能力。遇到问题时只能依赖文档和社区支持，尤其是如果社区不够活跃，可能一个反馈意见会非常久也没有人推进，影响后续的开发效率。</li><li><strong>依赖关系的复杂性</strong>：成熟框架往往携带大量依赖包，安装包体积庞大，在需要与别的项目代码配合使用可能出现依赖冲突问题。</li></ul><p>（2）从使用者到构建者的能力跃迁</p><p>构建自己的Agent框架，实际上是一个从”使用者”向”构建者”转变的过程。这种转变带来的价值是长远的。</p><ul><li><strong>深度理解Agent工作原理</strong>：通过亲手实现每个组件，开发者能够真正理解Agent的思考过程、工具调用机制、以及各种设计模式的好坏与区别。</li><li><strong>获得完全的控制权</strong>：自建框架意味着对每一行代码都有完全的掌控，可以根据具体需求进行精确调优，而不受第三方框架设计理念的束缚。</li><li><strong>培养系统设计能力</strong>：框架构建过程涉及模块化设计、接口抽象、错误处理等软件工程核心技能，这些能力对开发者的长期成长具有重要价值。</li></ul><p>（3）定制化需求与深度掌握的必要性</p><p>在实际应用中，不同场景对智能体的需求差异巨大，往往都需要在通用框架基础上做二次开发。</p><ul><li><strong>特定领域的优化需求</strong>：金融、医疗、教育等垂直领域往往需要针对性的提示词模板、特殊的工具集成、以及定制化的安全策略。</li><li><strong>性能与资源的精确控制</strong>：生产环境中，对响应时间、内存占用、并发处理能力都有严格要求，通用框架的”一刀切”方案往往无法满足精细化需求。</li><li><strong>学习与教学的透明性要求</strong>：在我们的教学场景中，学习者更期待的是清晰地看到智能体的每一步构建过程，理解不同范式的工作机制，这要求框架具有高度的可观测性和可解释性。</li></ul><h3 id="7-1-2-HelloAgents框架的设计理念"><a href="#7-1-2-HelloAgents框架的设计理念" class="headerlink" title="7.1.2 HelloAgents框架的设计理念"></a>7.1.2 HelloAgents框架的设计理念</h3><p>构建一个新的Agent框架，关键不在于功能的多少，而在于设计理念是否能真正解决现有框架的痛点。HelloAgents框架的设计围绕着一个核心问题展开：如何让学习者既能快速上手，又能深入理解Agent的工作原理？</p><p>当你初次接触任何成熟的框架时，可能会被其丰富的功能所吸引，但很快就会发现一个问题：要完成一个简单的任务，往往需要理解Chain、Agent、Tool、Memory、Retriever等十几个不同的概念。每个概念都有自己的抽象层，学习曲线变得异常陡峭。这种复杂性虽然带来了强大的功能，但也成为了初学者的障碍。HelloAgents框架试图在功能完整性和学习友好性之间找到平衡点，形成了四个核心的设计理念。</p><p>（1）轻量级与教学友好的平衡</p><p>一个优秀的学习框架应该具备完整的可读性。HelloAgents将核心代码按照章节区分开，这是基于一个简单的原则：任何有一定编程基础的开发者都应该能够在合理的时间内完全理解框架的工作原理。在依赖管理方面，框架采用了极简主义的策略。除了OpenAI的官方SDK和几个必要的基础库外，不引入任何重型依赖。如果遇到问题时，我们可以直接定位到框架本身的代码，而不需要在复杂的依赖关系中寻找答案。</p><p>（2）基于标准API的务实选择</p><p>OpenAI的API已经成为了行业标准，几乎所有主流的LLM提供商都在努力兼容这套接口。HelloAgents选择在这个标准之上构建，而不是重新发明一套抽象接口。这个决定主要是出于几点动机。首先是兼容性的保证，当你掌握了HelloAgents的使用方法后，迁移到其他框架或将其集成到现有项目中时，底层的API调用逻辑是完全一致的。其次是学习成本的降低。你不需要学习新的概念模型，因为所有的操作都基于你已经熟悉的标准接口。</p><p>（3）渐进式学习路径的精心设计</p><p>HelloAgents提供了一条清晰的学习路径。我们将会把每一章的学习代码，保存为一个可以pip下载的历史版本，因此无需担心代码的使用成本，因为每一个核心的功能都将会是你自己编写的。这种设计让你能够按照自己的需求和节奏前进。每一步的升级都是自然而然的，不会产生概念上的跳跃或理解上的断层。值得一提的是，我们这一章的内容，也是基于前六章的内容来完善的。同样，这一章也是为后续高级知识学习部分打下框架基础。</p><p>（4）统一的“工具”抽象：万物皆为工具</p><p>为了彻底贯彻轻量级与教学友好的理念，HelloAgents在架构上做出了一个关键的简化：除了核心的Agent类，一切皆为Tools。在许多其他框架中需要独立学习的Memory（记忆）、RAG（检索增强生成）、RL（强化学习）、MCP（协议）等模块，在HelloAgents中都被统一抽象为一种“工具”。这种设计的初衷是消除不必要的抽象层，让学习者可以回归到最直观的“智能体调用工具”这一核心逻辑上，从而真正实现快速上手和深入理解的统一。</p><h3 id="7-1-3-本章学习目标"><a href="#7-1-3-本章学习目标" class="headerlink" title="7.1.3 本章学习目标"></a>7.1.3 本章学习目标</h3><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><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><code class="hljs bash">hello-agents/<br>├── hello_agents/<br>│   │<br>│   ├── core/                     <span class="hljs-comment"># 核心框架层</span><br>│   │   ├── agent.py              <span class="hljs-comment"># Agent基类</span><br>│   │   ├── llm.py                <span class="hljs-comment"># HelloAgentsLLM统一接口</span><br>│   │   ├── message.py            <span class="hljs-comment"># 消息系统</span><br>│   │   ├── config.py             <span class="hljs-comment"># 配置管理</span><br>│   │   └── exceptions.py         <span class="hljs-comment"># 异常体系</span><br>│   │<br>│   ├── agents/                   <span class="hljs-comment"># Agent实现层</span><br>│   │   ├── simple_agent.py       <span class="hljs-comment"># SimpleAgent实现</span><br>│   │   ├── react_agent.py        <span class="hljs-comment"># ReActAgent实现</span><br>│   │   ├── reflection_agent.py   <span class="hljs-comment"># ReflectionAgent实现</span><br>│   │   └── plan_solve_agent.py   <span class="hljs-comment"># PlanAndSolveAgent实现</span><br>│   │<br>│   ├── tools/                    <span class="hljs-comment"># 工具系统层</span><br>│   │   ├── base.py               <span class="hljs-comment"># 工具基类</span><br>│   │   ├── registry.py           <span class="hljs-comment"># 工具注册机制</span><br>│   │   ├── chain.py              <span class="hljs-comment"># 工具链管理系统</span><br>│   │   ├── async_executor.py     <span class="hljs-comment"># 异步工具执行器</span><br>│   │   └── <span class="hljs-built_in">builtin</span>/              <span class="hljs-comment"># 内置工具集</span><br>│   │       ├── calculator.py     <span class="hljs-comment"># 计算工具</span><br>│   │       └── search.py         <span class="hljs-comment"># 搜索工具</span><br>└──<br></code></pre></td></tr></table></figure><p>在开始编写具体代码之前，我们需要先建立一个清晰的架构蓝图。HelloAgents的架构设计遵循了”分层解耦、职责单一、接口统一”的核心原则，这样既保持了代码的组织性，也便于按照章节扩展内容。</p><p><strong>快速开始：安装HelloAgents框架</strong></p><p>为了让读者能够快速体验本章的完整功能，我们提供了可直接安装的Python包。你可以通过以下命令安装本章对应的版本：</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><code class="hljs bash"><span class="hljs-comment"># python版本需要&gt;=3.10</span><br>pip install <span class="hljs-string">&quot;hello-agents==0.1.1&quot;</span><br></code></pre></td></tr></table></figure><p>本章的学习可以采用两种方式：</p><ol><li><strong>体验式学习</strong>：直接使用<code>pip</code>安装框架，运行示例代码，快速体验各种功能</li><li><strong>深度学习</strong>：跟随本章内容，从零开始实现每个组件，深入理解框架的设计思想和实现细节</li></ol><p>我们建议采用”先体验，后实现”的学习路径。在本章中，我们提供了完整的测试文件，你可以重写核心函数并运行测试，以检验你的实现是否正确。这种学习方式既保证了实践性，又确保了学习效果。如果你想深入了解框架的实现细节，或者希望参与到框架的开发中来，可以访问这个<a href="https://github.com/jjyaoao/helloagents">GitHub仓库</a>。</p><p>在开始之前，让我们用30秒体验使用Hello-agents构建简单智能体！</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><code class="hljs python"><span class="hljs-comment"># 配置好同级文件夹下.env中的大模型API, 可参考code文件夹配套的.env.example，也可以拿前几章的案例的.env文件复用。</span><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM<br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><br><span class="hljs-comment"># 加载环境变量</span><br>load_dotenv()<br><br><span class="hljs-comment"># 创建LLM实例 - 框架自动检测provider</span><br>llm = HelloAgentsLLM()<br><br><span class="hljs-comment"># 或手动指定provider（可选）</span><br><span class="hljs-comment"># llm = HelloAgentsLLM(provider=&quot;modelscope&quot;)</span><br><br><span class="hljs-comment"># 创建SimpleAgent</span><br>agent = SimpleAgent(<br>    name=<span class="hljs-string">&quot;AI助手&quot;</span>,<br>    llm=llm,<br>    system_prompt=<span class="hljs-string">&quot;你是一个有用的AI助手&quot;</span><br>)<br><br><span class="hljs-comment"># 基础对话</span><br>response = agent.run(<span class="hljs-string">&quot;你好！请介绍一下自己&quot;</span>)<br><span class="hljs-built_in">print</span>(response)<br><br><span class="hljs-comment"># 添加工具功能（可选）</span><br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> CalculatorTool<br>calculator = CalculatorTool()<br><span class="hljs-comment"># 需要实现7.4.1的MySimpleAgent进行调用，后续章节会支持此类调用方式</span><br><span class="hljs-comment"># agent.add_tool(calculator)</span><br><br><span class="hljs-comment"># 现在可以使用工具了</span><br>response = agent.run(<span class="hljs-string">&quot;请帮我计算 2 + 3 * 4&quot;</span>)<br><span class="hljs-built_in">print</span>(response)<br><br><span class="hljs-comment"># 查看对话历史</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;历史消息数: <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(agent.get_history())&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><h2 id="7-2-HelloAgentsLLM扩展"><a href="#7-2-HelloAgentsLLM扩展" class="headerlink" title="7.2 HelloAgentsLLM扩展"></a>7.2 HelloAgentsLLM扩展</h2><p>本节内容将在第 4.1.3 节创建的 <code>HelloAgentsLLM</code> 基础上进行迭代升级。我们将把这个基础客户端，改造为一个更具适应性的模型调用中枢。本次升级主要围绕以下三个目标展开：</p><ol><li><strong>多提供商支持</strong>：实现对 OpenAI、ModelScope、智谱 AI 等多种主流 LLM 服务商的无缝切换，避免框架与特定供应商绑定。</li><li><strong>本地模型集成</strong>：引入 VLLM 和 Ollama 这两种高性能本地部署方案，作为对第 3.2.3 节中 Hugging Face Transformers 方案的生产级补充，满足数据隐私和成本控制的需求。</li><li><strong>自动检测机制</strong>：建立一套自动识别机制，使框架能根据环境信息智能推断所使用的 LLM 服务类型，简化用户的配置过程。</li></ol><h3 id="7-2-1-支持多提供商"><a href="#7-2-1-支持多提供商" class="headerlink" title="7.2.1 支持多提供商"></a>7.2.1 支持多提供商</h3><p>我们之前定义的 <code>HelloAgentsLLM</code> 类，已经能够通过 <code>api_key</code> 和 <code>base_url</code> 这两个核心参数，连接任何兼容 OpenAI 接口的服务。这在理论上保证了通用性，但在实际应用中，不同的服务商在环境变量命名、默认 API 地址和推荐模型等方面都存在差异。如果每次切换服务商都需要用户手动查询并修改代码，会极大影响开发效率。为了解决这一问题，我们引入 <code>provider</code>。其改进思路是：让 <code>HelloAgentsLLM</code> 在内部处理不同服务商的配置细节，从而为用户提供一个统一、简洁的调用体验。具体的实现细节我们将在7.2.3节“自动检测机制”中详细阐述，在这里，我们首先关注如何利用这一机制来扩展框架。</p><p>下面，我们将演示如何通过继承 <code>HelloAgentsLLM</code>，来增加对 ModelScope 平台的支持。我们希望读者不仅学会如何“使用”框架，更能掌握如何“扩展”框架。直接修改已安装的库源码是一种不被推荐的做法，因为它会使后续的库升级变得困难。</p><p>（1）创建自定义LLM类并继承</p><p>假设我们的项目目录中有一个 <code>my_llm.py</code> 文件。我们首先从 <code>hello_agents</code> 库中导入 <code>HelloAgentsLLM</code> 基类，然后创建一个名为 <code>MyLLM</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><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><code class="hljs python"><span class="hljs-comment"># my_llm.py</span><br><span class="hljs-keyword">import</span> os<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Optional</span><br><span class="hljs-keyword">from</span> openai <span class="hljs-keyword">import</span> OpenAI<br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> HelloAgentsLLM<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyLLM</span>(<span class="hljs-title class_ inherited__">HelloAgentsLLM</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">    一个自定义的LLM客户端，通过继承增加了对ModelScope的支持。</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-keyword">pass</span> <span class="hljs-comment"># 暂时留空</span><br></code></pre></td></tr></table></figure><p>（2）重写 <code>__init__</code> 方法以支持新供应商</p><p>接下来，我们在 <code>MyLLM</code> 类中重写 <code>__init__</code> 方法。我们的目标是：当用户传入 <code>provider=&quot;modelscope&quot;</code> 时，执行我们自定义的逻辑；否则，就调用父类 <code>HelloAgentsLLM</code> 的原始逻辑，使其能够继续支持 OpenAI 等其他内置的供应商。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyLLM</span>(<span class="hljs-title class_ inherited__">HelloAgentsLLM</span>):<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        model: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        api_key: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        base_url: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        provider: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-string">&quot;auto&quot;</span>,</span><br><span class="hljs-params">        **kwargs</span><br><span class="hljs-params">    </span>):<br>        <span class="hljs-comment"># 检查provider是否为我们想处理的&#x27;modelscope&#x27;</span><br>        <span class="hljs-keyword">if</span> provider == <span class="hljs-string">&quot;modelscope&quot;</span>:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;正在使用自定义的 ModelScope Provider&quot;</span>)<br>            self.provider = <span class="hljs-string">&quot;modelscope&quot;</span><br>            <br>            <span class="hljs-comment"># 解析 ModelScope 的凭证</span><br>            self.api_key = api_key <span class="hljs-keyword">or</span> os.getenv(<span class="hljs-string">&quot;MODELSCOPE_API_KEY&quot;</span>)<br>            self.base_url = base_url <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;https://api-inference.modelscope.cn/v1/&quot;</span><br>            <br>            <span class="hljs-comment"># 验证凭证是否存在</span><br>            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.api_key:<br>                <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">&quot;ModelScope API key not found. Please set MODELSCOPE_API_KEY environment variable.&quot;</span>)<br><br>            <span class="hljs-comment"># 设置默认模型和其他参数</span><br>            self.model = model <span class="hljs-keyword">or</span> os.getenv(<span class="hljs-string">&quot;LLM_MODEL_ID&quot;</span>) <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;Qwen/Qwen2.5-VL-72B-Instruct&quot;</span><br>            self.temperature = kwargs.get(<span class="hljs-string">&#x27;temperature&#x27;</span>, <span class="hljs-number">0.7</span>)<br>            self.max_tokens = kwargs.get(<span class="hljs-string">&#x27;max_tokens&#x27;</span>)<br>            self.timeout = kwargs.get(<span class="hljs-string">&#x27;timeout&#x27;</span>, <span class="hljs-number">60</span>)<br>            <br>            <span class="hljs-comment"># 使用获取的参数创建OpenAI客户端实例</span><br>            self._client = OpenAI(api_key=self.api_key, base_url=self.base_url, timeout=self.timeout)<br><br>        <span class="hljs-keyword">else</span>:<br>            <span class="hljs-comment"># 如果不是 modelscope, 则完全使用父类的原始逻辑来处理</span><br>            <span class="hljs-built_in">super</span>().__init__(model=model, api_key=api_key, base_url=base_url, provider=provider, **kwargs)<br><br></code></pre></td></tr></table></figure><p>这段代码展示了“重写”的思想：我们拦截了 <code>provider=&quot;modelscope&quot;</code> 的情况并进行了特殊处理，对于其他所有情况，则通过 <code>super().__init__(...)</code> 交还给父类，保留了原有框架的全部功能。</p><p>（3）使用自定义的 <code>MyLLM</code> 类</p><p>现在，我们可以在项目的业务逻辑中，像使用原生 <code>HelloAgentsLLM</code> 一样使用我们自己的 <code>MyLLM</code> 类。</p><p>首先，在 <code>.env</code> 文件中配置 ModelScope 的 API 密钥：</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><code class="hljs bash"><span class="hljs-comment"># .env file</span><br>MODELSCOPE_API_KEY=<span class="hljs-string">&quot;your-modelscope-api-key&quot;</span><br></code></pre></td></tr></table></figure><p>然后，在主程序中导入并使用 <code>MyLLM</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><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><code class="hljs python"><span class="hljs-comment"># my_main.py</span><br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><span class="hljs-keyword">from</span> my_llm <span class="hljs-keyword">import</span> MyLLM <span class="hljs-comment"># 注意:这里导入我们自己的类</span><br><br><span class="hljs-comment"># 加载环境变量</span><br>load_dotenv()<br><br><span class="hljs-comment"># 实例化我们重写的客户端，并指定provider</span><br>llm = MyLLM(provider=<span class="hljs-string">&quot;modelscope&quot;</span>) <br><br><span class="hljs-comment"># 准备消息</span><br>messages = [&#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;你好，请介绍一下你自己。&quot;</span>&#125;]<br><br><span class="hljs-comment"># 发起调用，think等方法都已从父类继承，无需重写</span><br>response_stream = llm.think(messages)<br><br><span class="hljs-comment"># 打印响应</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;ModelScope Response:&quot;</span>)<br><span class="hljs-keyword">for</span> chunk <span class="hljs-keyword">in</span> response_stream:<br>    <span class="hljs-comment"># chunk在my_llm库中已经打印过一遍，这里只需要pass即可</span><br>    <span class="hljs-comment"># print(chunk, end=&quot;&quot;, flush=True)</span><br>    <span class="hljs-keyword">pass</span><br></code></pre></td></tr></table></figure><p>通过以上步骤，我们就在不修改 <code>hello-agents</code> 库源码的前提下，成功为其扩展了新的功能。这种方法不仅保证了代码的整洁和可维护性，也使得未来升级 <code>hello-agents</code> 库时，我们的定制化功能不会丢失。</p><h3 id="7-2-2-本地模型调用"><a href="#7-2-2-本地模型调用" class="headerlink" title="7.2.2 本地模型调用"></a>7.2.2 本地模型调用</h3><p>在第 3.2.3 节，我们学习了如何使用 Hugging Face Transformers 库在本地直接运行开源模型。该方法非常适合入门学习和功能验证，但其底层实现在处理高并发请求时性能有限，通常不作为生产环境的首选方案。</p><p>为了在本地实现高性能、生产级的模型推理服务，社区涌现出了 VLLM 和 Ollama 等优秀工具。它们通过连续批处理、PagedAttention 等技术，显著提升了模型的吞吐量和运行效率，并将模型封装为兼容 OpenAI 标准的 API 服务。这意味着，我们可以将它们无缝地集成到 <code>HelloAgentsLLM</code> 中。</p><p><strong>VLLM</strong></p><p>VLLM 是一个为 LLM 推理设计的高性能 Python 库。它通过 PagedAttention 等先进技术，可以实现比标准 Transformers 实现高出数倍的吞吐量。下面是在本地部署一个 VLLM 服务的完整步骤：</p><p>首先，需要根据你的硬件环境（特别是 CUDA 版本）安装 VLLM。推荐遵循其<a href="https://docs.vllm.ai/en/latest/getting_started/installation.html">官方文档</a>进行安装，以避免版本不匹配问题。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">pip install vllm<br></code></pre></td></tr></table></figure><p>安装完成后，使用以下命令即可启动一个兼容 OpenAI 的 API 服务。VLLM 会自动从 Hugging Face Hub 下载指定的模型权重（如果本地不存在）。我们依然以 Qwen1.5-0.5B-Chat 模型为例：</p><figure class="highlight applescript"><table><tr><td class="gutter"><pre><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><code class="hljs applescript"><span class="hljs-comment"># 启动 VLLM 服务，并加载 Qwen1.5-0.5B-Chat 模型</span><br>python -m vllm.entrypoints.openai.api_server \<br>    <span class="hljs-comment">--model Qwen/Qwen1.5-0.5B-Chat \</span><br>    <span class="hljs-comment">--host 0.0.0.0 \</span><br>    <span class="hljs-comment">--port 8000</span><br></code></pre></td></tr></table></figure><p>服务启动后，便会在 <code>http://localhost:8000/v1</code> 地址上提供与 OpenAI 兼容的 API。</p><p><strong>Ollama</strong></p><p>Ollama 进一步简化了本地模型的管理和部署，它将模型下载、配置和服务启动等步骤封装到了一条命令中，非常适合快速上手。访问 Ollama <a href="https://ollama.com/">官方网站</a>下载并安装适用于你操作系统的客户端。</p><p>安装后，打开终端，执行以下命令即可下载并运行一个模型（以 Llama 3 为例）。Ollama 会自动处理模型的下载、服务封装和硬件加速配置。</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs routeros"><span class="hljs-comment"># 首次运行会自动下载模型，之后会直接启动服务</span><br>ollama <span class="hljs-built_in">run</span> llama3<br></code></pre></td></tr></table></figure><p>当你在终端看到模型的交互提示时，即表示服务已经成功在后台启动。Ollama 默认会在 <code>http://localhost:11434/v1</code> 地址上暴露 OpenAI 兼容的 API 接口。</p><p><strong>接入 <code>HelloAgentsLLM</code></strong></p><p>由于 VLLM 和 Ollama 都遵循了行业标准 API，将它们接入 <code>HelloAgentsLLM</code> 的过程非常简单。我们只需在实例化客户端时，将它们视为一个新的 <code>provider</code> 即可。</p><p>例如，连接本地运行的 <strong>VLLM</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><code class="hljs python">llm_client = HelloAgentsLLM(<br>    provider=<span class="hljs-string">&quot;vllm&quot;</span>,<br>    model=<span class="hljs-string">&quot;Qwen/Qwen1.5-0.5B-Chat&quot;</span>, <span class="hljs-comment"># 需与服务启动时指定的模型一致</span><br>    base_url=<span class="hljs-string">&quot;http://localhost:8000/v1&quot;</span>,<br>    api_key=<span class="hljs-string">&quot;vllm&quot;</span> <span class="hljs-comment"># 本地服务通常不需要真实API Key，可填任意非空字符串</span><br>)<br></code></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><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 在 .env 文件中设置</span><br>LLM_BASE_URL=<span class="hljs-string">&quot;http://localhost:8000/v1&quot;</span><br>LLM_API_KEY=<span class="hljs-string">&quot;vllm&quot;</span><br><br><span class="hljs-comment"># Python 代码中直接实例化即可</span><br>llm_client = HelloAgentsLLM() <span class="hljs-comment"># 将自动检测为 vllm</span><br></code></pre></td></tr></table></figure><p>同理，连接本地的 <strong>Ollama</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><code class="hljs python">llm_client = HelloAgentsLLM(<br>    provider=<span class="hljs-string">&quot;ollama&quot;</span>,<br>    model=<span class="hljs-string">&quot;llama3&quot;</span>, <span class="hljs-comment"># 需与 `ollama run` 指定的模型一致</span><br>    base_url=<span class="hljs-string">&quot;http://localhost:11434/v1&quot;</span>,<br>    api_key=<span class="hljs-string">&quot;ollama&quot;</span> <span class="hljs-comment"># 本地服务同样不需要真实 Key</span><br>)<br></code></pre></td></tr></table></figure><p>通过这种统一的设计，我们的智能体核心代码无需任何修改，就可以在云端 API 和本地模型之间自由切换。这为后续应用的开发、部署、成本控制以及保护数据隐私提供了极大的灵活性。</p><h3 id="7-2-3-自动检测机制"><a href="#7-2-3-自动检测机制" class="headerlink" title="7.2.3 自动检测机制"></a>7.2.3 自动检测机制</h3><p>为了尽可能减少用户的配置负担并遵循“约定优于配置”的原则，<code>HelloAgentsLLM</code> 内部设计了两个核心辅助方法：<code>_auto_detect_provider</code> 和 <code>_resolve_credentials</code>。它们协同工作，<code>_auto_detect_provider</code> 负责根据环境信息推断服务商，而 <code>_resolve_credentials</code> 则根据推断结果完成具体的参数配置。</p><p><code>_auto_detect_provider</code> 方法负责根据环境信息，按照下述优先级顺序，尝试自动推断服务商：</p><ol><li><p><strong>最高优先级：检查特定服务商的环境变量</strong> 这是最直接、最可靠的判断依据。框架会依次检查 <code>MODELSCOPE_API_KEY</code>, <code>OPENAI_API_KEY</code>, <code>ZHIPU_API_KEY</code> 等环境变量是否存在。一旦发现任何一个，就会立即确定对应的服务商。</p></li><li><p><strong>次高优先级：根据 <code>base_url</code> 进行判断</strong> 如果用户没有设置特定服务商的密钥，但设置了通用的 <code>LLM_BASE_URL</code>，框架会转而解析这个 URL。</p><ul><li><p><strong>域名匹配</strong>：通过检查 URL 中是否包含 <code>&quot;api-inference.modelscope.cn&quot;</code>, <code>&quot;api.openai.com&quot;</code> 等特征字符串来识别云服务商。</p></li><li><p><strong>端口匹配</strong>：通过检查 URL 中是否包含 <code>:11434</code> (Ollama), <code>:8000</code> (VLLM) 等本地服务的标准端口来识别本地部署方案。</p></li></ul></li><li><p><strong>辅助判断：分析 API 密钥的格式</strong> 在某些情况下，如果上述两种方式都无法确定，框架会尝试分析通用环境变量 <code>LLM_API_KEY</code> 的格式。例如，某些服务商的 API 密钥有固定的前缀或独特的编码格式。不过，由于这种方式可能存在模糊性（例如多个服务商的密钥格式相似），因此它的优先级较低，仅作为辅助手段。</p></li></ol><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_auto_detect_provider</span>(<span class="hljs-params">self, api_key: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>], base_url: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>]</span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">    自动检测LLM提供商</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 1. 检查特定提供商的环境变量 (最高优先级)</span><br>    <span class="hljs-keyword">if</span> os.getenv(<span class="hljs-string">&quot;MODELSCOPE_API_KEY&quot;</span>): <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;modelscope&quot;</span><br>    <span class="hljs-keyword">if</span> os.getenv(<span class="hljs-string">&quot;OPENAI_API_KEY&quot;</span>): <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;openai&quot;</span><br>    <span class="hljs-keyword">if</span> os.getenv(<span class="hljs-string">&quot;ZHIPU_API_KEY&quot;</span>): <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;zhipu&quot;</span><br>    <span class="hljs-comment"># ... 其他服务商的环境变量检查</span><br><br>    <span class="hljs-comment"># 获取通用的环境变量</span><br>    actual_api_key = api_key <span class="hljs-keyword">or</span> os.getenv(<span class="hljs-string">&quot;LLM_API_KEY&quot;</span>)<br>    actual_base_url = base_url <span class="hljs-keyword">or</span> os.getenv(<span class="hljs-string">&quot;LLM_BASE_URL&quot;</span>)<br><br>    <span class="hljs-comment"># 2. 根据 base_url 判断</span><br>    <span class="hljs-keyword">if</span> actual_base_url:<br>        base_url_lower = actual_base_url.lower()<br>        <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;api-inference.modelscope.cn&quot;</span> <span class="hljs-keyword">in</span> base_url_lower: <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;modelscope&quot;</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;open.bigmodel.cn&quot;</span> <span class="hljs-keyword">in</span> base_url_lower: <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;zhipu&quot;</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;localhost&quot;</span> <span class="hljs-keyword">in</span> base_url_lower <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;127.0.0.1&quot;</span> <span class="hljs-keyword">in</span> base_url_lower:<br>            <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;:11434&quot;</span> <span class="hljs-keyword">in</span> base_url_lower: <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;ollama&quot;</span><br>            <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;:8000&quot;</span> <span class="hljs-keyword">in</span> base_url_lower: <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;vllm&quot;</span><br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;local&quot;</span> <span class="hljs-comment"># 其他本地端口</span><br><br>    <span class="hljs-comment"># 3. 根据 API 密钥格式辅助判断</span><br>    <span class="hljs-keyword">if</span> actual_api_key:<br>        <span class="hljs-keyword">if</span> actual_api_key.startswith(<span class="hljs-string">&quot;ms-&quot;</span>): <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;modelscope&quot;</span><br>        <span class="hljs-comment"># ... 其他密钥格式判断</span><br><br>    <span class="hljs-comment"># 4. 默认返回 &#x27;auto&#x27;，使用通用配置</span><br>    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;auto&quot;</span><br></code></pre></td></tr></table></figure><p>一旦 <code>provider</code> 被确定（无论是用户指定还是自动检测），<code>_resolve_credentials</code> 方法便会接手处理服务商的差异化配置。它会根据 <code>provider</code> 的值，去主动查找对应的环境变量，并为其设置默认的 <code>base_url</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><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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_resolve_credentials</span>(<span class="hljs-params">self, api_key: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>], base_url: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>]</span>) -&gt; <span class="hljs-built_in">tuple</span>[<span class="hljs-built_in">str</span>, <span class="hljs-built_in">str</span>]:<br>    <span class="hljs-string">&quot;&quot;&quot;根据provider解析API密钥和base_url&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> self.provider == <span class="hljs-string">&quot;openai&quot;</span>:<br>        resolved_api_key = api_key <span class="hljs-keyword">or</span> os.getenv(<span class="hljs-string">&quot;OPENAI_API_KEY&quot;</span>) <span class="hljs-keyword">or</span> os.getenv(<span class="hljs-string">&quot;LLM_API_KEY&quot;</span>)<br>        resolved_base_url = base_url <span class="hljs-keyword">or</span> os.getenv(<span class="hljs-string">&quot;LLM_BASE_URL&quot;</span>) <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;https://api.openai.com/v1&quot;</span><br>        <span class="hljs-keyword">return</span> resolved_api_key, resolved_base_url<br><br>    <span class="hljs-keyword">elif</span> self.provider == <span class="hljs-string">&quot;modelscope&quot;</span>:<br>        resolved_api_key = api_key <span class="hljs-keyword">or</span> os.getenv(<span class="hljs-string">&quot;MODELSCOPE_API_KEY&quot;</span>) <span class="hljs-keyword">or</span> os.getenv(<span class="hljs-string">&quot;LLM_API_KEY&quot;</span>)<br>        resolved_base_url = base_url <span class="hljs-keyword">or</span> os.getenv(<span class="hljs-string">&quot;LLM_BASE_URL&quot;</span>) <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;https://api-inference.modelscope.cn/v1/&quot;</span><br>        <span class="hljs-keyword">return</span> resolved_api_key, resolved_base_url<br>    <br>    <span class="hljs-comment"># ... 其他服务商的逻辑</span><br></code></pre></td></tr></table></figure><p>让我们通过一个简单的例子来感受自动检测带来的便利。假设一个用户想要使用本地的 Ollama 服务，他只需在 <code>.env</code> 文件中进行如下配置：</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><code class="hljs bash">LLM_BASE_URL=<span class="hljs-string">&quot;http://localhost:11434/v1&quot;</span><br>LLM_MODEL_ID=<span class="hljs-string">&quot;llama3&quot;</span><br></code></pre></td></tr></table></figure><p>他完全不需要配置 <code>LLM_API_KEY</code> 或在代码中指定 <code>provider</code>。然后，在 Python 代码中，他只需简单地实例化 <code>HelloAgentsLLM</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><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><code class="hljs python"><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> HelloAgentsLLM<br><br>load_dotenv()<br><br><span class="hljs-comment"># 无需传入 provider，框架会自动检测</span><br>llm = HelloAgentsLLM() <br><span class="hljs-comment"># 框架内部日志会显示检测到 provider 为 &#x27;ollama&#x27;</span><br><br><span class="hljs-comment"># 后续调用方式完全不变</span><br>messages = [&#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;你好！&quot;</span>&#125;]<br><span class="hljs-keyword">for</span> chunk <span class="hljs-keyword">in</span> llm.think(messages):<br>    <span class="hljs-built_in">print</span>(chunk, end=<span class="hljs-string">&quot;&quot;</span>)<br><br></code></pre></td></tr></table></figure><p>在这个过程中，<code>_auto_detect_provider</code> 方法通过解析 <code>LLM_BASE_URL</code> 中的 <code>&quot;localhost&quot;</code> 和 <code>:11434</code>，成功地将 <code>provider</code> 推断为 <code>&quot;ollama&quot;</code>。随后，<code>_resolve_credentials</code> 方法会为 Ollama 设置正确的默认参数。</p><p>相比于4.1.3节的基础实现，现在的HelloAgentsLLM具有以下显著优势：</p><div align="center">  <p>表 7.1 HelloAgentLLM不同版本特性对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/7-figures/table-01.png" alt="" width="90%"/></div><p>如上表7.1所示，这种演进体现了框架设计的重要原则：<strong>从简单开始，逐步完善</strong>。我们在保持接口简洁的同时，增强了功能的完整性。</p><h2 id="7-3-框架接口实现"><a href="#7-3-框架接口实现" class="headerlink" title="7.3 框架接口实现"></a>7.3 框架接口实现</h2><p>在上节中，我们构建了 <code>HelloAgentsLLM</code> 这一核心组件，解决了与大语言模型通信的关键问题。不过它还需要一系列配套的接口和组件来处理数据流、管理配置、应对异常，并为上层应用的构建提供一个清晰、统一的结构。本节将讲述以下三个核心文件：</p><ul><li><code>message.py</code>： 定义了框架内统一的消息格式，确保了智能体与模型之间信息传递的标准化。</li><li><code>config.py</code>： 提供了一个中心化的配置管理方案，使框架的行为易于调整和扩展。</li><li><code>agent.py</code>： 定义了所有智能体的抽象基类（<code>Agent</code>），为后续实现不同类型的智能体提供了统一的接口和规范。</li></ul><h3 id="7-3-1-Message-类"><a href="#7-3-1-Message-类" class="headerlink" title="7.3.1 Message 类"></a>7.3.1 Message 类</h3><p>在智能体与大语言模型的交互中，对话历史是至关重要的上下文。为了规范地管理这些信息，我们设计了一个简易 <code>Message</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><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><code class="hljs python"><span class="hljs-string">&quot;&quot;&quot;消息系统&quot;&quot;&quot;</span><br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Optional</span>, <span class="hljs-type">Dict</span>, <span class="hljs-type">Any</span>, <span class="hljs-type">Literal</span><br><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<br><span class="hljs-keyword">from</span> pydantic <span class="hljs-keyword">import</span> BaseModel<br><br><span class="hljs-comment"># 定义消息角色的类型，限制其取值</span><br>MessageRole = <span class="hljs-type">Literal</span>[<span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;assistant&quot;</span>, <span class="hljs-string">&quot;system&quot;</span>, <span class="hljs-string">&quot;tool&quot;</span>]<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Message</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;消息类&quot;&quot;&quot;</span><br>    <br>    content: <span class="hljs-built_in">str</span><br>    role: MessageRole<br>    timestamp: datetime = <span class="hljs-literal">None</span><br>    metadata: <span class="hljs-type">Optional</span>[<span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]] = <span class="hljs-literal">None</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, content: <span class="hljs-built_in">str</span>, role: MessageRole, **kwargs</span>):<br>        <span class="hljs-built_in">super</span>().__init__(<br>            content=content,<br>            role=role,<br>            timestamp=kwargs.get(<span class="hljs-string">&#x27;timestamp&#x27;</span>, datetime.now()),<br>            metadata=kwargs.get(<span class="hljs-string">&#x27;metadata&#x27;</span>, &#123;&#125;)<br>        )<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">to_dict</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;转换为字典格式（OpenAI API格式）&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> &#123;<br>            <span class="hljs-string">&quot;role&quot;</span>: self.role,<br>            <span class="hljs-string">&quot;content&quot;</span>: self.content<br>        &#125;<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__str__</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;[<span class="hljs-subst">&#123;self.role&#125;</span>] <span class="hljs-subst">&#123;self.content&#125;</span>&quot;</span><br></code></pre></td></tr></table></figure><p>该类的设计有几个关键点。首先，我们通过 <code>typing.Literal</code> 将 <code>role</code> 字段的取值严格限制为 <code>&quot;user&quot;</code>, <code>&quot;assistant&quot;</code>, <code>&quot;system&quot;</code>, <code>&quot;tool&quot;</code> 四种，这直接对应 OpenAI API 的规范，保证了类型安全。除了 <code>content</code> 和 <code>role</code> 这两个核心字段外，我们还增加了 <code>timestamp</code> 和 <code>metadata</code>，为日志记录和未来功能扩展预留了空间。最后，<code>to_dict()</code> 方法是其核心功能之一，负责将内部使用的 <code>Message</code> 对象转换为与 OpenAI API 兼容的字典格式，体现了“对内丰富，对外兼容”的设计原则。</p><h3 id="7-3-2-Config-类"><a href="#7-3-2-Config-类" class="headerlink" title="7.3.2 Config 类"></a>7.3.2 Config 类</h3><p><code>Config</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><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><code class="hljs python"><span class="hljs-string">&quot;&quot;&quot;配置管理&quot;&quot;&quot;</span><br><span class="hljs-keyword">import</span> os<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Optional</span>, <span class="hljs-type">Dict</span>, <span class="hljs-type">Any</span><br><span class="hljs-keyword">from</span> pydantic <span class="hljs-keyword">import</span> BaseModel<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Config</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;HelloAgents配置类&quot;&quot;&quot;</span><br>    <br>    <span class="hljs-comment"># LLM配置</span><br>    default_model: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;gpt-3.5-turbo&quot;</span><br>    default_provider: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;openai&quot;</span><br>    temperature: <span class="hljs-built_in">float</span> = <span class="hljs-number">0.7</span><br>    max_tokens: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">int</span>] = <span class="hljs-literal">None</span><br>    <br>    <span class="hljs-comment"># 系统配置</span><br>    debug: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">False</span><br>    log_level: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;INFO&quot;</span><br>    <br>    <span class="hljs-comment"># 其他配置</span><br>    max_history_length: <span class="hljs-built_in">int</span> = <span class="hljs-number">100</span><br>    <br><span class="hljs-meta">    @classmethod</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">from_env</span>(<span class="hljs-params">cls</span>) -&gt; <span class="hljs-string">&quot;Config&quot;</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;从环境变量创建配置&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> cls(<br>            debug=os.getenv(<span class="hljs-string">&quot;DEBUG&quot;</span>, <span class="hljs-string">&quot;false&quot;</span>).lower() == <span class="hljs-string">&quot;true&quot;</span>,<br>            log_level=os.getenv(<span class="hljs-string">&quot;LOG_LEVEL&quot;</span>, <span class="hljs-string">&quot;INFO&quot;</span>),<br>            temperature=<span class="hljs-built_in">float</span>(os.getenv(<span class="hljs-string">&quot;TEMPERATURE&quot;</span>, <span class="hljs-string">&quot;0.7&quot;</span>)),<br>            max_tokens=<span class="hljs-built_in">int</span>(os.getenv(<span class="hljs-string">&quot;MAX_TOKENS&quot;</span>)) <span class="hljs-keyword">if</span> os.getenv(<span class="hljs-string">&quot;MAX_TOKENS&quot;</span>) <span class="hljs-keyword">else</span> <span class="hljs-literal">None</span>,<br>        )<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">to_dict</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;转换为字典&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> self.<span class="hljs-built_in">dict</span>()<br></code></pre></td></tr></table></figure><p>首先，我们将配置项按逻辑划分为 <code>LLM配置</code>、<code>系统配置</code> 等，使结构一目了然。其次，每个配置项都设有合理的默认值，保证了框架在零配置下也能工作。最核心的是 <code>from_env()</code> 类方法，它允许用户通过设置环境变量来覆盖默认配置，无需修改代码，这在部署到不同环境时尤其有用。</p><h3 id="7-3-3-Agent-抽象基类"><a href="#7-3-3-Agent-抽象基类" class="headerlink" title="7.3.3 Agent 抽象基类"></a>7.3.3 Agent 抽象基类</h3><p><code>Agent</code> 类是整个框架的顶层抽象。它定义了一个智能体应该具备的通用行为和属性，但并不关心具体的实现方式。我们通过 Python 的 <code>abc</code> (Abstract Base Classes) 模块来实现它，这强制所有具体的智能体实现（如后续章节的 <code>SimpleAgent</code>, <code>ReActAgent</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><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><code class="hljs python"><span class="hljs-string">&quot;&quot;&quot;Agent基类&quot;&quot;&quot;</span><br><span class="hljs-keyword">from</span> abc <span class="hljs-keyword">import</span> ABC, abstractmethod<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Optional</span>, <span class="hljs-type">Any</span><br><span class="hljs-keyword">from</span> .message <span class="hljs-keyword">import</span> Message<br><span class="hljs-keyword">from</span> .llm <span class="hljs-keyword">import</span> HelloAgentsLLM<br><span class="hljs-keyword">from</span> .config <span class="hljs-keyword">import</span> Config<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Agent</span>(<span class="hljs-title class_ inherited__">ABC</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;Agent基类&quot;&quot;&quot;</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        name: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        llm: HelloAgentsLLM,</span><br><span class="hljs-params">        system_prompt: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        config: <span class="hljs-type">Optional</span>[Config] = <span class="hljs-literal">None</span></span><br><span class="hljs-params">    </span>):<br>        self.name = name<br>        self.llm = llm<br>        self.system_prompt = system_prompt<br>        self.config = config <span class="hljs-keyword">or</span> Config()<br>        self._history: <span class="hljs-built_in">list</span>[Message] = []<br>    <br><span class="hljs-meta">    @abstractmethod</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, input_text: <span class="hljs-built_in">str</span>, **kwargs</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;运行Agent&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">pass</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">add_message</span>(<span class="hljs-params">self, message: Message</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;添加消息到历史记录&quot;&quot;&quot;</span><br>        self._history.append(message)<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">clear_history</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;清空历史记录&quot;&quot;&quot;</span><br>        self._history.clear()<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_history</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-built_in">list</span>[Message]:<br>        <span class="hljs-string">&quot;&quot;&quot;获取历史记录&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> self._history.copy()<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__str__</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;Agent(name=<span class="hljs-subst">&#123;self.name&#125;</span>, provider=<span class="hljs-subst">&#123;self.llm.provider&#125;</span>)&quot;</span><br></code></pre></td></tr></table></figure><p>该类的设计体现了面向对象中的抽象原则。首先，它通过继承 <code>ABC</code> 被定义为一个不能直接实例化的抽象类。其构造函数 <code>__init__</code> 清晰地定义了 Agent 的核心依赖：名称、LLM 实例、系统提示词和配置。最重要的部分是使用 <code>@abstractmethod</code> 装饰的 <code>run</code> 方法，它强制所有子类必须实现此方法，从而保证了所有智能体都有统一的执行入口。此外，基类还提供了通用的历史记录管理方法，这些方法与 <code>Message</code> 类协同工作，体现了组件间的联系。</p><p>至此，我们已经完成了 <code>HelloAgents</code> 框架核心基础组件的设计与实现。</p><h2 id="7-4-Agent范式的框架化实现"><a href="#7-4-Agent范式的框架化实现" class="headerlink" title="7.4 Agent范式的框架化实现"></a>7.4 Agent范式的框架化实现</h2><p>本节内容将在第四章构建的三种经典Agent范式（ReAct、Plan-and-Solve、Reflection）基础上进行框架化重构，并新增SimpleAgent作为基础对话范式。我们将把这些独立的Agent实现，改造为基于统一架构的框架组件。本次重构主要围绕以下三个核心目标展开：</p><ol><li><strong>提示词工程的系统性提升</strong>：对第四章中的提示词进行深度优化，从特定任务导向转向通用化设计，同时增强格式约束和角色定义。</li><li><strong>接口与格式的标准化统一</strong>：建立统一的Agent基类和标准化的运行接口，所有Agent都遵循相同的初始化参数、方法签名和历史管理机制。</li><li><strong>高度可配置的自定义能力</strong>：支持用户自定义提示词模板、配置参数和执行策略。</li></ol><h3 id="7-4-1-SimpleAgent"><a href="#7-4-1-SimpleAgent" class="headerlink" title="7.4.1 SimpleAgent"></a>7.4.1 SimpleAgent</h3><p>SimpleAgent是最基础的Agent实现，它展示了如何在框架基础上构建一个完整的对话智能体。我们将通过继承框架基类来重写SimpleAgent。首先，在你的项目目录中创建一个<code>my_simple_agent.py</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><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><code class="hljs python"><span class="hljs-comment"># my_simple_agent.py</span><br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Optional</span>, Iterator<br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> SimpleAgent, HelloAgentsLLM, Config, Message<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MySimpleAgent</span>(<span class="hljs-title class_ inherited__">SimpleAgent</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">    重写的简单对话Agent</span><br><span class="hljs-string">    展示如何基于框架基类构建自定义Agent</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        name: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        llm: HelloAgentsLLM,</span><br><span class="hljs-params">        system_prompt: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        config: <span class="hljs-type">Optional</span>[Config] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        tool_registry: <span class="hljs-type">Optional</span>[<span class="hljs-string">&#x27;ToolRegistry&#x27;</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        enable_tool_calling: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span></span><br><span class="hljs-params">    </span>):<br>        <span class="hljs-built_in">super</span>().__init__(name, llm, system_prompt, config)<br>        self.tool_registry = tool_registry<br>        self.enable_tool_calling = enable_tool_calling <span class="hljs-keyword">and</span> tool_registry <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ <span class="hljs-subst">&#123;name&#125;</span> 初始化完成，工具调用: <span class="hljs-subst">&#123;<span class="hljs-string">&#x27;启用&#x27;</span> <span class="hljs-keyword">if</span> self.enable_tool_calling <span class="hljs-keyword">else</span> <span class="hljs-string">&#x27;禁用&#x27;</span>&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>接下来，我们需要重写Agent基类的抽象方法<code>run</code>。SimpleAgent支持可选的工具调用功能，也方便后续章节的扩展：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 继续在 my_simple_agent.py 中添加</span><br><span class="hljs-keyword">import</span> re<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MySimpleAgent</span>(<span class="hljs-title class_ inherited__">SimpleAgent</span>):<br>    <span class="hljs-comment"># ... 前面的 __init__ 方法</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, input_text: <span class="hljs-built_in">str</span>, max_tool_iterations: <span class="hljs-built_in">int</span> = <span class="hljs-number">3</span>, **kwargs</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">        重写的运行方法 - 实现简单对话逻辑，支持可选工具调用</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;🤖 <span class="hljs-subst">&#123;self.name&#125;</span> 正在处理: <span class="hljs-subst">&#123;input_text&#125;</span>&quot;</span>)<br><br>        <span class="hljs-comment"># 构建消息列表</span><br>        messages = []<br><br>        <span class="hljs-comment"># 添加系统消息（可能包含工具信息）</span><br>        enhanced_system_prompt = self._get_enhanced_system_prompt()<br>        messages.append(&#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;system&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: enhanced_system_prompt&#125;)<br><br>        <span class="hljs-comment"># 添加历史消息</span><br>        <span class="hljs-keyword">for</span> msg <span class="hljs-keyword">in</span> self._history:<br>            messages.append(&#123;<span class="hljs-string">&quot;role&quot;</span>: msg.role, <span class="hljs-string">&quot;content&quot;</span>: msg.content&#125;)<br><br>        <span class="hljs-comment"># 添加当前用户消息</span><br>        messages.append(&#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: input_text&#125;)<br><br>        <span class="hljs-comment"># 如果没有启用工具调用，使用简单对话逻辑</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.enable_tool_calling:<br>            response = self.llm.invoke(messages, **kwargs)<br>            self.add_message(Message(input_text, <span class="hljs-string">&quot;user&quot;</span>))<br>            self.add_message(Message(response, <span class="hljs-string">&quot;assistant&quot;</span>))<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ <span class="hljs-subst">&#123;self.name&#125;</span> 响应完成&quot;</span>)<br>            <span class="hljs-keyword">return</span> response<br><br>        <span class="hljs-comment"># 支持多轮工具调用的逻辑</span><br>        <span class="hljs-keyword">return</span> self._run_with_tools(messages, input_text, max_tool_iterations, **kwargs)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_get_enhanced_system_prompt</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;构建增强的系统提示词，包含工具信息&quot;&quot;&quot;</span><br>        base_prompt = self.system_prompt <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;你是一个有用的AI助手。&quot;</span><br><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.enable_tool_calling <span class="hljs-keyword">or</span> <span class="hljs-keyword">not</span> self.tool_registry:<br>            <span class="hljs-keyword">return</span> base_prompt<br><br>        <span class="hljs-comment"># 获取工具描述</span><br>        tools_description = self.tool_registry.get_tools_description()<br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> tools_description <span class="hljs-keyword">or</span> tools_description == <span class="hljs-string">&quot;暂无可用工具&quot;</span>:<br>            <span class="hljs-keyword">return</span> base_prompt<br><br>        tools_section = <span class="hljs-string">&quot;\n\n## 可用工具\n&quot;</span><br>        tools_section += <span class="hljs-string">&quot;你可以使用以下工具来帮助回答问题:\n&quot;</span><br>        tools_section += tools_description + <span class="hljs-string">&quot;\n&quot;</span><br><br>        tools_section += <span class="hljs-string">&quot;\n## 工具调用格式\n&quot;</span><br>        tools_section += <span class="hljs-string">&quot;当需要使用工具时，请使用以下格式:\n&quot;</span><br>        tools_section += <span class="hljs-string">&quot;`[TOOL_CALL:&#123;tool_name&#125;:&#123;parameters&#125;]`\n&quot;</span><br>        tools_section += <span class="hljs-string">&quot;例如:`[TOOL_CALL:search:Python编程]` 或 `[TOOL_CALL:memory:recall=用户信息]`\n\n&quot;</span><br>        tools_section += <span class="hljs-string">&quot;工具调用结果会自动插入到对话中，然后你可以基于结果继续回答。\n&quot;</span><br><br>        <span class="hljs-keyword">return</span> base_prompt + tools_section<br></code></pre></td></tr></table></figure><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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 继续在 my_simple_agent.py 中添加</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MySimpleAgent</span>(<span class="hljs-title class_ inherited__">SimpleAgent</span>):<br>    <span class="hljs-comment"># ... 前面的方法</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_run_with_tools</span>(<span class="hljs-params">self, messages: <span class="hljs-built_in">list</span>, input_text: <span class="hljs-built_in">str</span>, max_tool_iterations: <span class="hljs-built_in">int</span>, **kwargs</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;支持工具调用的运行逻辑&quot;&quot;&quot;</span><br>        current_iteration = <span class="hljs-number">0</span><br>        final_response = <span class="hljs-string">&quot;&quot;</span><br><br>        <span class="hljs-keyword">while</span> current_iteration &lt; max_tool_iterations:<br>            <span class="hljs-comment"># 调用LLM</span><br>            response = self.llm.invoke(messages, **kwargs)<br><br>            <span class="hljs-comment"># 检查是否有工具调用</span><br>            tool_calls = self._parse_tool_calls(response)<br><br>            <span class="hljs-keyword">if</span> tool_calls:<br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;🔧 检测到 <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(tool_calls)&#125;</span> 个工具调用&quot;</span>)<br>                <span class="hljs-comment"># 执行所有工具调用并收集结果</span><br>                tool_results = []<br>                clean_response = response<br><br>                <span class="hljs-keyword">for</span> call <span class="hljs-keyword">in</span> tool_calls:<br>                    result = self._execute_tool_call(call[<span class="hljs-string">&#x27;tool_name&#x27;</span>], call[<span class="hljs-string">&#x27;parameters&#x27;</span>])<br>                    tool_results.append(result)<br>                    <span class="hljs-comment"># 从响应中移除工具调用标记</span><br>                    clean_response = clean_response.replace(call[<span class="hljs-string">&#x27;original&#x27;</span>], <span class="hljs-string">&quot;&quot;</span>)<br><br>                <span class="hljs-comment"># 构建包含工具结果的消息</span><br>                messages.append(&#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;assistant&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: clean_response&#125;)<br><br>                <span class="hljs-comment"># 添加工具结果</span><br>                tool_results_text = <span class="hljs-string">&quot;\n\n&quot;</span>.join(tool_results)<br>                messages.append(&#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">f&quot;工具执行结果:\n<span class="hljs-subst">&#123;tool_results_text&#125;</span>\n\n请基于这些结果给出完整的回答。&quot;</span>&#125;)<br><br>                current_iteration += <span class="hljs-number">1</span><br>                <span class="hljs-keyword">continue</span><br><br>            <span class="hljs-comment"># 没有工具调用，这是最终回答</span><br>            final_response = response<br>            <span class="hljs-keyword">break</span><br><br>        <span class="hljs-comment"># 如果超过最大迭代次数，获取最后一次回答</span><br>        <span class="hljs-keyword">if</span> current_iteration &gt;= max_tool_iterations <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> final_response:<br>            final_response = self.llm.invoke(messages, **kwargs)<br><br>        <span class="hljs-comment"># 保存到历史记录</span><br>        self.add_message(Message(input_text, <span class="hljs-string">&quot;user&quot;</span>))<br>        self.add_message(Message(final_response, <span class="hljs-string">&quot;assistant&quot;</span>))<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ <span class="hljs-subst">&#123;self.name&#125;</span> 响应完成&quot;</span>)<br><br>        <span class="hljs-keyword">return</span> final_response<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_parse_tool_calls</span>(<span class="hljs-params">self, text: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">list</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;解析文本中的工具调用&quot;&quot;&quot;</span><br>        pattern = <span class="hljs-string">r&#x27;\[TOOL_CALL:([^:]+):([^\]]+)\]&#x27;</span><br>        matches = re.findall(pattern, text)<br><br>        tool_calls = []<br>        <span class="hljs-keyword">for</span> tool_name, parameters <span class="hljs-keyword">in</span> matches:<br>            tool_calls.append(&#123;<br>                <span class="hljs-string">&#x27;tool_name&#x27;</span>: tool_name.strip(),<br>                <span class="hljs-string">&#x27;parameters&#x27;</span>: parameters.strip(),<br>                <span class="hljs-string">&#x27;original&#x27;</span>: <span class="hljs-string">f&#x27;[TOOL_CALL:<span class="hljs-subst">&#123;tool_name&#125;</span>:<span class="hljs-subst">&#123;parameters&#125;</span>]&#x27;</span><br>            &#125;)<br><br>        <span class="hljs-keyword">return</span> tool_calls<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_execute_tool_call</span>(<span class="hljs-params">self, tool_name: <span class="hljs-built_in">str</span>, parameters: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;执行工具调用&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.tool_registry:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 错误:未配置工具注册表&quot;</span><br><br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-comment"># 智能参数解析</span><br>            <span class="hljs-keyword">if</span> tool_name == <span class="hljs-string">&#x27;calculator&#x27;</span>:<br>                <span class="hljs-comment"># 计算器工具直接传入表达式</span><br>                result = self.tool_registry.execute_tool(tool_name, parameters)<br>            <span class="hljs-keyword">else</span>:<br>                <span class="hljs-comment"># 其他工具使用智能参数解析</span><br>                param_dict = self._parse_tool_parameters(tool_name, parameters)<br>                tool = self.tool_registry.get_tool(tool_name)<br>                <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> tool:<br>                    <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 错误:未找到工具 &#x27;<span class="hljs-subst">&#123;tool_name&#125;</span>&#x27;&quot;</span><br>                result = tool.run(param_dict)<br><br>            <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;🔧 工具 <span class="hljs-subst">&#123;tool_name&#125;</span> 执行结果:\n<span class="hljs-subst">&#123;result&#125;</span>&quot;</span><br><br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 工具调用失败:<span class="hljs-subst">&#123;<span class="hljs-built_in">str</span>(e)&#125;</span>&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_parse_tool_parameters</span>(<span class="hljs-params">self, tool_name: <span class="hljs-built_in">str</span>, parameters: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">dict</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;智能解析工具参数&quot;&quot;&quot;</span><br>        param_dict = &#123;&#125;<br><br>        <span class="hljs-keyword">if</span> <span class="hljs-string">&#x27;=&#x27;</span> <span class="hljs-keyword">in</span> parameters:<br>            <span class="hljs-comment"># 格式: key=value 或 action=search,query=Python</span><br>            <span class="hljs-keyword">if</span> <span class="hljs-string">&#x27;,&#x27;</span> <span class="hljs-keyword">in</span> parameters:<br>                <span class="hljs-comment"># 多个参数:action=search,query=Python,limit=3</span><br>                pairs = parameters.split(<span class="hljs-string">&#x27;,&#x27;</span>)<br>                <span class="hljs-keyword">for</span> pair <span class="hljs-keyword">in</span> pairs:<br>                    <span class="hljs-keyword">if</span> <span class="hljs-string">&#x27;=&#x27;</span> <span class="hljs-keyword">in</span> pair:<br>                        key, value = pair.split(<span class="hljs-string">&#x27;=&#x27;</span>, <span class="hljs-number">1</span>)<br>                        param_dict[key.strip()] = value.strip()<br>            <span class="hljs-keyword">else</span>:<br>                <span class="hljs-comment"># 单个参数:key=value</span><br>                key, value = parameters.split(<span class="hljs-string">&#x27;=&#x27;</span>, <span class="hljs-number">1</span>)<br>                param_dict[key.strip()] = value.strip()<br>        <span class="hljs-keyword">else</span>:<br>            <span class="hljs-comment"># 直接传入参数，根据工具类型智能推断</span><br>            <span class="hljs-keyword">if</span> tool_name == <span class="hljs-string">&#x27;search&#x27;</span>:<br>                param_dict = &#123;<span class="hljs-string">&#x27;query&#x27;</span>: parameters&#125;<br>            <span class="hljs-keyword">elif</span> tool_name == <span class="hljs-string">&#x27;memory&#x27;</span>:<br>                param_dict = &#123;<span class="hljs-string">&#x27;action&#x27;</span>: <span class="hljs-string">&#x27;search&#x27;</span>, <span class="hljs-string">&#x27;query&#x27;</span>: parameters&#125;<br>            <span class="hljs-keyword">else</span>:<br>                param_dict = &#123;<span class="hljs-string">&#x27;input&#x27;</span>: parameters&#125;<br><br>        <span class="hljs-keyword">return</span> param_dict<br></code></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 继续在 my_simple_agent.py 中添加</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MySimpleAgent</span>(<span class="hljs-title class_ inherited__">SimpleAgent</span>):<br>    <span class="hljs-comment"># ... 前面的方法</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">stream_run</span>(<span class="hljs-params">self, input_text: <span class="hljs-built_in">str</span>, **kwargs</span>) -&gt; Iterator[<span class="hljs-built_in">str</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">        自定义的流式运行方法</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;🌊 <span class="hljs-subst">&#123;self.name&#125;</span> 开始流式处理: <span class="hljs-subst">&#123;input_text&#125;</span>&quot;</span>)<br><br>        messages = []<br><br>        <span class="hljs-keyword">if</span> self.system_prompt:<br>            messages.append(&#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;system&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: self.system_prompt&#125;)<br><br>        <span class="hljs-keyword">for</span> msg <span class="hljs-keyword">in</span> self._history:<br>            messages.append(&#123;<span class="hljs-string">&quot;role&quot;</span>: msg.role, <span class="hljs-string">&quot;content&quot;</span>: msg.content&#125;)<br><br>        messages.append(&#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: input_text&#125;)<br><br>        <span class="hljs-comment"># 流式调用LLM</span><br>        full_response = <span class="hljs-string">&quot;&quot;</span><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;📝 实时响应: &quot;</span>, end=<span class="hljs-string">&quot;&quot;</span>)<br>        <span class="hljs-keyword">for</span> chunk <span class="hljs-keyword">in</span> self.llm.stream_invoke(messages, **kwargs):<br>            full_response += chunk<br>            <span class="hljs-built_in">print</span>(chunk, end=<span class="hljs-string">&quot;&quot;</span>, flush=<span class="hljs-literal">True</span>)<br>            <span class="hljs-keyword">yield</span> chunk<br><br>        <span class="hljs-built_in">print</span>()  <span class="hljs-comment"># 换行</span><br><br>        <span class="hljs-comment"># 保存完整对话到历史记录</span><br>        self.add_message(Message(input_text, <span class="hljs-string">&quot;user&quot;</span>))<br>        self.add_message(Message(full_response, <span class="hljs-string">&quot;assistant&quot;</span>))<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ <span class="hljs-subst">&#123;self.name&#125;</span> 流式响应完成&quot;</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">add_tool</span>(<span class="hljs-params">self, tool</span>) -&gt; <span class="hljs-literal">None</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;添加工具到Agent（便利方法）&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.tool_registry:<br>            <span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ToolRegistry<br>            self.tool_registry = ToolRegistry()<br>            self.enable_tool_calling = <span class="hljs-literal">True</span><br><br>        self.tool_registry.register_tool(tool)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;🔧 工具 &#x27;<span class="hljs-subst">&#123;tool.name&#125;</span>&#x27; 已添加&quot;</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">has_tools</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-built_in">bool</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;检查是否有可用工具&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> self.enable_tool_calling <span class="hljs-keyword">and</span> self.tool_registry <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">remove_tool</span>(<span class="hljs-params">self, tool_name: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">bool</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;移除工具（便利方法）&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> self.tool_registry:<br>            self.tool_registry.unregister(tool_name)<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">list_tools</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-built_in">list</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;列出所有可用工具&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> self.tool_registry:<br>            <span class="hljs-keyword">return</span> self.tool_registry.list_tools()<br>        <span class="hljs-keyword">return</span> []<br></code></pre></td></tr></table></figure><p>创建一个测试文件<code>test_simple_agent.py</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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># test_simple_agent.py</span><br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> HelloAgentsLLM, ToolRegistry<br><span class="hljs-keyword">from</span> hello_agents.tools <span class="hljs-keyword">import</span> CalculatorTool<br><span class="hljs-keyword">from</span> my_simple_agent <span class="hljs-keyword">import</span> MySimpleAgent<br><br><span class="hljs-comment"># 加载环境变量</span><br>load_dotenv()<br><br><span class="hljs-comment"># 创建LLM实例</span><br>llm = HelloAgentsLLM()<br><br><span class="hljs-comment"># 测试1:基础对话Agent（无工具）</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=== 测试1:基础对话 ===&quot;</span>)<br>basic_agent = MySimpleAgent(<br>    name=<span class="hljs-string">&quot;基础助手&quot;</span>,<br>    llm=llm,<br>    system_prompt=<span class="hljs-string">&quot;你是一个友好的AI助手，请用简洁明了的方式回答问题。&quot;</span><br>)<br><br>response1 = basic_agent.run(<span class="hljs-string">&quot;你好，请介绍一下自己&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;基础对话响应: <span class="hljs-subst">&#123;response1&#125;</span>\n&quot;</span>)<br><br><span class="hljs-comment"># 测试2:带工具的Agent</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=== 测试2:工具增强对话 ===&quot;</span>)<br>tool_registry = ToolRegistry()<br>calculator = CalculatorTool()<br>tool_registry.register_tool(calculator)<br><br>enhanced_agent = MySimpleAgent(<br>    name=<span class="hljs-string">&quot;增强助手&quot;</span>,<br>    llm=llm,<br>    system_prompt=<span class="hljs-string">&quot;你是一个智能助手，可以使用工具来帮助用户。&quot;</span>,<br>    tool_registry=tool_registry,<br>    enable_tool_calling=<span class="hljs-literal">True</span><br>)<br><br>response2 = enhanced_agent.run(<span class="hljs-string">&quot;请帮我计算 15 * 8 + 32&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;工具增强响应: <span class="hljs-subst">&#123;response2&#125;</span>\n&quot;</span>)<br><br><span class="hljs-comment"># 测试3:流式响应</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=== 测试3:流式响应 ===&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;流式响应: &quot;</span>, end=<span class="hljs-string">&quot;&quot;</span>)<br><span class="hljs-keyword">for</span> chunk <span class="hljs-keyword">in</span> basic_agent.stream_run(<span class="hljs-string">&quot;请解释什么是人工智能&quot;</span>):<br>    <span class="hljs-keyword">pass</span>  <span class="hljs-comment"># 内容已在stream_run中实时打印</span><br><br><span class="hljs-comment"># 测试4:动态添加工具</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n=== 测试4:动态工具管理 ===&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;添加工具前: <span class="hljs-subst">&#123;basic_agent.has_tools()&#125;</span>&quot;</span>)<br>basic_agent.add_tool(calculator)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;添加工具后: <span class="hljs-subst">&#123;basic_agent.has_tools()&#125;</span>&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;可用工具: <span class="hljs-subst">&#123;basic_agent.list_tools()&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 查看对话历史</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n对话历史: <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(basic_agent.get_history())&#125;</span> 条消息&quot;</span>)<br></code></pre></td></tr></table></figure><p>在本节中，我们通过继承 <code>Agent</code> 基类，成功构建了一个功能完备且遵循框架规范的基础对话智能体 <code>MySimpleAgent</code>。它不仅支持基础对话，还具备可选的工具调用能力、流式响应和便利的工具管理方法。</p><h3 id="7-4-2-ReActAgent"><a href="#7-4-2-ReActAgent" class="headerlink" title="7.4.2 ReActAgent"></a>7.4.2 ReActAgent</h3><p>框架化的 ReActAgent 在保持核心逻辑不变的同时，提升了代码的组织性和可维护性，主要是通过提示词优化和与框架工具系统的集成。</p><p>（1）提示词模板的改进</p><p>保持了原有的格式要求，强调”每次只能执行一个步骤”，避免混乱，并明确了两种Action的使用场景。</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></pre></td><td class="code"><pre><code class="hljs python">MY_REACT_PROMPT = <span class="hljs-string">&quot;&quot;&quot;你是一个具备推理和行动能力的AI助手。你可以通过思考分析问题，然后调用合适的工具来获取信息，最终给出准确的答案。</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 可用工具</span><br><span class="hljs-string">&#123;tools&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 工作流程</span><br><span class="hljs-string">请严格按照以下格式进行回应，每次只能执行一个步骤:</span><br><span class="hljs-string"></span><br><span class="hljs-string">Thought: 分析当前问题，思考需要什么信息或采取什么行动。</span><br><span class="hljs-string">Action: 选择一个行动，格式必须是以下之一:</span><br><span class="hljs-string">- `&#123;&#123;tool_name&#125;&#125;[&#123;&#123;tool_input&#125;&#125;]` - 调用指定工具</span><br><span class="hljs-string">- `Finish[最终答案]` - 当你有足够信息给出最终答案时</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 重要提醒</span><br><span class="hljs-string">1. 每次回应必须包含Thought和Action两部分</span><br><span class="hljs-string">2. 工具调用的格式必须严格遵循:工具名[参数]</span><br><span class="hljs-string">3. 只有当你确信有足够信息回答问题时，才使用Finish</span><br><span class="hljs-string">4. 如果工具返回的信息不够，继续使用其他工具或相同工具的不同参数</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 当前任务</span><br><span class="hljs-string">**Question:** &#123;question&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">## 执行历史</span><br><span class="hljs-string">&#123;history&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">现在开始你的推理和行动:</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p>（2）重写ReActAgent的完整实现</p><p>创建<code>my_react_agent.py</code>文件来重写ReActAgent：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># my_react_agent.py</span><br><span class="hljs-keyword">import</span> re<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Optional</span>, <span class="hljs-type">List</span>, <span class="hljs-type">Tuple</span><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ReActAgent, HelloAgentsLLM, Config, Message, ToolRegistry<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyReActAgent</span>(<span class="hljs-title class_ inherited__">ReActAgent</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">    重写的ReAct Agent - 推理与行动结合的智能体</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params"></span><br><span class="hljs-params">        self,</span><br><span class="hljs-params">        name: <span class="hljs-built_in">str</span>,</span><br><span class="hljs-params">        llm: HelloAgentsLLM,</span><br><span class="hljs-params">        tool_registry: ToolRegistry,</span><br><span class="hljs-params">        system_prompt: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        config: <span class="hljs-type">Optional</span>[Config] = <span class="hljs-literal">None</span>,</span><br><span class="hljs-params">        max_steps: <span class="hljs-built_in">int</span> = <span class="hljs-number">5</span>,</span><br><span class="hljs-params">        custom_prompt: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span></span><br><span class="hljs-params">    </span>):<br>        <span class="hljs-built_in">super</span>().__init__(name, llm, system_prompt, config)<br>        self.tool_registry = tool_registry<br>        self.max_steps = max_steps<br>        self.current_history: <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>] = []<br>        self.prompt_template = custom_prompt <span class="hljs-keyword">if</span> custom_prompt <span class="hljs-keyword">else</span> MY_REACT_PROMPT<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ <span class="hljs-subst">&#123;name&#125;</span> 初始化完成，最大步数: <span class="hljs-subst">&#123;max_steps&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>其初始化参数的含义如下：</p><ul><li><code>name</code>： Agent的名称。</li><li><code>llm</code>： <code>HelloAgentsLLM</code>的实例，负责与大语言模型通信。</li><li><code>tool_registry</code>： <code>ToolRegistry</code>的实例，用于管理和执行Agent可用的工具。</li><li><code>system_prompt</code>： 系统提示词，用于设定Agent的角色和行为准则。</li><li><code>config</code>： 配置对象，用于传递框架级的设置。</li><li><code>max_steps</code>： ReAct循环的最大执行步数，防止无限循环。</li><li><code>custom_prompt</code>： 自定义的提示词模板，用于替换默认的ReAct提示词。</li></ul><p>框架化的ReActAgent将执行流程分解为清晰的步骤：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, input_text: <span class="hljs-built_in">str</span>, **kwargs</span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;运行ReAct Agent&quot;&quot;&quot;</span><br>    self.current_history = []<br>    current_step = <span class="hljs-number">0</span><br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n🤖 <span class="hljs-subst">&#123;self.name&#125;</span> 开始处理问题: <span class="hljs-subst">&#123;input_text&#125;</span>&quot;</span>)<br><br>    <span class="hljs-keyword">while</span> current_step &lt; self.max_steps:<br>        current_step += <span class="hljs-number">1</span><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n--- 第 <span class="hljs-subst">&#123;current_step&#125;</span> 步 ---&quot;</span>)<br><br>        <span class="hljs-comment"># 1. 构建提示词</span><br>        tools_desc = self.tool_registry.get_tools_description()<br>        history_str = <span class="hljs-string">&quot;\n&quot;</span>.join(self.current_history)<br>        prompt = self.prompt_template.<span class="hljs-built_in">format</span>(<br>            tools=tools_desc,<br>            question=input_text,<br>            history=history_str<br>        )<br><br>        <span class="hljs-comment"># 2. 调用LLM</span><br>        messages = [&#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: prompt&#125;]<br>        response_text = self.llm.invoke(messages, **kwargs)<br><br>        <span class="hljs-comment"># 3. 解析输出</span><br>        thought, action = self._parse_output(response_text)<br><br>        <span class="hljs-comment"># 4. 检查完成条件</span><br>        <span class="hljs-keyword">if</span> action <span class="hljs-keyword">and</span> action.startswith(<span class="hljs-string">&quot;Finish&quot;</span>):<br>            final_answer = self._parse_action_input(action)<br>            self.add_message(Message(input_text, <span class="hljs-string">&quot;user&quot;</span>))<br>            self.add_message(Message(final_answer, <span class="hljs-string">&quot;assistant&quot;</span>))<br>            <span class="hljs-keyword">return</span> final_answer<br><br>        <span class="hljs-comment"># 5. 执行工具调用</span><br>        <span class="hljs-keyword">if</span> action:<br>            tool_name, tool_input = self._parse_action(action)<br>            observation = self.tool_registry.execute_tool(tool_name, tool_input)<br>            self.current_history.append(<span class="hljs-string">f&quot;Action: <span class="hljs-subst">&#123;action&#125;</span>&quot;</span>)<br>            self.current_history.append(<span class="hljs-string">f&quot;Observation: <span class="hljs-subst">&#123;observation&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 达到最大步数</span><br>    final_answer = <span class="hljs-string">&quot;抱歉，我无法在限定步数内完成这个任务。&quot;</span><br>    self.add_message(Message(input_text, <span class="hljs-string">&quot;user&quot;</span>))<br>    self.add_message(Message(final_answer, <span class="hljs-string">&quot;assistant&quot;</span>))<br>    <span class="hljs-keyword">return</span> final_answer<br></code></pre></td></tr></table></figure><p>通过以上重构，我们将 ReAct 范式成功地集成到了框架中。核心改进在于利用了统一的 <code>ToolRegistry</code> 接口，并通过一个可配置、格式更严谨的提示词模板，提升了智能体执行思考-行动循环的稳定性。对于ReAct的测试案例，由于需要调用工具，所以统一放在文末提供测试代码。</p><h3 id="7-4-3-ReflectionAgent"><a href="#7-4-3-ReflectionAgent" class="headerlink" title="7.4.3 ReflectionAgent"></a>7.4.3 ReflectionAgent</h3><p>由于这几类Agent已经在第四章实现过核心逻辑，所以这里只给出对应的Prompt。与第四章专门针对代码生成的提示词不同，框架化的版本采用了通用化设计，使其适用于文本生成、分析、创作等多种场景，并通过<code>custom_prompts</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><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><code class="hljs python">DEFAULT_PROMPTS = &#123;<br>    <span class="hljs-string">&quot;initial&quot;</span>: <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">请根据以下要求完成任务:</span><br><span class="hljs-string"></span><br><span class="hljs-string">任务: &#123;task&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">请提供一个完整、准确的回答。</span><br><span class="hljs-string">&quot;&quot;&quot;</span>,<br>    <span class="hljs-string">&quot;reflect&quot;</span>: <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">请仔细审查以下回答，并找出可能的问题或改进空间:</span><br><span class="hljs-string"></span><br><span class="hljs-string"># 原始任务:</span><br><span class="hljs-string">&#123;task&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string"># 当前回答:</span><br><span class="hljs-string">&#123;content&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">请分析这个回答的质量，指出不足之处，并提出具体的改进建议。</span><br><span class="hljs-string">如果回答已经很好，请回答&quot;无需改进&quot;。</span><br><span class="hljs-string">&quot;&quot;&quot;</span>,<br>    <span class="hljs-string">&quot;refine&quot;</span>: <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">请根据反馈意见改进你的回答:</span><br><span class="hljs-string"></span><br><span class="hljs-string"># 原始任务:</span><br><span class="hljs-string">&#123;task&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string"># 上一轮回答:</span><br><span class="hljs-string">&#123;last_attempt&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string"># 反馈意见:</span><br><span class="hljs-string">&#123;feedback&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">请提供一个改进后的回答。</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br>&#125;<br></code></pre></td></tr></table></figure><p>你可以尝试根据第四章的代码，以及上文ReAct的实现，构建出自己的MyReflectionAgent。下面提供一个测试代码供验证想法。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># test_reflection_agent.py</span><br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> HelloAgentsLLM<br><span class="hljs-keyword">from</span> my_reflection_agent <span class="hljs-keyword">import</span> MyReflectionAgent<br><br>load_dotenv()<br>llm = HelloAgentsLLM()<br><br><span class="hljs-comment"># 使用默认通用提示词</span><br>general_agent = MyReflectionAgent(name=<span class="hljs-string">&quot;我的反思助手&quot;</span>, llm=llm)<br><br><span class="hljs-comment"># 使用自定义代码生成提示词（类似第四章）</span><br>code_prompts = &#123;<br>    <span class="hljs-string">&quot;initial&quot;</span>: <span class="hljs-string">&quot;你是Python专家，请编写函数:&#123;task&#125;&quot;</span>,<br>    <span class="hljs-string">&quot;reflect&quot;</span>: <span class="hljs-string">&quot;请审查代码的算法效率:\n任务:&#123;task&#125;\n代码:&#123;content&#125;&quot;</span>,<br>    <span class="hljs-string">&quot;refine&quot;</span>: <span class="hljs-string">&quot;请根据反馈优化代码:\n任务:&#123;task&#125;\n反馈:&#123;feedback&#125;&quot;</span><br>&#125;<br>code_agent = MyReflectionAgent(<br>    name=<span class="hljs-string">&quot;我的代码生成助手&quot;</span>,<br>    llm=llm,<br>    custom_prompts=code_prompts<br>)<br><br><span class="hljs-comment"># 测试使用</span><br>result = general_agent.run(<span class="hljs-string">&quot;写一篇关于人工智能发展历程的简短文章&quot;</span>)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;最终结果: <span class="hljs-subst">&#123;result&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><h3 id="7-4-4-PlanAndSolveAgent"><a href="#7-4-4-PlanAndSolveAgent" class="headerlink" title="7.4.4 PlanAndSolveAgent"></a>7.4.4 PlanAndSolveAgent</h3><p>与第四章自由文本的计划输出不同，框架化版本强制要求Planner以Python列表的格式输出计划，并提供了完整的异常处理机制，确保了后续步骤能够稳定执行。框架化的Plan-and-Solve提示词：</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><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><code class="hljs bash"><span class="hljs-comment"># 默认规划器提示词模板</span><br>DEFAULT_PLANNER_PROMPT = <span class="hljs-string">&quot;&quot;</span><span class="hljs-string">&quot;</span><br><span class="hljs-string">你是一个顶级的AI规划专家。你的任务是将用户提出的复杂问题分解成一个由多个简单步骤组成的行动计划。</span><br><span class="hljs-string">请确保计划中的每个步骤都是一个独立的、可执行的子任务，并且严格按照逻辑顺序排列。</span><br><span class="hljs-string">你的输出必须是一个Python列表，其中每个元素都是一个描述子任务的字符串。</span><br><span class="hljs-string"></span><br><span class="hljs-string">问题: &#123;question&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">请严格按照以下格式输出你的计划:</span><br><span class="hljs-string">```python</span><br><span class="hljs-string">[&quot;</span>步骤1<span class="hljs-string">&quot;, &quot;</span>步骤2<span class="hljs-string">&quot;, &quot;</span>步骤3<span class="hljs-string">&quot;, ...]</span><br><span class="hljs-string">```</span><br><span class="hljs-string">&quot;</span><span class="hljs-string">&quot;&quot;</span><br><br><span class="hljs-comment"># 默认执行器提示词模板</span><br>DEFAULT_EXECUTOR_PROMPT = <span class="hljs-string">&quot;&quot;</span><span class="hljs-string">&quot;</span><br><span class="hljs-string">你是一位顶级的AI执行专家。你的任务是严格按照给定的计划，一步步地解决问题。</span><br><span class="hljs-string">你将收到原始问题、完整的计划、以及到目前为止已经完成的步骤和结果。</span><br><span class="hljs-string">请你专注于解决&quot;</span>当前步骤<span class="hljs-string">&quot;，并仅输出该步骤的最终答案，不要输出任何额外的解释或对话。</span><br><span class="hljs-string"></span><br><span class="hljs-string"># 原始问题:</span><br><span class="hljs-string">&#123;question&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string"># 完整计划:</span><br><span class="hljs-string">&#123;plan&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string"># 历史步骤与结果:</span><br><span class="hljs-string">&#123;history&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string"># 当前步骤:</span><br><span class="hljs-string">&#123;current_step&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">请仅输出针对&quot;</span>当前步骤<span class="hljs-string">&quot;的回答:</span><br><span class="hljs-string">&quot;</span><span class="hljs-string">&quot;&quot;</span><br></code></pre></td></tr></table></figure><p>这一节仍然给出一个综合测试文件<code>test_plan_solve_agent.py</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><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><code class="hljs python"><span class="hljs-comment"># test_plan_solve_agent.py</span><br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><span class="hljs-keyword">from</span> hello_agents.core.llm <span class="hljs-keyword">import</span> HelloAgentsLLM<br><span class="hljs-keyword">from</span> my_plan_solve_agent <span class="hljs-keyword">import</span> MyPlanAndSolveAgent<br><br><span class="hljs-comment"># 加载环境变量</span><br>load_dotenv()<br><br><span class="hljs-comment"># 创建LLM实例</span><br>llm = HelloAgentsLLM()<br><br><span class="hljs-comment"># 创建自定义PlanAndSolveAgent</span><br>agent = MyPlanAndSolveAgent(<br>    name=<span class="hljs-string">&quot;我的规划执行助手&quot;</span>,<br>    llm=llm<br>)<br><br><span class="hljs-comment"># 测试复杂问题</span><br>question = <span class="hljs-string">&quot;一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果？&quot;</span><br><br>result = agent.run(question)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;\n最终结果: <span class="hljs-subst">&#123;result&#125;</span>&quot;</span>)<br><br><span class="hljs-comment"># 查看对话历史</span><br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;对话历史: <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(agent.get_history())&#125;</span> 条消息&quot;</span>)<br></code></pre></td></tr></table></figure><p>在最后可以补充一款新的提示词，可以尝试实现<code>custom_prompt</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><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><code class="hljs python"><span class="hljs-comment"># 创建专门用于数学问题的自定义提示词</span><br>math_prompts = &#123;<br>    <span class="hljs-string">&quot;planner&quot;</span>: <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">你是数学问题规划专家。请将数学问题分解为计算步骤:</span><br><span class="hljs-string"></span><br><span class="hljs-string">问题: &#123;question&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">输出格式:</span><br><span class="hljs-string">python</span><br><span class="hljs-string">[&quot;计算步骤1&quot;, &quot;计算步骤2&quot;, &quot;求总和&quot;]</span><br><span class="hljs-string"></span><br><span class="hljs-string">&quot;&quot;&quot;</span>,<br>    <span class="hljs-string">&quot;executor&quot;</span>: <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">你是数学计算专家。请计算当前步骤:</span><br><span class="hljs-string"></span><br><span class="hljs-string">问题: &#123;question&#125;</span><br><span class="hljs-string">计划: &#123;plan&#125;</span><br><span class="hljs-string">历史: &#123;history&#125;</span><br><span class="hljs-string">当前步骤: &#123;current_step&#125;</span><br><span class="hljs-string"></span><br><span class="hljs-string">请只输出数值结果:</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br>&#125;<br><br><span class="hljs-comment"># 使用自定义提示词创建数学专用Agent</span><br>math_agent = MyPlanAndSolveAgent(<br>    name=<span class="hljs-string">&quot;数学计算助手&quot;</span>,<br>    llm=llm,<br>    custom_prompts=math_prompts<br>)<br><br><span class="hljs-comment"># 测试数学问题</span><br>math_result = math_agent.run(question)<br><span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;数学专用Agent结果: <span class="hljs-subst">&#123;math_result&#125;</span>&quot;</span>)<br></code></pre></td></tr></table></figure><p>如表7.2所示，通过这种框架化的重构，我们不仅保持了第四章中各种Agent范式的核心功能，还大幅提升了代码的组织性、可维护性和扩展性。所有Agent现在都共享统一的基础架构，同时保持了各自的特色和优势。</p><div align="center">  <p>表 7.2 Agent不同章节实现对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/7-figures/table-02.png" alt="" width="90%"/></div><h3 id="7-4-5-FunctionCallAgent"><a href="#7-4-5-FunctionCallAgent" class="headerlink" title="7.4.5 FunctionCallAgent"></a>7.4.5 FunctionCallAgent</h3><p>FunctionCallAgent是hello-agents在0.2.8之后引入的Agent，它基于OpenAI原生函数调用机制的Agent，展示了如何使用OpenAI的函数调用机制来构建Agent。<br>它支持以下功能：</p><ul><li>_build_tool_schemas:通过工具的description构建OpenAI的function calling schema</li><li>_extract_message_content:从OpenAI的响应中提取文本</li><li>_parse_function_call_arguments:解析模型返回的JSON字符串参数</li><li>_convert_parameter_types:转换参数类型</li></ul><p>这些功能可以使其具备原生的OpenAI Function Calling的能力，对比使用prompt约束的方式，具备更强的鲁棒性。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_invoke_with_tools</span>(<span class="hljs-params">self, messages: <span class="hljs-built_in">list</span>[<span class="hljs-built_in">dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]], tools: <span class="hljs-built_in">list</span>[<span class="hljs-built_in">dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]], tool_choice: <span class="hljs-type">Union</span>[<span class="hljs-built_in">str</span>, <span class="hljs-built_in">dict</span>], **kwargs</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;调用底层OpenAI客户端执行函数调用&quot;&quot;&quot;</span><br>        client = <span class="hljs-built_in">getattr</span>(self.llm, <span class="hljs-string">&quot;_client&quot;</span>, <span class="hljs-literal">None</span>)<br>        <span class="hljs-keyword">if</span> client <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br>            <span class="hljs-keyword">raise</span> RuntimeError(<span class="hljs-string">&quot;HelloAgentsLLM 未正确初始化客户端，无法执行函数调用。&quot;</span>)<br><br>        client_kwargs = <span class="hljs-built_in">dict</span>(kwargs)<br>        client_kwargs.setdefault(<span class="hljs-string">&quot;temperature&quot;</span>, self.llm.temperature)<br>        <span class="hljs-keyword">if</span> self.llm.max_tokens <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:<br>            client_kwargs.setdefault(<span class="hljs-string">&quot;max_tokens&quot;</span>, self.llm.max_tokens)<br><br>        <span class="hljs-keyword">return</span> client.chat.completions.create(<br>            model=self.llm.model,<br>            messages=messages,<br>            tools=tools,<br>            tool_choice=tool_choice,<br>            **client_kwargs,<br>        )<br><br><span class="hljs-comment">#内部逻辑是对Openai 原生的functioncall作再封装</span><br><span class="hljs-comment">#OpenAI 原生functioncall示例</span><br><span class="hljs-keyword">from</span> openai <span class="hljs-keyword">import</span> OpenAI<br>client = OpenAI()<br><br>tools = [<br>  &#123;<br>    <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;function&quot;</span>,<br>    <span class="hljs-string">&quot;function&quot;</span>: &#123;<br>      <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;get_current_weather&quot;</span>,<br>      <span class="hljs-string">&quot;description&quot;</span>: <span class="hljs-string">&quot;Get the current weather in a given location&quot;</span>,<br>      <span class="hljs-string">&quot;parameters&quot;</span>: &#123;<br>        <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;object&quot;</span>,<br>        <span class="hljs-string">&quot;properties&quot;</span>: &#123;<br>          <span class="hljs-string">&quot;location&quot;</span>: &#123;<br>            <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,<br>            <span class="hljs-string">&quot;description&quot;</span>: <span class="hljs-string">&quot;The city and state, e.g. San Francisco, CA&quot;</span>,<br>          &#125;,<br>          <span class="hljs-string">&quot;unit&quot;</span>: &#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, <span class="hljs-string">&quot;enum&quot;</span>: [<span class="hljs-string">&quot;celsius&quot;</span>, <span class="hljs-string">&quot;fahrenheit&quot;</span>]&#125;,<br>        &#125;,<br>        <span class="hljs-string">&quot;required&quot;</span>: [<span class="hljs-string">&quot;location&quot;</span>],<br>      &#125;,<br>    &#125;<br>  &#125;<br>]<br>messages = [&#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;What&#x27;s the weather like in Boston today?&quot;</span>&#125;]<br>completion = client.chat.completions.create(<br>  model=<span class="hljs-string">&quot;gpt-5&quot;</span>,<br>  messages=messages,<br>  tools=tools,<br>  tool_choice=<span class="hljs-string">&quot;auto&quot;</span><br>)<br><br><span class="hljs-built_in">print</span>(completion)<br></code></pre></td></tr></table></figure><h2 id="7-5-工具系统"><a href="#7-5-工具系统" class="headerlink" title="7.5 工具系统"></a>7.5 工具系统</h2><p>本节内容将在前面构建的Agent基础架构上，深入探讨工具系统的设计与实现。我们将从基础设施建设开始，逐步深入到自定义开发设计。本节的学习目标围绕以下三个核心方面展开：</p><ol><li><p><strong>统一的工具抽象与管理</strong>：建立标准化的Tool基类和ToolRegistry注册机制，为工具的开发、注册、发现和执行提供统一的基础设施。</p></li><li><p><strong>实战驱动的工具开发</strong>：以数学计算工具为案例，展示如何设计和实现自定义工具，让读者掌握工具开发的完整流程。</p></li><li><p><strong>高级整合与优化策略</strong>：通过多源搜索工具的设计，展示如何整合多个外部服务，实现智能后端选择、结果合并和容错处理，体现工具系统在复杂场景下的设计思维。</p></li></ol><h3 id="7-5-1-工具基类与注册机制设计"><a href="#7-5-1-工具基类与注册机制设计" class="headerlink" title="7.5.1 工具基类与注册机制设计"></a>7.5.1 工具基类与注册机制设计</h3><p>在构建可扩展的工具系统时，我们需要首先建立一套标准化的基础设施。这套基础设施包括Tool基类、ToolRegistry注册表，以及工具管理机制。</p><p>（1）Tool基类的抽象设计</p><p>Tool基类是整个工具系统的核心抽象，它定义了所有工具必须遵循的接口规范：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Tool</span>(<span class="hljs-title class_ inherited__">ABC</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;工具基类&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, name: <span class="hljs-built_in">str</span>, description: <span class="hljs-built_in">str</span></span>):<br>        self.name = name<br>        self.description = description<br><br><span class="hljs-meta">    @abstractmethod</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self, parameters: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]</span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;执行工具&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">pass</span><br><br><span class="hljs-meta">    @abstractmethod</span><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_parameters</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">List</span>[ToolParameter]:<br>        <span class="hljs-string">&quot;&quot;&quot;获取工具参数定义&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">pass</span><br></code></pre></td></tr></table></figure><p>这个设计体现了面向对象设计的核心思想：通过统一的<code>run</code>方法接口，所有工具都能以一致的方式执行，接受字典参数并返回字符串结果，确保了框架的一致性。同时，工具具备了自描述能力，通过<code>get_parameters</code>方法能够清晰地告诉调用者自己需要什么参数，这种内省机制为自动化文档生成和参数验证提供了基础。而name和description等元数据的设计，则让工具系统具备了良好的可发现性和可理解性。</p><p>（2）ToolParameter参数定义系统</p><p>为了支持复杂的参数验证和文档生成，我们设计了ToolParameter类：</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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ToolParameter</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;工具参数定义&quot;&quot;&quot;</span><br>    name: <span class="hljs-built_in">str</span><br>    <span class="hljs-built_in">type</span>: <span class="hljs-built_in">str</span><br>    description: <span class="hljs-built_in">str</span><br>    required: <span class="hljs-built_in">bool</span> = <span class="hljs-literal">True</span><br>    default: <span class="hljs-type">Any</span> = <span class="hljs-literal">None</span><br></code></pre></td></tr></table></figure><p>这种设计让工具能够精确描述自己的参数需求，支持类型检查、默认值设置和文档自动生成。</p><p>（3）ToolRegistry注册表的实现</p><p>ToolRegistry是工具系统的管理中枢，它提供了工具的注册、发现、执行等核心功能，在这一节我们主要用到以下功能：</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><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ToolRegistry</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;HelloAgents工具注册表&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):<br>        self._tools: <span class="hljs-built_in">dict</span>[<span class="hljs-built_in">str</span>, Tool] = &#123;&#125;<br>        self._functions: <span class="hljs-built_in">dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-built_in">dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]] = &#123;&#125;<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">register_tool</span>(<span class="hljs-params">self, tool: Tool</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;注册Tool对象&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> tool.name <span class="hljs-keyword">in</span> self._tools:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;⚠️ 警告:工具 &#x27;<span class="hljs-subst">&#123;tool.name&#125;</span>&#x27; 已存在，将被覆盖。&quot;</span>)<br>        self._tools[tool.name] = tool<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 工具 &#x27;<span class="hljs-subst">&#123;tool.name&#125;</span>&#x27; 已注册。&quot;</span>)<br>        <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">register_function</span>(<span class="hljs-params">self, name: <span class="hljs-built_in">str</span>, description: <span class="hljs-built_in">str</span>, func: <span class="hljs-type">Callable</span>[[<span class="hljs-built_in">str</span>], <span class="hljs-built_in">str</span>]</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">        直接注册函数作为工具（简便方式）</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Args:</span><br><span class="hljs-string">            name: 工具名称</span><br><span class="hljs-string">            description: 工具描述</span><br><span class="hljs-string">            func: 工具函数，接受字符串参数，返回字符串结果</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> name <span class="hljs-keyword">in</span> self._functions:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;⚠️ 警告:工具 &#x27;<span class="hljs-subst">&#123;name&#125;</span>&#x27; 已存在，将被覆盖。&quot;</span>)<br><br>        self._functions[name] = &#123;<br>            <span class="hljs-string">&quot;description&quot;</span>: description,<br>            <span class="hljs-string">&quot;func&quot;</span>: func<br>        &#125;<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 工具 &#x27;<span class="hljs-subst">&#123;name&#125;</span>&#x27; 已注册。&quot;</span>)<br></code></pre></td></tr></table></figure><p>ToolRegistry支持两种注册方式：</p><ol><li><strong>Tool对象注册</strong>：适合复杂工具，支持完整的参数定义和验证</li><li><strong>函数直接注册</strong>：适合简单工具，快速集成现有函数</li></ol><p>（4）工具发现与管理机制</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">get_tools_description</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;获取所有可用工具的格式化描述字符串&quot;&quot;&quot;</span><br>    descriptions = []<br><br>    <span class="hljs-comment"># Tool对象描述</span><br>    <span class="hljs-keyword">for</span> tool <span class="hljs-keyword">in</span> self._tools.values():<br>        descriptions.append(<span class="hljs-string">f&quot;- <span class="hljs-subst">&#123;tool.name&#125;</span>: <span class="hljs-subst">&#123;tool.description&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 函数工具描述</span><br>    <span class="hljs-keyword">for</span> name, info <span class="hljs-keyword">in</span> self._functions.items():<br>        descriptions.append(<span class="hljs-string">f&quot;- <span class="hljs-subst">&#123;name&#125;</span>: <span class="hljs-subst">&#123;info[<span class="hljs-string">&#x27;description&#x27;</span>]&#125;</span>&quot;</span>)<br><br>    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;\n&quot;</span>.join(descriptions) <span class="hljs-keyword">if</span> descriptions <span class="hljs-keyword">else</span> <span class="hljs-string">&quot;暂无可用工具&quot;</span><br></code></pre></td></tr></table></figure><p>这个方法生成的描述字符串可以直接用于构建Agent的提示词，让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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">to_openai_schema</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;转换为 OpenAI function calling schema 格式</span><br><span class="hljs-string"></span><br><span class="hljs-string">        用于 FunctionCallAgent，使工具能够被 OpenAI 原生 function calling 使用</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Returns:</span><br><span class="hljs-string">            符合 OpenAI function calling 标准的 schema</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        parameters = self.get_parameters()<br><br>        <span class="hljs-comment"># 构建 properties</span><br>        properties = &#123;&#125;<br>        required = []<br><br>        <span class="hljs-keyword">for</span> param <span class="hljs-keyword">in</span> parameters:<br>            <span class="hljs-comment"># 基础属性定义</span><br>            prop = &#123;<br>                <span class="hljs-string">&quot;type&quot;</span>: param.<span class="hljs-built_in">type</span>,<br>                <span class="hljs-string">&quot;description&quot;</span>: param.description<br>            &#125;<br><br>            <span class="hljs-comment"># 如果有默认值，添加到描述中（OpenAI schema 不支持 default 字段）</span><br>            <span class="hljs-keyword">if</span> param.default <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:<br>                prop[<span class="hljs-string">&quot;description&quot;</span>] = <span class="hljs-string">f&quot;<span class="hljs-subst">&#123;param.description&#125;</span> (默认: <span class="hljs-subst">&#123;param.default&#125;</span>)&quot;</span><br><br>            <span class="hljs-comment"># 如果是数组类型，添加 items 定义</span><br>            <span class="hljs-keyword">if</span> param.<span class="hljs-built_in">type</span> == <span class="hljs-string">&quot;array&quot;</span>:<br>                prop[<span class="hljs-string">&quot;items&quot;</span>] = &#123;<span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>&#125;  <span class="hljs-comment"># 默认字符串数组</span><br><br>            properties[param.name] = prop<br><br>            <span class="hljs-comment"># 收集必需参数</span><br>            <span class="hljs-keyword">if</span> param.required:<br>                required.append(param.name)<br><br>        <span class="hljs-keyword">return</span> &#123;<br>            <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;function&quot;</span>,<br>            <span class="hljs-string">&quot;function&quot;</span>: &#123;<br>                <span class="hljs-string">&quot;name&quot;</span>: self.name,<br>                <span class="hljs-string">&quot;description&quot;</span>: self.description,<br>                <span class="hljs-string">&quot;parameters&quot;</span>: &#123;<br>                    <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;object&quot;</span>,<br>                    <span class="hljs-string">&quot;properties&quot;</span>: properties,<br>                    <span class="hljs-string">&quot;required&quot;</span>: required<br>                &#125;<br>            &#125;<br>        &#125;<br></code></pre></td></tr></table></figure><p>这个方法生成的schema可以直接用于原生的OpenAI SDK的工具调用。</p><h3 id="7-5-2-自定义工具开发"><a href="#7-5-2-自定义工具开发" class="headerlink" title="7.5.2 自定义工具开发"></a>7.5.2 自定义工具开发</h3><p>有了基础设施后，我们来看看如何开发一个完整的自定义工具。数学计算工具是一个很好的例子，因为它简单直观，最直接的方式是使用ToolRegistry的函数注册功能。</p><p>让我们创建一个自定义的数学计算工具。首先，在你的项目目录中创建<code>my_calculator_tool.py</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><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><code class="hljs python"><span class="hljs-comment"># my_calculator_tool.py</span><br><span class="hljs-keyword">import</span> ast<br><span class="hljs-keyword">import</span> operator<br><span class="hljs-keyword">import</span> math<br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ToolRegistry<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">my_calculate</span>(<span class="hljs-params">expression: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;简单的数学计算函数&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> expression.strip():<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;计算表达式不能为空&quot;</span><br><br>    <span class="hljs-comment"># 支持的基本运算</span><br>    operators = &#123;<br>        ast.Add: operator.add,      <span class="hljs-comment"># +</span><br>        ast.Sub: operator.sub,      <span class="hljs-comment"># -</span><br>        ast.Mult: operator.mul,     <span class="hljs-comment"># *</span><br>        ast.Div: operator.truediv,  <span class="hljs-comment"># /</span><br>    &#125;<br><br>    <span class="hljs-comment"># 支持的基本函数</span><br>    functions = &#123;<br>        <span class="hljs-string">&#x27;sqrt&#x27;</span>: math.sqrt,<br>        <span class="hljs-string">&#x27;pi&#x27;</span>: math.pi,<br>    &#125;<br><br>    <span class="hljs-keyword">try</span>:<br>        node = ast.parse(expression, mode=<span class="hljs-string">&#x27;eval&#x27;</span>)<br>        result = _eval_node(node.body, operators, functions)<br>        <span class="hljs-keyword">return</span> <span class="hljs-built_in">str</span>(result)<br>    <span class="hljs-keyword">except</span>:<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;计算失败，请检查表达式格式&quot;</span><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">_eval_node</span>(<span class="hljs-params">node, operators, functions</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;简化的表达式求值&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-built_in">isinstance</span>(node, ast.Constant):<br>        <span class="hljs-keyword">return</span> node.value<br>    <span class="hljs-keyword">elif</span> <span class="hljs-built_in">isinstance</span>(node, ast.BinOp):<br>        left = _eval_node(node.left, operators, functions)<br>        right = _eval_node(node.right, operators, functions)<br>        op = operators.get(<span class="hljs-built_in">type</span>(node.op))<br>        <span class="hljs-keyword">return</span> op(left, right)<br>    <span class="hljs-keyword">elif</span> <span class="hljs-built_in">isinstance</span>(node, ast.Call):<br>        func_name = node.func.<span class="hljs-built_in">id</span><br>        <span class="hljs-keyword">if</span> func_name <span class="hljs-keyword">in</span> functions:<br>            args = [_eval_node(arg, operators, functions) <span class="hljs-keyword">for</span> arg <span class="hljs-keyword">in</span> node.args]<br>            <span class="hljs-keyword">return</span> functions[func_name](*args)<br>    <span class="hljs-keyword">elif</span> <span class="hljs-built_in">isinstance</span>(node, ast.Name):<br>        <span class="hljs-keyword">if</span> node.<span class="hljs-built_in">id</span> <span class="hljs-keyword">in</span> functions:<br>            <span class="hljs-keyword">return</span> functions[node.<span class="hljs-built_in">id</span>]<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_calculator_registry</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;创建包含计算器的工具注册表&quot;&quot;&quot;</span><br>    registry = ToolRegistry()<br><br>    <span class="hljs-comment"># 注册计算器函数</span><br>    registry.register_function(<br>        name=<span class="hljs-string">&quot;my_calculator&quot;</span>,<br>        description=<span class="hljs-string">&quot;简单的数学计算工具，支持基本运算(+,-,*,/)和sqrt函数&quot;</span>,<br>        func=my_calculate<br>    )<br><br>    <span class="hljs-keyword">return</span> registry<br></code></pre></td></tr></table></figure><p>工具不仅支持基本的四则运算，还涵盖了常用的数学函数和常数，满足了大多数计算场景的需求。你也可以自己扩展这个文件，制作一个更加完备的计算函数。我们提供一个测试文件<code>test_my_calculator.py</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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># test_my_calculator.py</span><br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><span class="hljs-keyword">from</span> my_calculator_tool <span class="hljs-keyword">import</span> create_calculator_registry<br><br><span class="hljs-comment"># 加载环境变量</span><br>load_dotenv()<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_calculator_tool</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;测试自定义计算器工具&quot;&quot;&quot;</span><br><br>    <span class="hljs-comment"># 创建包含计算器的注册表</span><br>    registry = create_calculator_registry()<br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;🧪 测试自定义计算器工具\n&quot;</span>)<br><br>    <span class="hljs-comment"># 简单测试用例</span><br>    test_cases = [<br>        <span class="hljs-string">&quot;2 + 3&quot;</span>,           <span class="hljs-comment"># 基本加法</span><br>        <span class="hljs-string">&quot;10 - 4&quot;</span>,          <span class="hljs-comment"># 基本减法</span><br>        <span class="hljs-string">&quot;5 * 6&quot;</span>,           <span class="hljs-comment"># 基本乘法</span><br>        <span class="hljs-string">&quot;15 / 3&quot;</span>,          <span class="hljs-comment"># 基本除法</span><br>        <span class="hljs-string">&quot;sqrt(16)&quot;</span>,        <span class="hljs-comment"># 平方根</span><br>    ]<br><br>    <span class="hljs-keyword">for</span> i, expression <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(test_cases, <span class="hljs-number">1</span>):<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;测试 <span class="hljs-subst">&#123;i&#125;</span>: <span class="hljs-subst">&#123;expression&#125;</span>&quot;</span>)<br>        result = registry.execute_tool(<span class="hljs-string">&quot;my_calculator&quot;</span>, expression)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;结果: <span class="hljs-subst">&#123;result&#125;</span>\n&quot;</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_with_simple_agent</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;测试与SimpleAgent的集成&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> HelloAgentsLLM<br><br>    <span class="hljs-comment"># 创建LLM客户端</span><br>    llm = HelloAgentsLLM()<br><br>    <span class="hljs-comment"># 创建包含计算器的注册表</span><br>    registry = create_calculator_registry()<br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;🤖 与SimpleAgent集成测试:&quot;</span>)<br><br>    <span class="hljs-comment"># 模拟SimpleAgent使用工具的场景</span><br>    user_question = <span class="hljs-string">&quot;请帮我计算 sqrt(16) + 2 * 3&quot;</span><br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;用户问题: <span class="hljs-subst">&#123;user_question&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 使用工具计算</span><br>    calc_result = registry.execute_tool(<span class="hljs-string">&quot;my_calculator&quot;</span>, <span class="hljs-string">&quot;sqrt(16) + 2 * 3&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;计算结果: <span class="hljs-subst">&#123;calc_result&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 构建最终回答</span><br>    final_messages = [<br>        &#123;<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">f&quot;计算结果是 <span class="hljs-subst">&#123;calc_result&#125;</span>，请用自然语言回答用户的问题:<span class="hljs-subst">&#123;user_question&#125;</span>&quot;</span>&#125;<br>    ]<br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n🎯 SimpleAgent的回答:&quot;</span>)<br>    response = llm.think(final_messages)<br>    <span class="hljs-keyword">for</span> chunk <span class="hljs-keyword">in</span> response:<br>        <span class="hljs-built_in">print</span>(chunk, end=<span class="hljs-string">&quot;&quot;</span>, flush=<span class="hljs-literal">True</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n&quot;</span>)<br><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:<br>    test_calculator_tool()<br>    test_with_simple_agent()<br></code></pre></td></tr></table></figure><p>通过这个简化的数学计算工具案例，我们学会了如何快速开发自定义工具：编写一个简单的计算函数，通过ToolRegistry注册，然后与SimpleAgent集成使用。为了更直观的观察，这里提供了图7.1，可以清晰理解代码的运行逻辑。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/7-figures/01.png" alt="" width="90%"/>  <p>图 7.1 基于Helloagents的SimpleAgent运行工作流</p></div><h3 id="7-5-3-多源搜索工具"><a href="#7-5-3-多源搜索工具" class="headerlink" title="7.5.3 多源搜索工具"></a>7.5.3 多源搜索工具</h3><p>在实际应用中，我们经常需要整合多个外部服务来提供更强大的功能。搜索工具就是一个典型的例子，它整合多个搜索引擎，能提供更加完备的真实信息。在第一章我们使用过Tavily的搜索API，在第四章我们使用过SerpApi的搜索API。因此这次我们使用这两个API来实现多源搜索功能。如果没安装对应的python依赖可以运行下面这条脚本：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">pip install <span class="hljs-string">&quot;hello-agents[search]==0.1.1&quot;</span><br></code></pre></td></tr></table></figure><p>（1）搜索工具的统一接口设计</p><p>HelloAgents框架内置的SearchTool展示了如何设计一个高级的多源搜索工具：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">SearchTool</span>(<span class="hljs-title class_ inherited__">Tool</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">    智能混合搜索工具</span><br><span class="hljs-string"></span><br><span class="hljs-string">    支持多种搜索引擎后端，智能选择最佳搜索源:</span><br><span class="hljs-string">    1. 混合模式 (hybrid) - 智能选择TAVILY或SERPAPI</span><br><span class="hljs-string">    2. Tavily API (tavily) - 专业AI搜索</span><br><span class="hljs-string">    3. SerpApi (serpapi) - 传统Google搜索</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, backend: <span class="hljs-built_in">str</span> = <span class="hljs-string">&quot;hybrid&quot;</span>, tavily_key: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span>, serpapi_key: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = <span class="hljs-literal">None</span></span>):<br>        <span class="hljs-built_in">super</span>().__init__(<br>            name=<span class="hljs-string">&quot;search&quot;</span>,<br>            description=<span class="hljs-string">&quot;一个智能网页搜索引擎。支持混合搜索模式，自动选择最佳搜索源。&quot;</span><br>        )<br>        self.backend = backend<br>        self.tavily_key = tavily_key <span class="hljs-keyword">or</span> os.getenv(<span class="hljs-string">&quot;TAVILY_API_KEY&quot;</span>)<br>        self.serpapi_key = serpapi_key <span class="hljs-keyword">or</span> os.getenv(<span class="hljs-string">&quot;SERPAPI_API_KEY&quot;</span>)<br>        self.available_backends = []<br>        self._setup_backends()<br></code></pre></td></tr></table></figure><p>这个设计的核心思想是根据可用的API密钥和依赖库，自动选择最佳的搜索后端。</p><p>（2）TAVILY与SERPAPI搜索源的整合策略</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_search_hybrid</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;混合搜索 - 智能选择最佳搜索源&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 优先使用Tavily（AI优化的搜索）</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;tavily&quot;</span> <span class="hljs-keyword">in</span> self.available_backends:<br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-keyword">return</span> self._search_tavily(query)<br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;⚠️ Tavily搜索失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>            <span class="hljs-comment"># 如果Tavily失败，尝试SerpApi</span><br>            <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;serpapi&quot;</span> <span class="hljs-keyword">in</span> self.available_backends:<br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;🔄 切换到SerpApi搜索&quot;</span>)<br>                <span class="hljs-keyword">return</span> self._search_serpapi(query)<br><br>    <span class="hljs-comment"># 如果Tavily不可用，使用SerpApi</span><br>    <span class="hljs-keyword">elif</span> <span class="hljs-string">&quot;serpapi&quot;</span> <span class="hljs-keyword">in</span> self.available_backends:<br>        <span class="hljs-keyword">try</span>:<br>            <span class="hljs-keyword">return</span> self._search_serpapi(query)<br>        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;⚠️ SerpApi搜索失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br><br>    <span class="hljs-comment"># 如果都不可用，提示用户配置API</span><br>    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;❌ 没有可用的搜索源，请配置TAVILY_API_KEY或SERPAPI_API_KEY环境变量&quot;</span><br></code></pre></td></tr></table></figure><p>这种设计体现了高可用系统的核心理念：通过降级机制，系统能够从最优的搜索源逐步降级到可用的备选方案。当所有搜索源都不可用时，明确提示用户配置正确的API密钥。</p><p>（3）搜索结果的统一格式化</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">_search_tavily</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;使用Tavily搜索&quot;&quot;&quot;</span><br>    response = self.tavily_client.search(<br>        query=query,<br>        search_depth=<span class="hljs-string">&quot;basic&quot;</span>,<br>        include_answer=<span class="hljs-literal">True</span>,<br>        max_results=<span class="hljs-number">3</span><br>    )<br><br>    result = <span class="hljs-string">f&quot;🎯 Tavily AI搜索结果:<span class="hljs-subst">&#123;response.get(<span class="hljs-string">&#x27;answer&#x27;</span>, <span class="hljs-string">&#x27;未找到直接答案&#x27;</span>)&#125;</span>\n\n&quot;</span><br><br>    <span class="hljs-keyword">for</span> i, item <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(response.get(<span class="hljs-string">&#x27;results&#x27;</span>, [])[:<span class="hljs-number">3</span>], <span class="hljs-number">1</span>):<br>        result += <span class="hljs-string">f&quot;[<span class="hljs-subst">&#123;i&#125;</span>] <span class="hljs-subst">&#123;item.get(<span class="hljs-string">&#x27;title&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)&#125;</span>\n&quot;</span><br>        result += <span class="hljs-string">f&quot;    <span class="hljs-subst">&#123;item.get(<span class="hljs-string">&#x27;content&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)[:<span class="hljs-number">200</span>]&#125;</span>...\n&quot;</span><br>        result += <span class="hljs-string">f&quot;    来源: <span class="hljs-subst">&#123;item.get(<span class="hljs-string">&#x27;url&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)&#125;</span>\n\n&quot;</span><br><br>    <span class="hljs-keyword">return</span> result<br></code></pre></td></tr></table></figure><p>基于框架的设计思想，我们可以创建自己的高级搜索工具。这次我们使用类的方式来展示不同的实现方法，创建<code>my_advanced_search.py</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><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><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># my_advanced_search.py</span><br><span class="hljs-keyword">import</span> os<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Optional</span>, <span class="hljs-type">List</span>, <span class="hljs-type">Dict</span>, <span class="hljs-type">Any</span><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ToolRegistry<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyAdvancedSearchTool</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">    自定义高级搜索工具类</span><br><span class="hljs-string">    展示多源整合和智能选择的设计模式</span><br><span class="hljs-string">    &quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):<br>        self.name = <span class="hljs-string">&quot;my_advanced_search&quot;</span><br>        self.description = <span class="hljs-string">&quot;智能搜索工具，支持多个搜索源，自动选择最佳结果&quot;</span><br>        self.search_sources = []<br>        self._setup_search_sources()<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_setup_search_sources</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;设置可用的搜索源&quot;&quot;&quot;</span><br>        <span class="hljs-comment"># 检查Tavily可用性</span><br>        <span class="hljs-keyword">if</span> os.getenv(<span class="hljs-string">&quot;TAVILY_API_KEY&quot;</span>):<br>            <span class="hljs-keyword">try</span>:<br>                <span class="hljs-keyword">from</span> tavily <span class="hljs-keyword">import</span> TavilyClient<br>                self.tavily_client = TavilyClient(api_key=os.getenv(<span class="hljs-string">&quot;TAVILY_API_KEY&quot;</span>))<br>                self.search_sources.append(<span class="hljs-string">&quot;tavily&quot;</span>)<br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;✅ Tavily搜索源已启用&quot;</span>)<br>            <span class="hljs-keyword">except</span> ImportError:<br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;⚠️ Tavily库未安装&quot;</span>)<br><br>        <span class="hljs-comment"># 检查SerpApi可用性</span><br>        <span class="hljs-keyword">if</span> os.getenv(<span class="hljs-string">&quot;SERPAPI_API_KEY&quot;</span>):<br>            <span class="hljs-keyword">try</span>:<br>                <span class="hljs-keyword">import</span> serpapi<br>                self.search_sources.append(<span class="hljs-string">&quot;serpapi&quot;</span>)<br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;✅ SerpApi搜索源已启用&quot;</span>)<br>            <span class="hljs-keyword">except</span> ImportError:<br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;⚠️ SerpApi库未安装&quot;</span>)<br><br>        <span class="hljs-keyword">if</span> self.search_sources:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;🔧 可用搜索源: <span class="hljs-subst">&#123;<span class="hljs-string">&#x27;, &#x27;</span>.join(self.search_sources)&#125;</span>&quot;</span>)<br>        <span class="hljs-keyword">else</span>:<br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;⚠️ 没有可用的搜索源，请配置API密钥&quot;</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">search</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;执行智能搜索&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> query.strip():<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;❌ 错误:搜索查询不能为空&quot;</span><br><br>        <span class="hljs-comment"># 检查是否有可用的搜索源</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.search_sources:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;&quot;&quot;❌ 没有可用的搜索源，请配置以下API密钥之一:</span><br><span class="hljs-string"></span><br><span class="hljs-string">1. Tavily API: 设置环境变量 TAVILY_API_KEY</span><br><span class="hljs-string">   获取地址: https://tavily.com/</span><br><span class="hljs-string"></span><br><span class="hljs-string">2. SerpAPI: 设置环境变量 SERPAPI_API_KEY</span><br><span class="hljs-string">   获取地址: https://serpapi.com/</span><br><span class="hljs-string"></span><br><span class="hljs-string">配置后重新运行程序。&quot;&quot;&quot;</span><br><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;🔍 开始智能搜索: <span class="hljs-subst">&#123;query&#125;</span>&quot;</span>)<br><br>        <span class="hljs-comment"># 尝试多个搜索源，返回最佳结果</span><br>        <span class="hljs-keyword">for</span> source <span class="hljs-keyword">in</span> self.search_sources:<br>            <span class="hljs-keyword">try</span>:<br>                <span class="hljs-keyword">if</span> source == <span class="hljs-string">&quot;tavily&quot;</span>:<br>                    result = self._search_with_tavily(query)<br>                    <span class="hljs-keyword">if</span> result <span class="hljs-keyword">and</span> <span class="hljs-string">&quot;未找到&quot;</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> result:<br>                        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;📊 Tavily AI搜索结果:\n\n<span class="hljs-subst">&#123;result&#125;</span>&quot;</span><br><br>                <span class="hljs-keyword">elif</span> source == <span class="hljs-string">&quot;serpapi&quot;</span>:<br>                    result = self._search_with_serpapi(query)<br>                    <span class="hljs-keyword">if</span> result <span class="hljs-keyword">and</span> <span class="hljs-string">&quot;未找到&quot;</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> result:<br>                        <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;🌐 SerpApi Google搜索结果:\n\n<span class="hljs-subst">&#123;result&#125;</span>&quot;</span><br><br>            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>                <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;⚠️ <span class="hljs-subst">&#123;source&#125;</span> 搜索失败: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>                <span class="hljs-keyword">continue</span><br><br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;❌ 所有搜索源都失败了，请检查网络连接和API密钥配置&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_search_with_tavily</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;使用Tavily搜索&quot;&quot;&quot;</span><br>        response = self.tavily_client.search(query=query, max_results=<span class="hljs-number">3</span>)<br><br>        <span class="hljs-keyword">if</span> response.get(<span class="hljs-string">&#x27;answer&#x27;</span>):<br>            result = <span class="hljs-string">f&quot;💡 AI直接答案:<span class="hljs-subst">&#123;response[<span class="hljs-string">&#x27;answer&#x27;</span>]&#125;</span>\n\n&quot;</span><br>        <span class="hljs-keyword">else</span>:<br>            result = <span class="hljs-string">&quot;&quot;</span><br><br>        result += <span class="hljs-string">&quot;🔗 相关结果:\n&quot;</span><br>        <span class="hljs-keyword">for</span> i, item <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(response.get(<span class="hljs-string">&#x27;results&#x27;</span>, [])[:<span class="hljs-number">3</span>], <span class="hljs-number">1</span>):<br>            result += <span class="hljs-string">f&quot;[<span class="hljs-subst">&#123;i&#125;</span>] <span class="hljs-subst">&#123;item.get(<span class="hljs-string">&#x27;title&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)&#125;</span>\n&quot;</span><br>            result += <span class="hljs-string">f&quot;    <span class="hljs-subst">&#123;item.get(<span class="hljs-string">&#x27;content&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)[:<span class="hljs-number">150</span>]&#125;</span>...\n\n&quot;</span><br><br>        <span class="hljs-keyword">return</span> result<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_search_with_serpapi</span>(<span class="hljs-params">self, query: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;使用SerpApi搜索&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">import</span> serpapi<br><br>        search = serpapi.GoogleSearch(&#123;<br>            <span class="hljs-string">&quot;q&quot;</span>: query,<br>            <span class="hljs-string">&quot;api_key&quot;</span>: os.getenv(<span class="hljs-string">&quot;SERPAPI_API_KEY&quot;</span>),<br>            <span class="hljs-string">&quot;num&quot;</span>: <span class="hljs-number">3</span><br>        &#125;)<br><br>        results = search.get_dict()<br><br>        result = <span class="hljs-string">&quot;🔗 Google搜索结果:\n&quot;</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;organic_results&quot;</span> <span class="hljs-keyword">in</span> results:<br>            <span class="hljs-keyword">for</span> i, res <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(results[<span class="hljs-string">&quot;organic_results&quot;</span>][:<span class="hljs-number">3</span>], <span class="hljs-number">1</span>):<br>                result += <span class="hljs-string">f&quot;[<span class="hljs-subst">&#123;i&#125;</span>] <span class="hljs-subst">&#123;res.get(<span class="hljs-string">&#x27;title&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)&#125;</span>\n&quot;</span><br>                result += <span class="hljs-string">f&quot;    <span class="hljs-subst">&#123;res.get(<span class="hljs-string">&#x27;snippet&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>)&#125;</span>\n\n&quot;</span><br><br>        <span class="hljs-keyword">return</span> result<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_advanced_search_registry</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;创建包含高级搜索工具的注册表&quot;&quot;&quot;</span><br>    registry = ToolRegistry()<br><br>    <span class="hljs-comment"># 创建搜索工具实例</span><br>    search_tool = MyAdvancedSearchTool()<br><br>    <span class="hljs-comment"># 注册搜索工具的方法作为函数</span><br>    registry.register_function(<br>        name=<span class="hljs-string">&quot;advanced_search&quot;</span>,<br>        description=<span class="hljs-string">&quot;高级搜索工具，整合Tavily和SerpAPI多个搜索源，提供更全面的搜索结果&quot;</span>,<br>        func=search_tool.search<br>    )<br><br>    <span class="hljs-keyword">return</span> registry<br></code></pre></td></tr></table></figure><p>接下来可以测试我们自己编写的工具，创建<code>test_advanced_search.py</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><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><code class="hljs python"><span class="hljs-comment"># test_advanced_search.py</span><br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><span class="hljs-keyword">from</span> my_advanced_search <span class="hljs-keyword">import</span> create_advanced_search_registry, MyAdvancedSearchTool<br><br><span class="hljs-comment"># 加载环境变量</span><br>load_dotenv()<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_advanced_search</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;测试高级搜索工具&quot;&quot;&quot;</span><br><br>    <span class="hljs-comment"># 创建包含高级搜索工具的注册表</span><br>    registry = create_advanced_search_registry()<br><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;🔍 测试高级搜索工具\n&quot;</span>)<br><br>    <span class="hljs-comment"># 测试查询</span><br>    test_queries = [<br>        <span class="hljs-string">&quot;Python编程语言的历史&quot;</span>,<br>        <span class="hljs-string">&quot;人工智能的最新发展&quot;</span>,<br>        <span class="hljs-string">&quot;2024年科技趋势&quot;</span><br>    ]<br><br>    <span class="hljs-keyword">for</span> i, query <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(test_queries, <span class="hljs-number">1</span>):<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;测试 <span class="hljs-subst">&#123;i&#125;</span>: <span class="hljs-subst">&#123;query&#125;</span>&quot;</span>)<br>        result = registry.execute_tool(<span class="hljs-string">&quot;advanced_search&quot;</span>, query)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;结果: <span class="hljs-subst">&#123;result&#125;</span>\n&quot;</span>)<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;-&quot;</span> * <span class="hljs-number">60</span> + <span class="hljs-string">&quot;\n&quot;</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_api_configuration</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;测试API配置检查&quot;&quot;&quot;</span><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;🔧 测试API配置检查:&quot;</span>)<br><br>    <span class="hljs-comment"># 直接创建搜索工具实例</span><br>    search_tool = MyAdvancedSearchTool()<br><br>    <span class="hljs-comment"># 如果没有配置API，会显示配置提示</span><br>    result = search_tool.search(<span class="hljs-string">&quot;机器学习算法&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;搜索结果: <span class="hljs-subst">&#123;result&#125;</span>&quot;</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">test_with_agent</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;测试与Agent的集成&quot;&quot;&quot;</span><br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n🤖 与Agent集成测试:&quot;</span>)<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;高级搜索工具已准备就绪，可以与Agent集成使用&quot;</span>)<br><br>    <span class="hljs-comment"># 显示工具描述</span><br>    registry = create_advanced_search_registry()<br>    tools_desc = registry.get_tools_description()<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;工具描述:\n<span class="hljs-subst">&#123;tools_desc&#125;</span>&quot;</span>)<br><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:<br>    test_advanced_search()<br>    test_api_configuration()<br>    test_with_agent()<br></code></pre></td></tr></table></figure><p>通过这个高级搜索工具的设计实践，我们学会了如何使用类的方式来构建复杂的工具系统。相比函数方式，类方式更适合需要维护状态（如API客户端、配置信息）的工具。</p><h3 id="7-5-4-工具系统的高级特性"><a href="#7-5-4-工具系统的高级特性" class="headerlink" title="7.5.4 工具系统的高级特性"></a>7.5.4 工具系统的高级特性</h3><p>在掌握了基础的工具开发和多源整合后，我们来探讨工具系统的高级特性。这些特性能够让工具系统在复杂的生产环境中稳定运行，并为Agent提供更强大的能力。</p><p>（1）工具链式调用机制</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><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># tool_chain_manager.py</span><br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">List</span>, <span class="hljs-type">Dict</span>, <span class="hljs-type">Any</span>, <span class="hljs-type">Optional</span><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ToolRegistry<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ToolChain</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;工具链 - 支持多个工具的顺序执行&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, name: <span class="hljs-built_in">str</span>, description: <span class="hljs-built_in">str</span></span>):<br>        self.name = name<br>        self.description = description<br>        self.steps: <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]] = []<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">add_step</span>(<span class="hljs-params">self, tool_name: <span class="hljs-built_in">str</span>, input_template: <span class="hljs-built_in">str</span>, output_key: <span class="hljs-built_in">str</span> = <span class="hljs-literal">None</span></span>):<br>        <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">        添加工具执行步骤</span><br><span class="hljs-string"></span><br><span class="hljs-string">        Args:</span><br><span class="hljs-string">            tool_name: 工具名称</span><br><span class="hljs-string">            input_template: 输入模板，支持变量替换</span><br><span class="hljs-string">            output_key: 输出结果的键名，用于后续步骤引用</span><br><span class="hljs-string">        &quot;&quot;&quot;</span><br>        self.steps.append(&#123;<br>            <span class="hljs-string">&quot;tool_name&quot;</span>: tool_name,<br>            <span class="hljs-string">&quot;input_template&quot;</span>: input_template,<br>            <span class="hljs-string">&quot;output_key&quot;</span>: output_key <span class="hljs-keyword">or</span> <span class="hljs-string">f&quot;step_<span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(self.steps)&#125;</span>_result&quot;</span><br>        &#125;)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">execute</span>(<span class="hljs-params">self, registry: ToolRegistry, initial_input: <span class="hljs-built_in">str</span>, context: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>] = <span class="hljs-literal">None</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;执行工具链&quot;&quot;&quot;</span><br>        context = context <span class="hljs-keyword">or</span> &#123;&#125;<br>        context[<span class="hljs-string">&quot;input&quot;</span>] = initial_input<br><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;🔗 开始执行工具链: <span class="hljs-subst">&#123;self.name&#125;</span>&quot;</span>)<br><br>        <span class="hljs-keyword">for</span> i, step <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(self.steps, <span class="hljs-number">1</span>):<br>            tool_name = step[<span class="hljs-string">&quot;tool_name&quot;</span>]<br>            input_template = step[<span class="hljs-string">&quot;input_template&quot;</span>]<br>            output_key = step[<span class="hljs-string">&quot;output_key&quot;</span>]<br><br>            <span class="hljs-comment"># 替换模板中的变量</span><br>            <span class="hljs-keyword">try</span>:<br>                tool_input = input_template.<span class="hljs-built_in">format</span>(**context)<br>            <span class="hljs-keyword">except</span> KeyError <span class="hljs-keyword">as</span> e:<br>                <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 工具链执行失败:模板变量 <span class="hljs-subst">&#123;e&#125;</span> 未找到&quot;</span><br><br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  步骤 <span class="hljs-subst">&#123;i&#125;</span>: 使用 <span class="hljs-subst">&#123;tool_name&#125;</span> 处理 &#x27;<span class="hljs-subst">&#123;tool_input[:<span class="hljs-number">50</span>]&#125;</span>...&#x27;&quot;</span>)<br><br>            <span class="hljs-comment"># 执行工具</span><br>            result = registry.execute_tool(tool_name, tool_input)<br>            context[output_key] = result<br><br>            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;  ✅ 步骤 <span class="hljs-subst">&#123;i&#125;</span> 完成，结果长度: <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(result)&#125;</span> 字符&quot;</span>)<br><br>        <span class="hljs-comment"># 返回最后一步的结果</span><br>        final_result = context[self.steps[-<span class="hljs-number">1</span>][<span class="hljs-string">&quot;output_key&quot;</span>]]<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;🎉 工具链 &#x27;<span class="hljs-subst">&#123;self.name&#125;</span>&#x27; 执行完成&quot;</span>)<br>        <span class="hljs-keyword">return</span> final_result<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ToolChainManager</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;工具链管理器&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, registry: ToolRegistry</span>):<br>        self.registry = registry<br>        self.chains: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, ToolChain] = &#123;&#125;<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">register_chain</span>(<span class="hljs-params">self, chain: ToolChain</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;注册工具链&quot;&quot;&quot;</span><br>        self.chains[chain.name] = chain<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 工具链 &#x27;<span class="hljs-subst">&#123;chain.name&#125;</span>&#x27; 已注册&quot;</span>)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">execute_chain</span>(<span class="hljs-params">self, chain_name: <span class="hljs-built_in">str</span>, input_data: <span class="hljs-built_in">str</span>, context: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>] = <span class="hljs-literal">None</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;执行指定的工具链&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> chain_name <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.chains:<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">f&quot;❌ 工具链 &#x27;<span class="hljs-subst">&#123;chain_name&#125;</span>&#x27; 不存在&quot;</span><br><br>        chain = self.chains[chain_name]<br>        <span class="hljs-keyword">return</span> chain.execute(self.registry, input_data, context)<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">list_chains</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;列出所有工具链&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-built_in">list</span>(self.chains.keys())<br><br><span class="hljs-comment"># 使用示例</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_research_chain</span>() -&gt; ToolChain:<br>    <span class="hljs-string">&quot;&quot;&quot;创建一个研究工具链:搜索 -&gt; 计算 -&gt; 总结&quot;&quot;&quot;</span><br>    chain = ToolChain(<br>        name=<span class="hljs-string">&quot;research_and_calculate&quot;</span>,<br>        description=<span class="hljs-string">&quot;搜索信息并进行相关计算&quot;</span><br>    )<br><br>    <span class="hljs-comment"># 步骤1:搜索信息</span><br>    chain.add_step(<br>        tool_name=<span class="hljs-string">&quot;search&quot;</span>,<br>        input_template=<span class="hljs-string">&quot;&#123;input&#125;&quot;</span>,<br>        output_key=<span class="hljs-string">&quot;search_result&quot;</span><br>    )<br><br>    <span class="hljs-comment"># 步骤2:基于搜索结果进行计算（如果需要）</span><br>    chain.add_step(<br>        tool_name=<span class="hljs-string">&quot;my_calculator&quot;</span>,<br>        input_template=<span class="hljs-string">&quot;根据以下信息计算相关数值:&#123;search_result&#125;&quot;</span>,<br>        output_key=<span class="hljs-string">&quot;calculation_result&quot;</span><br>    )<br><br>    <span class="hljs-keyword">return</span> chain<br></code></pre></td></tr></table></figure><p>（2）异步工具执行支持</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># async_tool_executor.py</span><br><span class="hljs-keyword">import</span> asyncio<br><span class="hljs-keyword">import</span> concurrent.futures<br><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-type">Dict</span>, <span class="hljs-type">Any</span>, <span class="hljs-type">List</span>, <span class="hljs-type">Callable</span><br><span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ToolRegistry<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">AsyncToolExecutor</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;异步工具执行器&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, registry: ToolRegistry, max_workers: <span class="hljs-built_in">int</span> = <span class="hljs-number">4</span></span>):<br>        self.registry = registry<br>        self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=max_workers)<br><br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">execute_tool_async</span>(<span class="hljs-params">self, tool_name: <span class="hljs-built_in">str</span>, input_data: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-string">&quot;&quot;&quot;异步执行单个工具&quot;&quot;&quot;</span><br>        loop = asyncio.get_event_loop()<br><br>        <span class="hljs-keyword">def</span> <span class="hljs-title function_">_execute</span>():<br>            <span class="hljs-keyword">return</span> self.registry.execute_tool(tool_name, input_data)<br><br>        result = <span class="hljs-keyword">await</span> loop.run_in_executor(self.executor, _execute)<br>        <span class="hljs-keyword">return</span> result<br><br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">execute_tools_parallel</span>(<span class="hljs-params">self, tasks: <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-built_in">str</span>]]</span>) -&gt; <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>]:<br>        <span class="hljs-string">&quot;&quot;&quot;并行执行多个工具&quot;&quot;&quot;</span><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;🚀 开始并行执行 <span class="hljs-subst">&#123;<span class="hljs-built_in">len</span>(tasks)&#125;</span> 个工具任务&quot;</span>)<br><br>        <span class="hljs-comment"># 创建异步任务</span><br>        async_tasks = []<br>        <span class="hljs-keyword">for</span> task <span class="hljs-keyword">in</span> tasks:<br>            tool_name = task[<span class="hljs-string">&quot;tool_name&quot;</span>]<br>            input_data = task[<span class="hljs-string">&quot;input_data&quot;</span>]<br>            async_task = self.execute_tool_async(tool_name, input_data)<br>            async_tasks.append(async_task)<br><br>        <span class="hljs-comment"># 等待所有任务完成</span><br>        results = <span class="hljs-keyword">await</span> asyncio.gather(*async_tasks)<br><br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;✅ 所有工具任务执行完成&quot;</span>)<br>        <span class="hljs-keyword">return</span> results<br><br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__del__</span>(<span class="hljs-params">self</span>):<br>        <span class="hljs-string">&quot;&quot;&quot;清理资源&quot;&quot;&quot;</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">hasattr</span>(self, <span class="hljs-string">&#x27;executor&#x27;</span>):<br>            self.executor.shutdown(wait=<span class="hljs-literal">True</span>)<br><br><span class="hljs-comment"># 使用示例</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_parallel_execution</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;测试并行工具执行&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">from</span> hello_agents <span class="hljs-keyword">import</span> ToolRegistry<br><br>    registry = ToolRegistry()<br>    <span class="hljs-comment"># 假设已经注册了搜索和计算工具</span><br><br>    executor = AsyncToolExecutor(registry)<br><br>    <span class="hljs-comment"># 定义并行任务</span><br>    tasks = [<br>        &#123;<span class="hljs-string">&quot;tool_name&quot;</span>: <span class="hljs-string">&quot;search&quot;</span>, <span class="hljs-string">&quot;input_data&quot;</span>: <span class="hljs-string">&quot;Python编程&quot;</span>&#125;,<br>        &#123;<span class="hljs-string">&quot;tool_name&quot;</span>: <span class="hljs-string">&quot;search&quot;</span>, <span class="hljs-string">&quot;input_data&quot;</span>: <span class="hljs-string">&quot;机器学习&quot;</span>&#125;,<br>        &#123;<span class="hljs-string">&quot;tool_name&quot;</span>: <span class="hljs-string">&quot;my_calculator&quot;</span>, <span class="hljs-string">&quot;input_data&quot;</span>: <span class="hljs-string">&quot;2 + 2&quot;</span>&#125;,<br>        &#123;<span class="hljs-string">&quot;tool_name&quot;</span>: <span class="hljs-string">&quot;my_calculator&quot;</span>, <span class="hljs-string">&quot;input_data&quot;</span>: <span class="hljs-string">&quot;sqrt(16)&quot;</span>&#125;,<br>    ]<br><br>    <span class="hljs-comment"># 并行执行</span><br>    results = <span class="hljs-keyword">await</span> executor.execute_tools_parallel(tasks)<br><br>    <span class="hljs-keyword">for</span> i, result <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(results):<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;任务 <span class="hljs-subst">&#123;i+<span class="hljs-number">1</span>&#125;</span> 结果: <span class="hljs-subst">&#123;result[:<span class="hljs-number">100</span>]&#125;</span>...&quot;</span>)<br></code></pre></td></tr></table></figure><p>基于以上的设计和实现经验，我们可以总结出工具系统开发的核心理念：在设计层面，每个工具都应该遵循单一职责原则，专注于特定功能的同时保持接口的统一性，并将完善的异常处理和安全优先的输入验证作为基本要求。在性能优化方面，利用异步执行提高并发处理能力，同时合理管理外部连接和系统资源。</p><h2 id="7-6-本章小结"><a href="#7-6-本章小结" class="headerlink" title="7.6 本章小结"></a>7.6 本章小结</h2><p>在正式总结之前，我们想告诉大家一个好消息：对于本章实现的所有方法和功能，都在GitHub仓库中提供了完整的测试案例。你可以访问<a href="https://github.com/jjyaoao/HelloAgents/blob/main/examples/chapter07_basic_setup.py">这个链接</a>查看和运行这些测试代码。这个文件包含了四种Agent范式的演示、工具系统的集成测试、高级功能的使用示例，以及交互式的Agent体验。如果你想验证自己的实现是否正确，或者想深入了解框架的实际使用方式，这些测试案例将是有价值的参考。</p><p>回顾本章，我们完成了一项富有挑战的任务：一步步构建了一个基础的智能体框架——HelloAgents。这个过程始终遵循着“分层解耦、职责单一、接口统一”的核心原则。</p><p>在框架的具体实现中，我们再次实现了四种经典的Agent范式。从SimpleAgent的基础对话模式，到ReActAgent的推理与行动结合；从ReflectionAgent的自我反思与迭代优化，到PlanAndSolveAgent的分解规划与逐步执行。而工具系统作为Agent能力延伸的核心，其构建过程则是一次完整的工程实践。</p><p>更重要的是，第七章的构建并非终点，而是为后续更深入学习提供了必要的技术基础。我们在设计之初便充分考虑了后续内容的延展性，为高级功能的实现预留了必要的接口和扩展点。我们所建立的统一LLM接口、标准化消息系统、工具注册机制，共同构成了一个完备的技术底座。这使得我们在后续章节中，可以更加从容地去学习更高级的主题：第八章的记忆与RAG系统将基于此扩展Agent的能力边界；第九章的上下文工程将深入我们已经建立的消息处理机制；第十章的智能体协议则需要扩展新的工具。</p><p>接下来，我们将一起探索如何往框架中加入RAG系统与Memory机制，敬请期待第八章！</p><h2 id="习题"><a href="#习题" class="headerlink" title="习题"></a>习题</h2><ol><li><p>本章构建了 <code>HelloAgents</code> 框架，并阐述了”为何需要自建Agent框架”。请分析：</p><ul><li>在7.1.1节中提到了当前主流框架的四个主要局限性。结合你在<a href="../chapter6/%E7%AC%AC%E5%85%AD%E7%AB%A0%20%E6%A1%86%E6%9E%B6%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5.md#%E4%B9%A0%E9%A2%98">第六章习题</a>或实际项目中使用过的某个框架的实际经验，说明这些问题是如何影响开发效率的。</li><li><code>HelloAgents</code> 提出了”万物皆为工具”的设计理念，将 <code>Memory</code>、<code>RAG</code>、<code>MCP</code> 等模块都抽象为工具。这种设计有什么优势？是否存在局限性？请举例说明。</li><li>对比第四章从零实现的智能体代码和本章的框架化实现，框架化带来了哪些具体的改进？如果让你设计一个框架，你会优先考虑哪些设计原则？</li></ul></li><li><p>在7.2节中，我们扩展了 <code>HelloAgentsLLM</code> 以支持多模型供应商和本地模型调用。</p><blockquote><p><strong>提示</strong>：这是一道实践题，建议实际操作</p></blockquote><ul><li>参考7.2.1节的示例，尝试为 <code>HelloAgentsLLM</code> 添加一个新模型供应商的支持（如<code>Gemini</code>、<code>Anthropic</code>、<code>Kim</code>）。要求通过继承方式实现，并能够自动检测该提供商的环境变量。</li><li>在7.2.3节中介绍了自动检测机制的三个优先级。请分析：如果同时设置了 <code>OPENAI_API_KEY</code> 和 <code>LLM_BASE_URL=&quot;http://localhost:11434/v1&quot;</code>，框架最后会选择哪个提供商？这种优先级设计是否合理？</li><li>除了本章介绍的 <code>VLLM</code> 和 <code>Ollama</code>，还有 <code>SGLang</code> 等其他本地模型部署方案。请先搜索并了解 <code>SGLang</code> 的基本信息和特点，然后对比 <code>VLLM</code>、<code>SGLang</code> 和 <code>Ollama</code> 这三者在易用性、资源占用、推理速度、推理精度等方面的优劣。</li></ul></li><li><p>在7.3节中，我们实现了 <code>Message</code> 类、<code>Config</code> 类和 <code>Agent</code> 基类。请分析：</p><ul><li><code>Message</code> 类使用了 <code>Pydantic</code> 的 <code>BaseModel</code> 进行数据验证。这种设计在实际应用中有哪些优势？</li><li><code>Agent</code> 基类定义了 <code>run</code> 和 <code>_execute</code> 两个方法，其中 <code>run</code> 是公开接口，<code>_execute</code> 是抽象方法。这种设计模式叫什么？有什么好处？</li><li>在 <code>Config</code> 类中，我们使用了单例模式。请解释什么是单例模式，为什么配置管理需要使用单例模式？如果不使用单例会导致什么问题？</li></ul></li><li><p>在7.4节中，我们动手进行了四种 <code>Agent</code> 范式的框架化实现。</p><blockquote><p><strong>提示</strong>：这是一道实践题，建议实际操作</p></blockquote><ul><li>对比第四章从零实现的 <code>ReActAgent</code> 和本章框架化的 <code>ReActAgent</code>，列举3个具体的改进点，并说明这些改进如何提升了代码的可维护性和可扩展性。</li><li><code>ReflectionAgent</code> 实现了”执行-反思-优化”循环。请扩展这个实现，添加一个”质量评分”机制：在每次反思后，让 <code>LLM</code> 对当前版本的输出打分，只有分数低于阈值时才继续优化，否则提前终止。</li><li>请设计并实现一个新的 <code>Agent</code> 范式 <code>Tree-of-Thought Agent</code>，要求继承 <code>Agent</code> 基类，它能够在每一步生成多个可能的思考路径，然后选择最优路径继续。</li></ul></li><li><p>在7.5节中，我们构建了工具系统。请思考以下问题：</p><ul><li><code>BaseTool</code> 类定义了 <code>execute</code> 抽象方法，所有工具都必须实现这个方法。请解释为什么要强制所有工具实现统一的接口？如果某个工具需要返回多个值（如搜索工具返回标题、摘要、链接），应该如何设计？</li><li>在7.5.3节中实现了工具链（<code>ToolChain</code>）。请设计一个实际的应用场景，需要串联至少3个工具，并画出工具链的执行流程图。</li><li>异步工具执行器（<code>AsyncToolExecutor</code>）使用了线程池来并行执行工具。请分析：在什么情况下并行执行工具能带来性能提升？</li></ul></li><li><p>框架的可扩展性是设计的重要考量因素之一。你现在要扩展 <code>HelloAgents</code> 框架，为其实现一些有趣的新功能和特性。</p><ul><li>首先为 <code>HelloAgents</code> 添加一个”流式输出”功能，使得 <code>Agent</code> 在生成响应时能够实时返回中间结果（类似 <code>ChatGPT</code> 用户界面的打字效果）。请设计这个功能的实现方案，说明需要修改哪些类和方法。</li><li>然后为框架添加”多轮对话管理”功能，能够自动管理对话历史、支持对话分支和回溯，你会如何设计？需要新增哪些类？如何与现有的 <code>Message</code> 系统集成？</li><li>最后请为 <code>HelloAgents</code> 设计一个”插件系统”，允许第三方开发者通过插件的方式扩展框架功能（如添加新的 <code>Agent</code> 类型、新的工具类型等），而无需修改框架核心代码。要求画出插件系统的架构图并说明关键接口。</li></ul></li></ol>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC7%E7%AB%A0-%E6%9E%84%E5%BB%BA%E4%BD%A0%E7%9A%84Agent%E6%A1%86%E6%9E%B6/</id>
    <link href="http://jasondong97.github.io/2026/03/02/ai-agent-learning/%E7%AC%AC7%E7%AB%A0-%E6%9E%84%E5%BB%BA%E4%BD%A0%E7%9A%84Agent%E6%A1%86%E6%9E%B6/"/>
    <published>2026-03-01T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="第七章-构建你的智能体框架"><a href="#第七章-构建你的智能体框架" class="headerlink" title="第七章 构建你的智能体框架"></a>第七章 构建你的智能体框架</h1><p>在前面的章节中，我们讲解了智能体的基础知识，并体验了]]>
    </summary>
    <title>第七章 构建你的智能体框架</title>
    <updated>2026-03-08T09:24:16.326Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jason Dong</name>
    </author>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/categories/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <category term="Hello Agents 学习" scheme="http://jasondong97.github.io/tags/Hello-Agents-%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="第六章-框架开发实践"><a href="#第六章-框架开发实践" class="headerlink" title="第六章 框架开发实践"></a>第六章 框架开发实践</h1><p>在第四章中，我们通过编写原生代码，实现了 ReAct、Plan-and-Solve 和 Reflection 这几种智能体的核心工作流。这个过程让我们对智能体的内在执行逻辑有了理解。随后，在第五章，我们切换到“使用者”的视角，体验了低代码平台带来的便捷与高效。</p><p>本章的目标，就是探讨如何利用业界主流的一些<strong>智能体框架</strong>，来高效、规范地构建可靠的智能体应用。我们将首先概览当前市面上主流的智能体框架，然后并对几个具有代表性的框架，通过一个完整的实战案例，来体验框架驱动的开发模式。</p><h2 id="6-1-从手动实现到框架开发"><a href="#6-1-从手动实现到框架开发" class="headerlink" title="6.1 从手动实现到框架开发"></a>6.1 从手动实现到框架开发</h2><p>从编写一次性的脚本到使用一个成熟的框架，是软件工程领域一次重要的思维跃迁。我们在第四章中编写的代码，其主要目的是为了教学和理解。它们能很好地完成特定任务，但如果要用它们来构建多个、不同类型且逻辑复杂的智能体应用，很快就会遇到瓶颈。</p><p>一个框架的本质，是提供一套经过验证的“规范”。它将所有智能体共有的、重复性的工作（如主循环、状态管理、工具调用、日志记录等）进行抽象和封装，让我们在构建新的智能体时，能够专注于其独特的业务逻辑，而非通用的底层实现。</p><h3 id="6-1-1-为何需要智能体框架"><a href="#6-1-1-为何需要智能体框架" class="headerlink" title="6.1.1 为何需要智能体框架"></a>6.1.1 为何需要智能体框架</h3><p>在我们开始实战之前，首先需要明确为什么要使用框架。相比于直接编写独立的智能体脚本，使用框架的价值主要体现在以下几个方面：</p><ol><li><strong>提升代码复用与开发效率</strong>：这是最直接的价值。一个好的框架会提供一个通用的 <code>Agent</code> 基类或执行器，它封装了智能体运行的核心循环（Agent Loop）。无论是 ReAct 还是 Plan-and-Solve，都可以基于框架提供的标准组件快速搭建，从而避免重复劳动。</li><li><strong>实现核心组件的解耦与可扩展性</strong>：一个健壮的智能体系统应该由多个松散耦合的模块组成。框架的设计会强制我们分离不同的关注点：<ul><li><strong>模型层 (Model Layer)</strong>：负责与大语言模型交互，可以轻松替换不同的模型（OpenAI, Anthropic, 本地模型）。</li><li><strong>工具层 (Tool Layer)</strong>：提供标准化的工具定义、注册和执行接口，添加新工具不会影响其他代码。</li><li><strong>记忆层 (Memory Layer)</strong>：处理短期和长期记忆，可以根据需求切换不同的记忆策略（如滑动窗口、摘要记忆）。 这种模块化的设计使得整个系统极具可扩展性，更换或升级任何一个组件都变得简单。</li></ul></li><li><strong>标准化复杂的状态管理</strong>：我们在 <code>ReflectionAgent</code> 中实现的 <code>Memory</code> 类只是一个简单的开始。在真实的、长时运行的智能体应用中，状态管理是一个巨大的挑战，它需要处理上下文窗口限制、历史信息持久化、多轮对话状态跟踪等问题。一个框架可以提供一套强大而通用的状态管理机制，开发者无需每次都重新处理这些复杂问题。</li><li><strong>简化可观测性与调试过程</strong>：当智能体的行为变得复杂时，理解其决策过程变得至关重要。一个精心设计的框架可以内置强大的可观测性能力。例如，通过引入事件回调机制（Callbacks），我们可以在智能体生命周期的关键节点（如 <code>on_llm_start</code>, <code>on_tool_end</code>, <code>on_agent_finish</code>）自动触发日志记录或数据上报，从而轻松地追踪和调试智能体的完整运行轨迹。这远比在代码中手动添加 <code>print</code> 语句要高效和系统化。</li></ol><p>因此，从手动实现走向框架开发，不仅是代码组织方式的改变，更是构建复杂、可靠、可维护的智能体应用的必由之路。</p><h3 id="6-1-2-主流框架的选型与对比"><a href="#6-1-2-主流框架的选型与对比" class="headerlink" title="6.1.2 主流框架的选型与对比"></a>6.1.2 主流框架的选型与对比</h3><p>智能体框架的生态正在以前所未有的速度发展。如果说 LangChain 和 LlamaIndex 定义了第一代通用 LLM 应用框架的范式，那么新一代的框架则更加专注于解决特定领域的深层挑战，尤其是<strong>多智能体协作 (Multi-Agent Collaboration)</strong> 和 <strong>复杂工作流控制 (Complex Workflow Control)</strong>。</p><p>在本章的后续实战中，我们将聚焦于四个在这些前沿领域极具代表性的框架：AutoGen、AgentScope、CAMEL 和 LangGraph。它们的设计理念各不相同，分别代表了实现复杂智能体系统的不同技术路径，如表6.1所示。</p><div align="center">  <p>表 6.1 四种智能体框架对比</p>  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/6-figures/01.png" alt="" width="90%"/></div><ul><li><strong>AutoGen</strong>：AutoGen 的核心思想是通过对话实现协作<sup>[1]</sup>。它将多智能体系统抽象为一个由多个“可对话”智能体组成的群聊。开发者可以定义不同角色（如 <code>Coder</code>, <code>ProductManager</code>, <code>Tester</code>），并设定它们之间的交互规则（例如，<code>Coder</code> 写完代码后由 <code>Tester</code> 自动接管）。任务的解决过程，就是这些智能体在群聊中通过自动化消息传递，不断对话、协作、迭代直至最终目标达成的过程。</li><li><strong>AgentScope</strong>：AgentScope 是一个专为多智能体应用设计的、功能全面的开发平台<sup>[2]</sup>。它的核心特点是<strong>易用性</strong>和<strong>工程化</strong>。它提供了一套非常友好的编程接口，让开发者可以轻松定义智能体、构建通信网络，并管理整个应用的生命周期。其内置的<strong>消息传递机制</strong>和对分布式部署的支持，使其非常适合构建和运维复杂、大规模的多智能体系统。</li><li><strong>CAMEL</strong>：CAMEL 提供了一种新颖的、名为<strong>角色扮演 (Role-Playing)</strong> 的协作方法<sup>[3]</sup>。其核心理念是，我们只需要为两个智能体（例如，<code>AI研究员</code> 和 <code>Python程序员</code>）设定好各自的角色和共同的任务目标，它们就能在“<strong>初始提示 (Inception Prompting)</strong>”的引导下，自主地进行多轮对话，相互启发、相互配合，共同完成任务。它极大地降低了设计多智能体对话流程的复杂度。</li><li><strong>LangGraph</strong>：作为 LangChain 生态的扩展，LangGraph 另辟蹊径，将智能体的执行流程建模为<strong>图 (Graph)</strong><sup>[4]</sup>。在传统的链式结构中，信息只能单向流动。而 LangGraph 将每一步操作（如调用LLM、执行工具）定义为图中的一个<strong>节点 (Node)</strong>，并用<strong>边 (Edge)</strong> 来定义节点之间的跳转逻辑。这种设计天然支持<strong>循环 (Cycles)</strong>，使得实现如 Reflection 这样的迭代、修正、自我反思的复杂工作流变得异常简单和直观。</li></ul><p>在接下来的小节中，我们将对这四个框架，分别通过一个完整的实战案例，来深入体验框架驱动的开发模式。<strong>请注意</strong>，所有演示的项目源文件会放在<code>code</code>文件夹下，正文内只讲解原理部分。</p><h2 id="6-2-框架一：AutoGen"><a href="#6-2-框架一：AutoGen" class="headerlink" title="6.2 框架一：AutoGen"></a>6.2 框架一：AutoGen</h2><p>正如前文所述，AutoGen 的设计哲学根植于”以对话驱动协作”。它巧妙地将复杂的任务解决流程，映射为不同角色的智能体之间的一系列自动化对话。基于这一核心理念，AutoGen 框架持续演进。我们将以 <code>0.7.4</code> 版本为例，因为它是截止目前为止最新版本，代表了一次重要的架构重构，从类继承设计转向了更灵活的组合式架构。为了深入理解并应用这一框架，我们首先需要讲解其最核心的构成要素与底层的对话交互机制。</p><h3 id="6-2-1-AutoGen-的核心机制"><a href="#6-2-1-AutoGen-的核心机制" class="headerlink" title="6.2.1 AutoGen 的核心机制"></a>6.2.1 AutoGen 的核心机制</h3><p><code>0.7.4</code> 版本的发布是 AutoGen 发展的一个重要节点，它标志着框架在底层设计上的一次根本性革新。这次更新并非简单的功能叠加，而是对整体架构的重新思考，旨在提升框架的模块化、并发性能和开发者体验。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/6-figures/02.png" alt="" width="90%"/>  <p>图 6.1 AutoGen架构图</p></div><p>（1）框架结构的演进</p><p>如图6.1所示，新架构最显著的变化是引入了清晰的分层和异步优先的设计理念。</p><ul><li><strong>分层设计：</strong> 框架被拆分为两个核心模块：<ul><li><code>autogen-core</code>：作为框架的底层基础，封装了与语言模型交互、消息传递等核心功能。它的存在保证了框架的稳定性和未来扩展性。</li><li><code>autogen-agentchat</code>：构建于 <code>core</code> 之上，提供了用于开发对话式智能体应用的高级接口，简化了多智能体应用的开发流程。 这种分层策略使得各组件职责明确，降低了系统的耦合度。</li></ul></li><li><strong>异步优先：</strong> 新架构全面转向异步编程 (<code>async/await</code>)。在多智能体协作场景中，网络请求（如调用 LLM API）是主要耗时操作。异步模式允许系统在等待一个智能体响应时处理其他任务，从而避免了线程阻塞，显著提升了并发处理能力和系统资源的利用效率。</li></ul><p>（2）核心智能体组件</p><p>智能体是执行任务的基本单元。在 <code>0.7.4</code> 版本中，智能体的设计更加专注和模块化。</p><ul><li><strong>AssistantAgent (助理智能体)：</strong> 这是任务的主要解决者，其核心是封装了一个大型语言模型（LLM）。它的职责是根据对话历史生成富有逻辑和知识的回复，例如提出计划、撰写文章或编写代码。通过不同的系统消息（System Message），我们可以为其赋予不同的“专家”角色。</li><li><strong>UserProxyAgent (用户代理智能体)：</strong> 这是 AutoGen 中功能独特的组件。它扮演着双重角色：既是人类用户的“代言人”，负责发起任务和传达意图；又是一个可靠的“执行器”，可以配置为执行代码或调用工具，并将结果反馈给其他智能体。这种设计清晰地区分了“思考”（由 <code>AssistantAgent</code> 完成）与“行动”。</li></ul><p>（3）从 GroupChatManager 到 Team</p><p>当任务需要多个智能体协作时，就需要一个机制来协调对话流程。在早期版本中，<code>GroupChatManager</code> 承担了这一职责。而在新架构中，引入了更灵活的 <code>Team</code> 或群聊概念，例如 <code>RoundRobinGroupChat</code>。</p><ul><li><strong>轮询群聊 (RoundRobinGroupChat)：</strong> 这是一种明确的、顺序化的对话协调机制。它会让参与的智能体按照预定义的顺序依次发言。这种模式非常适用于流程固定的任务，例如一个典型的软件开发流程：产品经理先提出需求，然后工程师编写代码，最后由代码审查员进行检查。</li><li><strong>工作流：</strong><ol><li>首先，创建一个 <code>RoundRobinGroupChat</code> 实例，并将所有参与协作的智能体（如产品经理、工程师等）加入其中。</li><li>当一个任务开始时，群聊会按照预设的顺序，依次激活相应的智能体。</li><li>被选中的智能体根据当前的对话上下文进行响应。</li><li>群聊将新的回复加入对话历史，并激活下一个智能体。</li><li>这个过程会持续进行，直到达到最大对话轮次或满足预设的终止条件。</li></ol></li></ul><p>通过这种方式，AutoGen 将复杂的协作关系，简化为一个流程清晰、易于管理的自动化“圆桌会议”。开发者只需定义好每个团队成员的角色和发言顺序，剩下的协作流程便可由群聊机制自主驱动。</p><p>在下一节中，我们将通过构建一个模拟软件开发团队的实例，来亲身体验如何在新架构下定义不同角色的智能体，并将它们组织在一个由 <code>RoundRobinGroupChat</code> 协调的群聊中，以协作完成一个真实的编程任务。</p><h3 id="6-2-2-软件开发团队"><a href="#6-2-2-软件开发团队" class="headerlink" title="6.2.2 软件开发团队"></a>6.2.2 软件开发团队</h3><p>在理解了 AutoGen 的核心组件与对话机制后，本节将通过一个完整的实战案例来具体展示如何应用这些新特性。我们将构建一个模拟的软件开发团队，该团队由多个具有不同专业技能的智能体组成，它们将协作完成一个真实的软件开发任务。</p><p>（1）业务目标</p><p>我们的目标是开发一个功能明确的 Web 应用：<strong>实时显示比特币当前价格</strong>。这个任务虽小，却完整地覆盖了软件开发的典型环节：从需求分析、技术选型、编码实现到代码审查和最终测试。这使其成为检验 AutoGen 自动化协作流程的理想场景。</p><p>（2）智能体团队角色</p><p>为了模拟真实的软件开发流程，我们设计了四个职责分明的智能体角色：</p><ul><li><strong>ProductManager (产品经理):</strong> 负责将用户的模糊需求转化为清晰、可执行的开发计划。</li><li><strong>Engineer (工程师):</strong> 依据开发计划，负责编写具体的应用程序代码。</li><li><strong>CodeReviewer (代码审查员):</strong> 负责审查工程师提交的代码，确保其质量、可读性和健壮性。</li><li><strong>UserProxy (用户代理):</strong> 代表最终用户，发起初始任务，并负责执行和验证最终交付的代码。</li></ul><p>这种角色划分是多智能体系统设计中的关键一步，它将一个复杂任务分解为多个由领域“专家”处理的子任务。</p><h3 id="6-2-3-核心代码实现"><a href="#6-2-3-核心代码实现" class="headerlink" title="6.2.3 核心代码实现"></a>6.2.3 核心代码实现</h3><p>下面，我们将分步解析这个自动化团队的核心代码。</p><p>（1）模型客户端配置</p><p>所有基于 LLM 的智能体都需要一个模型客户端来与语言模型进行交互。AutoGen <code>0.7.4</code> 提供了标准化的 <code>OpenAIChatCompletionClient</code>，它可以方便地与任何兼容 OpenAI API 规范的模型服务（包括 OpenAI 官方服务、Azure OpenAI 以及本地模型服务如 Ollama等）进行对接。</p><p>我们通过一个独立的函数来创建和配置模型客户端，并通过环境变量管理 API Key 和服务地址，这是一种良好的工程实践，增强了代码的灵活性和安全性。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> autogen_ext.models.openai <span class="hljs-keyword">import</span> OpenAIChatCompletionClient<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_openai_model_client</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;创建并配置 OpenAI 模型客户端&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">return</span> OpenAIChatCompletionClient(<br>        model=os.getenv(<span class="hljs-string">&quot;LLM_MODEL_ID&quot;</span>, <span class="hljs-string">&quot;gpt-4o&quot;</span>),<br>        api_key=os.getenv(<span class="hljs-string">&quot;LLM_API_KEY&quot;</span>),<br>        base_url=os.getenv(<span class="hljs-string">&quot;LLM_BASE_URL&quot;</span>, <span class="hljs-string">&quot;https://api.openai.com/v1&quot;</span>)<br>    )<br></code></pre></td></tr></table></figure><p>（2）智能体角色的定义</p><p>定义智能体的核心在于编写高质量的系统消息 (System Message)。系统消息就像是给智能体设定的“行为准则”和“专业知识库”，它精确地规定了智能体的角色、职责、工作流程，甚至是与其他智能体交互的方式。一个精心设计的系统消息是确保多智能体系统能够高效、准确协作的关键。在我们的软件开发团队中，我们为每一个角色都创建了一个独立的函数来封装其定义。</p><p><strong>产品经理 (ProductManager)</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_product_manager</span>(<span class="hljs-params">model_client</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;创建产品经理智能体&quot;&quot;&quot;</span><br>    system_message = <span class="hljs-string">&quot;&quot;&quot;你是一位经验丰富的产品经理，专门负责软件产品的需求分析和项目规划。</span><br><span class="hljs-string"></span><br><span class="hljs-string">你的核心职责包括：</span><br><span class="hljs-string">1. **需求分析**：深入理解用户需求，识别核心功能和边界条件</span><br><span class="hljs-string">2. **技术规划**：基于需求制定清晰的技术实现路径</span><br><span class="hljs-string">3. **风险评估**：识别潜在的技术风险和用户体验问题</span><br><span class="hljs-string">4. **协调沟通**：与工程师和其他团队成员进行有效沟通</span><br><span class="hljs-string"></span><br><span class="hljs-string">当接到开发任务时，请按以下结构进行分析：</span><br><span class="hljs-string">1. 需求理解与分析</span><br><span class="hljs-string">2. 功能模块划分</span><br><span class="hljs-string">3. 技术选型建议</span><br><span class="hljs-string">4. 实现优先级排序</span><br><span class="hljs-string">5. 验收标准定义</span><br><span class="hljs-string"></span><br><span class="hljs-string">请简洁明了地回应，并在分析完成后说&quot;请工程师开始实现&quot;。&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">return</span> AssistantAgent(<br>        name=<span class="hljs-string">&quot;ProductManager&quot;</span>,<br>        model_client=model_client,<br>        system_message=system_message,<br>    )<br></code></pre></td></tr></table></figure><p><strong>工程师 (Engineer)</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_engineer</span>(<span class="hljs-params">model_client</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;创建软件工程师智能体&quot;&quot;&quot;</span><br>    system_message = <span class="hljs-string">&quot;&quot;&quot;你是一位资深的软件工程师，擅长 Python 开发和 Web 应用构建。</span><br><span class="hljs-string"></span><br><span class="hljs-string">你的技术专长包括：</span><br><span class="hljs-string">1. **Python 编程**：熟练掌握 Python 语法和最佳实践</span><br><span class="hljs-string">2. **Web 开发**：精通 Streamlit、Flask、Django 等框架</span><br><span class="hljs-string">3. **API 集成**：有丰富的第三方 API 集成经验</span><br><span class="hljs-string">4. **错误处理**：注重代码的健壮性和异常处理</span><br><span class="hljs-string"></span><br><span class="hljs-string">当收到开发任务时，请：</span><br><span class="hljs-string">1. 仔细分析技术需求</span><br><span class="hljs-string">2. 选择合适的技术方案</span><br><span class="hljs-string">3. 编写完整的代码实现</span><br><span class="hljs-string">4. 添加必要的注释和说明</span><br><span class="hljs-string">5. 考虑边界情况和异常处理</span><br><span class="hljs-string"></span><br><span class="hljs-string">请提供完整的可运行代码，并在完成后说&quot;请代码审查员检查&quot;。&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">return</span> AssistantAgent(<br>        name=<span class="hljs-string">&quot;Engineer&quot;</span>,<br>        model_client=model_client,<br>        system_message=system_message,<br>    )<br></code></pre></td></tr></table></figure><p><strong>代码审查员 (CodeReviewer)</strong></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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_code_reviewer</span>(<span class="hljs-params">model_client</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;创建代码审查员智能体&quot;&quot;&quot;</span><br>    system_message = <span class="hljs-string">&quot;&quot;&quot;你是一位经验丰富的代码审查专家，专注于代码质量和最佳实践。</span><br><span class="hljs-string"></span><br><span class="hljs-string">你的审查重点包括：</span><br><span class="hljs-string">1. **代码质量**：检查代码的可读性、可维护性和性能</span><br><span class="hljs-string">2. **安全性**：识别潜在的安全漏洞和风险点</span><br><span class="hljs-string">3. **最佳实践**：确保代码遵循行业标准和最佳实践</span><br><span class="hljs-string">4. **错误处理**：验证异常处理的完整性和合理性</span><br><span class="hljs-string"></span><br><span class="hljs-string">审查流程：</span><br><span class="hljs-string">1. 仔细阅读和理解代码逻辑</span><br><span class="hljs-string">2. 检查代码规范和最佳实践</span><br><span class="hljs-string">3. 识别潜在问题和改进点</span><br><span class="hljs-string">4. 提供具体的修改建议</span><br><span class="hljs-string">5. 评估代码的整体质量</span><br><span class="hljs-string"></span><br><span class="hljs-string">请提供具体的审查意见，完成后说&quot;代码审查完成，请用户代理测试&quot;。&quot;&quot;&quot;</span><br><br>    <span class="hljs-keyword">return</span> AssistantAgent(<br>        name=<span class="hljs-string">&quot;CodeReviewer&quot;</span>,<br>        model_client=model_client,<br>        system_message=system_message,<br>    )<br></code></pre></td></tr></table></figure><p><strong>用户代理 (UserProxy)</strong></p><p><code>UserProxyAgent</code> 是一个特殊的智能体，它不依赖 LLM 进行回复，而是作为用户在系统中的代理。它的 <code>description</code> 字段清晰地描述了其职责，尤其重要的是，它负责在任务最终完成后发出 <code>TERMINATE</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><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><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_user_proxy</span>():<br>    <span class="hljs-string">&quot;&quot;&quot;创建用户代理智能体&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">return</span> UserProxyAgent(<br>        name=<span class="hljs-string">&quot;UserProxy&quot;</span>,<br>        description=<span class="hljs-string">&quot;&quot;&quot;用户代理，负责以下职责：</span><br><span class="hljs-string">1. 代表用户提出开发需求</span><br><span class="hljs-string">2. 执行最终的代码实现</span><br><span class="hljs-string">3. 验证功能是否符合预期</span><br><span class="hljs-string">4. 提供用户反馈和建议</span><br><span class="hljs-string"></span><br><span class="hljs-string">完成测试后请回复 TERMINATE。&quot;&quot;&quot;</span>,<br>    )<br></code></pre></td></tr></table></figure><p>通过这四个独立的定义函数，我们不仅构建了一支功能完备的“虚拟团队”，也展示了通过系统消息进行“提示工程” ，是设计高效多智能体应用的核心环节。</p><p>（3）定义团队协作流程</p><p>在本案例中，软件开发的流程是相对固定的（需求-&gt;编码-&gt;审查-&gt;测试），因此 <code>RoundRobinGroupChat</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><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><code class="hljs python"><span class="hljs-keyword">from</span> autogen_agentchat.teams <span class="hljs-keyword">import</span> RoundRobinGroupChat<br><span class="hljs-keyword">from</span> autogen_agentchat.conditions <span class="hljs-keyword">import</span> TextMentionTermination<br><br><span class="hljs-comment"># 定义团队聊天和协作规则</span><br>team_chat = RoundRobinGroupChat(<br>    participants=[<br>        product_manager,<br>        engineer,<br>        code_reviewer,<br>        user_proxy<br>    ],<br>    termination_condition=TextMentionTermination(<span class="hljs-string">&quot;TERMINATE&quot;</span>),<br>    max_turns=<span class="hljs-number">20</span>,<br>)<br></code></pre></td></tr></table></figure><ul><li><strong>参与者顺序:</strong> <code>participants</code> 列表的顺序决定了智能体发言的先后次序。</li><li><strong>终止条件:</strong> <code>termination_condition</code> 是控制协作流程何时结束的关键。这里我们设定，当任何消息中包含关键词 “TERMINATE” 时，对话便结束。在我们的设计中，这个指令由 <code>UserProxy</code> 在完成最终测试后发出。</li><li><strong>最大轮次:</strong> <code>max_turns</code> 是一个安全阀，用于防止对话陷入无限循环，避免不必要的资源消耗。</li></ul><p>（4）启动与运行</p><p>由于 AutoGen <code>0.7.4</code> 采用异步架构，整个协作流程的启动和运行都在一个异步函数中完成，并最终通过 <code>asyncio.run()</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><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><code class="hljs python"><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">run_software_development_team</span>():<br>    <span class="hljs-comment"># ... 初始化客户端和智能体 ...</span><br>    <br>    <span class="hljs-comment"># 定义任务描述</span><br>    task = <span class="hljs-string">&quot;&quot;&quot;我们需要开发一个比特币价格显示应用，具体要求如下：</span><br><span class="hljs-string">            核心功能：</span><br><span class="hljs-string">            - 实时显示比特币当前价格（USD）</span><br><span class="hljs-string">            - 显示24小时价格变化趋势（涨跌幅和涨跌额）</span><br><span class="hljs-string">            - 提供价格刷新功能</span><br><span class="hljs-string"></span><br><span class="hljs-string">            技术要求：</span><br><span class="hljs-string">            - 使用 Streamlit 框架创建 Web 应用</span><br><span class="hljs-string">            - 界面简洁美观，用户友好</span><br><span class="hljs-string">            - 添加适当的错误处理和加载状态</span><br><span class="hljs-string"></span><br><span class="hljs-string">            请团队协作完成这个任务，从需求分析到最终实现。&quot;&quot;&quot;</span><br>    <br>    <span class="hljs-comment"># 异步执行团队协作，并流式输出对话过程</span><br>    result = <span class="hljs-keyword">await</span> Console(team_chat.run_stream(task=task))<br>    <span class="hljs-keyword">return</span> result<br><br><span class="hljs-comment"># 主程序入口</span><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&quot;__main__&quot;</span>:<br>    result = asyncio.run(run_software_development_team())<br></code></pre></td></tr></table></figure><p>当程序运行时，<code>task</code> 作为初始消息被传入 <code>team_chat</code>，产品经理作为第一个参与者接收到该消息，随后整个自动化协作流程便开始了。</p><p>（5）预期协作效果</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><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><code class="hljs bash">🔧 正在初始化模型客户端...<br>👥 正在创建智能体团队...<br>🚀 启动 AutoGen 软件开发团队协作...<br>============================================================<br>---------- TextMessage (user) ----------<br>我们需要开发一个比特币价格显示应用，具体要求如下：<br>...<br>请团队协作完成这个任务，从需求分析到最终实现。<br>---------- TextMessage (ProductManager) ----------<br><span class="hljs-comment">### 1. 需求理解与分析</span><br>...<br>请工程师开始实现。<br>---------- TextMessage (Engineer) ----------<br><span class="hljs-comment">### 技术方案实施</span><br>...<br>请代码审查员检查。<br>---------- TextMessage (CodeReviewer) ----------<br><span class="hljs-comment">### 代码审查</span><br>...<br>代码审查完成，请用户代理测试。    <br>---------- TextMessage (UserProxy) ----------<br>已经完成需求<br>---------- TextMessage (ProductManager) ----------<br>太好了，感谢您的反馈！如果在使用过程中有任何问题，或者有其他功能需求和改进建议，请随时告知我们。我们会持续提供支持和改进。期待您对我们的应用<br>有愉快的使用体验！<br>---------- TextMessage (Engineer) ----------<br>很高兴听到项目顺利完成。如果您或用户有任何问题或者需要帮助，请随时联系我们。感谢您对我们工作的支持，让我们一起确保应用稳定运行并不断优化用户<br>体验！<br>---------- TextMessage (CodeReviewer) ----------<br>非常感谢大家的努力与协作，使得项目能够顺利完成。未来若有更多技术支持的需求或者需要改进的地方，我们愿意为项目的持续优化贡<br>献力量。期待用户能够享受到流畅的体验，同时也欢迎提出更多的反馈与建议。再次感谢团队的合作！<br>---------- TextMessage (UserProxy) ----------<br>Enter your response: TERMINATE<br>============================================================<br>✅ 团队协作完成！<br><br>📋 协作结果摘要：<br>- 参与智能体数量：4个<br>- 任务完成状态：成功<br></code></pre></td></tr></table></figure><p>整个协作过程展现了 AutoGen 框架的优势：<strong>自然的对话驱动协作</strong>、<strong>角色专业化分工</strong>、<strong>流程自动化管理</strong>和<strong>完整的开发闭环</strong>。</p><h3 id="6-2-4-AutoGen-的优势与局限性分析"><a href="#6-2-4-AutoGen-的优势与局限性分析" class="headerlink" title="6.2.4 AutoGen 的优势与局限性分析"></a>6.2.4 AutoGen 的优势与局限性分析</h3><p>任何技术框架都有其特定的适用场景和设计权衡。在本节中，我们将客观地分析 AutoGen 的核心优势及其在实际应用中可能面临的局限性与挑战。</p><p>（1）优势</p><ul><li>如案例所示，我们无需为智能体团队设计复杂的状态机或控制流逻辑，而是将一个完整的软件开发流程，自然地映射为产品经理、工程师和审查员之间的对话。这种方式更贴近人类团队的协作模式，显著降低了为复杂任务建模的门槛。开发者可以将更多精力聚焦于定义“谁（角色）”以及“做什么（职责）”，而非“如何做（流程控制）”。</li><li>框架允许通过系统消息（System Message）为每个智能体赋予高度专业化的角色。在案例中，<code>ProductManager</code> 专注于需求，而 <code>CodeReviewer</code> 则专注于质量。一个精心设计的智能体可以在不同项目中被复用，易于维护和扩展。</li><li>对于流程化任务，<code>RoundRobinGroupChat</code> 这样机制提供了清晰、可预测的协作流程。同时，<code>UserProxyAgent</code> 的设计为“人类在环”（Human-in-the-loop）提供了天然的接口。它既可以作为任务的发起者，也可以是流程的监督者和最终的验收者。这种设计确保了自动化系统始终处于人类的监督之下。</li></ul><p>（2）局限性</p><ul><li>虽然 <code>RoundRobinGroupChat</code> 提供了顺序化的流程，但基于 LLM 的对话本质上具有不确定性。智能体可能会产生偏离预期的回复，导致对话走向意外的分支，甚至陷入循环。</li><li>当智能体团队的工作结果未达预期时，调试过程可能非常棘手。与传统程序不同，我们得到的不是清晰的错误堆栈，而是一长串的对话历史。这被称为“对话式调试”的难题。</li></ul><p>（3）非 OpenAI 模型的配置补充</p><p>如果你想使用非 OpenAI 系列的模型（如 DeepSeek、通义千问等），在 0.7.4 版本中需要在 <code>OpenAIChatCompletionClient</code> 的参数中传入模型信息字典。以 DeepSeek 为例：</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> autogen_ext.models.openai <span class="hljs-keyword">import</span> OpenAIChatCompletionClient<br><br>model_client = OpenAIChatCompletionClient(<br>    model=<span class="hljs-string">&quot;deepseek-chat&quot;</span>,<br>    api_key=os.getenv(<span class="hljs-string">&quot;DEEPSEEK_API_KEY&quot;</span>),<br>    base_url=<span class="hljs-string">&quot;https://api.deepseek.com/v1&quot;</span>,<br>    model_info=&#123;<br>        <span class="hljs-string">&quot;function_calling&quot;</span>: <span class="hljs-literal">True</span>,<br>        <span class="hljs-string">&quot;max_tokens&quot;</span>: <span class="hljs-number">4096</span>,<br>        <span class="hljs-string">&quot;context_length&quot;</span>: <span class="hljs-number">32768</span>,<br>        <span class="hljs-string">&quot;vision&quot;</span>: <span class="hljs-literal">False</span>,<br>        <span class="hljs-string">&quot;json_output&quot;</span>: <span class="hljs-literal">True</span>,<br>        <span class="hljs-string">&quot;family&quot;</span>: <span class="hljs-string">&quot;deepseek&quot;</span>,<br>        <span class="hljs-string">&quot;structured_output&quot;</span>: <span class="hljs-literal">True</span>,<br>    &#125;<br>)<br></code></pre></td></tr></table></figure><p>这个 <code>model_info</code> 字典帮助 AutoGen 了解模型的能力边界，从而更好地适配不同的模型服务。</p><h2 id="6-3-框架二：AgentScope"><a href="#6-3-框架二：AgentScope" class="headerlink" title="6.3 框架二：AgentScope"></a>6.3 框架二：AgentScope</h2><p>如果说 AutoGen 的设计哲学是”以对话驱动协作”，那么 AgentScope 则代表了另一种技术路径：<strong>工程化优先的多智能体平台</strong>。AgentScope 由阿里巴巴达摩院开发，专门为构建大规模、高可靠性的多智能体应用而设计。它不仅提供了直观易用的编程接口，更重要的是内置了分布式部署、容错恢复、可观测性等企业级特性，使其特别适合构建需要长期稳定运行的生产环境应用。</p><h3 id="6-3-1-AgentScope-的设计"><a href="#6-3-1-AgentScope-的设计" class="headerlink" title="6.3.1 AgentScope 的设计"></a>6.3.1 AgentScope 的设计</h3><p>与 AutoGen 相比，AgentScope 的核心差异在于其<strong>消息驱动的架构设计</strong>和<strong>工业级的工程实践</strong>。如果说 AutoGen 更像是一个灵活的”对话工作室”，那么 AgentScope 就是一个完整的”智能体操作系统”，为开发者提供了从开发、测试到部署的全生命周期支持。与许多框架采用的继承式设计不同，AgentScope 选择了<strong>组合式架构</strong>和<strong>消息驱动模式</strong>。这种设计不仅增强了系统的模块化程度，也为其出色的并发性能和分布式能力奠定了基础。</p><p>（1）分层架构体系</p><p>如图6.2所示，AgentScope 采用了清晰的分层模块化设计，从底层的基础组件到上层的应用编排，形成了一个完整的智能体开发生态。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/6-figures/03.png" alt="" width="90%"/>  <p>图 6.2 AgentScope架构图</p></div><p>在这个架构中，最底层是<strong>基础组件层 (Foundational Components)</strong>，它为整个框架提供了核心的构建块。<code>Message</code> 组件定义了统一的消息格式，支持从简单的文本交互到复杂的多模态内容；<code>Memory</code> 组件提供了短期和长期记忆管理；<code>Model API</code> 层抽象了对不同大语言模型的调用；而 <code>Tool</code> 组件则封装了智能体与外部世界交互的能力。</p><p>在基础组件之上，<strong>智能体基础设施层 (Agent-level Infrastructure)</strong> 提供了更高级的抽象。这一层不仅包含了各种预构建的智能体（如浏览器使用智能体、深度研究智能体），还实现了经典的 ReAct 范式，支持智能体钩子、并行工具调用、状态管理等高级特性。特别值得注意的是，这一层原生支持<strong>异步执行与实时控制</strong>，这是 AgentScope 相比其他框架的一个重要优势。</p><p><strong>多智能体协作层 (Multi-Agent Cooperation)</strong> 是 AgentScope 的核心创新所在。<code>MsgHub</code> 作为消息中心，负责智能体间的消息路由和状态管理；而 <code>Pipeline</code> 系统则提供了灵活的工作流编排能力，支持顺序、并发等多种执行模式。这种设计使得开发者可以轻松构建复杂的多智能体协作场景。</p><p>最上层的<strong>开发与部署层 (Deployment &amp; Development)</strong>则体现了 AgentScope 对工程化的重视。<code>AgentScope Runtime</code> 提供了生产级的运行时环境，而 <code>AgentScope Studio</code> 则为开发者提供了完整的可视化开发工具链。</p><p>（2）消息驱动</p><p>AgentScope 的核心创新在于其<strong>消息驱动架构</strong>。在这个架构中，所有的智能体交互都被抽象为<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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> agentscope.message <span class="hljs-keyword">import</span> Msg<br><br><span class="hljs-comment"># 消息的标准结构</span><br>message = Msg(<br>    name=<span class="hljs-string">&quot;Alice&quot;</span>,           <span class="hljs-comment"># 发送者名称</span><br>    content=<span class="hljs-string">&quot;Hello, Bob!&quot;</span>,  <span class="hljs-comment"># 消息内容</span><br>    role=<span class="hljs-string">&quot;user&quot;</span>,           <span class="hljs-comment"># 角色类型</span><br>    metadata=&#123;             <span class="hljs-comment"># 元数据信息</span><br>        <span class="hljs-string">&quot;timestamp&quot;</span>: <span class="hljs-string">&quot;2024-01-15T10:30:00Z&quot;</span>,<br>        <span class="hljs-string">&quot;message_type&quot;</span>: <span class="hljs-string">&quot;text&quot;</span>,<br>        <span class="hljs-string">&quot;priority&quot;</span>: <span class="hljs-string">&quot;normal&quot;</span><br>    &#125;<br>)<br></code></pre></td></tr></table></figure><p>将消息作为交互的基础单元，带来了几个关键优势：</p><ul><li><strong>异步解耦</strong>: 消息的发送方和接收方在时间上解耦，无需相互等待，天然支持高并发场景。</li><li><strong>位置透明</strong>: 智能体无需关心另一个智能体是在本地进程还是在远程服务器上，消息系统会自动处理路由。</li><li><strong>可观测性</strong>: 每一条消息都可以被记录、追踪和分析，极大地简化了复杂系统的调试与监控。</li><li><strong>可靠性</strong>: 消息可以被持久化存储和重试，即使系统出现故障，也能保证交互的最终一致性，提升了系统的容错能力。</li></ul><p>（3）智能体生命周期管理</p><p>在 AgentScope 中，每个智能体都有明确的生命周期（初始化、运行、暂停、销毁等），并基于一个统一的基类 <code>AgentBase</code> 来实现。开发者通常只需要关注其核心的 <code>reply</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><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><code class="hljs python"><span class="hljs-keyword">from</span> agentscope.agents <span class="hljs-keyword">import</span> AgentBase<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">CustomAgent</span>(<span class="hljs-title class_ inherited__">AgentBase</span>):<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, name: <span class="hljs-built_in">str</span>, **kwargs</span>):<br>        <span class="hljs-built_in">super</span>().__init__(name=name, **kwargs)<br>        <span class="hljs-comment"># 智能体初始化逻辑</span><br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">reply</span>(<span class="hljs-params">self, x: Msg</span>) -&gt; Msg:<br>        <span class="hljs-comment"># 智能体的核心响应逻辑</span><br>        response = self.model(x.content)<br>        <span class="hljs-keyword">return</span> Msg(name=self.name, content=response, role=<span class="hljs-string">&quot;assistant&quot;</span>)<br>    <br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">observe</span>(<span class="hljs-params">self, x: Msg</span>) -&gt; <span class="hljs-literal">None</span>:<br>        <span class="hljs-comment"># 智能体的观察逻辑（可选）</span><br>        self.memory.add(x)<br></code></pre></td></tr></table></figure><p>这种设计模式分离了智能体的内部逻辑与外部通信，开发者只需在 <code>reply</code> 方法中定义智能体“思考和回应”的方式即可。</p><p>（4）消息传递机制</p><p>AgentScope 内置了一个<strong>消息中心 (MsgHub)</strong>，它是整个消息驱动架构的中枢。MsgHub 不仅负责消息的路由和分发，还集成了持久化和分布式通信等高级功能，它有以下这些特点。</p><ul><li><strong>灵活的消息路由</strong>: 支持点对点、广播、组播等多种通信模式，可以构建灵活复杂的交互网络。</li><li><strong>消息持久化</strong>: 能够将所有消息自动保存到数据库（如 SQLite, MongoDB），确保了长期运行任务的状态可以被恢复。</li><li><strong>原生分布式支持</strong>: 这是 AgentScope 的标志性特性。智能体可以被部署在不同的进程或服务器上，<code>MsgHub</code> 会通过 RPC（远程过程调用）自动处理跨节点的通信，对开发者完全透明。</li></ul><p>这些由底层架构提供的工程化能力，使得 AgentScope 在处理需要高并发、高可靠性的复杂应用场景时，比传统的对话驱动框架更具优势。当然，这也要求开发者理解并适应消息驱动的异步编程范式。</p><p>在下一节中，我们将通过一个具体的实战案例，三国狼人杀游戏，来深入体验 AgentScope 框架的能力，特别是其在处理并发交互方面的优势。</p><h3 id="6-3-2-三国狼人杀游戏"><a href="#6-3-2-三国狼人杀游戏" class="headerlink" title="6.3.2 三国狼人杀游戏"></a>6.3.2 三国狼人杀游戏</h3><p>为了深入理解 AgentScope 的消息驱动架构和多智能体协作能力，我们将构建一个融合了中国古典文化元素的”三国狼人杀”游戏。这个案例不仅展示了 AgentScope 在处理复杂多智能体交互方面的优势，更重要的是，它演示了如何在一个需要<strong>实时协作</strong>、<strong>角色扮演</strong>和<strong>策略博弈</strong>的场景中，充分发挥消息驱动架构的威力。与传统狼人杀不同，我们的”三国狼人杀”将刘备、关羽、诸葛亮等经典角色引入游戏，每个智能体不仅要完成狼人杀的基本任务（如狼人击杀、预言家查验、村民推理），还要体现出对应三国人物的性格特点和行为模式。这种设计让我们能够观察到 AgentScope 在处理<strong>多层次角色建模</strong>方面的表现。</p><p>（1）架构设计与核心组件</p><p>本案例的系统设计遵循了分层解耦的原则，将游戏逻辑划分为三个独立的层次，每个层次都映射了 AgentScope 的一个或多个核心组件：</p><ul><li><strong>游戏控制层 (Game Control Layer)</strong>：由一个 <code>ThreeKingdomsWerewolfGame</code> 类作为游戏的主控制器，负责维护全局状态（如玩家存活列表、当前游戏阶段）、推进游戏流程（调用夜晚阶段、白天阶段）以及裁定胜负。</li><li><strong>智能体交互层 (Agent Interaction Layer)</strong>：完全由 <code>MsgHub</code> 驱动。所有智能体间的通信，无论是狼人间的秘密协商，还是白天的公开辩论，都通过消息中心进行路由和分发。</li><li><strong>角色建模层 (Role Modeling Layer)</strong>：每个玩家都是一个基于 <code>DialogAgent</code> 的实例。我们通过精心设计的系统提示词，为每个智能体注入了“游戏角色”和“三国人格”的双重身份。</li></ul><p>（2）消息驱动的游戏流程</p><p>本案例最核心的设计是以<strong>消息驱动</strong>代替<strong>状态机</strong>来管理游戏流程。在传统实现中，游戏阶段的转换通常由一个中心化的状态机（State Machine）控制。而在 AgentScope 的范式下，游戏流程被自然地建模为一系列定义好的消息交互模式。</p><p>例如，狼人阶段的实现，并非一个简单的函数调用，而是通过 <code>MsgHub</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><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><code class="hljs python"><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">werewolf_phase</span>(<span class="hljs-params">self, round_num: <span class="hljs-built_in">int</span></span>):<br>    <span class="hljs-string">&quot;&quot;&quot;狼人阶段 - 展示消息驱动的协作模式&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.werewolves:<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span><br>        <br>    <span class="hljs-comment"># 通过消息中心建立狼人专属通信频道</span><br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> MsgHub(<br>        self.werewolves,<br>        enable_auto_broadcast=<span class="hljs-literal">True</span>,<br>        announcement=<span class="hljs-keyword">await</span> self.moderator.announce(<br>            <span class="hljs-string">f&quot;狼人们，请讨论今晚的击杀目标。存活玩家：<span class="hljs-subst">&#123;format_player_list(self.alive_players)&#125;</span>&quot;</span><br>        ),<br>    ) <span class="hljs-keyword">as</span> werewolves_hub:<br>        <span class="hljs-comment"># 讨论阶段：狼人通过消息交换策略</span><br>        <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(MAX_DISCUSSION_ROUND):<br>            <span class="hljs-keyword">for</span> wolf <span class="hljs-keyword">in</span> self.werewolves:<br>                <span class="hljs-keyword">await</span> wolf(structured_model=DiscussionModelCN)<br>        <br>        <span class="hljs-comment"># 投票阶段：收集并统计狼人的击杀决策</span><br>        werewolves_hub.set_auto_broadcast(<span class="hljs-literal">False</span>)<br>        kill_votes = <span class="hljs-keyword">await</span> fanout_pipeline(<br>            self.werewolves,<br>            msg=<span class="hljs-keyword">await</span> self.moderator.announce(<span class="hljs-string">&quot;请选择击杀目标&quot;</span>),<br>            structured_model=WerewolfKillModelCN,<br>            enable_gather=<span class="hljs-literal">False</span>,<br>        )<br></code></pre></td></tr></table></figure><p>这种设计的优势在于，游戏逻辑被清晰地表达为“在特定上下文中，以何种模式进行消息交换”，而不是一连串僵硬的状态转换。白天讨论（全员广播）、预言家查验（点对点请求）等阶段也都遵循同样的设计范式。</p><p>（3）用结构化输出约束游戏规则</p><p>狼人杀游戏的一个关键挑战是如何确保智能体的行为符合游戏规则。AgentScope 的<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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">DiscussionModelCN</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;讨论阶段的输出格式&quot;&quot;&quot;</span><br>    reach_agreement: <span class="hljs-built_in">bool</span> = Field(<br>        description=<span class="hljs-string">&quot;是否已达成一致意见&quot;</span>,<br>        default=<span class="hljs-literal">False</span><br>    )<br>    confidence_level: <span class="hljs-built_in">int</span> = Field(<br>        description=<span class="hljs-string">&quot;对当前推理的信心程度(1-10)&quot;</span>,<br>        ge=<span class="hljs-number">1</span>, le=<span class="hljs-number">10</span>,<br>        default=<span class="hljs-number">5</span><br>    )<br>    key_evidence: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = Field(<br>        description=<span class="hljs-string">&quot;支持你观点的关键证据&quot;</span>,<br>        default=<span class="hljs-literal">None</span><br>    )<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">WitchActionModelCN</span>(<span class="hljs-title class_ inherited__">BaseModel</span>):<br>    <span class="hljs-string">&quot;&quot;&quot;女巫行动的输出格式&quot;&quot;&quot;</span><br>    use_antidote: <span class="hljs-built_in">bool</span> = Field(description=<span class="hljs-string">&quot;是否使用解药&quot;</span>)<br>    use_poison: <span class="hljs-built_in">bool</span> = Field(description=<span class="hljs-string">&quot;是否使用毒药&quot;</span>)<br>    target_name: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] = Field(description=<span class="hljs-string">&quot;毒药目标玩家姓名&quot;</span>)<br></code></pre></td></tr></table></figure><p>通过这种方式，我们不仅确保了智能体输出的<strong>格式一致性</strong>，更重要的是实现了<strong>游戏规则的自动化约束</strong>。例如，女巫智能体无法同时对同一目标使用解药和毒药，预言家每晚只能查验一名玩家，这些约束都通过数据模型的字段定义和验证逻辑自动执行。</p><p>（4）角色建模的双重挑战</p><p>在这个案例中，最有趣的技术挑战是如何让智能体同时扮演好两个层面的角色：<strong>游戏功能角色</strong>（狼人、预言家等）和<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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">get_role_prompt</span>(<span class="hljs-params">role: <span class="hljs-built_in">str</span>, character: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;获取角色提示词 - 融合游戏规则与人物性格&quot;&quot;&quot;</span><br>    base_prompt = <span class="hljs-string">f&quot;&quot;&quot;你是<span class="hljs-subst">&#123;character&#125;</span>，在这场三国狼人杀游戏中扮演<span class="hljs-subst">&#123;role&#125;</span>。</span><br><span class="hljs-string"></span><br><span class="hljs-string">重要规则：</span><br><span class="hljs-string">1. 你只能通过对话和推理参与游戏</span><br><span class="hljs-string">2. 不要尝试调用任何外部工具或函数</span><br><span class="hljs-string">3. 严格按照要求的JSON格式回复</span><br><span class="hljs-string"></span><br><span class="hljs-string">角色特点：</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br>    <br>    <span class="hljs-keyword">if</span> role == <span class="hljs-string">&quot;狼人&quot;</span>:<br>        <span class="hljs-keyword">return</span> base_prompt + <span class="hljs-string">f&quot;&quot;&quot;</span><br><span class="hljs-string">- 你是狼人阵营，目标是消灭所有好人</span><br><span class="hljs-string">- 夜晚可以与其他狼人协商击杀目标</span><br><span class="hljs-string">- 白天要隐藏身份，误导好人</span><br><span class="hljs-string">- 以<span class="hljs-subst">&#123;character&#125;</span>的性格说话和行动</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br></code></pre></td></tr></table></figure><p>这种设计让我们观察到了一个有趣的现象：不同的三国人物在扮演相同游戏角色时，会表现出截然不同的策略和话语风格。例如，扮演狼人的”曹操”可能会表现得更加狡猾和善于伪装，而扮演狼人的”张飞”则可能显得更加直接和冲动。</p><p>（5）并发处理与容错机制</p><p>AgentScope 的异步架构在这个多智能体游戏中发挥了重要作用。游戏中经常出现需要<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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 并行收集所有玩家的投票决策</span><br>vote_msgs = <span class="hljs-keyword">await</span> fanout_pipeline(<br>    self.alive_players,<br>    <span class="hljs-keyword">await</span> self.moderator.announce(<span class="hljs-string">&quot;请投票选择要淘汰的玩家&quot;</span>),<br>    structured_model=get_vote_model_cn(self.alive_players),<br>    enable_gather=<span class="hljs-literal">False</span>,<br>)<br></code></pre></td></tr></table></figure><p><code>fanout_pipeline</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><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><code class="hljs python"><span class="hljs-keyword">try</span>:<br>    response = <span class="hljs-keyword">await</span> wolf(<br>        <span class="hljs-string">&quot;请分析当前局势并表达你的观点。&quot;</span>,<br>        structured_model=DiscussionModelCN<br>    )<br><span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;⚠️ <span class="hljs-subst">&#123;wolf.name&#125;</span> 讨论时出错: <span class="hljs-subst">&#123;e&#125;</span>&quot;</span>)<br>    <span class="hljs-comment"># 创建默认响应，确保游戏继续进行</span><br>    default_response = DiscussionModelCN(<br>        reach_agreement=<span class="hljs-literal">False</span>,<br>        confidence_level=<span class="hljs-number">5</span>,<br>        key_evidence=<span class="hljs-string">&quot;暂时无法分析&quot;</span><br>    )<br></code></pre></td></tr></table></figure><p>这种设计确保了即使某个智能体出现异常，整个游戏流程也能继续进行。</p><p>（6）案例输出与总结</p><p>为了更直观地感受 AgentScope 的运行机制，以下是一段截取自游戏夜晚阶段的真实运行日志，展示了扮演“孙权”和“周瑜”的两个狼人智能体进行秘密协商并执行击杀的过程。</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><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><code class="hljs makefile">🎮 欢迎来到三国狼人杀！<br><br>=== 游戏初始化 ===<br><span class="hljs-section">游戏主持人: 📢 【孙权】你在这场三国狼人杀中扮演狼人，你的角色是孙权。夜晚可以击杀一名玩家</span><br><span class="hljs-section">游戏主持人: 📢 【周瑜】你在这场三国狼人杀中扮演狼人，你的角色是周瑜。夜晚可以击杀一名玩家</span><br>...<br><br><span class="hljs-section">游戏主持人: 📢 三国狼人杀游戏开始！参与者：孙权、周瑜、曹操、张飞、司马懿、赵云</span><br>✅ 游戏设置完成，共6名玩家<br><br>=== 第1轮游戏 ===<br>🌙 第1夜降临，天黑请闭眼...<br><br>【狼人阶段】<br><span class="hljs-section">游戏主持人: 📢 🐺 狼人请睁眼，选择今晚要击杀的目标...</span><br><span class="hljs-section">游戏主持人: 📢 狼人们，请讨论今晚的击杀目标。存活玩家：孙权、周瑜、曹操、张飞、司马懿、赵云</span><br><br><span class="hljs-section">孙权: 今晚我们应该除掉周瑜，此人智谋过人，对我们威胁很大。</span><br><span class="hljs-section">周瑜: 孙权，你言之有理。但周瑜虽智，却未必是今晚的最大威胁。曹操势力庞大，若不尽早除去，恐对我们不利。</span><br><span class="hljs-section">孙权: 曹操的确是个威胁，但周瑜若活着，他能够识破我们的计谋。不如先解决眼前的隐患。</span><br><span class="hljs-section">周瑜: 孙权，你的顾虑不无道理。但曹操若与我们为敌，他可以联合其他势力对我们构成更大的威胁。</span><br><span class="hljs-section">孙权: 你说的也有道理，曹操的联合确实麻烦。那我们就先对付曹操吧。</span><br><span class="hljs-section">周瑜: 很好，孙权。曹操才是我们今晚首要的目标。</span><br><br><span class="hljs-section">游戏主持人: 📢 请选择击杀目标</span><br><span class="hljs-section">孙权: 我同意，曹操必须被除掉。</span><br><span class="hljs-section">周瑜: 我同意，曹操是我们今晚要解决的目标。</span><br><br>【预言家阶段】<br><span class="hljs-section">游戏主持人: 📢 🔮 预言家请睁眼，选择要查验的玩家...</span><br><span class="hljs-section">曹操: 我要查验孙权。</span><br><span class="hljs-section">游戏主持人: 📢 查验结果：孙权是狼人</span><br><br>【女巫阶段】<br><span class="hljs-section">游戏主持人: 📢 🧙‍♀️ 女巫请睁眼...</span><br><span class="hljs-section">游戏主持人: 📢 今晚曹操被狼人击杀</span><br><span class="hljs-section">张飞: 我昨晚使用了解药救了曹操，现在解药已经用掉了。</span><br><span class="hljs-section">游戏主持人: 📢 你使用解药救了曹操</span><br><br><span class="hljs-section">游戏主持人: 📢 昨夜平安无事，无人死亡。</span><br><br>【白天讨论阶段】<br><span class="hljs-section">游戏主持人: 📢 ☀️ 第1天天亮了，请大家睁眼...</span><br><span class="hljs-section">游戏主持人: 📢 现在开始自由讨论。存活玩家：孙权、周瑜、曹操、张飞、司马懿、赵云</span><br><br><span class="hljs-section">孙权: 诸位，曹操势力庞大，对我们都是潜在的威胁。今晚我建议我们集中力量对付他。</span><br><span class="hljs-section">周瑜: 孙权所言极是，曹操不仅自身强大，还可能与其他玩家结盟，对我们构成更大的威胁。</span><br><span class="hljs-section">曹操: 我昨晚查验了孙权，本以为他是好人，但游戏主持人给出的结果却是狼人。这说明有狼人在说谎。</span><br><span class="hljs-section">张飞: 我昨晚确实救了曹操，说明他是被狼人袭击的。但曹操查验孙权的结果令人怀疑。</span><br><span class="hljs-section">司马懿: 曹操的查验结果和张飞的救人行动似乎存在矛盾，我们需要更多的信息来判断谁是狼人。</span><br><span class="hljs-section">赵云: 情况确实复杂，我们需要仔细分析各方的发言。</span><br><br>【投票阶段】<br><span class="hljs-section">游戏主持人: 📢 请投票选择要淘汰的玩家</span><br><br><span class="hljs-section">孙权: 曹操的威胁依然很大，我坚持认为应该投票给他。</span><br><span class="hljs-section">周瑜: 基于昨晚的情况，我认为我们应该先投票给曹操，他的威胁最大。</span><br><span class="hljs-section">曹操: 我选择投票给孙权。根据游戏主持人的反馈，孙权确实是狼人。</span><br><span class="hljs-section">张飞: 我坚持昨晚救了曹操的事实，但孙权被查出是狼人这一点让我感到困惑。</span><br><span class="hljs-section">司马懿: 我们需要更多的信息来判断谁是狼人。</span><br><br>[游戏继续...]<br></code></pre></td></tr></table></figure><h3 id="6-3-3-AgentScope-的优势与局限性分析"><a href="#6-3-3-AgentScope-的优势与局限性分析" class="headerlink" title="6.3.3 AgentScope 的优势与局限性分析"></a>6.3.3 AgentScope 的优势与局限性分析</h3><p>通过这个”三国狼人杀”案例，我们深度体验了 AgentScope 框架的核心优势。该框架以其消息驱动的架构为核心，将复杂的游戏流程优雅地映射为一系列并发、异步的消息传递事件，从而避免了传统状态机的僵硬与复杂。结合其强大的结构化输出能力，我们将游戏规则直接转化为代码层面的约束，极大地提升了系统的稳定性和可预测性。这种设计范式不仅在性能上展现了其原生并发的优势，更在容错处理上保证了即使单个智能体出现异常，整体流程也能稳健运行。</p><p>然而，AgentScope 的工程化优势也带来了一定的复杂性成本。其消息驱动架构虽然强大，但对开发者的技术要求较高，需要理解异步编程、分布式通信等概念。对于简单的多智能体对话场景，这种架构可能显得过于复杂，存在”过度工程化”的风险。此外，作为相对较新的框架，其生态系统和社区资源还有待进一步完善。因此，AgentScope 更适合需要构建大规模、高可靠性的生产级多智能体系统，而对于快速原型开发或简单应用场景，选择更轻量级的框架可能更为合适。</p><h2 id="6-4-框架三：CAMEL"><a href="#6-4-框架三：CAMEL" class="headerlink" title="6.4 框架三：CAMEL"></a>6.4 框架三：CAMEL</h2><p>与 AutoGen 和 AgentScope 这样功能全面的框架不同，CAMEL最初的核心目标是探索如何在最少的人类干预下，让两个智能体通过“角色扮演”自主协作解决复杂任务。</p><h3 id="6-4-1-CAMEL-的自主协作"><a href="#6-4-1-CAMEL-的自主协作" class="headerlink" title="6.4.1 CAMEL 的自主协作"></a>6.4.1 CAMEL 的自主协作</h3><p>CAMEL 实现自主协作的基石是两大核心概念：<strong>角色扮演 (Role-Playing)</strong> 和 <strong>引导性提示 (Inception Prompting)</strong>。</p><p>（1）角色扮演</p><p>在 CAMEL 最初的设计中，一个任务通常由两个智能体协作完成。这两个智能体被赋予了互补的、明确定义的“角色”。一个扮演<strong>“AI 用户” (AI User)</strong>，负责提出需求、下达指令和构思任务步骤；另一个则扮演<strong>“AI 助理” (AI Assistant)</strong>，负责根据指令执行具体操作和提供解决方案。</p><p>例如，在一个“开发股票交易策略分析工具”的任务中：</p><ul><li><strong>AI 用户</strong> 的角色可能是一位“资深股票交易员”。它懂市场、懂策略，但不懂编程。</li><li><strong>AI 助理</strong> 的角色则是一位“优秀的 Python 程序员”。它精通编程，但对股票交易一无所知。</li></ul><p>通过这种设定，任务的解决过程就被自然地转化为一场两位“跨领域专家”之间的对话。交易员提出专业需求，程序员将其转化为代码实现，两者协作完成任何一方都无法独立完成的复杂任务。</p><p>（2）引导性提示</p><p>仅仅设定角色还不够，如何确保两个 AI 在没有人类持续监督的情况下，能始终“待在自己的角色里”，并且高效地朝着共同目标前进呢？这就是 CAMEL 最核心的技术，引导性提示发挥作用的地方。“引导性提示”是在对话开始前，分别注入给两个智能体的一段精心设计的、结构化的初始指令（System Prompt）。这段指令就像是为智能体植入的“行动纲领”，它通常包含以下几个关键部分：</p><ul><li><strong>明确自身角色</strong>：例如，“你是一位资深的股票交易员…”</li><li><strong>告知协作者角色</strong>：例如，“你正在与一位优秀的 Python 程序员合作…”</li><li><strong>定义共同目标</strong>：例如，“你们的共同目标是开发一个股票交易策略分析工具。”</li><li><strong>设定行为约束和沟通协议</strong>：这是最关键的一环。例如，指令会要求 AI 用户“一次只提出一个清晰、具体的步骤”，并要求 AI 助理“在完成上一步之前不要追问更多细节”，同时规定双方需在回复的末尾使用特定标志（如 <code>&lt;SOLUTION&gt;</code>）来标识任务的完成。</li></ul><p>这些约束条件确保了对话不会偏离主题、不会陷入无效循环，而是以一种高度结构化、任务驱动的方式向前推进，如图6.3所示。</p><div align="center">  <img src="https://raw.githubusercontent.com/datawhalechina/Hello-Agents/main/docs/images/6-figures/04.png" alt="" width="90%"/>  <p>图 6.3 CAMEL创建股票机器人交易</p></div><p>在下一节，我们将通过一个具体的实例来体验这一过程。</p><h3 id="6-4-2-AI科普电子书"><a href="#6-4-2-AI科普电子书" class="headerlink" title="6.4.2 AI科普电子书"></a>6.4.2 AI科普电子书</h3><p>为了理解 CAMEL 框架的角色扮演能力，我们将构建一个具有实际价值的协作案例：让一位 AI 心理学家与一位 AI 作者合作，共同创作一本关于”拖延症心理学”的短篇电子书。这个案例体现了 CAMEL 的核心优势，让两个智能体在各自专业领域发挥所长，协作完成单个智能体难以胜任的复杂创作任务。</p><p>（1）任务设定</p><p><strong>场景设定</strong>：创作一本面向普通读者的拖延症心理学科普电子书，要求既有科学严谨性，又具备良好的可读性。</p><p><strong>智能体角色</strong>：</p><ul><li><strong>心理学家（Psychologist）</strong>：具备深厚的心理学理论基础，熟悉认知行为科学、神经科学等相关领域，能够提供专业的学术见解和实证研究支持</li><li><strong>作家（Writer）</strong>：拥有优秀的写作技巧和叙述能力，善于将复杂的学术概念转化为生动易懂的文字，注重读者体验和内容的可读性</li></ul><p>（2）定义协作任务</p><p>首先，我们需要明确两位 AI 专家的共同目标。我们通过一个内容详实的字符串 <code>task_prompt</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><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><code class="hljs python"><span class="hljs-keyword">from</span> colorama <span class="hljs-keyword">import</span> Fore<br><span class="hljs-keyword">from</span> camel.societies <span class="hljs-keyword">import</span> RolePlaying<br><span class="hljs-keyword">from</span> camel.utils <span class="hljs-keyword">import</span> print_text_animated<br><span class="hljs-keyword">from</span> camel.models <span class="hljs-keyword">import</span> ModelFactory<br><span class="hljs-keyword">from</span> camel.types <span class="hljs-keyword">import</span> ModelPlatformType<br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><span class="hljs-keyword">import</span> os<br><br>load_dotenv()<br>LLM_API_KEY = os.getenv(<span class="hljs-string">&quot;LLM_API_KEY&quot;</span>)<br>LLM_BASE_URL = os.getenv(<span class="hljs-string">&quot;LLM_BASE_URL&quot;</span>)<br>LLM_MODEL = os.getenv(<span class="hljs-string">&quot;LLM_MODEL&quot;</span>)<br><br><span class="hljs-comment">#创建模型,在这里以Qwen为例,调用的百炼大模型平台API</span><br>model = ModelFactory.create(<br>    model_platform=ModelPlatformType.QWEN,<br>    model_type=LLM_MODEL,<br>    url=LLM_BASE_URL,<br>    api_key=LLM_API_KEY<br>)<br><br><span class="hljs-comment"># 定义协作任务</span><br>task_prompt = <span class="hljs-string">&quot;&quot;&quot;</span><br><span class="hljs-string">创作一本关于&quot;拖延症心理学&quot;的短篇电子书，目标读者是对心理学感兴趣的普通大众。</span><br><span class="hljs-string">要求：</span><br><span class="hljs-string">1. 内容科学严谨，基于实证研究</span><br><span class="hljs-string">2. 语言通俗易懂，避免过多专业术语</span><br><span class="hljs-string">3. 包含实用的改善建议和案例分析</span><br><span class="hljs-string">4. 篇幅控制在8000-10000字</span><br><span class="hljs-string">5. 结构清晰，包含引言、核心章节和总结</span><br><span class="hljs-string">&quot;&quot;&quot;</span><br><br><span class="hljs-built_in">print</span>(Fore.YELLOW + <span class="hljs-string">f&quot;协作任务:\n<span class="hljs-subst">&#123;task_prompt&#125;</span>\n&quot;</span>)<br></code></pre></td></tr></table></figure><p><code>task_prompt</code> 是整个协作的“任务说明书”。它不仅是我们要完成的目标，也将在幕后被 CAMEL 用来生成“引导性提示”，确保两位智能体的对话始终围绕这个核心目标展开。</p><p>（3）初始化角色扮演“社会”</p><p>接下来，我们创建 <code>RolePlaying</code> 会话实例。这是 CAMEL 的核心操作，它根据我们提供的角色和任务，快速构建一个双智能体协作“社会”。</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><code class="hljs python"><span class="hljs-comment"># 初始化角色扮演会话</span><br><span class="hljs-comment"># AI 作家作为 &quot;user&quot;，负责提出写作结构和要求</span><br><span class="hljs-comment"># AI 心理学家作为 &quot;assistant&quot;，负责提供专业知识和内容</span><br>role_play_session = RolePlaying(<br>    assistant_role_name=<span class="hljs-string">&quot;心理学家&quot;</span>,<br>    user_role_name=<span class="hljs-string">&quot;作家&quot;</span>,<br>    task_prompt=task_prompt,<br>    model=model,<br>    with_task_specify=<span class="hljs-literal">False</span>, <span class="hljs-comment"># 在本例中，我们直接使用给定的task_prompt</span><br>)<br><br><span class="hljs-built_in">print</span>(Fore.CYAN + <span class="hljs-string">f&quot;具体任务描述:\n<span class="hljs-subst">&#123;role_play_session.task_prompt&#125;</span>\n&quot;</span>)<br></code></pre></td></tr></table></figure><p><code>RolePlaying</code> 是 CAMEL 提供的高级 API，它封装了复杂的提示工程。我们只需传入两个角色的名称和任务即可。在 CAMEL 的设计中，<code>user</code> 角色是对话的“推动者”和“需求方”，而 <code>assistant</code> 角色是“执行者”和“方案提供方”。因此，我们将负责规划结构的“作家”分配给 <code>user_role_name</code>，将负责提供专业知识的“心理学家”分配给 <code>assistant_role_name</code>。</p><p>（4）启动并运行自动化对话</p><p>最后，我们编写一个循环来驱动整个对话过程，让两位 AI 专家开始它们的自动化协作。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 开始协作对话</span><br>chat_turn_limit, n = <span class="hljs-number">30</span>, <span class="hljs-number">0</span><br><span class="hljs-comment"># 调用 init_chat() 来获得由 AI 生成的初始对话消息</span><br>input_msg = role_play_session.init_chat()<br><br><span class="hljs-keyword">while</span> n &lt; chat_turn_limit:<br>    n += <span class="hljs-number">1</span><br>    <span class="hljs-comment"># step() 方法驱动一轮完整的对话，AI 用户和 AI 助理各发言一次</span><br>    assistant_response, user_response = role_play_session.step(input_msg)<br>    <br>    <span class="hljs-comment"># 检查是否有消息返回，防止对话提前终止</span><br>    <span class="hljs-keyword">if</span> assistant_response.msg <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">or</span> user_response.msg <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br>        <span class="hljs-keyword">break</span><br>    <br>    print_text_animated(Fore.BLUE + <span class="hljs-string">f&quot;作家 (AI User):\n\n<span class="hljs-subst">&#123;user_response.msg.content&#125;</span>\n&quot;</span>)<br>    print_text_animated(Fore.GREEN + <span class="hljs-string">f&quot;心理学家 (AI Assistant):\n\n<span class="hljs-subst">&#123;assistant_response.msg.content&#125;</span>\n&quot;</span>)<br>    <br>    <span class="hljs-comment"># 检查任务完成标志</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;&lt;CAMEL_TASK_DONE&gt;&quot;</span> <span class="hljs-keyword">in</span> user_response.msg.content <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;&lt;CAMEL_TASK_DONE&gt;&quot;</span> <span class="hljs-keyword">in</span> assistant_response.msg.content:<br>        <span class="hljs-built_in">print</span>(Fore.MAGENTA + <span class="hljs-string">&quot;✅ 电子书创作完成！&quot;</span>)<br>        <span class="hljs-keyword">break</span><br>    <br>    <span class="hljs-comment"># 将助理的回复作为下一轮对话的输入</span><br>    input_msg = assistant_response.msg<br><br><span class="hljs-built_in">print</span>(Fore.YELLOW + <span class="hljs-string">f&quot;总共进行了 <span class="hljs-subst">&#123;n&#125;</span> 轮协作对话&quot;</span>)<br></code></pre></td></tr></table></figure><p>这段 <code>while</code> 循环是自动化协作的核心。对话由 <code>init_chat()</code> 方法基于任务和角色自动开启，无需人工编写开场白。循环的每一步都通过调用 <code>step()</code> 来驱动一轮完整的交互（作家提需求、心理学家给内容），并将上一轮心理学家的输出作为下一轮的输入，形成环-环相扣的创作链。整个过程将持续进行，直到达到预设的对话轮次上限，或任一智能体输出任务完成标志 <code>&lt;CAMEL_TASK_DONE&gt;</code> 后自动终止。</p><p>（5）协作流程展示</p><p>当执行上述代码后，我们并非只是得到一长串单调的问答，而是能够观察到一个高度结构化的、如同人类专家团队般的协作流程在自动进行。整个创作过程自然地分为几个阶段：</p><p><strong>第一阶段 (约 1-5 轮): 框架搭建与目标对齐</strong> 在对话的初期，“作家”智能体首先会扮演起主导者的角色，提出对电子书整体结构和章节安排的初步设想。随后，“心理学家”会从其专业角度对这个框架进行审视和补充，确保核心的学术模块（如理论基础、关键概念等）没有遗漏，从而在协作开始之初就对最终产出物达成共识。</p><p><strong>第二阶段 (约 6-20 轮): 核心内容生成与知识转译</strong> 这是最高效的内容创作阶段。协作模式会变为一种稳定的“请求-响应”循环：</p><ul><li><strong>心理学家</strong>：负责提供“硬核”的专业知识，如对“时间折扣理论”、“执行功能缺陷”等核心概念的科学解释，并引用相关的实验研究来支撑观点。</li><li><strong>作家</strong>：则发挥其“翻译官”的作用，将这些严谨但可能晦涩的学术概念，转化为生动、形象的比喻和贴近生活的案例。例如，它可能会将“大脑中的‘现在偏见’”这个概念，比作“一个只顾眼前糖果、不顾长远健康的任性孩子”。</li></ul><p><strong>第三阶段 (约 21-25 轮): 迭代优化与质量保证</strong> 当书籍的主体内容完成后，对话的重心会转移到对已有文本的打磨和完善上。此时，两位智能体的角色会发生微妙的变化：</p><ul><li><strong>作家</strong>：更侧重于审视文章的整体流畅性、逻辑衔接和语言风格，从“读者体验”出发提出修改建议。</li><li><strong>心理学家</strong>：则再次扮演“事实核查员”，确保在转译和润色的过程中，核心知识的科学准确性没有丢失，并为某些观点补充更有力的实证研究支持。</li></ul><p><strong>第四阶段 (收尾): 总结与升华</strong> 在最后的几轮对话中，双方会协作完成实用建议的总结和全书的回顾，确保电子书有一个清晰、有力的结尾，为读者留下深刻印象并提供实际价值。</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><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><code class="hljs makefile"><span class="hljs-section">协作任务:</span><br>创作一本关于<span class="hljs-string">&quot;拖延症心理学&quot;</span>的短篇电子书，目标读者是对心理学感兴趣的普通大众。<br>要求：<br>1. 内容科学严谨，基于实证研究<br>2. 语言通俗易懂，避免过多专业术语<br>3. 包含实用的改善建议和案例分析<br>4. 篇幅控制在8000-10000字<br>5. 结构清晰，包含引言、核心章节和总结<br><br><span class="hljs-section">具体任务描述:</span><br>为普通大众撰写8000–10000字短篇电子书《拖延症心理学》：实证为本、通俗易懂。结构：引言、成因（认知/情绪/奖励）、动机与决策、习惯形成与干预、实<br>用策略与练习、三则案例分析、总结与资源。每章含研究引用与可操作步骤。<br><br><span class="hljs-section">作家:</span><br><span class="hljs-section">Instruction: 请为电子书的“引言”章节撰写一段400–600字的中文草稿...</span><br><span class="hljs-section">Input: None</span><br><br><span class="hljs-section">心理学家:</span><br><span class="hljs-section">Solution:</span><br>草稿：拖延，是指明知应当完成某项任务却反复推迟或回避的行为与内在倾向。它既可以是偶发的时间管理问题...<br><br>Next request.<br><br><span class="hljs-section">作家:</span><br><span class="hljs-section">Instruction: 请把下面的引言草稿修订为一段450–550字的中文文本...</span><br><span class="hljs-section">Input: 草稿：拖延，是指明知应当完成某项任务却反复推迟或回避的行为...</span><br>.....<br></code></pre></td></tr></table></figure><h3 id="6-4-3-CAMEL-的优势与局限性分析"><a href="#6-4-3-CAMEL-的优势与局限性分析" class="headerlink" title="6.4.3 CAMEL 的优势与局限性分析"></a>6.4.3 CAMEL 的优势与局限性分析</h3><p>通过前面的电子书创作案例，我们深度体验了 CAMEL 框架独特的角色扮演范式。现在让我们客观地分析这种设计理念的优势与局限性，以便在实际项目中做出明智的技术选型。</p><p>（1）优势</p><p>CAMEL 最大的优势在于其”轻架构、重提示”的设计哲学。相比 AutoGen 的复杂对话管理和 AgentScope 的分布式架构，CAMEL 通过精心设计的初始提示就能实现高质量的智能体协作。这种自然涌现的协作行为，往往比硬编码的工作流更加灵活和高效。</p><p>值得注意的是，CAMEL 框架正在经历快速的发展和演进。从其 <a href="https://github.com/camel-ai/camel">GitHub 仓库</a> 可以看到，CAMEL 已经远不止是一个简单的双智能体协作框架，目前已经具备：</p><ul><li><strong>多模态能力</strong>：支持文本、图像、音频等多种模态的智能体协作</li><li><strong>工具集成</strong>：内置了丰富的工具库，包括搜索、计算、代码执行等</li><li><strong>模型适配</strong>：支持 OpenAI、Anthropic、Google、开源模型等多种 LLM 后端</li><li><strong>生态联动</strong>：与 LangChain、CrewAI、AutoGen 等主流框架实现了互操作性</li></ul><p>（2）主要局限性</p><ol><li>对提示工程的高度依赖</li></ol><p>CAMEL 的成功很大程度上取决于初始提示的质量。这带来了几个挑战：</p><ul><li><strong>提示设计门槛</strong>：需要深入理解目标领域和 LLM 的行为特性</li><li><strong>调试复杂性</strong>：当协作效果不佳时，很难定位是角色定义、任务描述还是交互规则的问题</li><li><strong>一致性挑战</strong>：不同的 LLM 对相同提示的理解可能存在差异</li></ul><ol start="2"><li>协作规模的限制</li></ol><p>虽然 CAMEL 在双智能体协作上表现出色，但在处理大规模多智能体场景时面临挑战：</p><ul><li><strong>对话管理</strong>：缺乏像 AutoGen 那样的复杂对话路由机制</li><li><strong>状态同步</strong>：没有 AgentScope 那样的分布式状态管理能力</li><li><strong>冲突解决</strong>：当多个智能体意见分歧时，缺乏有效的仲裁机制</li></ul><ol start="3"><li>任务适用性的边界</li></ol><p>CAMEL 特别适合需要深度协作和创造性思维的任务，但在某些场景下可能不是最优选择：</p><ul><li><strong>严格流程控制</strong>：对于需要精确步骤控制的任务，LangGraph 的图结构更合适</li><li><strong>大规模并发</strong>：AgentScope 的消息驱动架构在高并发场景下更有优势</li><li><strong>复杂决策树</strong>：AutoGen 的群聊模式在多方决策场景下更加灵活</li></ul><p>总的来说，CAMEL 代表了一种独特而优雅的多智能体协作范式。它通过”以人为本”的角色扮演设计，将复杂的系统工程问题转化为直观的人际协作模式。随着其生态系统的不断完善和功能的持续扩展，CAMEL 正在成为构建智能协作系统的重要选择之一。</p><h2 id="6-5-框架四：LangGraph"><a href="#6-5-框架四：LangGraph" class="headerlink" title="6.5 框架四：LangGraph"></a>6.5 框架四：LangGraph</h2><h3 id="6-5-1-LangGraph-的结构梳理"><a href="#6-5-1-LangGraph-的结构梳理" class="headerlink" title="6.5.1 LangGraph 的结构梳理"></a>6.5.1 LangGraph 的结构梳理</h3><p>LangGraph 作为 LangChain 生态系统的重要扩展，代表了智能体框架设计的一个全新方向。与前面介绍的基于“对话”的框架（如 AutoGen 和 CAMEL）不同，LangGraph 将智能体的执行流程建模为一种<strong>状态机（State Machine）</strong>，并将其表示为<strong>有向图（Directed Graph）</strong>。在这种范式中，图的<strong>节点（Nodes）</strong>代表一个具体的计算步骤（如调用 LLM、执行工具），而<strong>边（Edges）</strong>则定义了从一个节点到另一个节点的跳转逻辑。这种设计的革命性之处在于它天然支持循环，使得构建能够进行迭代、反思和自我修正的复杂智能体工作流变得前所未有的直观和简单。</p><p>要理解 LangGraph，我们需要先掌握它的三个基本构成要素。</p><p><strong>首先，是全局状态（State）</strong>。整个图的执行过程都围绕一个共享的状态对象进行。这个状态通常被定义为一个 Python 的 <code>TypedDict</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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> TypedDict, <span class="hljs-type">List</span><br><br><span class="hljs-comment"># 定义全局状态的数据结构</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">AgentState</span>(<span class="hljs-title class_ inherited__">TypedDict</span>):<br>    messages: <span class="hljs-type">List</span>[<span class="hljs-built_in">str</span>]      <span class="hljs-comment"># 对话历史</span><br>    current_task: <span class="hljs-built_in">str</span>        <span class="hljs-comment"># 当前任务</span><br>    final_answer: <span class="hljs-built_in">str</span>        <span class="hljs-comment"># 最终答案</span><br>    <span class="hljs-comment"># ... 任何其他需要追踪的状态</span><br></code></pre></td></tr></table></figure><p><strong>其次，是节点（Nodes）</strong>。每个节点都是一个接收当前状态作为输入、并返回一个更新后的状态作为输出的 Python 函数。节点是执行具体工作的单元。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 定义一个“规划者”节点函数</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">planner_node</span>(<span class="hljs-params">state: AgentState</span>) -&gt; AgentState:<br>    <span class="hljs-string">&quot;&quot;&quot;根据当前任务制定计划，并更新状态。&quot;&quot;&quot;</span><br>    current_task = state[<span class="hljs-string">&quot;current_task&quot;</span>]<br>    <span class="hljs-comment"># ... 调用LLM生成计划 ...</span><br>    plan = <span class="hljs-string">f&quot;为任务 &#x27;<span class="hljs-subst">&#123;current_task&#125;</span>&#x27; 生成的计划...&quot;</span><br>    <br>    <span class="hljs-comment"># 将新消息追加到状态中</span><br>    state[<span class="hljs-string">&quot;messages&quot;</span>].append(plan)<br>    <span class="hljs-keyword">return</span> state<br><br><span class="hljs-comment"># 定义一个“执行者”节点函数</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">executor_node</span>(<span class="hljs-params">state: AgentState</span>) -&gt; AgentState:<br>    <span class="hljs-string">&quot;&quot;&quot;执行最新计划，并更新状态。&quot;&quot;&quot;</span><br>    latest_plan = state[<span class="hljs-string">&quot;messages&quot;</span>][-<span class="hljs-number">1</span>]<br>    <span class="hljs-comment"># ... 执行计划并获得结果 ...</span><br>    result = <span class="hljs-string">f&quot;执行计划 &#x27;<span class="hljs-subst">&#123;latest_plan&#125;</span>&#x27; 的结果...&quot;</span><br>    <br>    state[<span class="hljs-string">&quot;messages&quot;</span>].append(result)<br>    <span class="hljs-keyword">return</span> state<br></code></pre></td></tr></table></figure><p><strong>最后，是边（Edges）</strong>。边负责连接节点，定义工作流的方向。最简单的边是常规边，它指定了一个节点的输出总是流向另一个固定的节点。而 LangGraph 最强大的功能在于<strong>条件边（Conditional Edges）</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">should_continue</span>(<span class="hljs-params">state: AgentState</span>) -&gt; <span class="hljs-built_in">str</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;条件函数：根据状态决定下一步路由。&quot;&quot;&quot;</span><br>    <span class="hljs-comment"># 假设如果消息少于3条，则需要继续规划</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(state[<span class="hljs-string">&quot;messages&quot;</span>]) &lt; <span class="hljs-number">3</span>:<br>        <span class="hljs-comment"># 返回的字符串需要与添加条件边时定义的键匹配</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;continue_to_planner&quot;</span><br>    <span class="hljs-keyword">else</span>:<br>        state[<span class="hljs-string">&quot;final_answer&quot;</span>] = state[<span class="hljs-string">&quot;messages&quot;</span>][-<span class="hljs-number">1</span>]<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;end_workflow&quot;</span><br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> langgraph.graph <span class="hljs-keyword">import</span> StateGraph, END<br><br><span class="hljs-comment"># 初始化一个状态图，并绑定我们定义的状态结构</span><br>workflow = StateGraph(AgentState)<br><br><span class="hljs-comment"># 将节点函数添加到图中</span><br>workflow.add_node(<span class="hljs-string">&quot;planner&quot;</span>, planner_node)<br>workflow.add_node(<span class="hljs-string">&quot;executor&quot;</span>, executor_node)<br><br><span class="hljs-comment"># 设置图的入口点</span><br>workflow.set_entry_point(<span class="hljs-string">&quot;planner&quot;</span>)<br><br><span class="hljs-comment"># 添加常规边，连接 planner 和 executor</span><br>workflow.add_edge(<span class="hljs-string">&quot;planner&quot;</span>, <span class="hljs-string">&quot;executor&quot;</span>)<br><br><span class="hljs-comment"># 添加条件边，实现动态路由</span><br>workflow.add_conditional_edges(<br>    <span class="hljs-comment"># 起始节点</span><br>    <span class="hljs-string">&quot;executor&quot;</span>,<br>    <span class="hljs-comment"># 判断函数</span><br>    should_continue,<br>    <span class="hljs-comment"># 路由映射：将判断函数的返回值映射到目标节点</span><br>    &#123;<br>        <span class="hljs-string">&quot;continue_to_planner&quot;</span>: <span class="hljs-string">&quot;planner&quot;</span>, <span class="hljs-comment"># 如果返回&quot;continue_to_planner&quot;，则跳回planner节点</span><br>        <span class="hljs-string">&quot;end_workflow&quot;</span>: END               <span class="hljs-comment"># 如果返回&quot;end_workflow&quot;，则结束流程</span><br>    &#125;<br>)<br><br><span class="hljs-comment"># 编译图，生成可执行的应用</span><br>app = workflow.<span class="hljs-built_in">compile</span>()<br><br><span class="hljs-comment"># 运行图</span><br>inputs = &#123;<span class="hljs-string">&quot;current_task&quot;</span>: <span class="hljs-string">&quot;分析最近的AI行业新闻&quot;</span>, <span class="hljs-string">&quot;messages&quot;</span>: []&#125;<br><span class="hljs-keyword">for</span> event <span class="hljs-keyword">in</span> app.stream(inputs):<br>    <span class="hljs-built_in">print</span>(event)<br></code></pre></td></tr></table></figure><h3 id="6-5-2-三步问答助手"><a href="#6-5-2-三步问答助手" class="headerlink" title="6.5.2 三步问答助手"></a>6.5.2 三步问答助手</h3><p>在理解了 LangGraph 的核心概念之后，我们将通过一个实战案例来巩固所学。我们将构建一个简化的问答对话助手，它会遵循一个清晰、固定的三步流程来回答用户的问题：</p><ol><li><strong>理解 (Understand)</strong>：首先，分析用户的查询意图。</li><li><strong>搜索 (Search)</strong>：然后，模拟搜索与意图相关的信息。</li><li><strong>回答 (Answer)</strong>：最后，基于意图和搜索到的信息，生成最终答案。</li></ol><p>这个案例将清晰地展示如何定义状态、创建节点以及将它们线性地连接成一个完整的工作流。我们将代码分解为四个核心步骤：定义状态、创建节点、构建图、以及运行应用。</p><p>（1）定义全局状态</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> TypedDict, Annotated<br><span class="hljs-keyword">from</span> langgraph.graph.message <span class="hljs-keyword">import</span> add_messages<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">SearchState</span>(<span class="hljs-title class_ inherited__">TypedDict</span>):<br>    messages: Annotated[<span class="hljs-built_in">list</span>, add_messages]<br>    user_query: <span class="hljs-built_in">str</span>      <span class="hljs-comment"># 经过LLM理解后的用户需求总结</span><br>    search_query: <span class="hljs-built_in">str</span>    <span class="hljs-comment"># 优化后用于Tavily API的搜索查询</span><br>    search_results: <span class="hljs-built_in">str</span>  <span class="hljs-comment"># Tavily搜索返回的结果</span><br>    final_answer: <span class="hljs-built_in">str</span>    <span class="hljs-comment"># 最终生成的答案</span><br>    step: <span class="hljs-built_in">str</span>            <span class="hljs-comment"># 标记当前步骤</span><br></code></pre></td></tr></table></figure><p>我们创建了 <code>SearchState</code> 这个 <code>TypedDict</code>，为状态对象定义了一个清晰的数据模式（Schema）。一个关键的设计是同时包含了 <code>user_query</code> 和 <code>search_query</code> 字段。这允许智能体先将用户的自然语言提问，优化成更适合搜索引擎的精炼关键词，从而显著提升搜索结果的质量。</p><p>（2）定义工作流节点</p><p>定义好状态结构后，下一步是创建构成我们工作流的各个节点。在 LangGraph 中，每个节点都是一个执行具体任务的 Python 函数。这些函数接收当前的状态对象作为输入，并返回一个包含更新后字段的字典。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> os<br><span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv<br><span class="hljs-keyword">from</span> langchain_openai <span class="hljs-keyword">import</span> ChatOpenAI<br><span class="hljs-keyword">from</span> langchain_core.messages <span class="hljs-keyword">import</span> HumanMessage, AIMessage, SystemMessage<br><span class="hljs-keyword">from</span> tavily <span class="hljs-keyword">import</span> TavilyClient<br><br><span class="hljs-comment"># 加载 .env 文件中的环境变量</span><br>load_dotenv()<br><br><span class="hljs-comment"># 初始化模型</span><br><span class="hljs-comment"># 我们将使用这个 llm 实例来驱动所有节点的智能</span><br>llm = ChatOpenAI(<br>    model=os.getenv(<span class="hljs-string">&quot;LLM_MODEL_ID&quot;</span>, <span class="hljs-string">&quot;gpt-4o-mini&quot;</span>),<br>    api_key=os.getenv(<span class="hljs-string">&quot;LLM_API_KEY&quot;</span>),<br>    base_url=os.getenv(<span class="hljs-string">&quot;LLM_BASE_URL&quot;</span>, <span class="hljs-string">&quot;https://api.openai.com/v1&quot;</span>),<br>    temperature=<span class="hljs-number">0.7</span><br>)<br><span class="hljs-comment"># 初始化Tavily客户端</span><br>tavily_client = TavilyClient(api_key=os.getenv(<span class="hljs-string">&quot;TAVILY_API_KEY&quot;</span>))<br></code></pre></td></tr></table></figure><p>现在，我们来逐一创建三个核心节点。</p><p>（1） 理解与查询节点</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">understand_query_node</span>(<span class="hljs-params">state: SearchState</span>) -&gt; <span class="hljs-built_in">dict</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;步骤1：理解用户查询并生成搜索关键词&quot;&quot;&quot;</span><br>    user_message = state[<span class="hljs-string">&quot;messages&quot;</span>][-<span class="hljs-number">1</span>].content<br>    <br>    understand_prompt = <span class="hljs-string">f&quot;&quot;&quot;分析用户的查询：&quot;<span class="hljs-subst">&#123;user_message&#125;</span>&quot;</span><br><span class="hljs-string">请完成两个任务：</span><br><span class="hljs-string">1. 简洁总结用户想要了解什么</span><br><span class="hljs-string">2. 生成最适合搜索引擎的关键词（中英文均可，要精准）</span><br><span class="hljs-string"></span><br><span class="hljs-string">格式：</span><br><span class="hljs-string">理解：[用户需求总结]</span><br><span class="hljs-string">搜索词：[最佳搜索关键词]&quot;&quot;&quot;</span><br><br>    response = llm.invoke([SystemMessage(content=understand_prompt)])<br>    response_text = response.content<br>    <br>    <span class="hljs-comment"># 解析LLM的输出，提取搜索关键词</span><br>    search_query = user_message <span class="hljs-comment"># 默认使用原始查询</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-string">&quot;搜索词：&quot;</span> <span class="hljs-keyword">in</span> response_text:<br>        search_query = response_text.split(<span class="hljs-string">&quot;搜索词：&quot;</span>)[<span class="hljs-number">1</span>].strip()<br>    <br>    <span class="hljs-keyword">return</span> &#123;<br>        <span class="hljs-string">&quot;user_query&quot;</span>: response_text,<br>        <span class="hljs-string">&quot;search_query&quot;</span>: search_query,<br>        <span class="hljs-string">&quot;step&quot;</span>: <span class="hljs-string">&quot;understood&quot;</span>,<br>        <span class="hljs-string">&quot;messages&quot;</span>: [AIMessage(content=<span class="hljs-string">f&quot;我将为您搜索：<span class="hljs-subst">&#123;search_query&#125;</span>&quot;</span>)]<br>    &#125;<br></code></pre></td></tr></table></figure><p>该节点通过一个结构化的提示，要求 LLM 同时完成“意图理解”和“关键词生成”两个任务，并将解析出的专用搜索关键词更新到状态的 <code>search_query</code> 字段中，为下一步的精确搜索做好准备。</p><p>（2）搜索节点</p><p>该节点负责执行智能体的“工具使用”能力，它将调用 Tavily API 进行真实的互联网搜索，并具备基础的错误处理功能。</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">tavily_search_node</span>(<span class="hljs-params">state: SearchState</span>) -&gt; <span class="hljs-built_in">dict</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;步骤2：使用Tavily API进行真实搜索&quot;&quot;&quot;</span><br>    search_query = state[<span class="hljs-string">&quot;search_query&quot;</span>]<br>    <span class="hljs-keyword">try</span>:<br>        <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;🔍 正在搜索: <span class="hljs-subst">&#123;search_query&#125;</span>&quot;</span>)<br>        response = tavily_client.search(<br>            query=search_query, search_depth=<span class="hljs-string">&quot;basic&quot;</span>, max_results=<span class="hljs-number">5</span>, include_answer=<span class="hljs-literal">True</span><br>        )<br>        <span class="hljs-comment"># ... (处理和格式化搜索结果) ...</span><br>        search_results = ... <span class="hljs-comment"># 格式化后的结果字符串</span><br>        <br>        <span class="hljs-keyword">return</span> &#123;<br>            <span class="hljs-string">&quot;search_results&quot;</span>: search_results,<br>            <span class="hljs-string">&quot;step&quot;</span>: <span class="hljs-string">&quot;searched&quot;</span>,<br>            <span class="hljs-string">&quot;messages&quot;</span>: [AIMessage(content=<span class="hljs-string">&quot;✅ 搜索完成！正在整理答案...&quot;</span>)]<br>        &#125;<br>    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:<br>        <span class="hljs-comment"># ... (处理错误) ...</span><br>        <span class="hljs-keyword">return</span> &#123;<br>            <span class="hljs-string">&quot;search_results&quot;</span>: <span class="hljs-string">f&quot;搜索失败：<span class="hljs-subst">&#123;e&#125;</span>&quot;</span>,<br>            <span class="hljs-string">&quot;step&quot;</span>: <span class="hljs-string">&quot;search_failed&quot;</span>,<br>            <span class="hljs-string">&quot;messages&quot;</span>: [AIMessage(content=<span class="hljs-string">&quot;❌ 搜索遇到问题...&quot;</span>)]<br>        &#125;<br></code></pre></td></tr></table></figure><p>此节点通过 <code>tavily_client.search</code> 发起真实的 API 调用。它被包裹在 <code>try...except</code> 块中，用于捕获可能的异常。如果搜索失败，它会更新 <code>step</code> 状态为 <code>&quot;search_failed&quot;</code>，这个状态将被下一个节点用来触发备用方案。</p><p>（3）回答节点</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">generate_answer_node</span>(<span class="hljs-params">state: SearchState</span>) -&gt; <span class="hljs-built_in">dict</span>:<br>    <span class="hljs-string">&quot;&quot;&quot;步骤3：基于搜索结果生成最终答案&quot;&quot;&quot;</span><br>    <span class="hljs-keyword">if</span> state[<span class="hljs-string">&quot;step&quot;</span>] == <span class="hljs-string">&quot;search_failed&quot;</span>:<br>        <span class="hljs-comment"># 如果搜索失败，执行回退策略，基于LLM自身知识回答</span><br>        fallback_prompt = <span class="hljs-string">f&quot;搜索API暂时不可用，请基于您的知识回答用户的问题：\n用户问题：<span class="hljs-subst">&#123;state[<span class="hljs-string">&#x27;user_query&#x27;</span>]&#125;</span>&quot;</span><br>        response = llm.invoke([SystemMessage(content=fallback_prompt)])<br>    <span class="hljs-keyword">else</span>:<br>        <span class="hljs-comment"># 搜索成功，基于搜索结果生成答案</span><br>        answer_prompt = <span class="hljs-string">f&quot;&quot;&quot;基于以下搜索结果为用户提供完整、准确的答案：</span><br><span class="hljs-string">用户问题：<span class="hljs-subst">&#123;state[<span class="hljs-string">&#x27;user_query&#x27;</span>]&#125;</span></span><br><span class="hljs-string">搜索结果：\n<span class="hljs-subst">&#123;state[<span class="hljs-string">&#x27;search_results&#x27;</span>]&#125;</span></span><br><span class="hljs-string">请综合搜索结果，提供准确、有用的回答...&quot;&quot;&quot;</span><br>        response = llm.invoke([SystemMessage(content=answer_prompt)])<br>    <br>    <span class="hljs-keyword">return</span> &#123;<br>        <span class="hljs-string">&quot;final_answer&quot;</span>: response.content,<br>        <span class="hljs-string">&quot;step&quot;</span>: <span class="hljs-string">&quot;completed&quot;</span>,<br>        <span class="hljs-string">&quot;messages&quot;</span>: [AIMessage(content=response.content)]<br>    &#125;<br></code></pre></td></tr></table></figure><p>该节点通过检查 <code>state[&quot;step&quot;]</code> 的值来执行条件逻辑。如果搜索失败，它会利用 LLM 的内部知识回答并告知用户情况。如果搜索成功，它则会使用包含实时搜索结果的提示，来生成一个有时效性且有据可依的回答。</p><p>（4）构建图</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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> langgraph.graph <span class="hljs-keyword">import</span> StateGraph, START, END<br><span class="hljs-keyword">from</span> langgraph.checkpoint.memory <span class="hljs-keyword">import</span> InMemorySaver<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_search_assistant</span>():<br>    workflow = StateGraph(SearchState)<br>    <br>    <span class="hljs-comment"># 添加节点</span><br>    workflow.add_node(<span class="hljs-string">&quot;understand&quot;</span>, understand_query_node)<br>    workflow.add_node(<span class="hljs-string">&quot;search&quot;</span>, tavily_search_node)<br>    workflow.add_node(<span class="hljs-string">&quot;answer&quot;</span>, generate_answer_node)<br>    <br>    <span class="hljs-comment"># 设置线性流程</span><br>    workflow.add_edge(START, <span class="hljs-string">&quot;understand&quot;</span>)<br>    workflow.add_edge(<span class="hljs-string">&quot;understand&quot;</span>, <span class="hljs-string">&quot;search&quot;</span>)<br>    workflow.add_edge(<span class="hljs-string">&quot;search&quot;</span>, <span class="hljs-string">&quot;answer&quot;</span>)<br>    workflow.add_edge(<span class="hljs-string">&quot;answer&quot;</span>, END)<br>    <br>    <span class="hljs-comment"># 编译图</span><br>    memory = InMemorySaver()<br>    app = workflow.<span class="hljs-built_in">compile</span>(checkpointer=memory)<br>    <span class="hljs-keyword">return</span> app<br></code></pre></td></tr></table></figure><p>（5）运行案例展示 </p><p>运行此脚本后，您可以提出一些需要实时信息的问题，例如我们第一章中的案例：<code>明天我要去北京，天气怎么样？有合适的景点吗</code></p><p>您会看到终端清晰地展示出智能体的“思考”过程：</p><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><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><code class="hljs markdown">🔍 智能搜索助手启动！<br>我会使用Tavily API为您搜索最新、最准确的信息<br>支持各种问题：新闻、技术、知识问答等<br>(输入 &#x27;quit&#x27; 退出)<br><br>🤔 您想了解什么: 明天我要去北京，天气怎么样？有合适的景点吗<br><br>============================================================<br>🧠 理解阶段: 我理解您的需求：理解：用户想了解明天北京的天气情况以及合适的景点推荐。  <br>搜索词：北京 明天 天气 景点推荐 Beijing weather tomorrow attractions<br>🔍 正在搜索: 北京 明天 天气 景点推荐 Beijing weather tomorrow attractions<br>🔍 搜索阶段: ✅ 搜索完成！找到了相关信息，正在为您整理答案...<br><br>💡 最终回答:<br>明天（2025年9月17日）北京的天气预报显示，预计将是多云，气温范围在17°C（62°F）到25°C（77°F）之间。这种温和的天气非常适合户外活动。<br><br><span class="hljs-section">### 合适的景点推荐：</span><br><span class="hljs-bullet">1.</span> <span class="hljs-strong">**长城**</span>：作为中国最著名的历史遗址之一，长城是必游之地。你可以选择八达岭或慕田峪这些较为受欢迎的段落进行游览。<br><br><span class="hljs-bullet">2.</span> <span class="hljs-strong">**故宫**</span>：故宫是明清两代的皇宫，拥有丰富的历史和文化，适合对中国历史感兴趣的游客。<br><br><span class="hljs-bullet">3.</span> <span class="hljs-strong">**天安门广场**</span>：这是中国的象征之一，广场上有许多重要的建筑和纪念碑，适合拍照留念。<br><br><span class="hljs-bullet">4.</span> <span class="hljs-strong">**颐和园**</span>：一个非常美丽的皇家园林，适合漫步和欣赏自然风光，尤其是湖泊和古建筑。<br><br><span class="hljs-bullet">5.</span> <span class="hljs-strong">**798艺术区**</span>：如果你对现代艺术感兴趣，798艺术区是一个集艺术、文化和创意于一体的地方，适合探索和拍摄。<br><br><span class="hljs-section">### 小贴士：</span><br><span class="hljs-bullet">-</span> 由于明天天气良好，建议提前规划出行路线，并准备一些水和小吃，以便在游览时保持充足的体力。<br><span class="hljs-bullet">-</span> 由于天气变化可能会影响游览体验，建议查看实时天气更新。<br><br>希望这些信息能帮助你安排一个愉快的北京之旅！如果你需要更多关于景点的信息或者旅行建议，欢迎随时询问。<br><br>============================================================<br><br>🤔 您想了解什么:<br></code></pre></td></tr></table></figure><p>并且他是一个可以持续交互的助手，你也可以继续向他发问。</p><h3 id="6-5-3-LangGraph-的优势与局限性分析"><a href="#6-5-3-LangGraph-的优势与局限性分析" class="headerlink" title="6.5.3 LangGraph 的优势与局限性分析"></a>6.5.3 LangGraph 的优势与局限性分析</h3><p>任何技术框架都有其特定的适用场景和设计权衡。在本节中，我们将客观地分析 LangGraph 的核心优势及其在实际应用中可能面临的局限性。</p><p>（1）优势</p><ul><li><p>如我们的智能搜索助手案例所示，LangGraph 将一个完整的实时问答流程，显式地定义为一个由状态、节点和边构成的“流程图”。这种设计的最大优势是<strong>高度的可控性与可预测性</strong>。开发者可以精确地规划智能体的每一步行为，这对于构建需要高可靠性和可审计性的生产级应用至关重要。其最强大的特性在于对<strong>循环（Cycles）的原生支持</strong>。通过条件边，我们可以轻松构建“反思-修正”循环，例如在我们的案例中，如果搜索失败，可以设计一个回退到备用方案的路径。这是构建能够自我优化和具备容错能力的智能体的关键。</p></li><li><p>此外，由于每个节点都是一个独立的 Python 函数，这带来了<strong>高度的模块化</strong>。同时，在流程中插入一个等待人类审核的节点也变得非常直接，为实现可靠的“人机协作”（Human-in-the-loop）提供了坚实的基础。</p></li></ul><p>（2）局限性</p><ul><li><p>与基于对话的框架相比，LangGraph 需要开发者编写更多的<strong>前期代码（Boilerplate）</strong>。定义状态、节点、边等一系列操作，使得对于简单任务而言，开发过程显得更为繁琐。开发者需要更多地思考“如何控制流程（how）”，而不仅仅是“做什么（what）”。由于工作流是预先定义的，LangGraph 的行为虽然可控，但也缺少了对话式智能体那种动态的、<strong>“涌现”式的交互</strong>。它的强项在于执行一个确定的、可靠的流程，而非模拟开放式的、不可预测的社会性协作。</p></li><li><p>调试过程同样存在挑战。虽然流程比对话历史更清晰，但问题可能出在多个环节：某个节点内部的逻辑错误、在节点间传递的状态数据发生异变，或是边跳转的条件判断失误。这要求开发者对整个图的运行机制有全局性的理解。</p></li></ul><h2 id="6-6-本章小结"><a href="#6-6-本章小结" class="headerlink" title="6.6 本章小结"></a>6.6 本章小结</h2><p>本章我们感受了目前最前沿的一些智能体框架，通过案例的形式进行实操体验。</p><p>我们看到，每一个框架都有自己实现智能体构建的思路：</p><ul><li><strong>AutoGen</strong> 将复杂的协作抽象为一场由多角色参与的、可自动进行的“群聊”，其核心在于“以对话驱动协作”。</li><li><strong>AgentScope</strong> 则着眼于工业级应用的健壮性与可扩展性，为构建高并发、分布式的多智能体系统提供了坚实的工程基础。</li><li><strong>CAMEL</strong> 以其轻量级的“角色扮演”和“引导性提示”范式，展示了如何用最少的代码激发两个专家智能体之间深度、自主的协作。</li><li><strong>LangGraph</strong> 则回归到更底层的“状态机”模型，通过显式的图结构赋予开发者对工作流的精确控制，尤其是其循环能力，为构建可反思、可修正的智能体铺平了道路。</li></ul><p>通过对这些框架的深入分析，我们可以提炼出一个设计的权衡：<strong>“涌现式协作”与“显式控制”之间的选择</strong>。AutoGen 和 CAMEL 更多地依赖于定义智能体的“角色”和“目标”，让复杂的协作行为从简单的对话规则中“涌现”出来，这种方式更贴近人类的交互模式，但有时难以预测和调试。而 LangGraph 要求开发者明确地定义每一个步骤和跳转条件，牺牲了一部分“涌现”的惊喜，换来了高度的可靠性、可控性和可观测性。同时，AgentScope 则揭示了第二个同样重要的维度：<strong>工程化</strong>。无论我们选择哪种协作范式，要将其从实验原型推向生产应用，都必须面对并发、容错、分布式部署等工程挑战。AgentScope 正是为解决这些问题而生，它代表了从“能运行”到“能稳定服务”的关键跨越。</p><p>总而言之，智能体并非只有一种构建方式。深入理解本章探讨的框架设计哲学，能让我们不仅仅成为更优秀的“工具使用者”，更能理解框架设计中的各种优劣与权衡。</p><p>在下一章中，我们将进入本教程的核心内容，从零开始，亲手构建一个属于我们自己的智能体框架，将所有理论与实践融会贯通。</p><h2 id="习题"><a href="#习题" class="headerlink" title="习题"></a>习题</h2><ol><li><p>本章介绍了四个各具特色的智能体框架：<code>AutoGen</code>、<code>AgentScope</code>、<code>CAMEL</code> 和 <code>LangGraph</code>。请分析：</p><ul><li>在6.1.2节的表6.1中，对比了这四个框架的多个维度。请选择其中两个你最熟悉的框架，从”协作模式”、”控制方式”、”适用场景”三个维度进一步深入对比。</li><li>本章提到了”涌现式协作”与”显式控制”之间的权衡，如何理解这两种设计哲学的含义。</li></ul></li><li><p>在6.2节的 <code>AutoGen</code> 案例中，我们构建了一个”软件开发团队”。请基于此案例进行扩展思考：</p><blockquote><p><strong>提示</strong>：这是一道动手实践题，建议实际操作</p></blockquote><ul><li>当前的团队使用 <code>RoundRobinGroupChat</code>（轮询群聊）模式，智能体按固定顺序发言。如果需求变更，工程师的代码需要返回给产品经理重新审核，应该如何修改协作流程？请设计一个支持”动态回退”的机制。</li><li>在案例中，我们通过 <code>System Message</code> 为每个智能体定义了角色和职责。请尝试为这个团队添加一个新角色”测试工程师”（<code>Quality Assurance</code>），并设计其系统消息，使其能够在代码审查后执行自动化测试。</li><li><code>AutoGen</code> 的对话式协作存在可能的不稳定性，可能导致对话偏离主题或陷入循环。请思考：如何设计一套”对话质量监控”机制，在检测到异常时及时干预？</li></ul></li><li><p>在6.3节的 <code>AgentScope</code> 案例中，我们实现了一个”三国狼人杀”游戏。请深入分析：</p><ul><li>案例中使用了 <code>MsgHub</code>（消息中心）来管理智能体间的通信。请解释消息驱动架构相比传统函数调用的优势是什么？在什么场景下这种架构特别有价值？</li><li>游戏中使用了结构化输出（如 <code>DiscussionModelCN</code>、<code>WitchActionModelCN</code>）来约束智能体行为。请设计一个新的游戏角色”猎人”，并定义其对应的结构化输出模型，包括字段定义和验证规则。</li><li><code>AgentScope</code> 支持分布式部署，这意味着不同的智能体可以运行在不同的服务器上。请思考：在”三国狼人杀”这样的实时游戏场景中，分布式部署会带来哪些技术挑战？如何保证消息的顺序性和一致性？</li></ul></li><li><p>在6.4节的 <code>CAMEL</code> 案例中，我们让心理学家和作家协作创作电子书。</p><ul><li>在案例中，协作会在检测到 <code>&lt;CAMEL_TASK_DONE&gt;</code> 标志时强制终止。但如果两个智能体意见分歧（一位认为可以终止，一位认为不应该终止），无法达成一致怎么办？请设计一个”冲突解决”的兼容机制。</li><li><code>CAMEL</code> 最初设计用于双智能体协作，但现在已经扩展支持多智能体。请查阅 <code>CAMEL</code> 的最新文档，了解其多智能体协作模块 <a href="https://docs.camel-ai.org/key_modules/workforce"><code>workforce</code></a>，并结合架构图说明其与 <code>AutoGen</code> 的群聊模式有何不同。</li></ul></li><li><p>在6.5节的 <code>LangGraph</code> 案例中，我们构建了一个”三步问答助手”。请分析：</p><ul><li><code>LangGraph</code> 将智能体流程建模为状态机和有向图。请画出案例中”理解-搜索-回答”流程的图结构，标注节点、边和状态转换条件。</li><li>当前的助手是一个线性流程。请扩展这个案例，添加一个”反思”节点：如果生成的答案质量低（例如过于简短或缺乏细节），系统应该重新搜索或重新生成答案。请设计这个循环机制的条件边逻辑。</li><li><code>LangGraph</code> 的优势在于对循环的原生支持。请设计一个更复杂的应用场景，充分利用这一特性：例如”代码生成-测试-修复”循环、”论文写作-审阅-修改”循环等。要求画出完整的图结构并说明关键节点的功能。</li></ul></li><li><p>框架选型是智能体产品开发过程中的关键决策之一。假设你是一家 <code>AI</code> 公司的技术架构师，公司计划开发以下三个智能体产品应用，请为每个应用选择最合适的框架（<code>AutoGen</code>、<code>AgentScope</code>、<code>CAMEL</code>、<code>LangGraph</code> 或不借助框架从零开发），并详细说明理由：</p><p><strong>应用A</strong>：智能客服系统，需要处理大量并发用户请求（每秒1000+），要求响应时间低于2秒，系统需要7×24小时稳定运行，并支持水平扩展。</p><p><strong>应用B</strong>：科研论文辅助写作平台，需要一个”研究员智能体”和一个”写作智能体”深度协作，共同完成文献综述、实验设计、数据分析和论文撰写。要求智能体能够进行多轮深度讨论，自主推进任务。</p><p><strong>应用C</strong>：金融风控审批系统，需要按照严格的流程处理贷款申请：资料审核 → 风险评估 → 额度计算 → 合规检查 → 人工复核 → 最终决策。每个环节都有明确的判断标准和分支逻辑，要求流程可追溯、可审计。</p></li></ol><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>[1] Wu Q, Bansal G, Zhang J, et al. Autogen: Enabling next-gen LLM applications via multi-agent conversations[C]&#x2F;&#x2F;First Conference on Language Modeling. 2024.</p><p>[2] Gao D, Li Z, Pan X, et al. Agentscope: A flexible yet robust multi-agent platform[J]. arXiv preprint arXiv:2402.14034, 2024.</p><p>[3] Li G, Hammoud H, Itani H, et al. Camel: Communicative agents for” mind” exploration of large language model society[J]. Advances in Neural Information Processing Systems, 2023, 36: 51991-52008.</p><p>[4] LangChain. LangGraph [EB&#x2F;OL]. (2024). <a href="https://github.com/langchain-ai/langgraph">https://github.com/langchain-ai/langgraph</a>.</p><p>[5] Microsoft. AutoGen - UserProxyAgent [EB&#x2F;OL]. (2024). <a href="https://microsoft.github.io/autogen/stable/reference/python/autogen_agentchat.agents.html#autogen_agentchat.agents.UserProxyAgent">https://microsoft.github.io/autogen/stable/reference/python/autogen_agentchat.agents.html#autogen_agentchat.agents.UserProxyAgent</a>.</p>]]>
    </content>
    <id>http://jasondong97.github.io/2026/03/01/ai-agent-learning/%E7%AC%AC6%E7%AB%A0-%E6%A1%86%E6%9E%B6%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5/</id>
    <link href="http://jasondong97.github.io/2026/03/01/ai-agent-learning/%E7%AC%AC6%E7%AB%A0-%E6%A1%86%E6%9E%B6%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5/"/>
    <published>2026-03-01T14:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="第六章-框架开发实践"><a href="#第六章-框架开发实践" class="headerlink" title="第六章 框架开发实践"></a>第六章 框架开发实践</h1><p>在第四章中，我们通过编写原生代码，实现了 ReAct、Plan-and-So]]>
    </summary>
    <title>第六章 框架开发实践</title>
    <updated>2026-03-08T09:24:16.322Z</updated>
  </entry>
</feed>
