<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Eugene&#39;s Page</title>
    <link>https://eugenepage.com/</link>
    
    <atom:link href="https://eugenepage.com/rss.xml" rel="self" type="application/rss+xml"/>
    
    <description>Notes and projects by Eugene Hsuan on AI, game development, virtual production, and film.</description>
    <pubDate>Fri, 25 Sep 2026 10:08:25 GMT</pubDate>
    <generator>http://hexo.io/</generator>
    
    <item>
      <title></title>
      <link>https://eugenepage.com/zh-CN/2026/09/25/20250801.UEConceptScrapbox/</link>
      <guid>https://eugenepage.com/zh-CN/2026/09/25/20250801.UEConceptScrapbox/</guid>
      <pubDate>Fri, 25 Sep 2026 10:08:25 GMT</pubDate>
      
        
        
      <description>&lt;h2 id=&quot;UE“反射”概念：&quot;&gt;&lt;a href=&quot;#UE“反射”概念：&quot; class=&quot;headerlink&quot; title=&quot;UE“反射”概念：&quot;&gt;&lt;/a&gt;UE“反射”概念：&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;反射&lt;/strong&gt;：UE 通过 UClass&amp;#x2</description>
        
      
      
      
      <content:encoded><![CDATA[<h2 id="UE“反射”概念："><a href="#UE“反射”概念：" class="headerlink" title="UE“反射”概念："></a>UE“反射”概念：</h2><ul><li><strong>反射</strong>：UE 通过 UClass&#x2F;UProperty 等系统在运行时提供类型信息和动态访问能力。<br>UE 的反射系统是通过 UHT 工具和特定宏实现的代码生成机制。你用 UCLASS 标记类、UPROPERTY 标记变量、UFUNCTION 标记函数，这些宏会被 UHT 识别。<br>UHT 在编译前扫描这些标记，生成.generated.h 和.cpp 文件，里面包含类的反射注册代码，比如 StaticClass () 函数和 UClass 对象的构造逻辑。<br>生成的代码会把类信息注册到引擎全局的 GObjectClasses 数组里，让引擎在运行时能动态获取类结构、调用函数或访问属性，这支撑了蓝图交互、垃圾回收等核心功能。</li></ul><p>因为 UE 需要在运行时动态处理代码信息。比如蓝图可视化编程，引擎得通过反射知道 C++ 类有哪些函数和变量，才能让蓝图调用它们。</p><p>比如你在 C++ 里写了一个角色类，里面有个 UFUNCTION 标记的跳跃函数 Jump ()。没有反射的话，蓝图编辑器根本不知道这个 Jump () 函数存在，因为编译后的机器码里，函数名和参数这些信息都被优化掉了。<br>有了反射，UHT 会在编译时为这个 Jump () 函数生成反射元数据，包括函数名、参数类型、返回值，以及它属于哪个类。引擎运行时能通过这些元数据，在蓝图编辑器里把 Jump () 函数显示出来，你才能拖拽节点调用它。<br>如果后续你在 C++ 里给 Jump () 加了一个高度参数，反射系统会自动更新元数据，蓝图里对应的函数节点也会同步显示出新参数，整个过程不需要手动写任何蓝图和 C++ 交互的绑定代码。</p><h2 id="回退操作-Command-模式（轻量级）："><a href="#回退操作-Command-模式（轻量级）：" class="headerlink" title="回退操作 Command 模式（轻量级）：**"></a>回退操作 Command 模式（轻量级）：**</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">每次操作封装为 ICommand &#123; Do(); Undo(); &#125;</span><br><span class="line">维护 undoStack 和 redoStack</span><br><span class="line">执行操作 → 压入 undoStack，清空 redoStack</span><br><span class="line">Undo → 弹出 undoStack，执行 Undo()，压入 redoStack</span><br></pre></td></tr></table></figure><p><strong>2. Snapshot 模式（适用于复杂场景）：</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></pre></td><td class="code"><pre><span class="line">操作前序列化整个对象状态的快照</span><br><span class="line">Undo 时直接恢复快照</span><br><span class="line">优点：实现简单，不容易出 bug</span><br><span class="line">缺点：内存开销大</span><br></pre></td></tr></table></figure><p><strong>实际项目中的混合方案：</strong></p><ul><li>简单属性修改 → Command 模式（记录 oldValue&#x2F;newValue）</li><li>复杂操作（节点图变更、场景编辑）→ Snapshot 或 Diff 模式</li><li>合并机制：连续同类操作合并（如拖拽 Slider 时合并为一条记录）</li></ul><h2 id="UE智能指针对比表"><a href="#UE智能指针对比表" class="headerlink" title="UE智能指针对比表"></a>UE智能指针对比表</h2><table><thead><tr><th>指针类型</th><th>管理对象</th><th>所有权</th><th>核心作用</th><th>适用场景</th></tr></thead><tbody><tr><td>TObjectPtr</td><td>UObject派生类</td><td>共享</td><td>安全访问UObject，自动参与垃圾回收</td><td>替代传统UPROPERTY指针，日常UObject引用</td></tr><tr><td>TWeakObjectPtr</td><td>UObject派生类</td><td>无</td><td>弱引用UObject，不阻止回收</td><td>避免循环引用，临时访问可能被销毁的UObject</td></tr><tr><td>TSoftObjectPtr</td><td>UObject派生类</td><td>无</td><td>软引用UObject，支持资源异步加载</td><td>引用可能未加载的资源，如关卡外的模型、纹理</td></tr><tr><td>TSharedPtr</td><td>非UObject类型</td><td>共享</td><td>通过引用计数管理生命周期</td><td>需要多持有者共享非UObject资源</td></tr><tr><td>TUniquePtr</td><td>非UObject类型</td><td>独占</td><td>唯一拥有对象，不可复制</td><td>管理无需共享的非UObject资源，如自定义数据结构</td></tr><tr><td>TWeakPtr</td><td>非UObject类型</td><td>无</td><td>弱引用TSharedPtr，不增加引用计数</td><td>配合TSharedPtr避免循环引用</td></tr></tbody></table><h2 id="关键区别说明"><a href="#关键区别说明" class="headerlink" title="关键区别说明"></a>关键区别说明</h2><ol><li><p><strong>管理对象边界</strong>：前三种严格用于UObject派生类，依赖UE垃圾回收系统；后三种用于非UObject类型，靠手动内存管理机制。</p></li><li><p><strong>UObject指针细分</strong>：</p></li></ol><ul><li>TObjectPtr是强引用，会让UObject保持存活，是日常开发的首选。</li><li>TWeakObjectPtr是弱引用，当UObject被标记为回收时，指针会自动置空，常用在UI控件引用角色对象这类场景。</li><li>TSoftObjectPtr存储的是资源路径而非直接内存地址，对象未加载时可异步加载，适合开放世界游戏引用远处的资源。</li></ul><ol start="3"><li><strong>非UObject指针细分</strong>：</li></ol><ul><li>TSharedPtr通过引用计数共享对象，当引用计数为0时自动释放内存，但需注意手动避免循环引用。</li><li>TUniquePtr是独占式指针，不允许复制，只能通过移动语义转移所有权，性能开销最小。</li><li>TWeakPtr需要绑定到TSharedPtr使用，当TSharedPtr释放对象后，TWeakPtr会自动失效，解决循环引用问题。</li></ul><h2 id="ECS-架构是什么？和传统-OOP-有什么区别？"><a href="#ECS-架构是什么？和传统-OOP-有什么区别？" class="headerlink" title="ECS 架构是什么？和传统 OOP 有什么区别？"></a>ECS 架构是什么？和传统 OOP 有什么区别？</h2><table><thead><tr><th></th><th>OOP</th><th>ECS</th></tr></thead><tbody><tr><td>数据布局</td><td>对象分散在堆上</td><td>Component 连续内存排列</td></tr><tr><td>缓存友好性</td><td>差（指针跳转）</td><td>好（数据局部性）</td></tr><tr><td>逻辑组织</td><td>方法绑定在类上</td><td>System 独立遍历 Component</td></tr><tr><td>组合性</td><td>需要多重继承&#x2F;组合模式</td><td>天然组合（挂 Component 即可）</td></tr><tr><td>其实ECS节省的是cpu去查找的时间。</td><td></td><td></td></tr></tbody></table><p><strong>核心概念：</strong></p><ul><li><strong>Entity</strong>：ID 标识，不存数据</li><li><strong>Component</strong>：纯数据（Position, Velocity, Health…）</li><li><strong>System</strong>：纯逻辑（MovementSystem 遍历所有 Position+Velocity 组件）</li></ul><p>核心区别：OOP 以对象为核心，数据与逻辑封装在类中，易形成复杂继承树；ECS 将数据与逻辑分离，实体为组件容器，系统批量处理同类组件，数据连续存储提升缓存效率，支持动态组合与并行计算。<br>UE5 Mass 系统案例：作为 ECS 实现，Mass 用 “片段” 存储实体数据，“处理器” 统一处理逻辑。如《黑客帝国》Demo 中的万人级 crowd 模拟，通过将角色位置、速度等数据打包连续存储，移动处理器可批量更新所有角色坐标，性能远超传统 Actor 方案。</p><h2 id="堆Stack-栈heap"><a href="#堆Stack-栈heap" class="headerlink" title="堆Stack 栈heap"></a>堆Stack 栈heap</h2><ul><li><strong>堆（Heap）</strong>：动态分配内存，大小不固定，生命周期由程序员控制，访问速度较慢，适合存储大对象或需要在运行时确定大小的数据。（没有固定的存取顺序）</li><li><strong>栈（Stack）</strong>：自动分配内存，大小固定，生命周期由函数调用控制，访问速度快，适合存储局部变量和函数参数。（有固定的存取顺序，后进先出）</li></ul><h2 id="Function-Calling-的原理是什么？你在项目中怎么用的？"><a href="#Function-Calling-的原理是什么？你在项目中怎么用的？" class="headerlink" title="Function Calling 的原理是什么？你在项目中怎么用的？"></a>Function Calling 的原理是什么？你在项目中怎么用的？</h2><p><strong>原理：</strong> LLM 不直接执行函数，而是 <strong>输出结构化的函数调用意图</strong>（函数名 + 参数），由宿主程序解析并执行。</p><h2 id="RAG-是什么？你是怎么实现的？"><a href="#RAG-是什么？你是怎么实现的？" class="headerlink" title="RAG 是什么？你是怎么实现的？"></a>RAG 是什么？你是怎么实现的？</h2><p><strong>RAG（Retrieval-Augmented Generation）</strong> &#x3D; 先检索相关文档，再让 LLM 基于检索结果回答。</p><h2 id="ControlNet-是什么？它解决了什么问题？"><a href="#ControlNet-是什么？它解决了什么问题？" class="headerlink" title="ControlNet 是什么？它解决了什么问题？"></a>ControlNet 是什么？它解决了什么问题？</h2><p><strong>参考答案：</strong></p><p><strong>ControlNet</strong> 为预训练 Diffusion Model 添加 <strong>空间控制能力</strong>。</p><p><strong>解决的问题：</strong> 原始 Text-to-Image 无法精确控制生成图像的构图、姿态、边缘等空间结构。</p><p><strong>原理：</strong></p><ul><li>在 Stable Diffusion 的 U-Net 每个 Block 上添加一个并行的 “Zero Convolution” 分支</li><li>输入额外的条件图（边缘检测&#x2F;Canny、深度图、姿态&#x2F;OpenPose、法线贴图等）</li><li>训练时只训练 ControlNet 分支，冻结原始模型</li></ul><p><strong>常见 ControlNet 类型：</strong></p><ul><li>Canny Edge：控制轮廓</li><li>Depth：控制深度结构</li><li>OpenPose：控制人物姿态</li><li>Segment：控制区域分割</li><li>Scribble：控制草图</li></ul><h2 id="LoRA-是什么？为什么它很受欢迎？"><a href="#LoRA-是什么？为什么它很受欢迎？" class="headerlink" title="LoRA 是什么？为什么它很受欢迎？"></a>LoRA 是什么？为什么它很受欢迎？</h2><p><strong>LoRA（Low-Rank Adaptation）</strong> 是一种参数高效微调方法。<br>LoRA 是一种参数高效的大模型微调技术，核心是冻结原模型权重，仅训练少量低秩矩阵来模拟任务适配所需的参数更新。它参数量仅为全量微调的 0.1%-1%，大幅降低显存占用和训练成本，且推理时可合并权重无额外延迟。<br>在游戏领域，能快速微调图生图模型生成风格统一的角色装备、场景素材，或微调对话模型让 NPC 生成符合设定的自然台词，适配小团队高效开发需求。</p><h2 id="MVC、MVP、MVVM-的区别是什么？"><a href="#MVC、MVP、MVVM-的区别是什么？" class="headerlink" title="MVC、MVP、MVVM 的区别是什么？"></a>MVC、MVP、MVVM 的区别是什么？</h2><table><thead><tr><th>模式</th><th>组件职责</th><th>组件关系</th><th>优缺点</th></tr></thead><tbody><tr><td>MVC</td><td>Model（数据）<br>View（界面）<br>Controller（逻辑）</td><td>Controller 直接操作 Model 和 View</td><td>简单直观，适合小型项目；Controller 可能变得臃肿</td></tr><tr><td>MVP</td><td>Model（数据）<br>View（界面）<br>Presenter（逻辑）</td><td>Presenter 直接操作 Model，间接更新 View</td><td>Presenter 可测试性强；View 依赖 Presenter，增加耦合</td></tr><tr><td>MVVM</td><td>Model（数据）<br>View（界面）<br>ViewModel（逻辑）</td><td>ViewModel 直接操作 Model，通过数据绑定更新 View</td><td>双向绑定简化 UI 更新；学习曲线较陡峭，可能引入性能问题</td></tr></tbody></table><ul><li>MVP的Preseter和MVVM的ViewModel在职责上非常相似，都是处理业务逻辑和数据交互的中介，但MVVM通过数据绑定机制让ViewModel直接更新View，减少了Presenter中大量的UI更新代码，使得代码更简洁、可测试性更强。MVVM适合复杂UI交互较多的项目，而MVP则更适合简单UI或需要严格分离测试的场景。</li></ul><h2 id="GPU-渲染流水线的完整阶段？"><a href="#GPU-渲染流水线的完整阶段？" class="headerlink" title="GPU 渲染流水线的完整阶段？"></a>GPU 渲染流水线的完整阶段？</h2><p><strong>参考答案：</strong></p><p>GPU 渲染管线（Rendering Pipeline）是 GPU 执行图形渲染的完整流程：</p><p><strong>应用阶段（CPU 侧）：</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></pre></td><td class="code"><pre><span class="line">1. 应用阶段（Application Stage）</span><br><span class="line">   → 视锥体裁剪（Frustum Culling）</span><br><span class="line">   → 批次合批（Draw Call Batching）</span><br><span class="line">   → 输出 Draw Call + 顶点数据到 GPU</span><br></pre></td></tr></table></figure><p><strong>几何阶段（GPU 顶点着色器）：</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></pre></td><td class="code"><pre><span class="line">2. 顶点着色器（Vertex Shader）</span><br><span class="line">   → 模型空间 → 世界空间 → 视图空间 → 齐次裁剪空间</span><br><span class="line">   → 顶点变换：LocalMatrix × WorldMatrix × ViewMatrix × ProjectionMatrix</span><br><span class="line"></span><br><span class="line">3. 曲面细分（Tessellation，可选）</span><br><span class="line">   → Hull Shader → Tessellator → Domain Shader</span><br><span class="line"></span><br><span class="line">4. 几何着色器（Geometry Shader，可选）</span><br><span class="line">   → 以图元为单位处理，可生成/销毁图元</span><br></pre></td></tr></table></figure><p><strong>光栅化阶段（Rasterization）：</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></pre></td><td class="code"><pre><span class="line">5. 图元装配 &amp; 裁剪</span><br><span class="line">   → Clipping（齐次空间裁剪）</span><br><span class="line">   → Perspective Divide → NDC → Viewport Transform</span><br><span class="line"></span><br><span class="line">6. 背面剔除（Back-face Culling）</span><br><span class="line">   → 根据顶 点环绕顺序（顺时针/逆时针）剔除背面</span><br><span class="line"></span><br><span class="line">7. 光栅化（Rasterization）</span><br><span class="line">   → 离散化：点/线/三角形 → 片段（Fragment）</span><br><span class="line">   → 视口变换：NDC → Screen Space</span><br></pre></td></tr></table></figure><p><strong>片段&#x2F;像素阶段：</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></pre></td><td class="code"><pre><span class="line">8. 片段着色器（Fragment / Pixel Shader）</span><br><span class="line">   → 逐像素着色：光照计算、纹理采样、颜色输出</span><br><span class="line"></span><br><span class="line">9. 逐片段操作（Per-Fragment Operations）</span><br><span class="line">   → 深度测试（Depth Test / Z-Test）</span><br><span class="line">   → 模板测试（Stencil Test）</span><br><span class="line">   → 混合（Alpha Blending）</span><br><span class="line">   → 输出到 Framebuffer</span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      
      
      <comments>https://eugenepage.com/zh-CN/2026/09/25/20250801.UEConceptScrapbox/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title></title>
      <link>https://eugenepage.com/2026/09/25/20250801.UEConceptScrapbox/</link>
      <guid>https://eugenepage.com/2026/09/25/20250801.UEConceptScrapbox/</guid>
      <pubDate>Fri, 25 Sep 2026 10:08:25 GMT</pubDate>
      
        
        
      <description>&lt;p&gt;lang: “en”&lt;/p&gt;
&lt;h2 id=&quot;UE-“Reflection”-Concept&quot;&gt;&lt;a href=&quot;#UE-“Reflection”-Concept&quot; class=&quot;headerlink&quot; title=&quot;UE “Reflection” Concept&quot;&gt;&lt;/a</description>
        
      
      
      
      <content:encoded><![CDATA[<p>lang: “en”</p><h2 id="UE-“Reflection”-Concept"><a href="#UE-“Reflection”-Concept" class="headerlink" title="UE “Reflection” Concept"></a>UE “Reflection” Concept</h2><ul><li><strong>Reflection</strong>: UE provides runtime type information and dynamic access capabilities through systems like UClass and UProperty.<br>UE’s reflection system is a code-generation mechanism implemented via the UHT tool and specific macros. You mark classes with UCLASS, variables with UPROPERTY, and functions with UFUNCTION — these macros are recognized by UHT.<br>Before compilation, UHT scans these markers and generates <code>.generated.h</code> and <code>.cpp</code> files containing the reflection registration code for each class, such as the <code>StaticClass()</code> function and the construction logic for UClass objects.<br>The generated code registers class information into the engine’s global <code>GObjectClasses</code> array, enabling the engine at runtime to dynamically retrieve class structure, invoke functions, or access properties — which in turn powers blueprint interaction, garbage collection, and other core features.</li></ul><p>This exists because UE needs to dynamically process code information at runtime. Blueprint visual scripting, for example, requires the engine to know via reflection which functions and variables a C++ class exposes so that blueprints can call them.</p><p>Say you write a character class in C++ with a <code>Jump()</code> function marked with UFUNCTION. Without reflection, the blueprint editor has no idea <code>Jump()</code> exists — the function name, parameters, and all that metadata get optimized away in the compiled machine code.<br>With reflection, UHT generates reflection metadata for <code>Jump()</code> at compile time, including its name, parameter types, return value, and which class it belongs to. At runtime, the engine uses this metadata to surface <code>Jump()</code> in the blueprint editor as a callable node you can drag in.<br>If you later add a height parameter to <code>Jump()</code> in C++, the reflection system automatically updates the metadata, and the corresponding blueprint node syncs up to show the new parameter — no manual binding code between blueprint and C++ required.</p><h2 id="Undo-Redo-—-Command-Pattern-Lightweight"><a href="#Undo-Redo-—-Command-Pattern-Lightweight" class="headerlink" title="Undo&#x2F;Redo — Command Pattern (Lightweight)"></a>Undo&#x2F;Redo — Command Pattern (Lightweight)</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Wrap each operation as ICommand &#123; Do(); Undo(); &#125;</span><br><span class="line">Maintain undoStack and redoStack</span><br><span class="line">Execute operation → push to undoStack, clear redoStack</span><br><span class="line">Undo → pop from undoStack, call Undo(), push to redoStack</span><br></pre></td></tr></table></figure><p><strong>2. Snapshot Pattern (for complex scenarios):</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></pre></td><td class="code"><pre><span class="line">Serialize a snapshot of the entire object state before each operation</span><br><span class="line">On Undo, restore the snapshot directly</span><br><span class="line">Pros: simple to implement, less prone to bugs</span><br><span class="line">Cons: high memory overhead</span><br></pre></td></tr></table></figure><p><strong>Hybrid approach for real projects:</strong></p><ul><li>Simple property edits → Command pattern (record oldValue&#x2F;newValue)</li><li>Complex operations (node graph changes, scene edits) → Snapshot or Diff pattern</li><li>Merge mechanism: collapse consecutive operations of the same type (e.g., dragging a slider merges into a single history entry)</li></ul><h2 id="UE-Smart-Pointer-Comparison"><a href="#UE-Smart-Pointer-Comparison" class="headerlink" title="UE Smart Pointer Comparison"></a>UE Smart Pointer Comparison</h2><table><thead><tr><th>Pointer Type</th><th>Managed Object</th><th>Ownership</th><th>Core Role</th><th>Use Case</th></tr></thead><tbody><tr><td>TObjectPtr</td><td>UObject-derived</td><td>Shared</td><td>Safe UObject access, participates in garbage collection automatically</td><td>Replaces traditional UPROPERTY pointers; everyday UObject references</td></tr><tr><td>TWeakObjectPtr</td><td>UObject-derived</td><td>None</td><td>Weak reference to UObject, does not prevent GC</td><td>Avoids circular references; temporary access to UObjects that may be destroyed</td></tr><tr><td>TSoftObjectPtr</td><td>UObject-derived</td><td>None</td><td>Soft reference to UObject, supports async asset loading</td><td>References assets that may not be loaded, e.g., meshes or textures outside the current level</td></tr><tr><td>TSharedPtr</td><td>Non-UObject types</td><td>Shared</td><td>Manages lifetime via reference counting</td><td>Multiple owners sharing a non-UObject resource</td></tr><tr><td>TUniquePtr</td><td>Non-UObject types</td><td>Exclusive</td><td>Sole ownership, non-copyable</td><td>Managing non-UObject resources that don’t need sharing, e.g., custom data structures</td></tr><tr><td>TWeakPtr</td><td>Non-UObject types</td><td>None</td><td>Weak reference to a TSharedPtr, does not increment ref count</td><td>Avoids circular references when used alongside TSharedPtr</td></tr></tbody></table><h2 id="Key-Distinctions"><a href="#Key-Distinctions" class="headerlink" title="Key Distinctions"></a>Key Distinctions</h2><ol><li><p><strong>Object boundary</strong>: The first three are strictly for UObject-derived classes and rely on UE’s garbage collection system; the last three are for non-UObject types and use manual memory management.</p></li><li><p><strong>UObject pointer breakdown</strong>:</p><ul><li>TObjectPtr is a strong reference that keeps the UObject alive — the go-to choice for everyday development.</li><li>TWeakObjectPtr is a weak reference; when a UObject is marked for collection, the pointer is automatically nulled. Common in scenarios like UI widgets holding references to character objects.</li><li>TSoftObjectPtr stores a resource path rather than a direct memory address. The asset can be asynchronously loaded when it isn’t in memory, making it ideal for open-world games referencing distant assets.</li></ul></li><li><p><strong>Non-UObject pointer breakdown</strong>:</p><ul><li>TSharedPtr shares an object via reference counting, automatically freeing memory when the count reaches zero. Be mindful of circular references — they must be avoided manually.</li><li>TUniquePtr is an exclusive pointer: no copying allowed, ownership transfers only through move semantics. Lowest performance overhead.</li><li>TWeakPtr must be bound to a TSharedPtr. Once TSharedPtr releases the object, TWeakPtr automatically becomes invalid, resolving circular reference issues.</li></ul></li></ol><h2 id="What-is-ECS-Architecture-How-Does-It-Differ-from-Traditional-OOP"><a href="#What-is-ECS-Architecture-How-Does-It-Differ-from-Traditional-OOP" class="headerlink" title="What is ECS Architecture? How Does It Differ from Traditional OOP?"></a>What is ECS Architecture? How Does It Differ from Traditional OOP?</h2><table><thead><tr><th></th><th>OOP</th><th>ECS</th></tr></thead><tbody><tr><td>Data layout</td><td>Objects scattered on the heap</td><td>Components laid out in contiguous memory</td></tr><tr><td>Cache friendliness</td><td>Poor (pointer chasing)</td><td>Good (data locality)</td></tr><tr><td>Logic organization</td><td>Methods bound to classes</td><td>Systems iterate over Components independently</td></tr><tr><td>Composability</td><td>Requires multiple inheritance &#x2F; composition patterns</td><td>Natural composition (just attach Components)</td></tr></tbody></table><p>ECS essentially saves CPU time on data lookups.</p><p><strong>Core concepts:</strong></p><ul><li><strong>Entity</strong>: an ID only, stores no data</li><li><strong>Component</strong>: pure data (Position, Velocity, Health…)</li><li><strong>System</strong>: pure logic (MovementSystem iterates all Position+Velocity components)</li></ul><p>Core difference: OOP centers on objects — data and logic are encapsulated in classes, which tends to grow complex inheritance trees. ECS separates data from logic: entities are containers for components, systems process batches of the same component type, contiguous data storage improves cache efficiency, and the architecture naturally supports dynamic composition and parallel computation.<br>UE5 Mass system example: as an ECS implementation, Mass stores entity data in “fragments” and unifies logic in “processors.” The Matrix Awakens demo’s crowd simulation of thousands of characters packs position, velocity, and other data into contiguous storage, letting the movement processor batch-update all character coordinates — performance that far exceeds the traditional Actor approach.</p><h2 id="Stack-vs-Heap"><a href="#Stack-vs-Heap" class="headerlink" title="Stack vs. Heap"></a>Stack vs. Heap</h2><ul><li><strong>Heap</strong>: Dynamically allocated memory, variable size, lifetime controlled by the programmer, slower access. Suitable for large objects or data whose size is determined at runtime. (No fixed access order.)</li><li><strong>Stack</strong>: Automatically allocated memory, fixed size, lifetime controlled by the function call, fast access. Suitable for local variables and function parameters. (Fixed access order: last in, first out.)</li></ul><h2 id="What-Is-Function-Calling-and-How-Have-You-Used-It-in-Projects"><a href="#What-Is-Function-Calling-and-How-Have-You-Used-It-in-Projects" class="headerlink" title="What Is Function Calling and How Have You Used It in Projects?"></a>What Is Function Calling and How Have You Used It in Projects?</h2><p><strong>How it works:</strong> The LLM doesn’t execute functions directly — it <strong>outputs a structured function-call intent</strong> (function name + arguments), which the host program parses and executes.</p><h2 id="What-Is-RAG-and-How-Did-You-Implement-It"><a href="#What-Is-RAG-and-How-Did-You-Implement-It" class="headerlink" title="What Is RAG and How Did You Implement It?"></a>What Is RAG and How Did You Implement It?</h2><p><strong>RAG (Retrieval-Augmented Generation)</strong> &#x3D; retrieve relevant documents first, then have the LLM answer based on those retrieved results.</p><h2 id="What-Is-ControlNet-and-What-Problem-Does-It-Solve"><a href="#What-Is-ControlNet-and-What-Problem-Does-It-Solve" class="headerlink" title="What Is ControlNet and What Problem Does It Solve?"></a>What Is ControlNet and What Problem Does It Solve?</h2><p><strong>Reference answer:</strong></p><p><strong>ControlNet</strong> adds <strong>spatial control capabilities</strong> to a pretrained Diffusion Model.</p><p><strong>The problem it solves:</strong> Raw Text-to-Image generation cannot precisely control the composition, pose, edges, or other spatial structure of generated images.</p><p><strong>How it works:</strong></p><ul><li>A parallel “Zero Convolution” branch is added to each block of Stable Diffusion’s U-Net</li><li>Additional conditioning images are fed as input (edge detection &#x2F; Canny, depth maps, pose &#x2F; OpenPose, normal maps, etc.)</li><li>During training, only the ControlNet branch is trained; the original model weights are frozen</li></ul><p><strong>Common ControlNet types:</strong></p><ul><li>Canny Edge: controls outlines</li><li>Depth: controls depth structure</li><li>OpenPose: controls human pose</li><li>Segment: controls region segmentation</li><li>Scribble: controls sketch-based guidance</li></ul><h2 id="What-Is-LoRA-and-Why-Is-It-So-Popular"><a href="#What-Is-LoRA-and-Why-Is-It-So-Popular" class="headerlink" title="What Is LoRA and Why Is It So Popular?"></a>What Is LoRA and Why Is It So Popular?</h2><p><strong>LoRA (Low-Rank Adaptation)</strong> is a parameter-efficient fine-tuning method.<br>LoRA freezes the original model weights and trains only a small number of low-rank matrices to approximate the parameter updates needed for task adaptation. The trainable parameter count is just 0.1%–1% of full fine-tuning, drastically reducing VRAM usage and training cost. During inference, the weights can be merged with the base model, adding no extra latency.<br>In game development, LoRA lets you quickly fine-tune an image-to-image model to generate stylistically consistent character equipment and environment assets, or fine-tune a dialogue model so NPCs produce setting-appropriate natural dialogue — a great fit for small teams that need to move fast.</p><h2 id="What-Is-the-Difference-Between-MVC-MVP-and-MVVM"><a href="#What-Is-the-Difference-Between-MVC-MVP-and-MVVM" class="headerlink" title="What Is the Difference Between MVC, MVP, and MVVM?"></a>What Is the Difference Between MVC, MVP, and MVVM?</h2><table><thead><tr><th>Pattern</th><th>Component Responsibilities</th><th>Component Relationships</th><th>Pros &#x2F; Cons</th></tr></thead><tbody><tr><td>MVC</td><td>Model (data) &#x2F; View (UI) &#x2F; Controller (logic)</td><td>Controller directly operates both Model and View</td><td>Simple and intuitive, good for small projects; Controller can become bloated</td></tr><tr><td>MVP</td><td>Model (data) &#x2F; View (UI) &#x2F; Presenter (logic)</td><td>Presenter directly operates Model, updates View indirectly</td><td>Presenter is highly testable; View depends on Presenter, increasing coupling</td></tr><tr><td>MVVM</td><td>Model (data) &#x2F; View (UI) &#x2F; ViewModel (logic)</td><td>ViewModel directly operates Model, updates View via data binding</td><td>Two-way binding simplifies UI updates; steeper learning curve, potential performance overhead</td></tr></tbody></table><p>MVP’s Presenter and MVVM’s ViewModel are very similar in responsibility — both act as intermediaries handling business logic and data interaction. The key difference is that MVVM’s data-binding mechanism lets ViewModel update the View directly, eliminating the large amount of UI-update code you’d write in a Presenter. This makes the code more concise and testable. MVVM suits projects with complex, frequent UI interactions; MVP fits simpler UIs or scenarios where strict test isolation is needed.</p><h2 id="What-Are-the-Complete-Stages-of-the-GPU-Rendering-Pipeline"><a href="#What-Are-the-Complete-Stages-of-the-GPU-Rendering-Pipeline" class="headerlink" title="What Are the Complete Stages of the GPU Rendering Pipeline?"></a>What Are the Complete Stages of the GPU Rendering Pipeline?</h2><p><strong>Reference answer:</strong></p><p>The GPU Rendering Pipeline is the full process by which a GPU executes graphics rendering:</p><p><strong>Application Stage (CPU side):</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></pre></td><td class="code"><pre><span class="line">1. Application Stage</span><br><span class="line">   → Frustum Culling</span><br><span class="line">   → Draw Call Batching</span><br><span class="line">   → Outputs Draw Calls + vertex data to GPU</span><br></pre></td></tr></table></figure><p><strong>Geometry Stage (GPU vertex shaders):</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></pre></td><td class="code"><pre><span class="line">2. Vertex Shader</span><br><span class="line">   → Model Space → World Space → View Space → Homogeneous Clip Space</span><br><span class="line">   → Vertex transform: LocalMatrix × WorldMatrix × ViewMatrix × ProjectionMatrix</span><br><span class="line"></span><br><span class="line">3. Tessellation (optional)</span><br><span class="line">   → Hull Shader → Tessellator → Domain Shader</span><br><span class="line"></span><br><span class="line">4. Geometry Shader (optional)</span><br><span class="line">   → Processes per-primitive, can emit or discard primitives</span><br></pre></td></tr></table></figure><p><strong>Rasterization Stage:</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></pre></td><td class="code"><pre><span class="line">5. Primitive Assembly &amp; Clipping</span><br><span class="line">   → Clipping (homogeneous space clipping)</span><br><span class="line">   → Perspective Divide → NDC → Viewport Transform</span><br><span class="line"></span><br><span class="line">6. Back-face Culling</span><br><span class="line">   → Discards back-facing primitives based on vertex winding order (CW/CCW)</span><br><span class="line"></span><br><span class="line">7. Rasterization</span><br><span class="line">   → Discretization: points / lines / triangles → Fragments</span><br><span class="line">   → Viewport Transform: NDC → Screen Space</span><br></pre></td></tr></table></figure><p><strong>Fragment &#x2F; Pixel Stage:</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></pre></td><td class="code"><pre><span class="line">8. Fragment Shader (Pixel Shader)</span><br><span class="line">   → Per-pixel shading: lighting calculations, texture sampling, color output</span><br><span class="line"></span><br><span class="line">9. Per-Fragment Operations</span><br><span class="line">   → Depth Test (Z-Test)</span><br><span class="line">   → Stencil Test</span><br><span class="line">   → Alpha Blending</span><br><span class="line">   → Output to Framebuffer</span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      
      
      <comments>https://eugenepage.com/2026/09/25/20250801.UEConceptScrapbox/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Landing AI in Game Production — A Kung-Fu Manual · Form 2 «Read the Board, Discern the Formation»: Game-Scene Understanding Through Image Masks and Semantic Abstraction</title>
      <link>https://eugenepage.com/2026/06/24/20260625.AIGameSeries-Article2/</link>
      <guid>https://eugenepage.com/2026/06/24/20260625.AIGameSeries-Article2/</guid>
      <pubDate>Wed, 24 Jun 2026 16:00:00 GMT</pubDate>
      
      <description>Why do LLMs fail to understand a large Unreal Engine world? This article introduces a 2D asset-mask method built on the editor&#39;s Hit Proxy system and GSDL, a variable-granularity scene description language, then compares five engine-to-LLM translation layers—MCP, CLI, snapshots, OpenUSD, and direct screenshots—and previews Vector Atlas for map-wide semantic encoding.</description>
      
      
      
      <content:encoded><![CDATA[<h1 id="Landing-AI-in-Game-Production-·-Form-2-«Read-the-Board-Discern-the-Formation»-Game-Scene-Understanding-Through-Image-Masks-and-Semantic-Abstraction"><a href="#Landing-AI-in-Game-Production-·-Form-2-«Read-the-Board-Discern-the-Formation»-Game-Scene-Understanding-Through-Image-Masks-and-Semantic-Abstraction" class="headerlink" title="Landing AI in Game Production · Form 2 «Read the Board, Discern the Formation»: Game-Scene Understanding Through Image Masks and Semantic Abstraction"></a>Landing AI in Game Production · Form 2 «Read the Board, Discern the Formation»: Game-Scene Understanding Through Image Masks and Semantic Abstraction</h1><blockquote><p><strong>Series intro</strong>: “The Kung-Fu Manual for Landing AI in Game Production” records the road I took—every step of it paid for in blood and tears.<br>In Form 1, «Within Easy Reach», we taught AI to recognize every asset in a project. It knows which Jiangnan-style low shrub <code>shrub_jn_01a</code> refers to. But that is not enough: <strong>recognizing every piece on the board does not mean understanding the formation.</strong> In Form 2, «Read the Board, Discern the Formation», we teach AI to understand the “formation” an artist has arranged across a map.</p><p><em>Note: almost every article in this series is handcrafted. If you only care about the result and not the implementation, feel free to skip Chapters 3 through 6.</em></p></blockquote><hr><h2 id="1-Origin-A-Scene-AI-Cannot-Understand"><a href="#1-Origin-A-Scene-AI-Cannot-Understand" class="headerlink" title="1. Origin: A Scene AI Cannot Understand"></a>1. Origin: A Scene AI Cannot Understand</h2><p>In Form 1, semantic encoding taught the AI to recognize individual assets. This time, the question is: <strong>where am I?</strong></p><p>As everyone knows, an LLM does not understand a scene. Even if you hand it a screenshot, it still cannot read the elaborate formation an artist has built:</p><pre><code>① &quot;What kind of visual style does this area have?&quot;     → AI: Make up something plausible.② &quot;Find the treasure chest beside a staircase.&quot;     → AI: Traverse the World Outliner and inspect objects one by one. Which one is beside a staircase? No idea.③ &quot;Here is a screenshot. Where is this location in my open world?&quot;     → AI: What on earth am I looking at?④ &quot;Find a cave I built out of piled rocks.&quot;     → AI: @#￥%……&amp;*</code></pre><p>Those are the problems this article sets out to solve. Map understanding turned out to contain too much material for one article, so I am splitting it into two: this one is about <strong>understanding the map</strong>, while the next will cover <strong>vector search across the entire map</strong>.</p><p>After this round of training, your AI will be able to <mark>understand</mark> a map in the real sense of the word, much as a person does, and <mark>memorize</mark> every scene and position it has sampled. For viewpoints that were never captured, I plan to infer them by interpolating between neighboring views, ultimately enabling <mark>millisecond-level</mark> lookup at any position in a large open world.</p><h2 id="2-What-Does-It-Mean-to-Understand-a-Scene"><a href="#2-What-Does-It-Mean-to-Understand-a-Scene" class="headerlink" title="2. What Does It Mean to Understand a Scene?"></a>2. What Does It Mean to Understand a Scene?</h2><p>Consider what happens when a person sees a scene.</p><p>First, they see the game world: “Huh, it is pitch-dark in here.” <strong>(visual)</strong></p><p>Then they sense the spatial relationships: “Wait, there is some light ahead.” <strong>(spatial)</strong></p><p>Finally, they understand what the scene means: “Oh—this must be the White Bone Demon’s cave.” <strong>(semantic)</strong></p><p>How would they describe the scene to someone else?</p><blockquote><p>I just found a place. You enter it by pressing E near <strong>Black Well</strong>. It is <strong>even darker than the cave at Flower-Fruit Mountain</strong>. I think it must be <strong>the hidden boss White Bone Demon’s lair</strong>.</p></blockquote><p>From that description, I separate understanding from expression:</p><table><thead><tr><th>Perception \ Expression</th><th>Reference</th><th>Comparison</th><th>Metaphor</th></tr></thead><tbody><tr><td><strong>Visual information</strong></td><td>🟢 Identify by visual style</td><td>🟡 Compare density and style intensity</td><td>🟠 Use cultural imagery to describe texture or style</td></tr><tr><td><strong>Spatial information</strong></td><td>🟡 Identify by spatial position</td><td>🟡 Compare relative position and distribution</td><td>🟠 Describe spatial form through another object</td></tr><tr><td><strong>Semantic information</strong></td><td>🟡 Identify by function or use</td><td>🟠 Express preference or negation</td><td>🔴 Describe function and narrative through metaphor</td></tr></tbody></table><blockquote><p>Difficulty: 🟢 easy ｜ 🟡 medium ｜ 🟠 hard ｜ 🔴 extremely hard. The harder cells are difficult for AI both to understand and to express accurately.</p></blockquote><p>The <strong>vertical axis—the perception layer</strong>—corresponds to three abilities an AI needs in order to understand a scene:</p><ul><li><strong>Visual information</strong> — <strong>What does this area look like?</strong> Style, density, texture, and aesthetic impression.</li><li><strong>Spatial information</strong> — <strong>How are A and B arranged in space?</strong> Who contains whom, what touches or intersects what, what supports what, and what occludes what: topology + direction + distance + support + occlusion.<br>Topological relations—RCC-8’s DC &#x2F; EC &#x2F; PO &#x2F; EQ &#x2F; TPP &#x2F; NTPP and so on—are already a mature area of qualitative spatial reasoning; see Cohn and Renz’s classic survey, <a href="https://users.cecs.anu.edu.au/~jrenz/papers/cohn-renz-krbook07.pdf">Qualitative Spatial Representation and Reasoning</a>. Support and occlusion are often treated separately in vision research, as in Biederman’s 1982 discussion of <a href="https://www.cs.princeton.edu/courses/archive/spring08/cos598B/Readings/Biederman1982.pdf">Support &#x2F; Interposition</a>. In an engine, however, they ultimately come from the same geometric data, so this framework groups them together by <strong>data source</strong>.</li><li><strong>Semantic information</strong> — <strong>What is A doing here, and what narrative role does it serve?</strong><ol><li><strong>Object meaning</strong> — What does this individual object imply?</li><li><strong>Compositional meaning</strong> — What does a group of objects mean together? This is mereology: for example, a gap hollowed out from a pile of rocks becomes a cave.</li><li><strong>Design semantics</strong> — This scene is run-down because it is meant to depict a village suffering through famine.</li><li><strong>Co-occurrence</strong> — Do A and B often appear together? Which pairings are stable?<br>This level touches several different fields at once: distributional semantics, RDF for knowledge representation, and FrameNet for frame semantics. Co-occurrence is learned from frequency or vectors, not calculated from geometry. Modeling how the current state affects the next state would move further into Markov models. I treat co-occurrence as part of semantics because it expresses scene logic above the level of individual geometry. That topic is somewhat beyond the scope of this article.</li></ol></li></ul><p>The <strong>horizontal axis—the expression layer</strong>, meaning whether the system can restate what it has learned, covers three strategies:</p><ul><li><strong>Reference</strong> corresponds to <em>deixis</em>: shared attention anchors both parties to the same object even without coordinates.</li><li><strong>Comparison</strong> uses vague quantifiers and vague language. It need not produce an exact number; it can instead communicate direction, degree, and an implied baseline.</li><li><strong>Metaphor</strong> corresponds to conceptual metaphor or cross-domain mapping: shared cultural experience is used to express space, texture, or function that would otherwise be difficult to state directly.</li></ul><p>An ideal AI scene-understanding tool should, in theory, cover every cell in that 3 × 3 matrix.</p><p>A good agent also needs a long-term scene memory: if we discussed something before, it should be able to retrieve it when the subject comes up again.</p><p>Now let us talk about implementation.</p><h2 id="3-Stop-Talking-Only-About-MCP-It-Is-a-Bottomless-Pit"><a href="#3-Stop-Talking-Only-About-MCP-It-Is-a-Bottomless-Pit" class="headerlink" title="3. Stop Talking Only About MCP: It Is a Bottomless Pit"></a>3. Stop Talking Only About MCP: It Is a Bottomless Pit</h2><p>What we actually need is a <strong>translation layer</strong> between engine data and the LLM: something that extracts whatever the LLM wants to know from the engine, then translates it into a form the model can use. MCP is the obvious first thought.</p><p>MCP has indeed been extremely popular for a while.</p><p>But MCP is not a pleasant job if what you want is a fast, dramatic payoff. It demands a great deal of engineering and tuning. Over the past few months I have spent an enormous amount of effort debugging it, even developing a tuning methodology and staring at telemetry every day. Honestly, the result is still not as good as I want.</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 620px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260817-171132.png" alt="Tuning dashboard showing tool calls, failures, and call trends" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">My tuning dashboard</figcaption>  </figure></div><p>Why?</p><p>If you have used any UE MCP tools, you have probably encountered several problems in the scene-understanding layer:</p><ol><li><p><strong>It cannot truly understand the style I am asking about.</strong><br>A person can look at a field and describe whether the grass is sparse or dense. The tool cannot “understand” or summarize that impression.</p></li><li><p><strong>Context explodes as the tool count grows geometrically.</strong><br>Many MCP implementations expose interfaces originally designed for UI directly to the LLM. That is like handing a swordsman a kitchen knife: usable, but awkward. UI was designed for people; its architecture never accounted for limited context windows, hallucination, and the other weaknesses of an AI.</p></li><li><p><strong>More tasks mean more interfaces.</strong><br>An atomic tool signature is fixed. A <code>find</code> tool may begin as a name search, but the requests quickly become “find a treasure chest beside a staircase” (neighborhood reasoning), “find a shrub in this visual style” (semantic filtering), or “count the rocks in this area” (aggregation). The same <code>find</code> now hides several fundamentally different computations. One rigid interface cannot span all of them: either the number of tools explodes or the depth of analysis collapses. My testing also found that offering more MCP tools can actually lower completion rate—not because the tools lack capability, but because the model no longer knows which one to choose.</p></li><li><p><strong>There is no validation, so one bad step poisons everything after it.</strong><br>Every tool call can fail. Most MCP systems only “provide data”; they do not tell you whether the data is correct. If the agent says there is a pine tree at <code>xxx</code>, should you believe it? Without a mirror in which it can inspect its own work, errors snowball through multi-step reasoning until the whole task drifts off course.</p></li><li><p><strong>It is slow and expensive—often slower than doing the job yourself.</strong><br>Every probe crosses a bridge, waits for the editor, and burns tokens. By the time it finishes a lap, I could have clicked through the task myself. A system whose nominal capabilities cannot beat the time and cost of a human using the editor directly has no place in serious production.</p></li></ol><p>MCP is not the only option available to us:</p><table><thead><tr><th>Approach</th><th>One-line principle</th><th>Per-call latency</th><th>Context</th><th>Read&#x2F;write</th><th>AI compatibility</th><th>Cross-version</th><th>Compute per call</th><th>Fatal flaw</th></tr></thead><tbody><tr><td><strong>MCP</strong></td><td>Run an MCP server in the engine via JSON-RPC</td><td>🟡 seconds</td><td>🔴 every interface exposed</td><td>✅</td><td>🟢 protocol-native</td><td>🔴 tied to APIs</td><td>🟡</td><td><strong>slow + expensive + context explosion + wrong granularity</strong></td></tr><tr><td><strong>CLI + Commandlet</strong></td><td>Launch headless UE to run a command</td><td>🔴 30s+ startup tax</td><td>🟢 persisted output</td><td>✅</td><td>🟡 needs another parser</td><td>🟡 tied to versions</td><td>🔴 process fork</td><td><strong>cold start makes interaction impossible</strong></td></tr><tr><td><strong>Exported text snapshot</strong></td><td>Cook the scene into JSON&#x2F;XML on disk</td><td>🟡 seconds</td><td>🔴 explodes on large scenes</td><td>🔴 read-heavy</td><td>🟡 superficially friendly</td><td>🟡 brittle format</td><td>🟡</td><td><strong>stale + one-way + brittle</strong></td></tr><tr><td><strong>OpenUSD</strong></td><td>Standardized scene description with bidirectional I&#x2F;O</td><td>🟢 seconds</td><td>🟡 structured</td><td>✅</td><td>🟢 text-native</td><td>🟢 stable standard</td><td>🟢</td><td><strong>not unified across the game industry</strong></td></tr><tr><td><strong>Direct screenshots</strong></td><td>Render + let a VLM inspect the image</td><td>🔴 tens of seconds</td><td>🔴 image tokens</td><td>🔴 observation-heavy</td><td>🟢 multimodal-native</td><td>🟢 flexible</td><td>🔴 very high</td><td><strong>expensive + view&#x2F;render-state dependent + loses structure</strong></td></tr></tbody></table><p>By where the information comes from, those five approaches form three groups:</p><p><strong>MCP + CLI</strong> acquire information <strong>online through engine interfaces</strong>. The data is fresh and reflects the current scene, but the interfaces must be maintained, the returned payload can be huge, and every engine upgrade carries a migration cost.</p><p><strong>Exported snapshots + OpenUSD</strong> reconstruct the scene from <strong>offline data</strong>. They avoid occupying a running editor, can be versioned, and are convenient for cross-tool workflows, but they are stale and large scenes can produce enormous snapshots.</p><p><strong>Direct screenshots</strong> acquire information through <strong>images + a VLM</strong>, much closer to how a person observes a scene. Their semantic intuition is strong and they are flexible across engine versions, but the ceiling is set entirely by the VLM’s visual ability and by the information present in the image.</p><p>Only children choose one. I want all of them—combined in pairs.</p><p>For the three kinds of information on the vertical axis of the earlier 3 × 3 matrix, I propose three corresponding implementations:</p><ul><li>Visual information ➡️ <strong>2D asset masks</strong>: VLM screenshots + exported snapshot text, extending what an image can communicate.</li><li>Spatial information ➡️ <strong>GSDL, the General Scene Description Language</strong>: a variable-granularity, human-language-level USD + MCP layer that tackles context explosion and weak spatial perception.</li><li>Semantic information ➡️ <strong>Vector Atlas, map-wide semantic encoding</strong>: Commandlets combined with the two methods above. The implementation has enough difficult bottlenecks to deserve a separate article.</li></ul><p>Let us examine each in turn.</p><h2 id="4-The-2D-Asset-Mask-Method"><a href="#4-The-2D-Asset-Mask-Method" class="headerlink" title="4. The 2D Asset-Mask Method"></a>4. The 2D Asset-Mask Method</h2><h3 id="4-1-Why-a-Screenshot-Alone-Is-Not-Enough"><a href="#4-1-Why-a-Screenshot-Alone-Is-Not-Enough" class="headerlink" title="4.1 Why a Screenshot Alone Is Not Enough"></a>4.1 Why a Screenshot Alone Is Not Enough</h3><p>For visual-semantic understanding, the most direct and human-like approach is to hand an image to a VLM—or to an LLM augmented with image-understanding capability.</p><p>In the ideal future, a large model would read video, understand spatial relationships, and act accordingly. That begins to sound like a world model. Today’s LLMs do not seem to be there yet.</p><p>Some products export extra channels such as <strong>depth and normal maps</strong> to improve a VLM’s spatial perception. But this depends on what the model saw in training. Even before considering the enormous image-token cost, understanding begins to drift when the image is blurry or when the stylized way an Actor is displayed differs from the training distribution. In my view, feeding raw images directly to the model is therefore not an especially good solution.</p><p>But what is the root cause of “not understanding”?</p><p>Return to the basics of computer vision. Understanding an image means recovering structured semantic information from its pixels: what is here, and what is over there?</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 620px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-015740.png" alt="Instance-segmentation example from Fei-Fei Li's CS231 slides" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Image from Fei-Fei Li's CS231 slides</figcaption>  </figure></div><p>Common foundational computer-vision tasks include image classification, object detection, semantic segmentation, and instance segmentation.</p><p>In early July, my colleague Fufu from the platform team proposed an image-recognition-plus-raycast approach: use a small model to identify the Actors in a scene, then raycast to retrieve the corresponding Actor. In other words, object recognition followed by instance selection. This inserts a small visual-analysis model between the application and the LLM, letting the LLM actively acquire and segment the contents of the image.</p><p><em>By the way, placing an intermediate layer between a large model and an application is a wonderful agent-design trick. GSDL, which appears later, follows the same logic.</em></p><p>That proposal inspired me.</p><p>But it returns us to the same problem: if the model itself distinguishes objects directly from the image, it will sometimes fail, and the resulting analysis is unstable.</p><p>So why not reverse the direction? Let the engine report which Actor owns every pixel and output a mask table to the LLM, compensating for the model’s inability to separate every component of an image reliably.</p><p>That is my <strong>2D asset-mask method</strong>. When the LLM asks for an image from a position, the editor responds. Optionally, instead of returning only the image, it also reports everything in the frame, compresses that visual information into text, and labels what every part of the image represents. The method slashes token cost while providing extremely stable, high-recall instance segmentation.</p><h3 id="4-2-Establishing-Visual-Ownership"><a href="#4-2-Establishing-Visual-Ownership" class="headerlink" title="4.2 Establishing Visual Ownership"></a>4.2 Establishing Visual Ownership</h3><p>The editor already maintains a Hit Proxy rendering and readback mechanism so users can click objects. Its buffer corresponds to the viewport: every hittable pixel maps to a Hit Proxy, which can then be resolved back to an Actor, Component, or another editor object. We can reuse this mechanism and expose pixel ownership to the LLM. Compared with running a separate vision segmentation model, acquiring and parsing this data is cheap.</p><div class="media-layout media-layout--quad">  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-015943.png" alt="Original screenshot from the game viewport" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Original game-viewport screenshot</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-020021.png" alt="ID-color mask where each color represents one Actor" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">ID-color mask: one color per Actor</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-020004.png" alt="Layout composite combining the scene, masks, and measured coverage labels" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Layout composite: scene × masks + measured coverage labels</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-015848.png" alt="Focus view with mushrooms highlighted in cyan, decorative pillars in magenta, and everything else dimmed" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Focus view: mushrooms in cyan, decorative pillars in magenta, everything else dimmed</figcaption>  </figure></div><p>Once we know every Actor in the image, do we understand the image’s semantics?</p><p>Yes. Absolutely.</p><p>By a fortunate coincidence, I had just finished an asset-understanding tool—the previous installment in this series.</p><div class="media-layout media-layout--pair">  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-021528.png" alt="Asset descriptions generated in NeuroBrowser" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Asset descriptions in NeuroBrowser</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-021955.png" alt="Asset-mask table produced by NeuroMap" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Asset-mask table produced by NeuroMap</figcaption>  </figure></div><p>While building asset understanding, I generated a description for every asset—the left image above—and artists continue editing and improving those descriptions.</p><p>The mask statistics on the right reveal, among other things:</p><ol><li>how much of the image each asset occupies, how it is distributed, and how far away it is;</li><li>which source asset it comes from, so it can link back to asset semantics;</li><li>what the instance is called in the scene and which scene parameters it has.</li></ol><h3 id="4-3-Edge-Cases-and-Local-Vision-Model-Support"><a href="#4-3-Edge-Cases-and-Local-Vision-Model-Support" class="headerlink" title="4.3 Edge Cases and Local Vision-Model Support"></a>4.3 Edge Cases and Local Vision-Model Support</h3><p>Careful readers may have noticed that three kinds of “special asset” reported by Hit Proxy need dedicated channels. I handle each separately:</p><ol><li><strong>Procedural foliage</strong> — split it apart and map each foliage Actor back to the semantics of its source asset.</li><li><strong>Terrain</strong> — Hit Proxy reports an entire Landscape Proxy: one name covering a huge swath of pixels. Artists, however, paint multiple user layers into the weightmap—grass, sand, snow, water, and so on. The integration layer therefore follows a dedicated route: world coordinate → proxy-local UV, calculated from proxy scale → weightmap texture sample → <code>ULandscapeLayerInfoObject::GetLayerName()</code>. No visual inference is involved anywhere in the chain, which makes it highly stable.</li><li><strong>Built-in engine objects such as sky and atmosphere</strong> — there are few of them, so I provide handwritten semantics rather than depending on Hit Proxy.</li></ol><p>Now we give the VLM the descriptions + image + mask information, organized at several LoD levels, and ask it to generate a description of the frame. Yes, this is a model describing a description—recursion achieved. These LoD levels can be exposed as interface parameters for different levels of detail.</p><p><em>Here, LoD means the granularity of information shown to the model, not mesh geometric detail, although the numbering direction remains consistent.</em></p><p>We can go further by deploying a small vision-language model locally behind the MCP layer. For example, Qwen2.5-VL-7B int4 averages 5.95 GB of VRAM and peaks at 6.65 GB, small enough to run on most artists’ workstations. Deploying Qwen3.8 27B on the studio LAN is not especially difficult either.</p><p>Interpreting images and their attached information is a classic perception task and does not require a long chain of reasoning—the sweet spot for a small model. Modern VLMs are pre-trained on large image or image-text corpora and already possess basic visual-semantic ability. This local sidecar lets a cheap, effective, non-multimodal model such as DeepSeek V4 Flash 0731 use our visual-understanding layer as well.</p><p>Roughly speaking, a small model is sufficient on one side of the following boundary and begins to struggle on the other:</p><table><thead><tr><th>Dimension</th><th>🟢 A small model is enough</th><th>🔴 A small model begins to struggle</th></tr></thead><tbody><tr><td>Spatial</td><td>“There is a treasure chest &#x2F; mushroom &#x2F; rock &#x2F; distant silhouette in the image”</td><td>“The exact distance between the chest and the nearest mushroom; back-projecting a precise 3D coordinate”</td></tr><tr><td>Visual</td><td>“Warm palette, cave atmosphere, low-poly style”</td><td>“A 0.7 silhouette deviation from the concept art; which subject damages the composition”</td></tr><tr><td>Semantic</td><td>“What is present”—perception</td><td>“Why was it placed this way, and how should it change?”—reasoning + decision-making</td></tr></tbody></table><p>The exact deployment choice naturally depends on the environment.</p><p><em>Update, August 10: Alibaba recently released <a href="https://github.com/QwenLM/Qwen-MM-Plugins">Qwen-MM-Plugins</a>, which is very close to my idea. It moves visual understanding into the harness layer and uses multi-resolution processing and frame sampling to understand images and video, essentially completing the sidecar capability I describe here. I will upgrade my approach accordingly.</em></p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 620px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260817-170645.png" alt="Qwen-MM-Plugins architecture for giving any agent harness native multimodal support" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Qwen-MM-Plugins architecture (github.com/QwenLM/Qwen-MM-Plugins)</figcaption>  </figure></div><p>With this framework, everything in an image can be assigned semantic ownership. Auxiliary objects that Hit Proxy never hits—post-process volumes, lights, and the like—leave no trace in an image mask, so we need another probing dimension.</p><p>Enter GSDL.</p><h2 id="5-GSDL-The-General-Scene-Description-Language"><a href="#5-GSDL-The-General-Scene-Description-Language" class="headerlink" title="5. GSDL: The General Scene Description Language"></a>5. GSDL: The General Scene Description Language</h2><p>Never heard of it?</p><p>Of course not. I made it up. 🐶</p><h3 id="5-1-How-Do-We-Understand-Space"><a href="#5-1-How-Do-We-Understand-Space" class="headerlink" title="5.1 How Do We Understand Space?"></a>5.1 How Do We Understand Space?</h3><p>The 2D mask method only fills half the gap. It tells the model <strong>what is in the image</strong>—visual information plus content semantics—but the spatial-relations row in the 3 × 3 matrix remains unsolved.</p><p>The editor was built for people. A person can glance at a viewport and immediately feel whether an area is dense or sparse, whether objects are scattered or huddled together. The editor cannot answer those questions directly.</p><p>The data we need is not hiding in a single engine field, waiting to be retrieved. It must be <strong>measured, aggregated, and inferred</strong> from Actors, coordinates, bounding boxes, collision, terrain, rays, and rendering results. The engine does not expose those conclusions as ready-made APIs. A person acquires them intuitively by walking through a scene; an LLM does not.</p><p>That is what GSDL does. Through measurement, it cooks and compresses low-level engine state into scene facts that an AI can query, compare, and verify: “there is a mountain at A,” “there is a depression at B,” and so on.</p><p>GSDL is also built on MCP, but it does not expose raw engine interfaces directly. A layer of GSDL query functions sits in between, and those functions are the language’s most important rules and conventions. Every query first performs its measurements inside the scene, then converts the result into standardized language for the LLM. In that sense, it can be viewed as an OpenUSD-like textual description language.</p><p>But “description language” only names the final output. More precisely, <strong>GSDL is a complete methodology for observing, measuring, and describing a scene for AI. One end connects to geometric and rendering facts in the engine; the other connects to asset semantics. Between them, it organizes what exists, how it is distributed, and how the pieces relate into structured language at multiple LoD levels for the LLM.</strong></p><div class="canvas-embed" data-canvas-slug="attachments/Canvas/mcp-query-loop.en"><svg xmlns="http://www.w3.org/2000/svg" class="canvas-svg" data-canvas-revision="bda37079d90b" width="509" height="280" viewBox="-120 -40 1200 660" preserveAspectRatio="xMidYMid meet" role="img" aria-label="mcp-query-loop.en"><title>mcp-query-loop.en</title><defs><marker id="canvas-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" /></marker></defs><g class="canvas-groups"><g class="canvas-node canvas-node--group" data-id="b572bb250efcbad3" data-x="340" data-y="0" data-width="300" data-height="580" data-color="custom" style="--canvas-node-accent:#a7f3d0"><rect class="canvas-group__bg" x="340" y="0" width="300" height="580" rx="12" /><text class="canvas-group__label" x="352" y="22">② Query Inside UE</text></g><g class="canvas-node canvas-node--group" data-id="5cfa297760b8a416" data-x="700" data-y="0" data-width="340" data-height="420" data-color="custom" style="--canvas-node-accent:#fbcfe8"><rect class="canvas-group__bg" x="700" y="0" width="340" height="420" rx="12" /><text class="canvas-group__label" x="712" y="22">③ Compress into Standardized Language</text></g><g class="canvas-node canvas-node--group" data-id="faca46ce5d698ba6" data-x="-80" data-y="20" data-width="340" data-height="335" data-color="custom" style="--canvas-node-accent:#c7d2fe"><rect class="canvas-group__bg" x="-80" y="20" width="340" height="335" rx="12" /><text class="canvas-group__label" x="-68" y="42">① Large Language Model</text></g></g><g class="canvas-edges"><g class="canvas-edge-group" data-id="fd653b4d34e27831" data-from-node="31c9333cf894383a" data-to-node="662e9e99a91bebdb" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 235 73 C 276.6879945414611 73, 318.3120054585389 77, 360 77" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="0898129a83536ec6" data-from-node="662e9e99a91bebdb" data-to-node="6aa9b9dd3f8e01e1" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 490 104 C 490 144, 490 160, 490 200" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="2dfb95721cb0ef3b" data-from-node="6aa9b9dd3f8e01e1" data-to-node="21d77697417ddb26" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 490 254 C 490 294, 490 285, 490 325" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="016d21134901fe6b" data-from-node="21d77697417ddb26" data-to-node="229a9e99ce52ac2e" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 490 379 C 490 419, 490 420, 490 460" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="26757a79145d96c4" data-from-node="229a9e99ce52ac2e" data-to-node="4c210a572a8a0638" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 620 487 C 770.7849830424473 487, 574.2150169575527 47, 725 47" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="7a85a9327f01e66e" data-from-node="4c210a572a8a0638" data-to-node="e5f5f4079075ba13" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 860 74 C 860 114, 860 130, 860 170" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="9533d7015116d90e" data-from-node="e5f5f4079075ba13" data-to-node="c222923740c10c1d" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 860 260 C 860 300, 860 295, 860 335" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="d6571dc128dab9cc" data-from-node="c222923740c10c1d" data-to-node="05f44c30b82ff4cf" data-from-side="top" data-to-side="top"><path class="canvas-edge" d="M 860 335 C 860 76.01260219419515, 89.5 -23.987397805804846, 89.5 235" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="474.75" y="285" text-anchor="middle">Next query</text></g><g class="canvas-edge-group" data-id="020393000e47e963" data-from-node="05f44c30b82ff4cf" data-to-node="31c9333cf894383a" data-from-side="left" data-to-side="left" style="--canvas-edge-color:#94a3b8"><path class="canvas-edge" d="M -56 262 C -119 262, -119 73, -56 73" fill="none" marker-end="url(#canvas-arrow)" /></g></g><g class="canvas-nodes"><g class="canvas-node canvas-node--text" data-id="662e9e99a91bebdb" data-x="360" data-y="50" data-width="260" data-height="54" data-color="custom" style="--canvas-node-accent:#059669"><rect class="canvas-node__bg" x="360" y="50" width="260" height="54" rx="8" /><foreignObject x="360" y="50" width="260" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>MCP Query Interface</strong></p><p>GSDL rules and conventions</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="e5f5f4079075ba13" data-x="720" data-y="170" data-width="280" data-height="90" data-color="custom" style="--canvas-node-accent:#db2777"><rect class="canvas-node__bg" x="720" y="170" width="280" height="90" rx="8" /><foreignObject x="720" y="170" width="280" height="90"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>GSDL Encoder</strong></p><p>LoD0–LoD3 · converge to character budget</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="05f44c30b82ff4cf" data-x="-56" data-y="235" data-width="291" data-height="54" data-color="custom" style="--canvas-node-accent:#4f46e5"><rect class="canvas-node__bg" x="-56" y="235" width="291" height="54" rx="8" /><foreignObject x="-56" y="235" width="291" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Read normalized result → reason</strong></p><p>Choose the next query</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="31c9333cf894383a" data-x="-56" data-y="36" data-width="291" data-height="74" data-color="custom" style="--canvas-node-accent:#4f46e5"><rect class="canvas-node__bg" x="-56" y="36" width="291" height="74" rx="8" /><foreignObject x="-56" y="36" width="291" height="74"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Issue an MCP call</strong></p><p>describe_region · find_opening · compare …</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="c222923740c10c1d" data-x="730" data-y="335" data-width="260" data-height="60" data-color="custom" style="--canvas-node-accent:#db2777"><rect class="canvas-node__bg" x="730" y="335" width="260" height="60" rx="8" /><foreignObject x="730" y="335" width="260" height="60"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Normalized GSDL text</strong></p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="4c210a572a8a0638" data-x="725" data-y="20" data-width="270" data-height="54" data-color="custom" style="--canvas-node-accent:#db2777"><rect class="canvas-node__bg" x="725" y="20" width="270" height="54" rx="8" /><foreignObject x="725" y="20" width="270" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Spatial Kernel</strong></p><p>Clustering · relation inference</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="6aa9b9dd3f8e01e1" data-x="360" data-y="200" data-width="260" data-height="54" data-color="custom" style="--canvas-node-accent:#059669"><rect class="canvas-node__bg" x="360" y="200" width="260" height="54" rx="8" /><foreignObject x="360" y="200" width="260" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>File Bridge</strong></p><p>Request &#x2F; response polling</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="21d77697417ddb26" data-x="360" data-y="325" data-width="260" data-height="54" data-color="custom" style="--canvas-node-accent:#ea580c"><rect class="canvas-node__bg" x="360" y="325" width="260" height="54" rx="8" /><foreignObject x="360" y="325" width="260" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>UE Scene Probe</strong></p><p>Enumerate · sample · raycast</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="229a9e99ce52ac2e" data-x="360" data-y="460" data-width="260" data-height="54" data-color="custom" style="--canvas-node-accent:#ea580c"><rect class="canvas-node__bg" x="360" y="460" width="260" height="54" rx="8" /><foreignObject x="360" y="460" width="260" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Raw Query Result</strong></p><p>Large and not yet compressed</p></div></foreignObject></g></g></svg><span class="canvas-embed__expand" aria-hidden="true" title="点击放大">⛶</span></div><blockquote><p>This diagram shows one complete query loop. MCP is only a thin wrapper: it does not do the work itself; it forwards calls into the engine.</p><p><strong>①</strong> The model issues an MCP call—<code>describe_region</code>, <code>find_opening</code>, <code>compare</code>, and so on—which arrives at <strong>②</strong>, the MCP query interface: the GSDL query layer described above.</p><p><strong>②</strong> A file bridge carries the request into UE, where scene probes perform the actual measurements and produce raw, uncompressed facts.</p><p><strong>③</strong> The spatial kernel clusters those facts and infers relationships. The GSDL encoder then compresses them according to LoD and a character budget. Only then does the model receive the result.</p><p>After reading the result, the model reasons about what to measure next and returns to <strong>①</strong> with a new question. The query keeps circling through this loop.</p></blockquote><p>In short, GSDL has two main jobs:</p><ol><li>understand spatial information by organizing discrete objects into density, structure, relations, visibility, and semantic ownership;</li><li>compress acquired information into concise but rigorously structured semantic statements.</li></ol><p>Let us examine them separately.</p><h3 id="5-2-How-Is-Space-Measured"><a href="#5-2-How-Is-Space-Measured" class="headerlink" title="5.2 How Is Space Measured?"></a>5.2 How Is Space Measured?</h3><p>I will visualize one interface so its implementation is easier to understand.</p><p>The interface is called <code>find_opening</code>. It tests whether a cavity contains openings. In a cave, for example, it can measure how many entrances exist; in a room, it can determine whether a window is open.</p><p><em>UE is an excellent simulation tool. There is a surprising amount of “measurement” you can do inside it.</em></p><p>The engine already exposes raycasting—<code>LineTraceSingleByChannel</code> in UE—as a native physics query. Walls, rocks, and doorframes are positive space. An opening is the <strong>negative space</strong> enclosed by them.</p><p>So <code>find_opening</code> does not search for Actors whose names contain <code>Door</code> or <code>Archway</code>. It works backward through collision queries: rays that hit geometry trace the walls of a cavity, while groups of rays that escape continuously into the distance indicate an opening. The system then returns to either side of that opening to measure the real frame.</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 620px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260819-030817.gif" alt="Five-stage find_opening animation, from candidate sampling and multi-station rays to precise frame measurement" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Five-stage find_opening animation, using a simulated production-scene case</figcaption>  </figure></div><p>The implementation can be divided into five steps:</p><ol><li><strong>Find interior points.</strong> Lay down candidate samples inside the cavity and keep those that are genuinely enclosed.</li><li><strong>Choose observation stations.</strong> Select several spatially separated interior points as viewpoints.</li><li><strong>Cast rays.</strong> Fire rays around each station and find directions that escape into the distance.</li><li><strong>Fuse and classify.</strong> Combine observations from all stations and distinguish entrances, high windows, and internal passages.</li><li><strong>Measure exact dimensions.</strong> Return to the edges of each opening and measure its real width and height.</li></ol><p>The low-level C++ Probe retrieves terrain, instances, collision hits, and ray results from the live Editor. A Python spatial kernel handles candidate sampling, station selection, sector fusion, classification, and precise-measurement scheduling. The LLM no longer receives more than ten thousand raw rays. It receives a compact set of scene facts with <strong>type, position, direction, width, height, source station, and evidence grade</strong>. It can quote the measurements directly or revisit the supplied camera positions for screenshot verification.</p><p>That is how we complete the spatial side of scene understanding.</p><h3 id="5-3-How-Is-Language-Compressed"><a href="#5-3-How-Is-Language-Compressed" class="headerlink" title="5.3 How Is Language Compressed?"></a>5.3 How Is Language Compressed?</h3><p>After the five measurement stages above, we are holding more than ten thousand rays, hundreds of sample points, and over three thousand instances. We cannot hand all that directly to the model—the context window would explode. This is why the earlier Canvas diagram includes a Python data-reduction layer between the raw probes and the LLM.</p><p>Compression here does not mean shortening sentences. It is a set of language-design principles:</p><ol><li><strong>Description is not a data dump.</strong> The tool cooks raw instance data into statements: “this region contains 3,242 instances across 200 asset types, organized into 12 clusters; the largest cluster lies to the southeast…” If the text exceeds its budget, it automatically falls back to a coarser LoD. The underlying detail remains available at finer levels.</li><li><strong>Disclose progressively; summarize first.</strong> Descriptions have four levels, LoD0 through LoD3. LoD3 is a one-sentence summary. LoD2 is an asset inventory with statistics. LoD1 expands clusters and spatial relations. LoD0 preserves per-instance detail. The result is also divided into themed channels. By default, the model receives only LoD3: spend a few tokens deciding whether the region deserves attention, then drill down only when necessary—exactly how a person explores a scene, scanning from afar before walking closer.</li><li><strong>Aggregation is reversible.</strong> Compressed detail is not destroyed. LoD1 shows clusters; if the model wants LoD0 for one cluster, <code>expand</code> drills into only that cluster instead of describing the whole region again.</li><li><strong>Truncation must confess.</strong> Every compressed or sampled result says so: <code>truncated (~30% sampled)</code>, <code>(3 of 148)</code>, or an approximate value prefixed with <code>~</code>. Every number tells the model whether it came from the full population or a sample.</li></ol><p>This is what I meant earlier by “compressing acquired information into rigorously structured semantic expression”: <strong>we compress tokens while progressive disclosure preserves access to the information.</strong></p><p>Here is the token cost of four output levels for the same <code>describe_region</code> query:</p><table><thead><tr><th>Output form</th><th>What the model receives</th><th align="right">Characters</th><th align="right">≈ tokens</th></tr></thead><tbody><tr><td>Raw JSON data dump</td><td>One coordinate array per instance</td><td align="right">461,193</td><td align="right">~115,000</td></tr><tr><td>LoD3 summary</td><td>One sentence</td><td align="right"><strong>244</strong></td><td align="right"><strong>61</strong></td></tr><tr><td>LoD2 inventory</td><td>Asset inventory + density statistics</td><td align="right">840</td><td align="right">210</td></tr><tr><td>LoD1 relations</td><td>Clusters + spatial relations + statistics</td><td align="right">2,616</td><td align="right">654</td></tr><tr><td>LoD0 detail, one expanded cluster</td><td>One category only: a 20-instance scattered-rock cluster</td><td align="right"><strong>1,687</strong></td><td align="right"><strong>421</strong></td></tr><tr><td>LoD0 detail, entire region</td><td>One or two lines per instance, about 23 tokens each. <strong>The system never normally requests an entire region this way.</strong> A forced full dump can even exceed raw JSON because GSDL carries many annotations.</td><td align="right">921,223</td><td align="right">~230,000</td></tr></tbody></table><p>Here is the complete LoD3 result:</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><span class="line">@gsdl v0.1</span><br><span class="line">@project: ScatterTest</span><br><span class="line">@asset_classes: [Foliage, StaticMesh]</span><br><span class="line">@enrichment: []</span><br><span class="line">@frame: m</span><br><span class="line"></span><br><span class="line">region(id=R7, bbox=[0.0..200.0, 0.0..200.0, 0.0..18.0]m) &#123;</span><br><span class="line"></span><br><span class="line">  # -- summary (LoD3) --</span><br><span class="line">  R7 summary “anchor=watch_tower; 10021 instances/3 classes; density=0.25/m²; orientation=scattered.”.</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Line by line:</p><ul><li><code>@gsdl v0.1</code> — the version declaration.</li><li><code>@project: ScatterTest</code> — the map or project being observed.</li><li><code>@asset_classes: [Foliage, StaticMesh]</code> — the broad asset classes present in the region.</li><li><code>@enrichment: []</code> — the semantic-enrichment slot, connected to the asset-semantic encoding from Form 1. It is empty in this example.</li><li><code>@frame: m</code> — the unit contract. Every following length is in meters and every angle is a compass angle, so the model never has to guess whether <code>92.05</code> means centimeters or meters.</li><li><code>region(id=R7, bbox=[0.0..200.0, 0.0..200.0, 0.0..18.0]m)</code> — the region envelope. Every statement inside the braces applies to exactly this bounding box and not an inch beyond it.</li><li><code># -- summary (LoD3) --</code> — a channel heading that identifies the theme of the following block.</li><li><code>R7 summary ...</code> — the only body sentence and the only sentence form in the entire output: a <strong>triple</strong>. The subject is R7, the predicate is <code>summary</code>, and the object is a semicolon-separated compressed statement terminated by a period. Predicates come from a fixed vocabulary—<code>located_at</code>, <code>bound</code>, <code>near</code>, <code>density</code>, and so on—rather than free-form prose.</li></ul><p>Inside that predicate:</p><p><code>anchor=watch_tower</code> names the landmark. The anchor is selected by salience—visual area × rarity. Among ten thousand six-meter pine trees, only the eighteen-meter watchtower deserves the title. Every later cluster relation hangs from it: <code>pine_c2 of watch_tower</code> or <code>pine_c2 offset (dir=NE, d=43.2m)</code> uses the tower as an origin. <code>10021 instances/3 classes</code> reports scale, <code>density=0.25/m²</code> reports spacing, and <code>orientation=scattered</code> is a binned orientation-entropy result: ordered placement or disorder.</p><p>People give directions in the same order: first a landmark—“near Black Well”—then a relative comparison—“darker than the cave at Flower-Fruit Mountain.” The anchor turns that human habit into grammar.</p><p>Two other grammatical elements are not visible in this sample.</p><p>The first is <strong>precision marking</strong>:</p><ul><li>measured values have no prefix: <code>pine_c1.1 pose [92.05, 12.65, 0.0]m</code>; every number was measured;</li><li>estimated values use <code>~</code>: <code>watch_tower salience ~1.00</code>; salience is a calculated estimate;</li><li>classifications use <code>#</code>: <code>size_dist &#123;M:20, L:10001&#125;#</code>; bins are rule-based judgments rather than direct measurements.</li></ul><p>The second is <strong>reference resolution</strong>. Similar objects are combined into clusters and the long tail is merged into a diffuse field. No matter how many instances exist, the language usually collapses them into only a dozen or so names. If you have a box of pencils, you do not need to say “pencil A, pencil B, pencil C…” Clustering is another form of referential compression.</p><p>Anchor, scale, density, and orientation all fit into those 61 tokens. The model can drill into whichever part it cares about.</p><p>LoD0 gives each instance one or two lines:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pine_c1.1      pose  [92.05, 12.65, 0.0]m @ yaw=99.7°.</span><br><span class="line">pine_c1.1      bound [0.85×0.85×6.41]m.</span><br></pre></td></tr></table></figure><p>The model calls the appropriate interface itself to retrieve the detail it needs.</p><p>Do you remember the 3 × 3 understanding&#x2F;expression matrix from Chapter 2? I proposed three expression strategies: reference, comparison, and metaphor. Now we can evaluate them.</p><p><strong>Reference</strong> is exactly what anchors and clusters provide. <code>pine_c2</code> and “the group northeast of the watchtower” refer to the same thing. <code>offset (dir=NE, d=43.2m)</code> is the formal version of “near Black Well.”</p><p><strong>Comparison</strong> is quantized in GSDL. The system can provide precise numbers and comparative bands. But bands require quantization and judgment, and judgment introduces instability. That is why comparison received an orange, hard rating in the matrix.</p><p>The third strategy, <strong>metaphor</strong>—“this cave looks like the White Bone Demon’s lair”—is genuinely difficult. It depends partly on the LLM’s own understanding and partly on Vector Atlas, introduced later, which needs artists to teach and annotate scene semantics.</p><h3 id="5-4-Self-Evolving-Interfaces"><a href="#5-4-Self-Evolving-Interfaces" class="headerlink" title="5.4 Self-Evolving Interfaces"></a>5.4 Self-Evolving Interfaces</h3><p>The interface above is only one example. Around July 20, 2026, GSDL’s measurable spatial capabilities looked roughly like this:</p><table><thead><tr><th>Capability</th><th>Representative interfaces</th><th>Core implementation</th><th>Questions it can answer</th></tr></thead><tbody><tr><td><strong>Topology</strong></td><td><code>compare(on=&quot;spatial&quot;)</code>, <code>query</code></td><td>World-AABB reasoning using RCC-8; scale-adaptive tolerances; DC &#x2F; EC &#x2F; PO &#x2F; TPP relation codes</td><td>Are they disconnected, touching, overlapping, or contained?</td></tr><tr><td><strong>Direction</strong></td><td><code>compare(on=&quot;spatial&quot;)</code>, <code>describe_region</code></td><td>Eight compass directions; facing &#x2F; parallel &#x2F; opposed &#x2F; oblique orientation</td><td>Which side is A on? Are they facing or back-to-back?</td></tr><tr><td><strong>Distance</strong></td><td><code>compare(on=&quot;spatial&quot;)</code>, <code>query</code></td><td>AABB surface gap, center distance, vertical offset; footprint-scaled <code>near</code> levels</td><td>How far apart? Which is higher? Does this count as “next to”?</td></tr><tr><td><strong>Support</strong></td><td><code>describe_region</code>, <code>relation_trace</code>, <code>compare</code></td><td>Vertical contact + footprint overlap as support evidence; sparse support graph</td><td>What supports what? “The cup is on the table” becomes a measurement.</td></tr><tr><td><strong>Passage and occlusion</strong></td><td><code>find_opening</code>, <code>skyline</code>, <code>query_view</code>, <code>rays</code> &#x2F; <code>los_ring</code></td><td>Multi-station rays, escape-direction clustering, frame remeasurement; viewpoint-relative occlusion jointly verified by rays and masks</td><td>How many exits does the cave have? What blocks the view?</td></tr></tbody></table><p>Those capabilities correspond directly to the spatial relations discussed above. GSDL also includes supporting spatial capabilities:</p><table><thead><tr><th>Capability</th><th>Representative interfaces</th><th>Core implementation</th><th>Questions it can answer</th></tr></thead><tbody><tr><td><strong>Survey</strong></td><td><code>scan_density</code>, <code>describe_region</code></td><td>Enumerate Actors &#x2F; Instances &#x2F; Landscape; grid aggregation; snapshots, clusters, layered summaries</td><td>What is here? Where is it concentrated? Scattered or clustered?</td></tr><tr><td><strong>Localization</strong></td><td><code>search_subjects</code>, <code>find_by_class</code>, <code>actor_meta</code>, <code>semantic_search</code></td><td>Name and Chinese-alias indexes, class queries, asset-metadata joins, cross-checking retrieval results against instances</td><td>What is the “treasure chest” called? Where is it? Which asset does it use?</td></tr></tbody></table><p>Before discussing spatial relations, the LLM must first know what exists and which instance a phrase refers to. Only then does it call the deeper reasoning interfaces.</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 340px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260817-222622.png" alt="GSDL interface statistics showing call count, failures or refusals, LLM characters, and latency" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">GSDL interface call volume and latency dashboard</figcaption>  </figure></div><p>GSDL exposes a growing collection of question-oriented measurement capabilities. The list was not designed all at once; each evaluation round pushed it in a new direction. What you see above is only one snapshot in time. At present there are roughly 35 interfaces, about 20 of which are stable.</p><p>I first tried iterating on them in the conventional way. The result was negligible, mainly because iteration was too slow and my own judgment too subjective. I later switched to <strong>evaluation-driven development with <code>/Loop</code></strong>: establish a bank of scene questions, have an LLM judge each round from its execution traces and answers, then let those results drive the next interface change. The improvement was dramatic.</p><p>My <code>/Loop</code> process has two stages.</p><p>For the first stage, I wrote 36 test questions by hand. The earliest bank contained only 11 questions; I later expanded it to 36, manually phrasing every question and annotating every answer. It nearly killed me.</p><p>The questions cover more than a dozen categories, including:</p><ul><li><strong>Description and understanding</strong>: what is visible in the current viewport, how a district is planned, and how its functions are divided.</li><li><strong>Spatial relations</strong>: how far A is from B, in which direction, which is higher, and which blocks the other.</li><li><strong>Anomaly detection</strong>: floating objects, intersections, and obviously misplaced assets.</li><li><strong>Passage structure</strong>: how many openings a cave has and how wide they are.</li><li><strong>Composite decomposition</strong>: which six of fourteen archway assets form one complete gate, or which components make up a fortified enclosure.</li><li><strong>Honest denial</strong>: if the scene contains no cars and no neon, can the model say “none” with evidence instead of hallucinating one?</li></ul><p>Every question is written in natural language and deliberately avoids naming any tool.</p><p>I run the <code>/Loop</code> with two agents. <strong>One runs the evaluation</strong>: it opens N subagents with clean contexts and asks each to call MCP; a Fable 5 lead agent only schedules work, writes the summary, and produces requirements. <strong>The other changes the code</strong>: Opus or GLM 5.2 optimizes the interface logic from those requirements.</p><p><em>The MCP-testing subagents use local Qwen3.6 27B and MiniMax M3. SOTA models do achieve higher completion rates, but production-scale MCP usage can become frighteningly expensive. These tools are meant for production, not leaderboards, so they must be adapted to the models that will actually be deployed.</em></p><p>I focus on three hard groups of metrics:</p><ul><li><strong>Delivery</strong>: completion rate, accuracy, and honesty.</li><li><strong>Cost</strong>: actual token usage, interface call count, and the interface-schema tax.</li><li><strong>Speed</strong>: time per question, average latency in milliseconds, maximum latency, and watchdog timeout count. I sometimes also track health and discoverability.</li></ul><p>Large and small models differ sharply in some respects, particularly tool use. Small models often forget which interfaces exist. I eventually built a routing layer that the model can call before querying the scene, so it can confirm which tool is appropriate. This trades more routing calls for a higher completion rate:</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 860px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260821-025635.png" alt="Per-case comparison of tool-call traces before and after routing" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Per-case tool-call comparison before and after routing</figcaption>  </figure></div><p>Here is how the three hard metrics changed, using ten questions under the same protocol on July 21–22:</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 860px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260821-025518.png" alt="GSDL Loop metrics showing token usage, call count, and correct answers across rounds" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">The three hard metrics across iteration rounds</figcaption>  </figure></div><p>Within a few days, tokens fell by 54%, calls fell by 64%, and the system reached a perfect score and held it.</p><p>I later expanded the question bank. After more than ten additional rounds, the metrics flattened out. Pushing further no longer made much sense; overfitting began to appear.</p><p>Time for a stronger dose.</p><p>That brought me to the second stage of <code>/Loop</code> development:</p><div class="canvas-embed" data-canvas-slug="attachments/Canvas/gsdl-evolution-loop.en"><svg xmlns="http://www.w3.org/2000/svg" class="canvas-svg" data-canvas-revision="35a21f629b50" width="582" height="280" viewBox="20 -80 1580 760" preserveAspectRatio="xMidYMid meet" role="img" aria-label="gsdl-evolution-loop.en"><title>gsdl-evolution-loop.en</title><defs><marker id="canvas-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" /></marker></defs><g class="canvas-groups"><g class="canvas-node canvas-node--group" data-id="a4c4c4c4c4c4c4c4" data-x="60" data-y="260" data-width="880" data-height="360" data-color="custom" style="--canvas-node-accent:#bbf7d0"><rect class="canvas-group__bg" x="60" y="260" width="880" height="360" rx="12" /><text class="canvas-group__label" x="72" y="282">④ Test · Feedback Loop</text></g><g class="canvas-node canvas-node--group" data-id="a1c1c1c1c1c1c1c1" data-x="480" data-y="-40" data-width="740" data-height="180" data-color="custom" style="--canvas-node-accent:#bae6fd"><rect class="canvas-group__bg" x="480" y="-40" width="740" height="180" rx="12" /><text class="canvas-group__label" x="492" y="-18">① Daytime · Collect Usage Signals</text></g><g class="canvas-node canvas-node--group" data-id="a2c2c2c2c2c2c2c2" data-x="1280" data-y="200" data-width="280" data-height="240" data-color="custom" style="--canvas-node-accent:#e9d5ff"><rect class="canvas-group__bg" x="1280" y="200" width="280" height="240" rx="12" /><text class="canvas-group__label" x="1292" y="222">② Scheduled · Organize Requirements</text></g><g class="canvas-node canvas-node--group" data-id="a3c3c3c3c3c3c3c3" data-x="1020" data-y="500" data-width="280" data-height="140" data-color="custom" style="--canvas-node-accent:#fed7aa"><rect class="canvas-group__bg" x="1020" y="500" width="280" height="140" rx="12" /><text class="canvas-group__label" x="1032" y="522">③ Nighttime · Implement Capabilities</text></g></g><g class="canvas-edges"><g class="canvas-edge-group" data-id="e101010101010101" data-from-node="b101010101010101" data-to-node="b202020202020202" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 700 27 C 740 27, 700 50, 740 50" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="e202020202020202" data-from-node="b202020202020202" data-to-node="b303030303030303" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 940 50 C 980 50, 940 27, 980 27" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="e303030303030303" data-from-node="b303030303030303" data-to-node="b404040404040404" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 1090 54 C 1090 177.13317090767123, 1420 96.86682909232876, 1420 220" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="e404040404040404" data-from-node="b404040404040404" data-to-node="b505050505050505" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 1420 300 C 1420 340, 1420 290, 1420 330" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="e505050505050505" data-from-node="b505050505050505" data-to-node="b606060606060606" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 1420 384 C 1420 481.80706632049765, 1160 422.19293367950235, 1160 520" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="1290" y="452" text-anchor="middle">Gap</text></g><g class="canvas-edge-group" data-id="e606060606060606" data-from-node="b606060606060606" data-to-node="b707070707070707" data-from-side="left" data-to-side="right"><path class="canvas-edge" d="M 1040 547 C 971.3624657267533 547, 928.6375342732467 447, 860 447" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="950" y="497" text-anchor="middle">Deliver</text></g><g class="canvas-edge-group" data-id="e707070707070707" data-from-node="b707070707070707" data-to-node="b808080808080808" data-from-side="left" data-to-side="right"><path class="canvas-edge" d="M 640 447 C 591.4659340714632 447, 648.5340659285368 307, 600 307" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="620" y="377" text-anchor="middle">Results</text></g><g class="canvas-edge-group" data-id="e808080808080808" data-from-node="b808080808080808" data-to-node="b909090909090909" data-from-side="left" data-to-side="right"><path class="canvas-edge" d="M 380 307 C 340 307, 340 330, 300 330" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="340" y="318.5" text-anchor="middle">Summary</text></g><g class="canvas-edge-group" data-id="e909090909090909" data-from-node="b909090909090909" data-to-node="ba0a0a0a0a0a0a0a" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 190 380 C 190 430, 310 420, 310 470" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="250" y="425" text-anchor="middle">Feedback</text></g><g class="canvas-edge-group" data-id="ea0a0a0a0a0a0a0a" data-from-node="ba0a0a0a0a0a0a0a" data-to-node="b606060606060606" data-from-side="right" data-to-side="bottom" style="--canvas-edge-color:#dc2626"><path class="canvas-edge" d="M 420 520 C 667.3225514271686 520, 1160 821.3225514271686, 1160 574" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="790" y="547" text-anchor="middle">Close loop · fix</text></g></g><g class="canvas-nodes"><g class="canvas-node canvas-node--text" data-id="b101010101010101" data-x="500" data-y="0" data-width="200" data-height="54" data-color="custom" style="--canvas-node-accent:#0284c7"><rect class="canvas-node__bg" x="500" y="0" width="200" height="54" rx="8" /><foreignObject x="500" y="0" width="200" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>User Calls a Tool</strong></p><p>Everyday GSDL usage</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b202020202020202" data-x="740" data-y="0" data-width="200" data-height="100" data-color="custom" style="--canvas-node-accent:#0284c7"><rect class="canvas-node__bg" x="740" y="0" width="200" height="100" rx="8" /><foreignObject x="740" y="0" width="200" height="100"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Hook Fires</strong></p><p>Record the question silently</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b303030303030303" data-x="980" data-y="0" data-width="220" data-height="54" data-color="custom" style="--canvas-node-accent:#0284c7"><rect class="canvas-node__bg" x="980" y="0" width="220" height="54" rx="8" /><foreignObject x="980" y="0" width="220" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>User Rating</strong></p><p>LLM completion score</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b909090909090909" data-x="80" data-y="280" data-width="220" data-height="100" data-color="custom" style="--canvas-node-accent:#16a34a"><rect class="canvas-node__bg" x="80" y="280" width="220" height="100" rx="8" /><foreignObject x="80" y="280" width="220" height="100"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Test Report</strong></p><p>Pass &#x2F; fail &#x2F; recommendations</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b707070707070707" data-x="640" data-y="420" data-width="220" data-height="54" data-color="custom" style="--canvas-node-accent:#16a34a"><rect class="canvas-node__bg" x="640" y="420" width="220" height="54" rx="8" /><foreignObject x="640" y="420" width="220" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Subagent Swarm</strong></p><p>Run real MCP tests</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="ba0a0a0a0a0a0a0a" data-x="200" data-y="470" data-width="220" data-height="100" data-color="custom" style="--canvas-node-accent:#dc2626"><rect class="canvas-node__bg" x="200" y="470" width="220" height="100" rx="8" /><foreignObject x="200" y="470" width="220" height="100"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Feedback to Executor Agent</strong></p><p>Fix → next round</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b606060606060606" data-x="1040" data-y="520" data-width="240" data-height="54" data-color="custom" style="--canvas-node-accent:#ea580c"><rect class="canvas-node__bg" x="1040" y="520" width="240" height="54" rx="8" /><foreignObject x="1040" y="520" width="240" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Nighttime Executor Agent</strong></p><p>Implement new capabilities</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b404040404040404" data-x="1300" data-y="220" data-width="240" data-height="80" data-color="custom" style="--canvas-node-accent:#7c3aed"><rect class="canvas-node__bg" x="1300" y="220" width="240" height="80" rx="8" /><foreignObject x="1300" y="220" width="240" height="80"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Scheduled Agent</strong></p><p>Summarize interaction logs</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b505050505050505" data-x="1300" data-y="330" data-width="240" data-height="54" data-color="custom" style="--canvas-node-accent:#7c3aed"><rect class="canvas-node__bg" x="1300" y="330" width="240" height="54" rx="8" /><foreignObject x="1300" y="330" width="240" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Capability Requirements</strong></p><p>Identify capability gaps</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b808080808080808" data-x="380" data-y="280" data-width="220" data-height="54" data-color="custom" style="--canvas-node-accent:#16a34a"><rect class="canvas-node__bg" x="380" y="280" width="220" height="54" rx="8" /><foreignObject x="380" y="280" width="220" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Test Lead Agent</strong></p><p>Aggregate and judge</p></div></foreignObject></g></g></svg><span class="canvas-embed__expand" aria-hidden="true" title="点击放大">⛶</span></div><p>I built a passive-update system. When users call MCP tools during their normal work, a Hook records the exact question and the conversation in a background database. Users are also occasionally asked to rate how completely the LLM finished the task.</p><p>A scheduled agent turns those interaction records into a capability-requirements table whenever it detects a weak area. At night, after I leave work, an executor agent implements the capability. Another lead agent then launches many subagents to test the MCP changes, summarizes their reports and recommendations, and feeds them back to the executor. The executor iterates again. The loop continues until the new interface stabilizes.</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 250px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260819-034651.png" alt="Two NVIDIA DGX Spark systems on a wooden table" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Two heavily armed DGX Sparks, purchased at great expense</figcaption>  </figure></div><p>To make this loop work, I even bought two DGX Sparks to run DeepSeek V4 Flash locally. They are chewing grass as I write this—an otherwise pointless display of armament. I hope my Leader sees this paragraph and gives me a raise. ლ(╹◡╹ლ)</p><p>The <code>/Loop</code> is still not as complete as I want. I am continuously tuning it, and it contains far more concerns than the sketch suggests: user authorization, data redaction, sandboxing, audit, rollback, and so on. That is another subject I want to cover next time: a self-evolving agent system driven by real user data.</p><h3 id="5-5-A-GSDL-Call-in-Practice"><a href="#5-5-A-GSDL-Call-in-Practice" class="headerlink" title="5.5 A GSDL Call in Practice"></a>5.5 A GSDL Call in Practice</h3><p>Here is one real GSDL invocation from a test I ran on July 23, 2026:</p><blockquote><p><strong>“How many passable openings does this cave have, and how wide is each one?”</strong></p></blockquote><p>It neatly illustrates the difference between “reading Actors” and “understanding space.”</p><p>Without GSDL—for example, with a conventional UE MCP approach—the LLM searches for assets whose names contain <code>Archway</code> and treats components such as <code>SM_WallArchway_12x3</code> and <code>SM_WallArchway_3x6</code> as openings. In my test, those <code>Archway</code> Actors were the walls surrounding the openings, not the openings themselves, so the model misclassified them.</p><p>With GSDL:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">→ nm_status             ← confirm tool status</span><br><span class="line">→ describe_region       ← survey the region</span><br><span class="line">→ find_opening          ← measure openings with rays</span><br><span class="line">→ fly_to                ← fly to the site</span><br><span class="line">→ capture_view          ← verify with a screenshot</span><br><span class="line"></span><br><span class="line">→ answer with type, position, width, and evidence grade</span><br></pre></td></tr></table></figure><p>Before the <code>find_opening</code> route existed, the model used 47 tool calls and consumed 64,820 returned characters, yet still mistook wall components containing <code>Archway</code> for entrances. After routing was added, it needed only five calls and 15,540 characters to complete both measurement and visual verification.</p><div class="media-layout media-layout--pair media-layout--compact">  <figure style="flex: 1 1 0; min-width: 0; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260820-191300.png" alt="A lit opening between cave walls, with the distant scene visible through it" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption>Rendered view of the cave opening</figcaption>  </figure>  <figure style="flex: 1 1 0; min-width: 0; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260820-191249.png" alt="Per-pixel instance segmentation separating the cave walls from background objects" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption>Pixel-level instance segmentation from the same camera</figcaption>  </figure></div><p>The model then flew to each candidate opening and captured a verification image. The mask contained 494 pixels belonging to <code>BP_Sky_Sphere</code>, showing that the line of sight passed through the opening into the outside background. That is only visual supporting evidence for the opening’s existence. Whether it is passable and how wide it is still come from collision rays near the ground and precise frame measurements.</p><p>In the end, the system classified ten candidate structures into ground-level passable openings or passages, elevated windows, and skylights, then reported the positions and measured widths of the ground openings. It even found an opening I had never noticed. The important point is not “how many Actors named <code>Archway</code> did it find?” It is that the system can distinguish a cave wall, an internal passage, an exterior entrance, and an elevated opening that cannot be traversed directly.</p><h3 id="5-6-Open-Sourcing-the-Project"><a href="#5-6-Open-Sourcing-the-Project" class="headerlink" title="5.6 Open-Sourcing the Project"></a>5.6 Open-Sourcing the Project</h3><p>Thank you for making it this far.</p><p>Once this busy period is over and the company-specific material has been separated, I will open-source GSDL on GitHub. I would be delighted if people tried it and sent suggestions; one person’s energy is simply too limited.</p><p>The repository placeholder is already here: <a href="https://github.com/youdrew/OpenGSDL">OpenGSDL</a>. I expect to upload the code in early September. It will contain both the mask method and GSDL, packaged as a multi-version UE MCP plugin that you can extend or use alongside Epic’s official MCP.</p><h2 id="6-Vector-Atlas-Semantic-Encoding-for-the-Entire-Map"><a href="#6-Vector-Atlas-Semantic-Encoding-for-the-Entire-Map" class="headerlink" title="6. Vector Atlas: Semantic Encoding for the Entire Map"></a>6. Vector Atlas: Semantic Encoding for the Entire Map</h2><p>I built Vector Atlas because every live model query against a map is painfully slow. It has to load a scene, acquire masks, capture screenshots, and so on. In our project, one such operation takes at least 60 seconds. Searching across an entire world multiplies that latency dramatically. Could the process be made much faster?</p><p>I also need far more semantic information about the scene. Artists must annotate much of it; I could never label an entire large map alone. Scene semantics—“this is a particular NPC’s house,” for example—also change as production evolves and therefore cannot be hard-coded. I need a dynamic, editable service for collecting scene semantics.</p><p>That is why I built <strong>Vector Atlas</strong>, a semantic encoding of the whole map.</p><p>It combines the 2D mask method and GSDL.</p><blockquote><p>Encode every map <strong>offline</strong> into a semantically searchable table of viewpoints. People can search the entire map by meaning, add viewpoints manually, and edit their semantics. The same structure also becomes cross-session long-term memory for AI: an agent can add a viewpoint, preserve it as a memory, and retrieve it later.</p></blockquote><p>The promise from the beginning—to “memorize” every place and query it in milliseconds—is fulfilled here. It is an engineered trade: storage for performance.</p><p>This article is already far too long, and Vector Atlas is itself a very complicated project. How should search be optimized? How should viewpoints be captured? How should it be deployed? How can the system infer an angle it never photographed? Each question deserves serious thought.</p><p>So Vector Atlas, together with the real-user-data self-evolving agent system introduced above, will be the subject of the next article. By then, the system should also be more complete.</p><p>For now, here is a short tool demo.</p><h2 id="7-The-Version-for-Carbon-Based-Lifeforms"><a href="#7-The-Version-for-Carbon-Based-Lifeforms" class="headerlink" title="7. The Version for Carbon-Based Lifeforms"></a>7. The Version for Carbon-Based Lifeforms</h2><div class="media-layout media-layout--quad">  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/article2-carbon-search-redacted-v2.gif" alt="Semantic search" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Figure 1. Semantic search for scene locations across the entire map</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/article2-carbon-asset-redacted-v2.gif" alt="Reaching the underlying asset" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Figure 2. Click a mask to jump directly to the corresponding asset</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/article2-carbon-camera-redacted-v2.gif" alt="Viewpoint adjustment anywhere on the complete map" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Figure 3. Jump the camera to any region without loading the map</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/article2-carbon-mask-redacted-v2.gif" alt="Asset-mask information display" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Figure 4. Even an asset covering only 0.01% of an image is recorded</figcaption>  </figure></div><h2 id="8-The-Version-for-Silicon-Based-Lifeforms—and-a-Comparison-with-Epic’s-Approach"><a href="#8-The-Version-for-Silicon-Based-Lifeforms—and-a-Comparison-with-Epic’s-Approach" class="headerlink" title="8. The Version for Silicon-Based Lifeforms—and a Comparison with Epic’s Approach"></a>8. The Version for Silicon-Based Lifeforms—and a Comparison with Epic’s Approach</h2><p>I prepared 11 questions and had each model use either Epic’s official MCP or my MCP, NeuroMap build 08-14. Every question started from a clean context, and I repeated the evaluation with three models.</p><table><thead><tr><th>Metric</th><th align="right">DSV4flash<br>+ local Qwen3.5-9B-4bit<br>Epic</th><th align="right">DSV4flash<br>+ local Qwen3.5-9B-4bit<br>NeuroMap</th><th align="right">Qwen3.8 27B<br>Epic</th><th align="right">Qwen3.8 27B<br>NeuroMap</th><th align="right">GPT-5.6 Luna<br>Epic</th><th align="right">GPT-5.6 Luna<br>NeuroMap</th></tr></thead><tbody><tr><td>Harness</td><td align="right">Claude Code</td><td align="right">Claude Code</td><td align="right">Claude Code</td><td align="right">Claude Code</td><td align="right">Codex</td><td align="right">Codex</td></tr><tr><td>Completion rate</td><td align="right">100% (11&#x2F;11)</td><td align="right"><strong>100% (11&#x2F;11)</strong></td><td align="right">72.7% (8&#x2F;11)</td><td align="right"><strong>90.9% (10&#x2F;11)</strong></td><td align="right">100% (11&#x2F;11)</td><td align="right"><strong>100% (11&#x2F;11)</strong></td></tr><tr><td>Accuracy</td><td align="right">77.3%</td><td align="right"><strong>77.3%</strong></td><td align="right">59.1%</td><td align="right"><strong>86.4%</strong></td><td align="right">90.9%</td><td align="right"><strong>90.9%</strong></td></tr><tr><td>Honesty</td><td align="right"><strong>95.5%</strong></td><td align="right">90.9%</td><td align="right">68.2%</td><td align="right"><strong>72.7%</strong></td><td align="right">86.4%</td><td align="right"><strong>90.9%</strong></td></tr><tr><td>Total time, official primary runs</td><td align="right">468m 7.0s</td><td align="right"><strong>64m 52.2s</strong></td><td align="right">611m 8.9s</td><td align="right"><strong>291m 21.8s</strong></td><td align="right">141m 35.5s</td><td align="right"><strong>40m 3.6s</strong></td></tr><tr><td>Mean time across all 11 questions</td><td align="right">42m 33.4s</td><td align="right"><strong>5m 53.8s</strong></td><td align="right">55m 33.5s</td><td align="right"><strong>26m 29.3s</strong></td><td align="right">12m 52.3s</td><td align="right"><strong>3m 38.5s</strong></td></tr><tr><td>Maximum time for one question</td><td align="right">197m 4.3s</td><td align="right"><strong>20m 15.0s</strong></td><td align="right">220m 3.8s</td><td align="right"><strong>84m 50.3s</strong></td><td align="right">52m 1.6s</td><td align="right"><strong>18m 6.3s</strong></td></tr><tr><td>Total MCP calls</td><td align="right">514</td><td align="right"><strong>261</strong></td><td align="right">318</td><td align="right"><strong>221</strong></td><td align="right">7,919</td><td align="right"><strong>313</strong></td></tr><tr><td>Input &#x2F; output</td><td align="right">20,179,996 &#x2F; 261,572</td><td align="right"><strong>6,158,052 &#x2F; 119,825</strong></td><td align="right">10,173,709 &#x2F; 234,831</td><td align="right"><strong>8,438,133 &#x2F; 150,085</strong></td><td align="right">47,692,261 &#x2F; 207,474</td><td align="right"><strong>21,170,665 &#x2F; 94,384</strong></td></tr><tr><td>Fixed first-turn overhead</td><td align="right"><strong>700 tokens</strong></td><td align="right">12,326 tokens</td><td align="right"><strong>569 tokens</strong></td><td align="right">14,761 tokens</td><td align="right"><strong>~350 tokens</strong></td><td align="right">~12,900 tokens</td></tr></tbody></table><p>Overall, NeuroMap matched or exceeded delivery quality while sharply reducing tool calls and runtime. In the Luna evaluation, Epic’s MCP made <strong>7,919 calls</strong>; NeuroMap made only <strong>313</strong>, about <strong>one twenty-fifth as many</strong>, and took only <strong>28% of the total time</strong>.</p><p>Epic’s MCP exposes general-purpose, atomic editor operations. The model must route repeatedly and inspect objects one by one—almost a traversal of every Actor in the scene. As scene complexity grows, one question can fragment into hundreds or thousands of calls, and round-trip overhead expands rapidly. NeuroMap aggregates region description, anomaly detection, opening analysis, and other spatial computation on the UE side, then compresses the result with GSDL. Across all three models, that produced fewer calls, shorter long tails, and higher delivery rates.</p><p>The most striking accuracy result came from Qwen: my MCP raised accuracy from <strong>59.1%</strong> to <strong>86.4%</strong>. Aggregated, problem-oriented tools are not merely cheaper; they also reduce the burden on a weaker model that would otherwise have to traverse Actors, choose tools, and infer spatial relations by itself.</p><p>NeuroMap still has a relatively high schema overhead, and its tool set continues to be consolidated. There is more optimization ahead.</p><p>The following is one real workflow. The AI receives only a vague description—“a table with a Go board on top”—then locates it in a large scene, adjusts the camera, and saves a screenshot.</p><div class="media-layout media-layout--pair media-layout--compact">  <figure style="flex: 1 1 0; min-width: 0; margin: 0;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/article2-mcp-before-scene-redacted.png" alt="Giving the AI a vague task to locate a Go table" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666; text-align: center;">① The prompt describes only the remembered appearance and use, with no asset name or coordinate.</figcaption>  </figure>  <figure style="flex: 1 1 0; min-width: 0; margin: 0;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/article2-mcp-after-scene-redacted.png" alt="The AI locates the Go table and captures a screenshot" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666; text-align: center;">② About 57 seconds later, it has located the Go table, framed the view, and saved the screenshot to the desktop.</figcaption>  </figure></div><h2 id="9-Afterword"><a href="#9-Afterword" class="headerlink" title="9. Afterword"></a>9. Afterword</h2><p>Have you noticed something? NeuroBrowser—the plugin from Form 1—and NeuroMap—the plugin from this installment—both inject AI capabilities into the engine editor. Their initials are NB and NM; internally, the Chinese pronunciation gave them the nicknames “Your Dad” and “Your Mom.” Together, they feel a little like <strong>rebuilding the editor around AI capabilities</strong>.</p><p>Perhaps this is what the next generation of game editors should look like.</p><p>What do you think?</p>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/UnrealEngine/">UnrealEngine</category>
      
      <category domain="https://eugenepage.com/tags/AI/">AI</category>
      
      <category domain="https://eugenepage.com/tags/MCP/">MCP</category>
      
      <category domain="https://eugenepage.com/tags/SceneUnderstanding/">SceneUnderstanding</category>
      
      <category domain="https://eugenepage.com/tags/GSDL/">GSDL</category>
      
      
      <comments>https://eugenepage.com/2026/06/24/20260625.AIGameSeries-Article2/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>游戏生产AI落地武林秘籍（第二式：观局识阵）：图像掩码及语义抽象实现游戏场景理解</title>
      <link>https://eugenepage.com/zh-CN/2026/06/24/20260625.AIGameSeries-Article2/</link>
      <guid>https://eugenepage.com/zh-CN/2026/06/24/20260625.AIGameSeries-Article2/</guid>
      <pubDate>Wed, 24 Jun 2026 16:00:00 GMT</pubDate>
      
      <description>LLM 看不懂 UE 大世界？本文提出 2D 资产掩码方案（基于引擎 hit proxy 的高稳定性实例分割）与 GSDL 通用场景描述语言（颗粒度可变的 USD + MCP），并对比 MCP/CLI/快照/OpenUSD/直读截图五种引擎-LLM 转译层方案，让 AI 真正理解并「背下」全地图；文末预告 Vector Atlas 全地图语义编码。</description>
      
      
      
      <content:encoded><![CDATA[<h1 id="游戏生产AI落地武林秘籍·第二式「观局识阵」：图像掩码及语义抽象实现游戏场景理解"><a href="#游戏生产AI落地武林秘籍·第二式「观局识阵」：图像掩码及语义抽象实现游戏场景理解" class="headerlink" title="游戏生产AI落地武林秘籍·第二式「观局识阵」：图像掩码及语义抽象实现游戏场景理解"></a>游戏生产AI落地武林秘籍·第二式「观局识阵」：图像掩码及语义抽象实现游戏场景理解</h1><blockquote><p><strong>系列导读</strong>：「游戏生产AI落地武林秘籍」记录我来时路，都是血泪史。<br>我们在第一式「手到擒来」里教会了 AI 认识项目里的每一个资产——它知道 <code>shrub_jn_01a</code> 是哪棵江南矮灌木。但这还不够：<strong>认识每个兵卒，不等于看得懂整盘棋。</strong> 第二式「观局识阵」，要让 AI 看懂美术在地图上摆下的”阵”。</p><p><em>注：每一篇文章几乎纯手工打造，请放心食用。如果你想直接看实现效果，不在乎实现方法，可以越过第三至第六章</em>。</p></blockquote><hr><h2 id="一、缘起：无法理解的场景"><a href="#一、缘起：无法理解的场景" class="headerlink" title="一、缘起：无法理解的场景"></a>一、缘起：无法理解的场景</h2><p>第一式我们通过语义编码认识了每个资产，这一回，我们要回答的是：我在哪里？</p><p>众所周知，LLM是不理解场景的，即使截图给它，它也看不懂美术摆下的龙门阵：</p><ol><li>“这个区域大概是什么风格？”<ul><li>AI：幻觉回答一下。</li></ul></li><li>“帮我去找场景中一个在楼梯边的宝箱”<ul><li>AI：遍历世界大纲里面的东西，然后一个个地去找。然后哪个在楼梯边？不知道。</li></ul></li><li>“给你一张图，这个图上的位置在我大世界的哪里啊？”<ul><li>AI：什么玩意儿？这我咋找？</li></ul></li><li>“帮我找到一个我用石头堆成的山洞”<ul><li>AI：@#￥%……&amp;*</li></ul></li></ol><p>本文要解决的就是这些问题（地图理解因为内容有点多故分成两篇文章，此篇是第一篇，讲述理解地图，第二篇会讲全地图的向量检索）。<br>经过本轮修炼，你的 AI 能像人一样，<mark>真正意义上“理解”</mark>你的地图，并且将采集过的场景和位置<mark>“背”</mark>下来；对于未拍摄的视角，则计划通过相邻视角的插值推断加以补全，最终实现大世界地图任意位置的<mark>毫秒级</mark>查询。</p><h2 id="二、如何理解一个场景？"><a href="#二、如何理解一个场景？" class="headerlink" title="二、如何理解一个场景？"></a>二、如何理解一个场景？</h2><p>我们来看一下，人类看到一个场景时，是怎么样的？</p><ol><li>最先，是看到游戏场景 → “诶，黑黢黢的”（视觉）</li><li>然后，感受了其中的空间关系 → “咦，前面有点光”（空间）</li><li>最后，理解了场景所表达的含义 → “哦，这里那个白骨精的山洞”（语义）</li></ol><p>他会怎么向别人表述这个场景，他说：</p><blockquote><p>我刚刚去了一个场景，从 <strong>黑井附近</strong> 按E就可以进去，那个地方 <strong>比花果山山洞还要黑</strong>，我感觉那里应该就是 <strong>隐藏关卡boss白骨精的老窝</strong> 了。</p></blockquote><p>根据上面的描述，我将理解和表述能力拆分开来：</p><table><thead><tr><th>感知 \ 表述</th><th>指代</th><th>比较</th><th>隐喻</th></tr></thead><tbody><tr><td><strong>视觉信息</strong></td><td>🟢 用视觉风格指认</td><td>🟡 比较疏密与风格强度</td><td>🟠 借文化意象讲质感风格</td></tr><tr><td><strong>空间信息</strong></td><td>🟡 用空间位置指认对象</td><td>🟡 比较相对位置与分布</td><td>🟠 借他物形容空间形态</td></tr><tr><td><strong>语义信息</strong></td><td>🟡 用功能用途指认</td><td>🟠 表达偏好或否定</td><td>🔴 借他物讲功能与叙事</td></tr></tbody></table><blockquote><p>难度图例：🟢 易 ｜ 🟡 中 ｜ 🟠 难 ｜ 🔴 极难（对 AI 来说既难以理解，又难以准确表达）</p></blockquote><p><strong>纵轴 · 感知层</strong> 对应 AI 理解场景的三类能力——</p><ul><li><strong>「视觉信息」</strong>——<strong>这片东西看起来怎么样？</strong>（风格、疏密、质感、美学印象）。</li><li><strong>「空间信息」</strong>——<strong>A 和 B 在空间位置上，是怎么样的？</strong>（被谁包住、贴着谁、和谁相交、谁承载谁、谁遮挡谁——拓扑 + 方向 + 距离 + 承载 + 遮挡）。<br>  其中<strong>拓扑关系</strong>（RCC-8：DC &#x2F; EC &#x2F; PO &#x2F; EQ &#x2F; TPP &#x2F; NTPP…）是定性空间推理里相当成熟的研究领域，可参考 Cohn &amp; Renz 的经典综述 <a href="https://users.cecs.anu.edu.au/~jrenz/papers/cohn-renz-krbook07.pdf">Qualitative Spatial Representation and Reasoning</a>。承载关系与遮挡关系在视觉感知研究里常被单列（如 <a href="https://www.cs.princeton.edu/courses/archive/spring08/cos598B/Readings/Biederman1982.pdf">Biederman 1982</a> 的 Support &#x2F; Interposition），但在引擎里它们落在同一套几何数据上，故本框架按<strong>数据源</strong>归并进空间。</li><li><strong>「语义信息」</strong>——<strong>A 在场景里是干嘛用的、起什么叙事作用？</strong><ol><li><strong>物件含义</strong>——这一个物品里面隐含着什么表达？</li><li><strong>组合关系</strong>——组合在一起是什么意义（分体论 mereology，比如一堆石头中间掏个口子叫山洞）。</li><li><strong>设计语义</strong>——这个场景很破败，是想表达一个正在经历饥荒的村庄。</li><li><strong>共现关系</strong>——A 和 B 经常一起出现吗、什么搭配最稳？<br> 这一层会同时碰到分布语义学、RDF（知识关系表示）和 FrameNet（框架语义）等不同方向。共现本身是从频次 &#x2F; 向量里“学”出来的，不是从几何里“算”出来的；如果再进一步建模“当前状态如何影响下一个状态”，才进入 Markov（马尔可夫）模型的范围。我把共现作为语义的一部分，因为它表达的是比单体几何更高一层的场景逻辑。这个涉及到一定的表达，有点超纲本文内容。</li></ol></li></ul><p><strong>横轴 · 表述层（能否把学到的转述出来）</strong> 对应本文关心的三种表达策略——</p><ul><li>「指代」对应 <strong>deixis</strong>（指示语），靠共同注意锚定，不给坐标，也能指向同一个对象；</li><li>「比较」会用到 <strong>vague quantifier &#x2F; vague language</strong>（模糊量词 &#x2F; 模糊语言），不一定给出精确数字，而是给出方向、程度和隐含基线；</li><li>「隐喻」对应 <strong>conceptual metaphor</strong>（概念隐喻 &#x2F; 跨域映射），借一种共享的文化经验来表达原本很难直接说清的空间、质感或功能。</li></ul><p>一个理想的AI场景理解工具，理论上应该能做到上述 3 * 3 坐标里面所有的东西。<br>另外，一个优秀的agent，理论上还需要具备：长程场景记忆系统，即我们聊过的东西，再次提起还能查得到。<br>好，接下来我们来讨论实现。</p><h2 id="三、别只聊MCP了，它注定是一个无底洞"><a href="#三、别只聊MCP了，它注定是一个无底洞" class="headerlink" title="三、别只聊MCP了，它注定是一个无底洞"></a>三、别只聊MCP了，它注定是一个无底洞</h2><p>其实，我们想要的是一个转译层，夹在引擎数据和大语言模型之间，把LLM想要了解的信息，从引擎中抽取，然后转译给LLM。那可能很自然地想到了MCP。<br>确实，MCP是过去一段时间很火的方案。<br>然而MCP这事并不是一个好活儿（不能快速地产出立竿见影的效果），它需要大量的资源投入及调优。<br>（事实上在过去一段时间里，我耗费了很多精力来调试，我甚至整理了一套调优方法论。每天盯着埋点数据，老实说，效果仍然称不上满意）。</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 620px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260817-171132.png" alt="调优数据面板：工具调用、故障与调用趋势统计" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">我的调优数据面板</figcaption>  </figure></div><p>为什么？<br>如果用过一些 UE 的 MCP 工具，就会发现，当前游戏引擎的场景理解部分存在许多问题：</p><ol><li><p><strong>它并不能真正理解我要的场景风格</strong>：<br>人是有抽象能力的，即我看到一片草地，我能说出这片草地的疏密；它不能“理解”，也不能“总结”。</p></li><li><p><strong>上下文爆炸，工具数量几何倍数增长</strong>：<br>现在很多 MCP 的实际做法是将给 UI 的接口开放给 LLM，这件事情就像是给一个剑客发了一把菜刀，能用，但很别扭。UI 是给人用的，从架构层面上一开始就没有考虑到上下文容量、幻觉等 AI 弱势项。</p></li><li><p><strong>更多任务，就意味着更多接口</strong>：<br>原子工具的签名是死的，比如 <code>find</code> 这个功能，一开始只用来按名字查资产；可任务很快变成“找一个在楼梯边的宝箱”（要邻近关系判断）、“找这种风格的灌木”（要语义过滤）、“数一下这片区域有多少块石头”（要聚合统计）。同一个 <code>find</code>，背后其实是几种完全不同的运算。一个写死的接口扛不住这种跨度，硬扛就是要么工具数量爆炸，要么分析深度不够。而且，根据我这段时间的测试，我发现提供越多 MCP 接口，反而会得到更差的完成率——问题不在工具能力不足，而是模型根本不知道选择什么工具。</p></li><li><p><strong>没有校验，错一步后面全错</strong>：<br>Agent 每调一次工具都可能出错，绝大多数 MCP只管”给数据”，不管”对不对”。它说”xxx有棵松树”，你信不信？没有一面让它照自己的镜子，错误就在多步推理里越滚越大，最后跑偏了你都不知道是哪一步开始歪的。</p></li><li><p><strong>又慢又贵，不如自己干</strong>：<br>单次探查要过桥、要等编辑器、要烧 token，等它转完一圈，我自己点几下都弄完了。账面能力再全，单次的时间和费用打不过”人直上手”，那它在严肃工程里就立不住脚。</p></li></ol><p>其实，除了MCP，我们手边还有很多可以利用的方案：</p><table><thead><tr><th>方案</th><th>原理一句话</th><th>单次响应时间</th><th>上下文</th><th>读写</th><th>AI 兼容</th><th>跨版本</th><th>单次算力</th><th>致命坑</th></tr></thead><tbody><tr><td><strong>MCP</strong></td><td>引擎开 MCP server（JSON-RPC）</td><td>🟡 s</td><td>🔴 接口全开</td><td>✅</td><td>🟢 协议原生</td><td>🔴 绑 API</td><td>🟡</td><td><strong>慢 + 贵 + 上文炸 + 粒度错位</strong></td></tr><tr><td><strong>CLI + Commandlet</strong></td><td>启动 headless UE 跑命令</td><td>🔴 30s+（每次付）</td><td>🟢 落盘</td><td>✅</td><td>🟡 要二次解析</td><td>🟡 绑版本</td><td>🔴 fork</td><td><strong>冷启动慢 &#x3D; 不可交互</strong></td></tr><tr><td><strong>导出快照文本</strong></td><td>引擎 cook → JSON&#x2F;XML 落盘</td><td>🟡 s</td><td>🔴 大场景炸</td><td>🔴 偏读</td><td>🟡 伪友好</td><td>🟡 格式脆</td><td>🟡</td><td><strong>时差 + 单向 + 格式脆</strong></td></tr><tr><td><strong>OpenUSD</strong></td><td>标准化场景描述，双端读写</td><td>🟢 s</td><td>🟡 结构化</td><td>✅</td><td>🟢 文本母语</td><td>🟢 标准稳</td><td>🟢</td><td><strong>游戏行业未统一</strong></td></tr><tr><td><strong>直读截图</strong></td><td>渲染 + VLM 看图</td><td>🔴 十 s+</td><td>🔴 图像 token</td><td>🔴 偏观察</td><td>🟢 多模态原生</td><td>🟢 灵活</td><td>🔴 极高</td><td><strong>贵 + 受视角和渲染状态影响 + 失结构</strong></td></tr></tbody></table><p>上面这五种方案，按”信息从哪来”可以归成三类：</p><ul><li><strong>MCP + CLI</strong>：通过引擎接口<strong>在线获取</strong>信息。好处是数据新鲜度最高、能实时反映当前场景状态；代价是接口要自己维护、返回数据量大，还要吃引擎版本迭代的成本。</li><li><strong>导出快照 + OpenUSD</strong>：以<strong>离线数据</strong>还原场景信息。好处是不占引擎运行时、可版本化、便于跨工具读写；代价是有时差、且大场景下快照体积容易爆炸。</li><li><strong>直读截图</strong>：通过<strong>图像 + VLM</strong>获取信息，更接近人观察场景的方式。好处是语义直觉强、跨版本灵活；坏处是上限完全取决于 VLM 对图像的理解力和图像本身的信息量。</li></ul><p>小孩子才做选择，我全都要！我将上面的方案，两两结合。<br>针对视觉、空间与语义（上文 3 * 3 坐标纵轴坐标），我分别提出了三个应对的方法及技术实现：</p><ul><li>视觉信息 ➡️ 2D资产掩码方案 （VLM截图 + 导出快照文本，扩展图片的理解力）</li><li>空间信息 ➡️ GSDL 通用场景描述语言 （颗粒度可变的人类语言级USD + MCP，解决上下文爆炸问题及场景空间感知弱项）</li><li>语义信息 ➡️ Vector Atlas 全地图语义编码（Commandlet + 上面二者结合后的全地图扩展，在实现上遇到的卡点太多，值得我单独开一篇文章更详细地来聊）</li></ul><p>下面展开介绍。</p><h2 id="四、2D资产掩码方案"><a href="#四、2D资产掩码方案" class="headerlink" title="四、2D资产掩码方案"></a>四、2D资产掩码方案</h2><h3 id="1-视觉理解为什么只看截图不够"><a href="#1-视觉理解为什么只看截图不够" class="headerlink" title="1.  视觉理解为什么只看截图不够"></a>1.  视觉理解为什么只看截图不够</h3><p>首先来讲视觉信息的语义理解，理论上来说，直接将图片拿给VLM或是图像理解能力扩展后的LLM，是最直接，最接近人类感知的方案。<br>理想情况下，如果大模型可以直接读懂视频，并看懂空间关系后，执行对应的操作，这听起来像世界模型干的事情。但目前LLM似乎还不是很能达到类似的效果。</p><p>所以我看到一些产品会额外导出<strong>深度图、法线图</strong>等辅助通道，给 VLM 补足空间感知能力，但这通常仰赖模型的训练数据，抛开图片本身的巨量token消耗不谈，一旦当画面变得很模糊，或者画面中的actor带有的风格化显示，和训练数据不一致的时候，就会产生理解的漂移。<br>所以在我看来直接喂图的方案并不是一个很优的解决方案。</p><p>但”看不懂”的根源在哪？<br>我们可以回到计算机视觉的课程<br>——理解画面，本质上是理解像素拼出的图像所代表的物体的含义：把像素还原成”这里是什么、那里是什么”的结构化语义信息。</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 620px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-015740.png" alt="李飞飞 CS231 实例分割示例" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">图片源自于李飞飞 CS231 ppt</figcaption>  </figure></div><p>在图像理解领域，常见的基础任务包括图像分类、目标检测、语义分割和实例分割。<br>中台的同事 Fufu 在七月初提出来的一个图像识别-射线检测的方案：先通过小模型区分出场景中的 actor（物体识别），然后再通过射线检测，去 get 到这个 actor（实例分割）。这个方案通过视觉分析的小模型的中间层，实现LLM主动地去获取画面中的内容。对画面内容进行分割、解析，以达到对画面的理解的能力。<br>（BTW：在大模型和应用之间叠中间层，这是 agent 设计中一个很棒的 trick，我后面提到的 GSDL 也是基于这个逻辑）</p><p>这个方案启发了我。</p><p>可是，这又回到了那个问题，如果只是让模型去直接区分图像，会有一定的失败概率，就又会造成分析的不稳定性这个问题的出现。<br>于是，为何不反着来？引擎主动提供每个位置上的点分别代表什么actor，然后输出一张掩码表给LLM，以此补足LLM无法完整地区分画面的每个组成部分的问题。</p><p>这就是我的“2D 资产掩码方案”——LLM提出要在某个位置拍摄图像，编辑器响应，在拍图图像时，（可选）不只是拍摄图像信息，而是一次性给出整个画面的所有内容，将图片信息压缩成文字，并标注画面中每个部分代表的是什么。（该方案不仅token 消耗猛猛降，同时又达到超高物体识别率，稳定性极高的实例分割 Instance Segmentation方案）</p><h3 id="2-如何建立视觉归属"><a href="#2-如何建立视觉归属" class="headerlink" title="2. 如何建立视觉归属"></a>2. 如何建立视觉归属</h3><p>实际上，引擎编辑器为了支持物体点选，会维护一套 Hit Proxy（点选代理）的渲染与读取机制。其缓冲区与视口对应，可命中的像素会关联一个 Hit Proxy，进而还原到 Actor、Component 或其他编辑器对象。我们可以复用这套数据，将像素归属主动提供给 LLM；相比额外运行视觉分割模型，它的获取和解析成本很低。</p><div class="media-layout media-layout--quad">  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-015943.png" alt="原始截图（游戏视角）" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">原始截图（游戏视角）</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-020021.png" alt="id 色掩码（一色=一 actor）" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">id 色掩码（一色一 actor）</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-020004.png" alt="layout 混合图（场景×掩码+实测占比标签）" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">layout 混合图（场景×掩码+实测占比标签）</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-015848.png" alt="focus 图（多目标分色高亮：蘑菇群=青、装饰柱=品红，余者压暗）" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">focus 图（多目标分色高亮：蘑菇群=青、装饰柱=品红，余者压暗）</figcaption>  </figure></div><p>拿到了画面中的每一个actor后，就可以知道画面的语义了吗？</p><p>是的！当然可以。<br>因为好巧不巧，我前段时间刚好做了一个资产理解工具（上一期）</p><div class="media-layout media-layout--pair">  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-021528.png" alt="场景描述生成 1" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">NeuroBrowser里面的资产描述</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260806-021955.png" alt="场景描述生成 2" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">NeuroMap统计出来的资产掩码表单</figcaption>  </figure></div><p>在做资产理解的时候，我就做了一件事情，我为每个资产都生成了描述（左图，而且这些描述还在被美术不断地更新，优化）。</p><p>资产掩码信息统计下来（右图），能够知道：</p><ol><li>这个资产在画面中的占比、分布、距离；</li><li>它的原始资产是什么（能链接到资产语义）；</li><li>它在场景中叫什么名字，在场景中的参数。</li></ol><p>等信息。</p><h3 id="3-边缘情况及视觉模型本地支持"><a href="#3-边缘情况及视觉模型本地支持" class="headerlink" title="3. 边缘情况及视觉模型本地支持"></a>3. 边缘情况及视觉模型本地支持</h3><p>细心的读者可能已经注意到——hit proxy 报回来的对象里，会有三类”特殊资产”要走专属通道，我对他们进行了专门处理：</p><ol><li><strong>程序化植被</strong>（拆开来，按照foliage的actor归属对应的资产语义）；</li><li><strong>地形</strong>（hit proxy 报回来的是一整座 Landscape proxy（一块名字、一大片像素），可美术其实在 weightmap 里刷了 N 个用户层（草 &#x2F; 沙 &#x2F; 雪 &#x2F; 水……）。这时接合层要走一条专路：世界坐标 → proxy 局部 UV（按 proxy 的 scale 算）→ weightmap 纹理采样 → ULandscapeLayerInfoObject::GetLayerName()——整条链路不会有任何视觉推断，稳定性会极高）</li><li><strong>天空&#x2F;大气</strong>等引擎内置对象（因为数量比较少，手写语义，不依赖 hit proxy）。</li></ol><p>现在，我们把这些描述 + 画面 + 掩码信息（按不同 LoD 组织）拿给 VLM，让它为这个画面生成一个描述（套娃了属实是），就可以得到最基本的单次截图信息。这些 LoD 可以做成接口参数，以适配不同颗粒度的使用场景。</p><p><em>这里的 LoD 描述的是“给模型看的信息颗粒度”，不是 Mesh 的几何精度，但编号方向保持一致。</em></p><p>想要更进一步，我们甚至可以在 MCP 中本地部署一个小的视觉语言模型（譬如 Qwen2.5-VL-7B int4 VRAM平均占用5.95GB，峰值6.65GB，可以在大部分的美术本地跑起来。或者团队局域网部署qwen3.8 27b，也不是很难的事情）。<br>图片及信息解释是典型的感知任务（perception），不存在较长的逻辑链路——这正是小模型的甜区。现在的 VLM 普遍经过大规模图像或图文数据预训练，已经具备了基础的视觉语义能力。这样套一个本地部署中间层，可以让Deepseek v4 flash 0731这样便宜又好用的非多模态模型也能支持我们的视觉理解；<br>至于小模型能做什么、不能做什么，大致可以这样划分：</p><table><thead><tr><th>维度</th><th>🟢 小模型够用的情况</th><th>🔴 小模型开始吃力的情况</th></tr></thead><tbody><tr><td>空间</td><td>“画面里有宝箱 &#x2F; 蘑菇 &#x2F; 石块 &#x2F; 远处剪影”</td><td>“宝箱和最近蘑菇的精确距离、3D 坐标反投影”</td></tr><tr><td>视觉</td><td>“暖色调、洞穴氛围、低多边形风格”</td><td>“这张图和概念图在剪影上的偏差 0.7 &#x2F; 哪个主语破坏了构图”</td></tr><tr><td>语义</td><td>“有什么”（感知）</td><td>“为什么这样摆、应该怎么改”（推理 + 决策）</td></tr></tbody></table><p>但这个部署问题就取决于不同环境的具体情况。</p><p>（8.10日补：阿里前几天发布了<a href="https://github.com/QwenLM/Qwen-MM-Plugins">Qwen-MM-Plugins</a>和我的想法很像，将视觉理解这个事情放到Harness层面，通过多层分辨率设计与抽帧方案实现图片和视频的理解，基本上完成了我这个sidecar视觉辅助能力。我在后面也会升级我的方案）</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 620px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260817-170645.png" alt="Qwen-MM-Plugins：让任意 agent harness 原生支持多模态" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Qwen-MM-Plugins 架构示意（github.com/QwenLM/Qwen-MM-Plugins）</figcaption>  </figure></div><p>所以基于我的这个框架，画面里的所有东西都能有语义归属。至于 PPV &#x2F; 灯光等压根不被命中的辅助对象，图像掩码里自然不会留下它们的痕迹，那么就需要另一种probe的维度（有请GSDL）。</p><h2 id="五、GSDL（通用场景描述语言）"><a href="#五、GSDL（通用场景描述语言）" class="headerlink" title="五、GSDL（通用场景描述语言）"></a>五、GSDL（通用场景描述语言）</h2><p>没听过这个东西是吧。<br>我自己造的。🐶</p><h3 id="1-如何理解空间？"><a href="#1-如何理解空间？" class="headerlink" title="1. 如何理解空间？"></a>1. 如何理解空间？</h3><p>2D 掩码方案实际上只补上了一半内容——它让模型知道了“画面里有什么”（视觉信息 + 内容语义），而文首那张场景理解能力表格里的“空间关系”依旧是没做的。</p><p>引擎编辑器是为人打造的，对于人而言，只要扫一眼视口，就能感觉到这片区域密不密、东西是散开的还是抱团的；引擎编辑器是不能直接回答这些问题的。</p><p>我们要的这些数据，并不是引擎某个字段里藏着、可以直接伸手去拿的数据。它要从 Actor、坐标、包围盒、碰撞、地形、射线和渲染结果里重新<strong>测量、统计和推导</strong>出来，但引擎并没有提供类似的现成接口。这些信息对于人来说，逛一遍场景就能感性地获得，但是对LLM来说不是。<br>我的 GSDL 干的就是这件事：通过一些测量的手段，把引擎提供的底层状态，烹饪、压缩成 AI 可以查询、比较和复核的场景事实（scene facts，比如A处有一片高山，B处有一个洼地）。</p><p>实现上，它也基于MCP，但并不是直接拿引擎的裸接口——中间隔着一层GSDL查询函数作为中转（这层查询函数，就是GSDL最重要的规则和约定）。每次查询都由这层函数先在场景里完成测量，再把结果转换成标准化的语言返还给大语言模型。所以某种意义上，它也可以被看作一种OpenUSD式的文本描述语言。<br>不过，“描述语言”只是它最终的产出形态。更准确地说，<strong>GSDL 是一整套面向 AI 的场景观测、测量与描述方法论：一头衔接着引擎里的几何和渲染事实，一头衔接着资产语义，再把“这里有什么、怎么分布、彼此是什么关系”整理成多档 LoD 的结构化语言描述，交给 LLM。</strong></p><div class="canvas-embed" data-canvas-slug="attachments/Canvas/mcp-query-loop"><svg xmlns="http://www.w3.org/2000/svg" class="canvas-svg" data-canvas-revision="47f4195041c9" width="492" height="280" viewBox="-120 -40 1160 660" preserveAspectRatio="xMidYMid meet" role="img" aria-label="mcp-query-loop"><title>mcp-query-loop</title><defs><marker id="canvas-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" /></marker></defs><g class="canvas-groups"><g class="canvas-node canvas-node--group" data-id="b572bb250efcbad3" data-x="340" data-y="0" data-width="280" data-height="580" data-color="custom" style="--canvas-node-accent:#a7f3d0"><rect class="canvas-group__bg" x="340" y="0" width="280" height="580" rx="12" /><text class="canvas-group__label" x="352" y="22">② 进入 UE 内部查询</text></g><g class="canvas-node canvas-node--group" data-id="5cfa297760b8a416" data-x="680" data-y="0" data-width="320" data-height="420" data-color="custom" style="--canvas-node-accent:#fbcfe8"><rect class="canvas-group__bg" x="680" y="0" width="320" height="420" rx="12" /><text class="canvas-group__label" x="692" y="22">③ 压缩为标准化语言</text></g><g class="canvas-node canvas-node--group" data-id="faca46ce5d698ba6" data-x="-80" data-y="20" data-width="340" data-height="335" data-color="custom" style="--canvas-node-accent:#c7d2fe"><rect class="canvas-group__bg" x="-80" y="20" width="340" height="335" rx="12" /><text class="canvas-group__label" x="-68" y="42">① 大语言模型</text></g></g><g class="canvas-edges"><g class="canvas-edge-group" data-id="fd653b4d34e27831" data-from-node="31c9333cf894383a" data-to-node="662e9e99a91bebdb" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 235 73 C 276.6879945414611 73, 318.3120054585389 77, 360 77" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="0898129a83536ec6" data-from-node="662e9e99a91bebdb" data-to-node="6aa9b9dd3f8e01e1" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 480 104 C 480 144, 480 160, 480 200" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="2dfb95721cb0ef3b" data-from-node="6aa9b9dd3f8e01e1" data-to-node="21d77697417ddb26" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 480 254 C 480 294, 480 285, 480 325" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="016d21134901fe6b" data-from-node="21d77697417ddb26" data-to-node="229a9e99ce52ac2e" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 480 379 C 480 419, 480 420, 480 460" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="26757a79145d96c4" data-from-node="229a9e99ce52ac2e" data-to-node="4c210a572a8a0638" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 600 487 C 750.7849830424473 487, 554.2150169575527 47, 705 47" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="7a85a9327f01e66e" data-from-node="4c210a572a8a0638" data-to-node="e5f5f4079075ba13" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 830 74 C 830 114, 830 130, 830 170" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="9533d7015116d90e" data-from-node="e5f5f4079075ba13" data-to-node="c222923740c10c1d" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 830 224 C 830 264, 830 295, 830 335" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="d6571dc128dab9cc" data-from-node="c222923740c10c1d" data-to-node="05f44c30b82ff4cf" data-from-side="top" data-to-side="top"><path class="canvas-edge" d="M 830 335 C 830 85.92610422696728, 89.5 -14.073895773032717, 89.5 235" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="459.75" y="285" text-anchor="middle">下一轮查询</text></g><g class="canvas-edge-group" data-id="020393000e47e963" data-from-node="05f44c30b82ff4cf" data-to-node="31c9333cf894383a" data-from-side="left" data-to-side="left" style="--canvas-edge-color:#94a3b8"><path class="canvas-edge" d="M -56 262 C -119 262, -119 73, -56 73" fill="none" marker-end="url(#canvas-arrow)" /></g></g><g class="canvas-nodes"><g class="canvas-node canvas-node--text" data-id="662e9e99a91bebdb" data-x="360" data-y="50" data-width="240" data-height="54" data-color="custom" style="--canvas-node-accent:#059669"><rect class="canvas-node__bg" x="360" y="50" width="240" height="54" rx="8" /><foreignObject x="360" y="50" width="240" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>MCP 查询接口</strong></p><p>GSDL 的规则与约定</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="e5f5f4079075ba13" data-x="700" data-y="170" data-width="260" data-height="54" data-color="custom" style="--canvas-node-accent:#db2777"><rect class="canvas-node__bg" x="700" y="170" width="260" height="54" rx="8" /><foreignObject x="700" y="170" width="260" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>GSDL 编码器</strong></p><p>LoD0–LoD3 · 字符预算收敛</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="05f44c30b82ff4cf" data-x="-56" data-y="235" data-width="291" data-height="54" data-color="custom" style="--canvas-node-accent:#4f46e5"><rect class="canvas-node__bg" x="-56" y="235" width="291" height="54" rx="8" /><foreignObject x="-56" y="235" width="291" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>读取标准化结果 → 思考</strong></p><p>决定下一轮查询</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="31c9333cf894383a" data-x="-56" data-y="36" data-width="291" data-height="74" data-color="custom" style="--canvas-node-accent:#4f46e5"><rect class="canvas-node__bg" x="-56" y="36" width="291" height="74" rx="8" /><foreignObject x="-56" y="36" width="291" height="74"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>发起 MCP 调用</strong></p><p>describe_region · find_opening · compare …</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="c222923740c10c1d" data-x="710" data-y="335" data-width="240" data-height="60" data-color="custom" style="--canvas-node-accent:#db2777"><rect class="canvas-node__bg" x="710" y="335" width="240" height="60" rx="8" /><foreignObject x="710" y="335" width="240" height="60"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>标准化 GSDL 文本</strong></p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="4c210a572a8a0638" data-x="705" data-y="20" data-width="250" data-height="54" data-color="custom" style="--canvas-node-accent:#db2777"><rect class="canvas-node__bg" x="705" y="20" width="250" height="54" rx="8" /><foreignObject x="705" y="20" width="250" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>空间内核</strong></p><p>聚类 · 关系推导</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="6aa9b9dd3f8e01e1" data-x="360" data-y="200" data-width="240" data-height="54" data-color="custom" style="--canvas-node-accent:#059669"><rect class="canvas-node__bg" x="360" y="200" width="240" height="54" rx="8" /><foreignObject x="360" y="200" width="240" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>文件桥接</strong></p><p>请求 &#x2F; 响应轮询</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="21d77697417ddb26" data-x="360" data-y="325" data-width="240" data-height="54" data-color="custom" style="--canvas-node-accent:#ea580c"><rect class="canvas-node__bg" x="360" y="325" width="240" height="54" rx="8" /><foreignObject x="360" y="325" width="240" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>UE 场景探针</strong></p><p>枚举 · 采样 · 射线检测</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="229a9e99ce52ac2e" data-x="360" data-y="460" data-width="240" data-height="54" data-color="custom" style="--canvas-node-accent:#ea580c"><rect class="canvas-node__bg" x="360" y="460" width="240" height="54" rx="8" /><foreignObject x="360" y="460" width="240" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>原始查询结果</strong></p><p>体积大，尚未压缩</p></div></foreignObject></g></g></svg><span class="canvas-embed__expand" aria-hidden="true" title="点击放大">⛶</span></div><blockquote><p>这张图就是一次完整查询的环路。MCP 在这里只是一层薄封装——它自己不干活，只负责把调用转进引擎：<br><strong>①</strong> 模型发起 MCP 调用（<code>describe_region</code>、<code>find_opening</code>、<code>compare</code>…），先落进 <strong>②</strong> 的“MCP 查询接口”——也就是上一段说的那层 GSDL 查询函数；<br><strong>②</strong> 请求经文件桥接进入 UE，由场景探针真正完成测量，吐出的是尚未压缩的原始事实；<br><strong>③</strong> 空间内核把这些事实聚类、推导出关系，GSDL 编码器再按 LoD 和字符预算把它们压缩成标准化语言，模型这才读到结果；<br>读完之后，模型思考、决定下一轮该测什么，再带着新的疑问回到 <strong>①</strong>——查询就这样一圈圈转下去。</p></blockquote><p>综上，它主要负责两件事情：</p><ol><li>理解空间信息（把离散对象组织成密度、结构、关系、可见性和语义归属）</li><li>获取的信息压缩（把获取的信息通过一定的规则压缩成规则严谨的语义表述）</li></ol><p>下面我分别介绍。</p><h3 id="2-空间是如何被测量的？"><a href="#2-空间是如何被测量的？" class="headerlink" title="2. 空间是如何被测量的？"></a>2. 空间是如何被测量的？</h3><p>我拿其中一个接口做图形化的展示，让你更加明白地了解它的实现逻辑。<br>下面这个接口叫作<code>find_opening</code>。它主要测试当前的腔体环境中是否存在开口。<br>比如说在一个山洞中，通过这种算法能测出环境中存在几个洞口、房间有没有开着的窗户之类的应用场景。</p><p><em>其实UE本身是一个很棒的仿真工具，在里面其实可以做很多“测量”的工作。</em></p><p>游戏引擎本身就有射线检测接口——UE 里叫 <code>LineTraceSingleByChannel</code>，是物理引擎原生开放给上层做碰撞查询的能力。墙、岩石和门框都是正空间，洞口恰恰是它们围出来的<strong>负空间</strong>。<br>因此<code>find_opening</code>的查询方法，不是通过搜索名字中带 <code>Door</code> 或 <code>Archway</code> 的 Actor，而是反过来使用碰撞查询：命中的射线勾勒洞壁，能够连续逃逸到远处的射线组成开口，再回到开口两侧寻找真实门框。</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 620px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260819-030817.gif" alt="find_opening 从候选采样、多测站射线到门框精测的五阶段动画" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">find_opening 五阶段动画（模拟项目实际场景效果案例）</figcaption>  </figure></div><p>实现大致可以拆成五步：</p><ol><li><strong>找内部点。</strong> 在腔体内铺一批采样点，挑出真正处于洞内的。</li><li><strong>选观察位。</strong> 从中取几个互相错开的点，作为观察位置。</li><li><strong>打射线。</strong> 从各观察位向四周发射线，找通向远处的方向。</li><li><strong>汇总分类。</strong> 合并各处观察，区分洞口、高窗与内部通道。</li><li><strong>量具体尺寸。</strong> 回到洞口边缘细测，给出实际宽高。</li></ol><p>底层的 C++ Probe 负责从 Editor 现场取得地形、实例、碰撞命中和射线结果；Python 空间内核负责候选采样、测站选择、扇区融合、类型判断与精测调度。然后将经过测算的结果结论给到LLM，模型拿到的不再是一万多条原始射线，而是一组带有<strong>类型、位置、方向、宽高、来源测站和证据等级</strong>的场景事实。模型既能直接引用测量值，也能沿工具给出的机位再去截图复核。<br>于是我们便完成了对场景的空间关系的理解。</p><h3 id="3-语言是如何被压缩的？"><a href="#3-语言是如何被压缩的？" class="headerlink" title="3. 语言是如何被压缩的？"></a>3. 语言是如何被压缩的？</h3><p>上一节的五步测量走完，手里攥着的是一万多条射线、几百个采样点、三千多个实例的原始事实。这些东西没法直接交给模型——上下文会当场爆炸。（所以我在上面的Canvas图表中有一个python数据整理层，来压缩数据信息再给LLM，下面讲的就是在这个python压缩层）</p><p>压缩不是把句子改短，它是语言层的一套设计原则：</p><ol><li><strong>描述不等于倒数据。</strong> 工具把原始实例数据烹饪成陈述——<code>“*此区域 3242 个实例、200 种资产、聚成 12 簇，最大簇在东南...*”</code>(类似于这样的描述)。给模型看的文本超了预算的话，就自动退到更粗一级的 LoD，信息本身仍保留在更细的层级里。</li><li><strong>分层披露，先给摘要。</strong> 描述分 LoD0–LoD3 四档：LoD3 是一段话摘要，LoD2 是资产清单与统计，LoD1 展开簇和空间关系，LoD0 保留逐实例明细；另有 channels 按主题切块。模型默认只拿 LoD3 摘要：先花小 token 判断值不值得深看，值得再逐层下钻——和人逛场景一个道理，先扫全景，有兴趣才走近。</li><li><strong>聚合是可逆的。</strong> 压缩掉的信息不是丢了：LoD1 里看到的是簇，要哪个簇的 LoD0 逐实例精度，<code>expand</code> 只钻那一个，不必重描整片。</li><li><strong>截断必须自曝。</strong> 凡是压过的地方都带记号：<code>truncated (~30% sampled)</code>、<code>(3 of 148)</code>、宽度带 <code>~</code>——模型读到的每个数字都知道自己是全量还是样本。</li></ol><p>这就是第一节说的“把获取的信息压缩成规则严谨的语义表述”：<strong>压缩的是 token，通过渐进披露保留信息。</strong></p><p>举个例子。同一个区域执行 <code>describe_region</code>，四档输出的token消耗：</p><table><thead><tr><th>输出形态</th><th>模型读到什么</th><th>字符</th><th>≈ token</th></tr></thead><tbody><tr><td>裸 JSON（倒数据）</td><td>每个实例一行坐标数组</td><td>461,193</td><td>~115,000</td></tr><tr><td>LoD3 摘要</td><td>一句话</td><td><strong>244</strong></td><td><strong>61</strong></td></tr><tr><td>LoD2 清单</td><td>物种清单 + 密度统计</td><td>840</td><td>210</td></tr><tr><td>LoD1 关系</td><td>簇 + 空间关系 + 统计</td><td>2,616</td><td>654</td></tr><tr><td>LoD0 明细（expand 单簇）</td><td>只钻一个类别，散石簇 20 实例</td><td><strong>1,687</strong></td><td><strong>421</strong></td></tr><tr><td>LoD0 明细（整区域）</td><td>逐实例明细，约 23 token&#x2F;个，<strong>不存在一次性拿整个区域的情况。</strong> 如果强制全量输出甚至会比原始Json更多，因为我们有很多标注符。</td><td>921,223</td><td>~230,000</td></tr></tbody></table><p>LoD3 全文原样贴出来：</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><span class="line">@gsdl v0.1</span><br><span class="line">@project: ScatterTest</span><br><span class="line">@asset_classes: [Foliage, StaticMesh]</span><br><span class="line">@enrichment: []</span><br><span class="line">@frame: m</span><br><span class="line"></span><br><span class="line">region(id=R7, bbox=[0.0..200.0, 0.0..200.0, 0.0..18.0]m) &#123;</span><br><span class="line"></span><br><span class="line">  # -- summary (LoD3) --</span><br><span class="line">  R7 summary “锚=watch_tower; 10021实例/3类; 密度0.25/m²; 朝向散.”.</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>LoD3 这段全文，逐行每句话在说什么：</p><ul><li><code>@gsdl v0.1</code>——版本声明；</li><li><code>@project: ScatterTest</code>——所在地图；</li><li><code>@asset_classes: [Foliage, StaticMesh]</code>——区域里出现的资产大类；</li><li><code>@enrichment: []</code>——语义增强槽，接的是第一式的资产语义编码，这片区域没用到；</li><li><code>@frame: m</code>——单位约定：往后所有长度一律米、角度一律罗盘度，模型不用猜 92.05 是厘米还是米；</li><li><code>region(id=R7, bbox=[0.0..200.0, 0.0..200.0, 0.0..18.0]m)</code>——区域外壳：花括号里的每条陈述，管辖范围就是这个包围盒，一寸不多一寸不少；</li><li><code># -- summary (LoD3) --</code>——通道块标题，标记下面属于哪个主题块；</li><li><code>R7 summary “锚=watch_tower; 10021实例/3类; 密度0.25/m²; 朝向散.”.</code>——唯一的正文，也是全文唯一的句式：<strong>三元组</strong>。主语 R7、谓词 summary、宾语是这句分号串联的压缩陈述，行尾句号收束；谓词都来自一张固定词汇表（located_at、bound、near、density……），没有自由发挥的散文。</li></ul><p>谓语里面：<br><code>锚=watch_tower</code>——锚是这片区域的地标，由显著度（视觉面积×稀有度）选出，一万棵六米高的松树里，就那座 18 米的瞭望塔配得上；此后簇与簇的空间关系都挂在它身上（<code>pine_c2 of watch_tower</code>、<code>pine_c2 offset (dir=NE, d=43.2m)</code> 就是以它为原点的相对表述）。<code>10021实例/3类</code> 是量级，<code>密度0.25/m²</code> 是疏密，<code>朝向散</code> 是朝向熵的档位（摆放的是乱还是规整）。<br>人指路也是这个顺序——先报地标（“黑井附近”），再给相对关系（“比花果山山洞还黑”）；锚，就是把人类这个习惯写成了语法。</p><p>这段里示例还有两个语法元素你看不到。<br>一个是<strong>精度描述</strong>：</p><ul><li>实测值不带符号（<code>pine_c1.1 pose [92.05, 12.65, 0.0]m</code>，每个数都是量出来的）；</li><li>估计值带 <code>~</code>（<code>watch_tower salience ~1.00</code>，显著度是算出来的估计）；</li><li>分类判定带 <code>#</code>（<code>size_dist &#123;M:20, L:10001&#125;#</code>，分桶是规则判的，不是量的）。</li></ul><p>另一个是<strong>指代方案</strong>：</p><p>会把相似的东西整合成一个“簇”，碎尾合并成弥散场，再多的实例，最后会整合成十来个名字（即十几种元素）。比如一盒铅笔，我们没必要说，铅笔A、铅笔B…这也是指代上的一种压缩技巧。</p><p>总之，锚点、量级、密度、朝向都在这 61 个 token 里——模型会根据自己在意的点往下深究。<br>LoD0 是一个实例一到两行，长这样：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pine_c1.1      pose  [92.05, 12.65, 0.0]m @ yaw=99.7°.</span><br><span class="line">pine_c1.1      bound [0.85×0.85×6.41]m.</span><br></pre></td></tr></table></figure><p>模型可以自己调用，去获取自己在意的信息。</p><p>聊到这里，你还记得我在 “二、如何理解一个场景” 这一段里，提及的理解&#x2F;表述 的那个 3* 3的表格吗？我提及表述层有我总结的三种表达策略：「指代」「比较」「隐喻」，现在可以验收了：<br>「指代」——锚和簇干的就是这个：模型说 <code>pine_c2</code>、说“瞭望塔东北那团”，指的是同一个东西；<code>offset (dir=NE, d=43.2m)</code> 就是“黑井附近”的正式写法。<br>「比较」——在 GSDL 里是档位化的，既可以给出精确的数值，也要能给出对比档位。但是档位就涉及到量化、判断，而一旦出现判断就会不稳定。所以我在 3* 3的表格里给了比较一个🟠（难）。</p><p>第三种「隐喻」（比如：这个洞像白骨精的老窝）确实很难，一方面要靠LLM的自我理解能力，另一方面靠文章后面提到的Vector Atlas（全地图语义编码），需要美术去教和标注。</p><h3 id="4-自进化的接口"><a href="#4-自进化的接口" class="headerlink" title="4. 自进化的接口"></a>4. 自进化的接口</h3><p>上面提到的接口只是一个例子。GSDL 当前能测量的空间能力大致长这样（2026 年 7 月 20 日左右的版本）：</p><table><thead><tr><th>能力</th><th>代表接口</th><th>核心实现</th><th>能回答的问题</th></tr></thead><tbody><tr><td><strong>拓扑</strong></td><td><code>compare(on=&quot;spatial&quot;)</code>、<code>query</code></td><td>world-AABB 判定（RCC-8），容差随尺度自适应，输出 DC &#x2F; EC &#x2F; PO &#x2F; TPP 等关系码</td><td>隔着、贴着、相交，还是谁包着谁？</td></tr><tr><td><strong>方向</strong></td><td><code>compare(on=&quot;spatial&quot;)</code>、<code>describe_region</code></td><td>罗盘八向；朝向判定：相向 &#x2F; 并排 &#x2F; 对置 &#x2F; 斜交</td><td>A 在 B 哪边？相向还是背对？</td></tr><tr><td><strong>距离</strong></td><td><code>compare(on=&quot;spatial&quot;)</code>、<code>query</code></td><td>AABB 表面间隙、中心距、垂直偏移；<code>near</code> 按脚印阈值分级</td><td>隔多远？谁高谁低？算不算“挨着”？</td></tr><tr><td><strong>承载</strong></td><td><code>describe_region</code>、<code>relation_trace</code>、<code>compare</code></td><td>垂直接触 + 脚印重叠的支撑证据，稀疏支撑图</td><td>谁承载谁？“杯子放在桌上”是量出来的</td></tr><tr><td><strong>通行遮挡</strong></td><td><code>find_opening</code>、<code>skyline</code>、<code>query_view</code>、<code>rays</code> &#x2F; <code>los_ring</code></td><td>多测站射线、逃逸方向聚类、门框重测；遮挡相对机位，射线与掩码共判</td><td>洞穴几个出口？视线被什么挡住？</td></tr></tbody></table><p>表格里面的功能对应着我们上文提到的空间关系。<br>然后还会包含一些辅助空间功能——</p><table><thead><tr><th>能力</th><th>代表接口</th><th>核心实现</th><th>能回答的问题</th></tr></thead><tbody><tr><td><strong>普查</strong></td><td><code>scan_density</code>、<code>describe_region</code></td><td>枚举 Actor &#x2F; Instance &#x2F; Landscape，网格聚合，生成快照、簇与分层摘要</td><td>这里有什么？集中在哪？散布还是聚集？</td></tr><tr><td><strong>定位</strong></td><td><code>search_subjects</code>、<code>find_by_class</code>、<code>actor_meta</code>、<code>semantic_search</code></td><td>名称与中文别名索引、类查询、资产元数据联接、检索结果与实例交叉验证</td><td>“宝箱”叫什么？在哪？用什么资产？</td></tr></tbody></table><p>先知道这里有什么、说的是哪个实例，才谈得上空间关系。LLM才会调用具体的理解接口去做事情。</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 340px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260817-222622.png" alt="GSDL 接口调用统计表：调用次数、故障/拒绝、LLM 字符与耗时" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">GSDL 各接口调用量与耗时统计面板</figcaption>  </figure></div><p>GSDL 对外提供的是一组面向问题、且仍在生长的测量能力。这份清单不是一次性设计出来的，而是被每一轮评测数据推着长出来的，所以上面给你看到的只是它某一个时间切片的样子。<br>（目前大概有 35 个接口，其中大概20个左右是目前稳定的）</p><p>我之前也试过用常规的方式来对它进行迭代。但是效果微乎其微（问题主要是：迭代速度太慢，另外我的个人判断也过于主观），后面我改成了“基于 &#x2F;Loop 的评测驱动”的方式来优化接口：先建好一批场景测试题，LLM 直接基于每轮的执行数据和结果自己判断自我迭代，效果有了质的飞跃。</p><p>我的 <code>/Loop</code> 循环开发大致分为两个阶段：<br>第一个阶段，我给它写了36道测试题（因为测试题不够，最早是11道题，后来扩展为36道，全是手动提问并人肉标注答案，累死俺了）。</p><p>这些测试题涵盖了：</p><ul><li><strong>描述与理解</strong>（当前视口里有什么、一片城区是怎么规划的、功能怎么分区）</li><li><strong>空间关系</strong>：(A 离 B 多远、在哪个方向、谁比谁高、谁挡着谁）</li><li><strong>异常检测</strong>：(有没有悬空、穿模、明显摆错位的东西）</li><li><strong>通行结构</strong>(这个洞穴有几个洞口、分别多宽）</li><li><strong>复合体拆解</strong>（14 个牌坊资产里哪 6 个组成完整牌坊、一座瓮城由哪些构件构成）</li><li><strong>诚实否认</strong>（场景里没有汽车、没有霓虹灯，能否有依据地说“没有”，而不是硬猜一个）等等十几个方向的内容。</li></ul><p>所有题目都用自然语言出题、并且刻意不点名任何工具名。</p><p>我使用两个agent都跑<code>/Loop</code>循环，<strong>一个跑测试</strong>（开N个空白上下文的子agent去调用mcp进行提问，Fable5主agent只负责调度，写总结文档，和提需求），<strong>一个改代码</strong>（opus&#x2F;GLM5.2 根据前者的需求优化接口逻辑）。<br><em>（跑mcp测试的子 agent 用本地 qwen3.6 27b、minimax m3。SOTA 模型的完成率确实更高，但真实场景里一旦大量铺开，MCP 消耗会是个可怕的数字——工具是面向生产的，不是用来跑分的，那就得按生产环境里真正会用的模型来适配）</em></p><p>我主要看三个主要硬性指标：</p><ul><li><strong>交付能力</strong>（完成率、正确率、诚实度) ；</li><li><strong>成本</strong>（真实Token消耗、接口调用次数、接口 schema 税）；</li><li><strong>速度</strong>（单题耗时、均耗时 ms、最大 ms、看门狗超时数），有时候还会关注点其他指标，包括健康度、可能发现性。</li></ul><p>根据我的测试来看，大模型和小模型在某些层面确实有很明显的差距，比如体现在对工具的利用能力上：小模型会经常忘记有哪些工具接口。所以我后面甚至特地做了一个路由层，模型在对场景进行查询之前，可以先调用路由层去确认使用哪个工具会更加合适一点，以调用次数换完成率：</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 860px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260821-025635.png" alt="案例路由优化前后的工具调用记录对比" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">案例路由优化前后的工具调用记录对比</figcaption>  </figure></div><p>三个硬指标的变化是这样的（取 7 月 21–22 日、10 题同口径的数据）：</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 860px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260821-025518.png" alt="GSDL /Loop 迭代指标表：Token 消耗、调用次数与正确题数逐轮变化" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">三个硬指标随迭代轮次的变化</figcaption>  </figure></div><p>几天下来 Token 降了 54%，调用降了 64%，正确题数升到满分并守住。<br>之后，又扩展了题库。再经过十多轮迭代后，指标就进入了一个比较平缓的状态。然后再往下就没有太大的意义了，再往下就会出现过拟合的情况。<br>那这个时候就要加大药量了。<br>于是我转向了第二阶段的&#x2F;loop的开发工作：</p><div class="canvas-embed" data-canvas-slug="attachments/Canvas/gsdl-evolution-loop"><svg xmlns="http://www.w3.org/2000/svg" class="canvas-svg" data-canvas-revision="cd3fe7a58458" width="582" height="280" viewBox="20 -80 1580 760" preserveAspectRatio="xMidYMid meet" role="img" aria-label="gsdl-evolution-loop"><title>gsdl-evolution-loop</title><defs><marker id="canvas-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" /></marker></defs><g class="canvas-groups"><g class="canvas-node canvas-node--group" data-id="a4c4c4c4c4c4c4c4" data-x="60" data-y="260" data-width="880" data-height="360" data-color="custom" style="--canvas-node-accent:#bbf7d0"><rect class="canvas-group__bg" x="60" y="260" width="880" height="360" rx="12" /><text class="canvas-group__label" x="72" y="282">④ 测试 · 反馈闭环</text></g><g class="canvas-node canvas-node--group" data-id="a1c1c1c1c1c1c1c1" data-x="480" data-y="-40" data-width="740" data-height="180" data-color="custom" style="--canvas-node-accent:#bae6fd"><rect class="canvas-group__bg" x="480" y="-40" width="740" height="180" rx="12" /><text class="canvas-group__label" x="492" y="-18">① 白天 · 采集调用信号</text></g><g class="canvas-node canvas-node--group" data-id="a2c2c2c2c2c2c2c2" data-x="1280" data-y="200" data-width="280" data-height="240" data-color="custom" style="--canvas-node-accent:#e9d5ff"><rect class="canvas-group__bg" x="1280" y="200" width="280" height="240" rx="12" /><text class="canvas-group__label" x="1292" y="222">② 定时 · 整理需求</text></g><g class="canvas-node canvas-node--group" data-id="a3c3c3c3c3c3c3c3" data-x="1020" data-y="500" data-width="280" data-height="140" data-color="custom" style="--canvas-node-accent:#fed7aa"><rect class="canvas-group__bg" x="1020" y="500" width="280" height="140" rx="12" /><text class="canvas-group__label" x="1032" y="522">③ 夜间 · 实现能力</text></g></g><g class="canvas-edges"><g class="canvas-edge-group" data-id="e101010101010101" data-from-node="b101010101010101" data-to-node="b202020202020202" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 700 27 C 740 27, 700 27, 740 27" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="e202020202020202" data-from-node="b202020202020202" data-to-node="b303030303030303" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 940 27 C 980 27, 940 27, 980 27" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="e303030303030303" data-from-node="b303030303030303" data-to-node="b404040404040404" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 1090 54 C 1090 177.13317090767123, 1420 96.86682909232876, 1420 220" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="e404040404040404" data-from-node="b404040404040404" data-to-node="b505050505050505" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 1420 300 C 1420 340, 1420 290, 1420 330" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="e505050505050505" data-from-node="b505050505050505" data-to-node="b606060606060606" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 1420 384 C 1420 481.80706632049765, 1160 422.19293367950235, 1160 520" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="1290" y="452" text-anchor="middle">缺口</text></g><g class="canvas-edge-group" data-id="e606060606060606" data-from-node="b606060606060606" data-to-node="b707070707070707" data-from-side="left" data-to-side="right"><path class="canvas-edge" d="M 1040 547 C 971.3624657267533 547, 928.6375342732467 447, 860 447" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="950" y="497" text-anchor="middle">交付</text></g><g class="canvas-edge-group" data-id="e707070707070707" data-from-node="b707070707070707" data-to-node="b808080808080808" data-from-side="left" data-to-side="right"><path class="canvas-edge" d="M 640 447 C 591.4659340714632 447, 648.5340659285368 307, 600 307" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="620" y="377" text-anchor="middle">结果</text></g><g class="canvas-edge-group" data-id="e808080808080808" data-from-node="b808080808080808" data-to-node="b909090909090909" data-from-side="left" data-to-side="right"><path class="canvas-edge" d="M 380 307 C 340 307, 340 307, 300 307" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="340" y="307" text-anchor="middle">汇总</text></g><g class="canvas-edge-group" data-id="e909090909090909" data-from-node="b909090909090909" data-to-node="ba0a0a0a0a0a0a0a" data-from-side="bottom" data-to-side="top"><path class="canvas-edge" d="M 190 334 C 190 394.4575149266914, 310 409.5424850733086, 310 470" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="250" y="402" text-anchor="middle">反馈</text></g><g class="canvas-edge-group" data-id="ea0a0a0a0a0a0a0a" data-from-node="ba0a0a0a0a0a0a0a" data-to-node="b606060606060606" data-from-side="right" data-to-side="bottom" style="--canvas-edge-color:#dc2626"><path class="canvas-edge" d="M 420 497 C 667.998431894684 497, 1160 821.998431894684, 1160 574" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="790" y="535.5" text-anchor="middle">闭环 · 修复</text></g></g><g class="canvas-nodes"><g class="canvas-node canvas-node--text" data-id="b101010101010101" data-x="500" data-y="0" data-width="200" data-height="54" data-color="custom" style="--canvas-node-accent:#0284c7"><rect class="canvas-node__bg" x="500" y="0" width="200" height="54" rx="8" /><foreignObject x="500" y="0" width="200" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>用户调用工具</strong></p><p>日常 GSDL 调用</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b202020202020202" data-x="740" data-y="0" data-width="200" data-height="54" data-color="custom" style="--canvas-node-accent:#0284c7"><rect class="canvas-node__bg" x="740" y="0" width="200" height="54" rx="8" /><foreignObject x="740" y="0" width="200" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>Hook 触发</strong></p><p>后台静默记录问题</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b303030303030303" data-x="980" data-y="0" data-width="220" data-height="54" data-color="custom" style="--canvas-node-accent:#0284c7"><rect class="canvas-node__bg" x="980" y="0" width="220" height="54" rx="8" /><foreignObject x="980" y="0" width="220" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>用户打分</strong></p><p>LLM 完成度评分</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b909090909090909" data-x="80" data-y="280" data-width="220" data-height="54" data-color="custom" style="--canvas-node-accent:#16a34a"><rect class="canvas-node__bg" x="80" y="280" width="220" height="54" rx="8" /><foreignObject x="80" y="280" width="220" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>测试报表</strong></p><p>通过 &#x2F; 失败 &#x2F; 建议</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b707070707070707" data-x="640" data-y="420" data-width="220" data-height="54" data-color="custom" style="--canvas-node-accent:#16a34a"><rect class="canvas-node__bg" x="640" y="420" width="220" height="54" rx="8" /><foreignObject x="640" y="420" width="220" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>子 Agent 群</strong></p><p>调用 MCP 真实测试</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="ba0a0a0a0a0a0a0a" data-x="200" data-y="470" data-width="220" data-height="54" data-color="custom" style="--canvas-node-accent:#dc2626"><rect class="canvas-node__bg" x="200" y="470" width="220" height="54" rx="8" /><foreignObject x="200" y="470" width="220" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>反馈执行 Agent</strong></p><p>修复 → 下一轮</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b606060606060606" data-x="1040" data-y="520" data-width="240" data-height="54" data-color="custom" style="--canvas-node-accent:#ea580c"><rect class="canvas-node__bg" x="1040" y="520" width="240" height="54" rx="8" /><foreignObject x="1040" y="520" width="240" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>夜间执行 Agent</strong></p><p>实现新能力</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b404040404040404" data-x="1300" data-y="220" data-width="240" data-height="80" data-color="custom" style="--canvas-node-accent:#7c3aed"><rect class="canvas-node__bg" x="1300" y="220" width="240" height="80" rx="8" /><foreignObject x="1300" y="220" width="240" height="80"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>定时 Agent</strong></p><p>汇总交流记录</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b505050505050505" data-x="1300" data-y="330" data-width="240" data-height="54" data-color="custom" style="--canvas-node-accent:#7c3aed"><rect class="canvas-node__bg" x="1300" y="330" width="240" height="54" rx="8" /><foreignObject x="1300" y="330" width="240" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>能力需求表</strong></p><p>识别能力缺口</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="b808080808080808" data-x="380" data-y="280" data-width="220" data-height="54" data-color="custom" style="--canvas-node-accent:#16a34a"><rect class="canvas-node__bg" x="380" y="280" width="220" height="54" rx="8" /><foreignObject x="380" y="280" width="220" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>测试主 Agent</strong></p><p>汇总与判定</p></div></foreignObject></g></g></svg><span class="canvas-embed__expand" aria-hidden="true" title="点击放大">⛶</span></div><p>我构建了被动更新系统：用户每天调用MCP工具时会触发我的Hook、在后台数据库会记录用户调用工具时候的具体问题，以及对话记录，用户也被要求时不时给LLM的任务完成度打分。<br>这些交流记录会被一个定时的Agent整理成能力需求表（如果发现比较弱的能力的话）。在夜间我下班后，由一个执行的Agent来负责实现这些能力，然后另外一个Agent会调用大量的子agent，对mcp进行测试，最后由这些子agent的主agent总结测试报表与意见。反馈给执行agent。然后执行再进行迭代…以此循环数次，直到新接口的指标稳定下来。</p><div class="media-layout media-layout--center">  <figure style="flex: 1 1 280px; max-width: 250px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260819-034651.png" alt="两台 NVIDIA DGX Spark 迷你主机放在木桌上" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">重金购入的两台 DGX Spark（军火展示）</figcaption>  </figure></div><p>为了做好这个loop循环，我甚至重金购买了两台DGX Spark来本地跑deepseek v4 flash（吃草中…一些不知所谓的军火展示，希望Leader看到这段话给我涨点工资谢谢ლ(╹◡╹ლ）。</p><p>其实现在这个<code>/loop</code>我并没有布置到我满意的程度，它还在不停的调优（这里面有太多的事情，包括用户授权、数据脱敏、沙箱、审计、回滚等一大堆问题）。这也是我下一期想聊的内容之一——基于用户真实数据的自进化的Agent系统。</p><h3 id="5-GSDL的调用案例"><a href="#5-GSDL的调用案例" class="headerlink" title="5. GSDL的调用案例"></a>5. GSDL的调用案例</h3><p>下面给大家看一下GSDL的一个真实调用案例（我在2026 年 7 月 23 日进行的测试）：</p><blockquote><p><strong>“这个洞穴有几个可以通行的洞口？它们分别有多宽？”</strong></p></blockquote><p>它很能说明“读取 Actor”和“理解空间”的差别。<br>如果没有GSDL的话（例如 UE 的 MCP 方案），LLM会直接搜索名字中带 <code>Archway</code> 的资产，把 <code>SM_WallArchway_12x3</code>、<code>SM_WallArchway_3x6</code> 等构件当成洞口。在我的实际测试中，这些叫<code>Archway</code> 的 Actor 实际上是围成洞口的墙，不是洞口本身。这会导致误判。</p><p>有GSDL后：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">→ nm_status             ← 确认工具状态</span><br><span class="line">→ describe_region       ← 区域普查</span><br><span class="line">→ find_opening          ← 射线测洞口</span><br><span class="line">→ fly_to                ← 飞抵现场</span><br><span class="line">→ capture_view          ← 截图复核</span><br><span class="line"></span><br><span class="line">→ 带类型、位置、宽度与证据等级的回答</span><br></pre></td></tr></table></figure><p>同一个问题，加入 <code>find_opening</code> 路由之前，模型用了 47 次工具调用、消耗 64,820 字符，仍然把名字带 <code>Archway</code> 的墙体构件当成洞口；加入路由后，只用了 5 次调用和 15,540 字符，就完成了测量与视觉复核。</p><div class="media-layout media-layout--pair media-layout--compact">  <figure style="flex: 1 1 0; min-width: 0; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260820-191300.png" alt="洞穴岩壁间的洞口，透光可见远处场景" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption>洞口区域实景渲染</figcaption>  </figure>  <figure style="flex: 1 1 0; min-width: 0; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260820-191249.png" alt="同一机位的逐像素实例分割，用于区分画面中的洞壁与背景物体" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption>同一机位的像素级实例分割</figcaption>  </figure></div><p>模型随后飞到候选洞口前进行截图复核。掩码中出现了 494 个属于 <code>BP_Sky_Sphere</code> 的像素，说明视线确实能够穿过开口看到外部背景；但这只能作为“开口存在”的视觉旁证。它是否能够通行、宽度是多少，仍由贴近地面的碰撞射线与门框精测结果决定。</p><p>最终，系统把候选结构分成了10个地面可通行洞口或通道、高位窗口和天窗，并给出了地面开口的位置与实测宽度。它甚至找出了我此前没有注意到的开口。这里真正重要的不是“搜到了多少个叫 <code>Archway</code> 的 Actor”，而是它能够区分洞壁、内部通道、对外洞口和不可直接通行的高位开口。</p><h3 id="6-项目开源"><a href="#6-项目开源" class="headerlink" title="6. 项目开源"></a>6. 项目开源</h3><p>感谢你看到这里。<br>这个GSDL项目，我这段时间忙完之后，剥离公司项目内容后，会在Github上开源，欢迎大家一来尝试和提建议。一个人的精力确实太有限了。<br>项目地址暂时先放在这里了：<a href="https://github.com/youdrew/OpenGSDL">OpenGSDL</a> 大概9月初会上传。里面会包含着掩码方案 + GSDL。会以 UE 多版本 MCP 插件的形式呈现，你可以基于它做二次开发，或者直接用来辅助UE的官方MCP。</p><h2 id="六、Vector-Atlas（全地图语义编码）"><a href="#六、Vector-Atlas（全地图语义编码）" class="headerlink" title="六、Vector Atlas（全地图语义编码）"></a>六、Vector Atlas（全地图语义编码）</h2><p>我之所以做这个东西，是因为每一次模型去查询地图里面的东西，其实都是一个<strong>非常久的延迟操作</strong>（在这个过程中，要去加载场景，获取掩码、截图…），至少对于我们的项目来说，是一个60s+的操作。更别提，要进行全地图大规模的查询，会显著拉长单次任务的完成时间。所以我在想，能不能让这个过程更加的迅速一点。</p><p>并且，我还<strong>需要更多关于这个场景的语义信息</strong>，这些语义是需要美术来标注的，我一个人是几乎不可能完成整个大地图全部场景的标注，并且场景的语义（比如说，这里是某个NPC的家），会随着项目的推进，一直动态变化，这些也不可能靠我硬编码来实现。所以我需要一个动态地、可编辑的、场景语义收集服务。</p><p>所以我做了Vector Atlas（全地图语义编码）这么一个东西。<br>它结合了我们上面提到的 2D掩码 及 GSDL 两种能力为一身。</p><blockquote><p>把我们全部地图<strong>离线</strong>编码成一张”可语义检索的视角表”，人可以快速地通过语义检索整张地图里面的内容（可人为添加视角，可人为编辑视角语义），同时也作为 AI 跨会话的长期记忆存储能力（AI添加视角、保留为AI长期记忆，AI 可检索）。</p></blockquote><p>开篇我提到的：把每个位置「背」下来、毫秒级查询的承诺，就靠它兑现。用存储，换性能的工程化实现。</p><p>因为这篇文章实在太长了，且Vector Atlas这个东西也是一个非常复杂的项目（要考虑到如何优化搜索、如何拍摄、如何部署、如何让AI得到未拍摄的角度…这些都是要思考的东西）。<br>所以 Vector Atlas 加上（上面提到的）“基于用户真实数据的自进化的Agent系统”（第二期自进化接口），我会留到下一篇文章详细阐述，那时候，我的系统也更加完善了。在这里仅先简单地预告一下。</p><p>下面欣赏一下工具的Demo吧。</p><h2 id="七、给碳基生物的版本"><a href="#七、给碳基生物的版本" class="headerlink" title="七、给碳基生物的版本"></a>七、给碳基生物的版本</h2><div class="media-layout media-layout--quad">  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/article2-carbon-search-redacted-v2.gif" alt="语义搜索" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">图1 全地图场景位置语义搜索</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/article2-carbon-asset-redacted-v2.gif" alt="触达资产" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">图2 触达资产 点击掩码立刻跳转对应资产</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/article2-carbon-camera-redacted-v2.gif" alt="全地图任意位置视角调整" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">图3 全地图任意区域位置不用加载 机位跳转</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/article2-carbon-mask-redacted-v2.gif" alt="资产掩码信息展示" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">图4 即使图像掩码占比0.01%的资产也会有记录</figcaption>  </figure></div><h2 id="八、给硅基生物用的版本以及与Epic方案对比"><a href="#八、给硅基生物用的版本以及与Epic方案对比" class="headerlink" title="八、给硅基生物用的版本以及与Epic方案对比"></a>八、给硅基生物用的版本以及与Epic方案对比</h2><p>我准备了 11 道测试题，让每个模型分别只使用 Epic 官方 MCP 或我的 MCP（NeuroMap 08-14版），并保证每道题都是全新上下文，进行三个模型的测试。</p><table><thead><tr><th>指标</th><th align="right">DSV4flash<br>+ 本地Qwen3.5-9B-4bit<br>Epic</th><th align="right">DSV4flash<br>+ 本地Qwen3.5-9B-4bit<br>NeuroMap</th><th align="right">Qwen3.8 27b<br>Epic</th><th align="right">Qwen3.8 27b<br>NeuroMap</th><th align="right">GPT5.6 Luna<br>Epic</th><th align="right">GPT5.6 Luna<br>NeuroMap</th></tr></thead><tbody><tr><td>Harness</td><td align="right">Claude Code</td><td align="right">Claude Code</td><td align="right">Claude Code</td><td align="right">Claude Code</td><td align="right">Codex</td><td align="right">Codex</td></tr><tr><td>完成率</td><td align="right">100%（11&#x2F;11）</td><td align="right"><strong>100%（11&#x2F;11）</strong></td><td align="right">72.7%（8&#x2F;11）</td><td align="right"><strong>90.9%（10&#x2F;11）</strong></td><td align="right">100%（11&#x2F;11）</td><td align="right"><strong>100%（11&#x2F;11）</strong></td></tr><tr><td>正确率</td><td align="right">77.3%</td><td align="right"><strong>77.3%</strong></td><td align="right">59.1%</td><td align="right"><strong>86.4%</strong></td><td align="right">90.9%</td><td align="right"><strong>90.9%</strong></td></tr><tr><td>诚实度</td><td align="right"><strong>95.5%</strong></td><td align="right">90.9%</td><td align="right">68.2%</td><td align="right"><strong>72.7%</strong></td><td align="right">86.4%</td><td align="right"><strong>90.9%</strong></td></tr><tr><td>正式主记录总耗时</td><td align="right">468分7.0秒</td><td align="right"><strong>64分52.2秒</strong></td><td align="right">611分8.9秒</td><td align="right"><strong>291分21.8秒</strong></td><td align="right">141分35.5秒</td><td align="right"><strong>40分3.6秒</strong></td></tr><tr><td>全11题平均耗时</td><td align="right">42分33.4秒</td><td align="right"><strong>5分53.8秒</strong></td><td align="right">55分33.5秒</td><td align="right"><strong>26分29.3秒</strong></td><td align="right">12分52.3秒</td><td align="right"><strong>3分38.5秒</strong></td></tr><tr><td>单题最大耗时</td><td align="right">197分4.3秒</td><td align="right"><strong>20分15.0秒</strong></td><td align="right">220分3.8秒</td><td align="right"><strong>84分50.3秒</strong></td><td align="right">52分1.6秒</td><td align="right"><strong>18分6.3秒</strong></td></tr><tr><td>MCP 调用总数</td><td align="right">514</td><td align="right"><strong>261</strong></td><td align="right">318</td><td align="right"><strong>221</strong></td><td align="right">7,919</td><td align="right"><strong>313</strong></td></tr><tr><td>输入 &#x2F; 输出</td><td align="right">20,179,996 &#x2F; 261,572</td><td align="right"><strong>6,158,052 &#x2F; 119,825</strong></td><td align="right">10,173,709 &#x2F; 234,831</td><td align="right"><strong>8,438,133 &#x2F; 150,085</strong></td><td align="right">47,692,261 &#x2F; 207,474</td><td align="right"><strong>21,170,665 &#x2F; 94,384</strong></td></tr><tr><td>首轮固定开销</td><td align="right"><strong>700 Token</strong></td><td align="right">12,326 Token</td><td align="right"><strong>569 Token</strong></td><td align="right">14,761 Token</td><td align="right"><strong>~350 Token</strong></td><td align="right">~12,900 Token</td></tr></tbody></table><p>总体上，交付能力整体持平或领先的情况下，NeuroMap 显著减少了调用次数和任务耗时。其中，Luna 这一轮，Epic MCP一共调用了 <strong>7,919 次</strong>；NeuroMap 只调用 <strong>313 次</strong>，约为它的 <strong>1&#x2F;25</strong>，总耗时也只有 <strong>28%</strong>。<br>Epic MCP 提供的是通用、原子化的编辑器操作，模型需要反复路由并逐个查询对象（几乎是在遍历场景actor）。场景一复杂，一道题就可能被拆成成百上千次调用，这些往返会迅速放大。NeuroMap 把区域描述、异常检测、洞口分析等空间计算放在 UE 侧聚合，再用 GSDL 压缩返回，所以三个模型上都表现出更少的调用、更短的长尾和更高的交付率。</p><p>正确率上，最值得注意的是 Qwen这一轮测试，我的MCP从 <strong>59.1%</strong> 大幅提高到 <strong>86.4%</strong>。这说明聚合式接口不仅更省，也能明显降低较弱模型自己遍历 Actor、判断工具和归纳空间关系的难度。</p><p>不过，NeuroMap 当前的 Schema 开销仍然偏高，工具集合也还在持续收敛，后面还得持续优化。</p><p>下面是一次实际工作流：只给 AI 一个模糊描述，让它在大场景中寻找“摆着围棋的桌子”，定位目标、调整镜头并保存截图。</p><div class="media-layout media-layout--pair media-layout--compact">  <figure style="flex: 1 1 0; min-width: 0; margin: 0;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/article2-mcp-before-scene-redacted.png" alt="向 AI 输入模糊的围棋桌定位任务" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666; text-align: center;">① 只描述记忆中的外观与用途，没有提供资产名或坐标。</figcaption>  </figure>  <figure style="flex: 1 1 0; min-width: 0; margin: 0;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/article2-mcp-after-scene-redacted.png" alt="AI 定位围棋桌并完成截图" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666; text-align: center;">② 约 57 秒后定位到围棋桌，完成取景并把截图保存到桌面。</figcaption>  </figure></div><h2 id="九、后记"><a href="#九、后记" class="headerlink" title="九、后记"></a>九、后记</h2><p>不知道你有没有发现一个问题，不管是NeuroBrowser（第一期的插件）还是NeuroMap（这一期插件，二者简称NB和NM，我们内部简称他们为<strong>您爸</strong>插件和<strong>您妈</strong>插件），其实都在把 AI 能力渗入引擎编辑器，有点“以AI能力再造编辑器”的感觉？</p><p>或许下一代游戏编辑器应该长这样呢？您觉得呢？</p>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/UnrealEngine/">UnrealEngine</category>
      
      <category domain="https://eugenepage.com/tags/AI/">AI</category>
      
      <category domain="https://eugenepage.com/tags/MCP/">MCP</category>
      
      <category domain="https://eugenepage.com/tags/SceneUnderstanding/">SceneUnderstanding</category>
      
      <category domain="https://eugenepage.com/tags/GSDL/">GSDL</category>
      
      
      <comments>https://eugenepage.com/zh-CN/2026/06/24/20260625.AIGameSeries-Article2/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Landing AI in Game Production — A Kung-Fu Manual · Form 1 «Within Easy Reach»: Making AI Understand a Project&#39;s Existing Assets</title>
      <link>https://eugenepage.com/2026/06/01/20260602.AIGameSeries-Article1/</link>
      <guid>https://eugenepage.com/2026/06/01/20260602.AIGameSeries-Article1/</guid>
      <pubDate>Mon, 01 Jun 2026 16:00:00 GMT</pubDate>
      
        
        
      <description>&lt;h1 id=&quot;Landing-AI-in-Game-Production-·-Form-1-«Within-Easy-Reach»-Making-AI-Understand-a-Project’s-Existing-Assets&quot;&gt;&lt;a href=&quot;#Landing-AI-in</description>
        
      
      
      
      <content:encoded><![CDATA[<h1 id="Landing-AI-in-Game-Production-·-Form-1-«Within-Easy-Reach»-Making-AI-Understand-a-Project’s-Existing-Assets"><a href="#Landing-AI-in-Game-Production-·-Form-1-«Within-Easy-Reach»-Making-AI-Understand-a-Project’s-Existing-Assets" class="headerlink" title="Landing AI in Game Production · Form 1 «Within Easy Reach»: Making AI Understand a Project’s Existing Assets"></a>Landing AI in Game Production · Form 1 «Within Easy Reach»: Making AI Understand a Project’s Existing Assets</h1><blockquote><p><strong>Series intro</strong>: “The Kung-Fu Manual for Landing AI in Game Production” is a hands-on series for game technical artists (TAs) and tool developers — a record of the road I walked, all of it written in blood and tears.</p></blockquote><hr><h2 id="1-Origin-Don’t-Leave-AI-in-the-Dark"><a href="#1-Origin-Don’t-Leave-AI-in-the-Dark" class="headerlink" title="1. Origin: Don’t Leave AI in the Dark"></a>1. Origin: Don’t Leave AI in the Dark</h2><p>Our project is a large open-world game with an asset library on the order of <strong>100k items</strong>, spanning a dozen-plus major categories — trees, rocks, shrubs, buildings, and more — and each asset also has mobile variants, LODs, Impostors, and sub-resources.</p><p>For AI to take part in game production, it first has to “know” what’s in the project. But the reality is —</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></pre></td><td class="code"><pre><span class="line">&quot;Place a low Jiangnan-style shrub&quot;</span><br><span class="line">  → What the AI sees is shrub_a1_01a, shrub_a1_02b</span><br><span class="line">  → Which one is the low one? Which one belongs in this scene? No idea.</span><br></pre></td></tr></table></figure><p>Humans aren’t much better than AI here. Artists find assets the same way — digging through folders, guessing at filenames, asking colleagues, retrieving from memory. A 2024 master’s thesis from Aalto University in Finland[^1] documents the same predicament: in HypeHype’s 5,000+ asset library, searching “forest” fails to find an asset whose description says “woods.”</p><p>So <strong>making assets understandable</strong> is the prerequisite for AI to participate in production. But for AI to understand human things, humans have to teach it (annotation). Annotation is time-consuming, and artists have no obligation to help you with it. So I also need an SOP that lets artists annotate assets <em>while</em> they search — trying to spin a virtuous cycle out of that.</p><hr><h2 id="2-The-Distance-Between-Assets-and-Understanding"><a href="#2-The-Distance-Between-Assets-and-Understanding" class="headerlink" title="2. The Distance Between Assets and Understanding"></a>2. The Distance Between Assets and Understanding</h2><p>In fact, whether you’re a human or an AI, understanding an asset involves <strong>three different kinds of distance</strong>.</p><ol><li>What is this asset called? (semantic)</li><li>What does it look like? (visual)</li><li>What is it for? Where and how is it used? (logical)</li></ol><p><em>BTW: most good search tools can retrieve every character related to an article — even a string that’s just a stray note buried in a document. But you know how the brain works: you might only remember some “punctum” (Roland Barthes, <em>Camera Lucida</em>) tucked in a corner that once moved you, yet you just can’t recall the title of the thing. Every game-engine search box I know of is exactly like this — it can only find filenames, not some blueprint note inside a file, and it doesn’t support more complex search logic.</em><br><em>That’s a product-design problem, and it isn’t “distance” yet. A great search box should be like Everything (yes, I’m praising it by name) — able to search everything in the system! So our search capability should, in theory, be able to fuzzily retrieve an asset from a mere impression. That’s exactly what I’m out to build.</em></p><p><strong>Layer 1: Semantic distance.</strong> Literal matching across filenames, paths, and tags — searching “rock” finds <code>rock_granite_01</code>, but searching “石头” or “岩石” (Chinese for “stone”&#x2F;“rock”) finds nothing, even though the words mean almost the same thing, or differ only by language.</p><p><strong>Layer 2: Visual distance.</strong> You want to describe a building of a particular form, but — alas, my literature teacher must have passed away too early — you can’t precisely name terms like “庑殿 &#x2F; 闇栔 &#x2F; 甍 &#x2F; 甓” (obscure classical Chinese architectural terms), yet you <em>know</em> it’s that exact thing! The poverty of language turns out to be painfully real.</p><p><strong>Layer 3: Logical distance.</strong> Searching “a worn-out object you could put in a corner” — this isn’t describing appearance, it’s describing <strong>purpose and state</strong>. Visual retrieval can’t help here. There’s reasoning baked in: how do you know you can put a broom in the corner of a home, rather than a pig-slaughtering knife? That requires understanding the need and reasoning from it.</p><p>Each of the three layers has its blind spots and its strengths, and each suits a different scenario.</p><hr><h2 id="3-The-Framework-Inner-Method"><a href="#3-The-Framework-Inner-Method" class="headerlink" title="3. The Framework: Inner Method"></a>3. The Framework: Inner Method</h2><div class="callout" data-callout="abstract" style="--callout-color: 0, 176, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/></svg><span class="callout-title-inner">Architecture Overview</span></div><div class="callout-content"><div class="canvas-embed" data-canvas-slug="attachments/Canvas/20260602.AIGameSeries-Architecture"><svg xmlns="http://www.w3.org/2000/svg" class="canvas-svg" data-canvas-revision="7ff889a3b3fe" width="192" height="280" viewBox="-380 -1080 1620 2360" preserveAspectRatio="xMidYMid meet" role="img" aria-label="20260602.AIGameSeries-Architecture"><title>20260602.AIGameSeries-Architecture</title><defs><marker id="canvas-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" /></marker></defs><g class="canvas-groups"><g class="canvas-node canvas-node--group" data-id="a100000000000020" data-x="-320" data-y="200" data-width="1440" data-height="680" data-color="4"><rect class="canvas-group__bg" x="-320" y="200" width="1440" height="680" rx="12" /><text class="canvas-group__label" x="-308" y="222">⚡ 语义检索层（100 - 300ms）— FastAPI 后端</text></g><g class="canvas-node canvas-node--group" data-id="a100000000000010" data-x="-40" data-y="-1040" data-width="500" data-height="540" data-color="6"><rect class="canvas-group__bg" x="-40" y="-1040" width="500" height="540" rx="12" /><text class="canvas-group__label" x="-28" y="-1018">用户入口</text></g><g class="canvas-node canvas-node--group" data-id="2269b9ba802f3f5d" data-x="-340" data-y="-420" data-width="350" data-height="380" data-color="3"><rect class="canvas-group__bg" x="-340" y="-420" width="350" height="380" rx="12" /><text class="canvas-group__label" x="-328" y="-398">基础检索层（&lt; 100ms）</text></g><g class="canvas-node canvas-node--group" data-id="a100000000000030" data-x="100" data-y="1080" data-width="600" data-height="160" data-color="2"><rect class="canvas-group__bg" x="100" y="1080" width="600" height="160" rx="12" /><text class="canvas-group__label" x="112" y="1102">🎯 精排层（300-2000ms） CLI / MCP调用</text></g></g><g class="canvas-edges"><g class="canvas-edge-group" data-id="e1000000000000001" data-from-node="a100000000000011" data-to-node="a100000000000012" data-from-side="right" data-to-side="left" data-color="6"><path class="canvas-edge" d="M 360 -973 C 506.8181036369683 -973, -66.81810363696829 -633, 80 -633" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="220" y="-803" text-anchor="middle">查询</text></g><g class="canvas-edge-group" data-id="e1000000000000002" data-from-node="a100000000000012" data-to-node="a100000000000021" data-from-side="bottom" data-to-side="top" data-color="4"><path class="canvas-edge" d="M 220 -586 C 220 -412.21341312466666, -170 -413.78658687533334, -170 -240" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="25" y="-413" text-anchor="middle">SQL</text></g><g class="canvas-edge-group" data-id="e1000000000000003" data-from-node="a100000000000012" data-to-node="a100000000000022" data-from-side="bottom" data-to-side="top" data-color="4"><path class="canvas-edge" d="M 220 -586 C 220 -279.52977306106874, -140 -46.47022693893126, -140 260" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="40" y="-163" text-anchor="middle">视觉</text></g><g class="canvas-edge-group" data-id="e1000000000000004" data-from-node="a100000000000012" data-to-node="a100000000000023" data-from-side="bottom" data-to-side="top" data-color="4"><path class="canvas-edge" d="M 220 -586 C 220 -222.09891453857966, 910 -103.90108546142034, 910 260" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="565" y="-163" text-anchor="middle">描述</text></g><g class="canvas-edge-group" data-id="e1000000000000005" data-from-node="a100000000000021" data-to-node="a100000000000024" data-from-side="bottom" data-to-side="top" data-color="4"><path class="canvas-edge" d="M -170 -126 C -170 154.7244596080331, 180 359.2755403919669, 180 640" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="5" y="257" text-anchor="middle">SQL 结果</text></g><g class="canvas-edge-group" data-id="e1000000000000006" data-from-node="a100000000000022" data-to-node="a100000000000024" data-from-side="bottom" data-to-side="top" data-color="4"><path class="canvas-edge" d="M -140 354 C -140 497.06020488669174, 180 496.93979511330826, 180 640" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="20" y="497" text-anchor="middle">视觉结果</text></g><g class="canvas-edge-group" data-id="e1000000000000007" data-from-node="a100000000000023" data-to-node="a100000000000024" data-from-side="bottom" data-to-side="top" data-color="4"><path class="canvas-edge" d="M 910 354 C 910 615.3418365963544, 180 378.6581634036457, 180 640" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="545" y="497" text-anchor="middle">描述结果</text></g><g class="canvas-edge-group" data-id="e1000000000000008" data-from-node="a100000000000024" data-to-node="a100000000000012" data-from-side="right" data-to-side="right" data-color="4"><path class="canvas-edge" d="M 380 687 C 820.0505021522467 687, 800.0505021522467 -633, 360 -633" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="370" y="27" text-anchor="middle">&lt; 100ms 即时返回</text></g><g class="canvas-edge-group" data-id="e1000000000000009" data-from-node="a100000000000024" data-to-node="a100000000000031" data-from-side="bottom" data-to-side="top" data-color="2"><path class="canvas-edge" d="M 180 734 C 180 882.0975654387637, 400 971.9024345612363, 400 1120" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="290" y="927" text-anchor="middle">候选送精排</text></g><g class="canvas-edge-group" data-id="e1000000000000010" data-from-node="a100000000000030" data-to-node="a100000000000012" data-from-side="right" data-to-side="right" data-color="2"><path class="canvas-edge" d="M 700 1160 C 1308.3172600616301 1160, 968.3172600616301 -633, 360 -633" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="530" y="263.5" text-anchor="middle">300-600ms 精排刷新</text></g><g class="canvas-edge-group" data-id="e1000000000000011" data-from-node="a100000000000023" data-to-node="a100000000000060" data-from-side="right" data-to-side="left" data-color="5"><path class="canvas-edge" d="M 1060 307 C 1228.9079302131456 307, 751.0920697868544 -180, 920 -180" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="990" y="63.5" text-anchor="middle">cloud_sync 双向同步</text></g><g class="canvas-edge-group" data-id="243036cbd0cb25d2" data-from-node="a100000000000021" data-to-node="a100000000000060" data-from-side="right" data-to-side="left" data-color="5"><path class="canvas-edge" d="M -20 -183 C 293.33492907395083 -183, 606.6650709260491 -180, 920 -180" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="450" y="-181.5" text-anchor="middle">SQL检索描述信息</text></g></g><g class="canvas-nodes"><g class="canvas-node canvas-node--text" data-id="a100000000000011" data-x="80" data-y="-1000" data-width="280" data-height="54" data-color="6"><rect class="canvas-node__bg" x="80" y="-1000" width="280" height="54" rx="8" /><foreignObject x="80" y="-1000" width="280" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#👤-用户查询" class="headerlink" title="👤 用户查询"></a>👤 用户查询</div><p>文字 &#x2F; 图片 &#x2F; 图文混合 &#x2F; 过滤条件</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000021" data-x="-320" data-y="-240" data-width="300" data-height="114" data-color="3"><rect class="canvas-node__bg" x="-320" y="-240" width="300" height="114" rx="8" /><foreignObject x="-320" y="-240" width="300" height="114"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#📋-SQL-检索" class="headerlink" title="📋 SQL 检索"></a>📋 SQL 检索</div><p><strong>sql_capability.py</strong></p><p>FTS5 全文 + 标签过滤<br>数值范围 + 逻辑组合<br>trigram 中文补充召回</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000022" data-x="-290" data-y="260" data-width="300" data-height="94" data-color="4"><rect class="canvas-node__bg" x="-290" y="260" width="300" height="94" rx="8" /><foreignObject x="-290" y="260" width="300" height="94"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#👁️-视觉检索" class="headerlink" title="👁️ 视觉检索"></a>👁️ 视觉检索</div><p><strong>visual_capability.py</strong></p><p>文字→图片 &#x2F; 图片→图片<br>图文联合 α·img+(1-α)·txt</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000012" data-x="80" data-y="-680" data-width="280" data-height="94" data-color="6"><rect class="canvas-node__bg" x="80" y="-680" width="280" height="94" rx="8" /><foreignObject x="80" y="-680" width="280" height="94"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#🖼️-UE4-Slate-插件" class="headerlink" title="🖼️ UE4 Slate 插件"></a>🖼️ UE4 Slate 插件</div><p>搜索框 + 缩略图网格 + 拖拽<br>路径树 + 标签过滤 + 资产族折叠<br>详情面板 + 权重档位 + Analytics</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000060" data-x="920" data-y="-207" data-width="280" data-height="54" data-color="5"><rect class="canvas-node__bg" x="920" y="-207" width="280" height="54" rx="8" /><foreignObject x="920" y="-207" width="280" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#☁️-云端数据库" class="headerlink" title="☁️ 云端数据库"></a>☁️ 云端数据库</div><p>描述 &#x2F; 标签 &#x2F; 标签规则</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000023" data-x="760" data-y="260" data-width="300" data-height="94" data-color="4"><rect class="canvas-node__bg" x="760" y="260" width="300" height="94" rx="8" /><foreignObject x="760" y="260" width="300" height="94"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#📝-描述词检索" class="headerlink" title="📝 描述词检索"></a>📝 描述词检索</div><p><strong>desc_capability.py</strong></p><p>全量描述 embedding<br>富文本：类别+标签+描述</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000024" data-x="-20" data-y="640" data-width="400" data-height="94" data-color="4"><rect class="canvas-node__bg" x="-20" y="640" width="400" height="94" rx="8" /><foreignObject x="-20" y="640" width="400" height="94"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#🔄-RRF-融合排序" class="headerlink" title="🔄 RRF 融合排序"></a>🔄 RRF 融合排序</div><p><strong>fusion.py</strong> | k&#x3D;60</p><p>SQL + Visual + Desc → 统一排序<br>三路权重用户可调</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000031" data-x="140" data-y="1120" data-width="520" data-height="100" data-color="2"><rect class="canvas-node__bg" x="140" y="1120" width="520" height="100" rx="8" /><foreignObject x="140" y="1120" width="520" height="100"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#🤖-LLM-Re-rank" class="headerlink" title="🤖 LLM Re-rank"></a>🤖 LLM Re-rank</div><p><strong>llm_capability.py</strong></p><p>query + 候选 description → relevance score<br>渐进式：先出即时结果，精排后自动刷新</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="06e5336795590aba" data-x="320" data-y="-40" data-width="260" data-height="60"><rect class="canvas-node__bg" x="320" y="-40" width="260" height="60" rx="8" /><foreignObject x="320" y="-40" width="260" height="60"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"></div></foreignObject></g></g></svg><span class="canvas-embed__expand" aria-hidden="true" title="点击放大">⛶</span></div></div></div><p>First, an “inner method” of tool design: <strong>users are willing to wait for a tool, but the longer they wait, the higher their expectations climb.</strong> Waiting a full minute only to get a terrible result is the most unacceptable outcome. So the whole framework is built around <strong>layering by response speed</strong> — fast results first, slow ones backfilled, never leaving the user hanging.</p><p>Following the three “distances” from Section 2, I split retrieval into three layers, each one reaching for a different distance:</p><ul><li><strong>Layer 1 · Base retrieval (fastest, &lt; 100ms)</strong>: optimizations on top of UE’s native search, doing literal and rule-based matching over filenames &#x2F; paths &#x2F; tags. It’s the “safety net” — it works without any AI model.</li><li><strong>Layer 2 · Vector retrieval (instant, &lt; 100ms)</strong>: this layer actually runs <strong>two parallel paths</strong> — <strong>visual vectors</strong> reach for the «visual distance» (search-by-image), and <strong>text &#x2F; description vectors</strong> reach for the «semantic distance» (synonyms, Chinese↔English, paraphrases all become searchable, closing the gap that Layer 1’s literal matching can’t cross). Each path recalls independently, then the results are fused and re-ranked.</li><li><strong>Layer 3 · Logical retrieval (re-rank, async in the background)</strong>: CLI and MCP bring an external LLM into the loop to reach for the hardest «logical distance» — understanding purpose, state, and intent, the kind of query that needs reasoning.</li></ul><blockquote><p>One line to untangle the naming: <strong>the three-layer framework maps to the three distances, but the «semantic distance» is closed by a relay between Layer 1 (literal matching) and Layer 2 (text vectors)</strong> — which is why, despite being called a “three-layer framework,” it technically runs four paths: «SQL + Visual + Description + LLM». The NeuroBrowser in Section 5 is exactly this inner method put into practice.</p></blockquote><h2 id="4-Sword-Summit-at-Mount-Hua-·-Model-Selection"><a href="#4-Sword-Summit-at-Mount-Hua-·-Model-Selection" class="headerlink" title="4. Sword Summit at Mount Hua · Model Selection"></a>4. Sword Summit at Mount Hua · Model Selection</h2><p>The framework is designed; now for the most critical question: <strong>which models?</strong></p><p>This isn’t a question you can answer by intuition or by paper leaderboards. There’s a domain gap between game render images and natural images — our thumbnails are rendered in-engine (sky sphere + multi-directional lights + stylized materials), and look nothing like the Flickr &#x2F; LAION datasets CLIP[^2] was trained on. Leaderboard scores definitely don’t transfer directly.</p><p>So I ran two benchmarks: <strong>visual-encoder selection</strong> %% (maps to subsection 1) %% and <strong>description &#x2F; text-embedding selection</strong> %% (maps to subsection 3) %%. The dataset is 100 real project thumbnails + 5 sets of artist-annotated Ground Truth (each set &#x3D; 1 Chinese description + 3 correct images).</p><p>Also, description embedding has no initial data, and making artists annotate it from scratch isn’t quite appropriate either — so I directly used an LLM to generate the initial descriptions for all assets, then ran text-semantic embedding over those descriptions (for future asset ingestion this approach can be reused too, with artists then editing the LLM-generated descriptions). That’s why I also benchmarked a batch of different multimodal LLMs %% (maps to subsection 2) %%.</p><h3 id="1-Visual-Encoder-14-Models-Two-Rounds-of-Head-to-Head"><a href="#1-Visual-Encoder-14-Models-Two-Rounds-of-Head-to-Head" class="headerlink" title="1. Visual Encoder: 14 Models, Two Rounds of Head-to-Head"></a>1. Visual Encoder: 14 Models, Two Rounds of Head-to-Head</h3><p>Image → semantic vector</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></pre></td><td class="code"><pre><span class="line">flowchart LR</span><br><span class="line">    A[&quot;🖼️ Asset thumbnail&quot;] --&gt; B[&quot;Visual model&quot;]</span><br><span class="line">    B --&gt; C[&quot;1024-dim vector&quot;]</span><br><span class="line">    C --&gt; D[&quot;FAISS search&quot;]</span><br><span class="line"></span><br><span class="line">    style A fill:#4a90d9,color:#fff</span><br><span class="line">    style B fill:#e8793a,color:#fff</span><br><span class="line">    style C fill:#50b86c,color:#fff</span><br><span class="line">    style D fill:#9b59b6,color:#fff</span><br></pre></td></tr></table></figure><p>Round 1 started from the 4 models recommended by the Aalto thesis (a minimal test). The core finding: <strong>CLIP without Chinese support completely fails on Chinese queries</strong> — Recall@10 of only 13.3%, basically random. The version with an added xlm-roberta multilingual text tower led by a wide margin at 66.7% — a 5–10× gap.</p><p>Round 2 expanded along this lead to 10 new models, covering 4 tiers: 2025 SOTA (FG-CLIP 2 family), Chinese-native training (Chinese-CLIP family), multilingual up-and-comers (MEXMA &#x2F; NLLB &#x2F; AltCLIP), and the scaled-up family of Round 1’s strongest approach.</p><p>The result was unexpected: <strong>Qihoo 360’s FG-CLIP 2 large</strong> (0.9B params, natively bilingual) took a double crown at P@3&#x3D;73.3% and R@10&#x3D;93.3%. In 4 of the 5 queries it nailed all correct answers within the Top-2 — that’s an industrially usable level. A 26.6-point improvement over Round 1’s strongest.</p><p>Even more surprising, FG-CLIP 2 large actually beats the larger so400m (1.0B) by 20 points on P@3 — bigger isn’t always better; training steps and adaptation to thumbnail size may matter more.</p><p>On bang-for-buck, Chinese-CLIP ViT-L&#x2F;14@336 ties FG-CLIP 2 large for first on R@10 (93.3%), with a smaller dimension (768 vs 1024), the simplest loading, and the most mature ecosystem — a solid backup.</p><p>So I ultimately chose FG-CLIP 2 large as the encoder for image semantics.</p><blockquote><p>⚠️ <strong>First, a splash of cold water</strong>: all these percentages rest on <strong>5 GT sets × 3 correct images &#x3D; 15 data points</strong>, where a single misplaced result is a ±6.7pp swing. So phrases like “73.3% double crown” and “+26.6pp over Round 1” point in the right direction, but <strong>the size of the lead probably has luck baked in</strong> — it can’t be treated as settled until GT is expanded to 30+ sets. This warning applies equally to the text benchmark in subsection 3 below — don’t let any single high score go to your head.</p></blockquote><div style="display: flex; flex-wrap: wrap; gap: 16px; justify-content: center; margin: 24px 0;">  <figure style="flex: 1 1 280px; max-width: 360px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260624-000308.png" alt="Visual encoder model comparison" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Visual encoder model comparison</figcaption>  </figure></div><h3 id="2-Benchmarking-LLM-Generation"><a href="#2-Benchmarking-LLM-Generation" class="headerlink" title="2. Benchmarking LLM Generation"></a>2. Benchmarking LLM Generation</h3><p>Thumbnail → Chinese description</p><p>As described above, I need a first wave of initialization text for text semantics, so I planned to use an LLM to generate the initial descriptions for all assets, with artists then editing on top of that text to better fit the “production content” scenario. But understanding an asset and generating a description is no simple matter — different LLMs vary in generation time, quality, and stability. So I ran a test:</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></pre></td><td class="code"><pre><span class="line">flowchart LR</span><br><span class="line">    A[&quot;🖼️ Asset thumbnail&quot;] --&gt; B[&quot;Multimodal LLM&quot;]</span><br><span class="line">    B --&gt; C[&quot;Chinese description text&quot;]</span><br><span class="line">    C --&gt; D[&quot;Text embedding&quot;]</span><br><span class="line"></span><br><span class="line">    style A fill:#4a90d9,color:#fff</span><br><span class="line">    style B fill:#e8793a,color:#fff</span><br><span class="line">    style C fill:#50b86c,color:#fff</span><br><span class="line">    style D fill:#9b59b6,color:#fff</span><br></pre></td></tr></table></figure><p>I pulled 13 mainstream multimodal LLMs (late May 2026) to generate descriptions for the same batch of 100 asset thumbnails (the thumbnail itself takes some care — ideally high-resolution, and if you can, even multi-angle thumbnails; I used a single 2048×2048 thumbnail, and it’s best to avoid over- or under-exposure), benchmarking across four dimensions: <strong>cost, speed, quality, stability</strong>:</p><table><thead><tr><th>Model</th><th>Avg len (chars)</th><th>min~max</th><th>Cost&#x2F;img</th><th>Total (100)</th><th>Pure query time</th><th>Total time</th><th>Stability</th></tr></thead><tbody><tr><td><strong>kimi-k2.6</strong></td><td>245</td><td>138~483</td><td>¥0.084</td><td>¥8.37</td><td>10.2s</td><td>20.4min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td><strong>gemini-3.1-flash-lite</strong></td><td>165</td><td>73~254</td><td><strong>¥0.026</strong></td><td><strong>¥2.62</strong></td><td><strong>8.6s</strong></td><td>17.6min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>kimi-k2.6-yd</td><td>303</td><td>95~2448</td><td>¥0.053</td><td>¥5.28</td><td>14.5s</td><td>27.5min</td><td>⭐⭐⭐⭐</td></tr><tr><td><strong>claude-haiku-4-5</strong></td><td><strong>298</strong></td><td>186~435</td><td>¥0.104</td><td>¥10.40</td><td>13.3s</td><td>25.5min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>glm-5v-turbo</td><td>246</td><td>136~432</td><td>¥0.121</td><td>¥12.45</td><td>12.9s</td><td>24.8min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>qwen3.5-plus</td><td>154</td><td>7~269</td><td>¥0.030</td><td>¥3.00</td><td>10.4s</td><td>20.6min</td><td>⭐⭐</td></tr><tr><td>qwen3.6-plus</td><td>173</td><td>13~1347</td><td>¥0.130</td><td>¥13.00</td><td>16.2s</td><td>30.3min</td><td>⭐⭐</td></tr><tr><td>gpt-5.2</td><td>199</td><td>106~308</td><td>¥0.181</td><td>¥18.06</td><td>20.9s</td><td>38.1min</td><td>⭐⭐⭐⭐</td></tr><tr><td>claude-opus-4-7</td><td>256</td><td>140~417</td><td>¥0.221</td><td>¥22.11</td><td>19.6s</td><td>36.0min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>claude-sonnet-4-6</td><td>248</td><td>140~412</td><td>¥0.258</td><td>¥25.79</td><td>20.4s</td><td>~37min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>gemini-3.5-flash</td><td>212</td><td>106~452</td><td>¥0.303</td><td>¥30.28</td><td>18.3s</td><td>~33min</td><td>⭐⭐⭐⭐</td></tr><tr><td>gpt-5.4</td><td>242</td><td>147~416</td><td>¥0.567</td><td>¥56.74</td><td>15.4s</td><td>29.0min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>mimo-v2.5-free</td><td>172</td><td>73~320</td><td>¥0 (free)</td><td>¥0</td><td>20.6s</td><td>37.6min</td><td>⭐⭐⭐</td></tr></tbody></table><ul><li>The benchmark times are for reference only and depend on the API provider too. This is just my environment.</li><li>Also, LLM generation cost depends on the prompt provided. Reproduction costs will differ.</li><li>In testing I used a fixed prompt and ran each image in its own independent context.</li></ul><div style="display: flex; flex-wrap: wrap; gap: 16px; justify-content: center; margin: 24px 0;">  <figure style="flex: 1 1 280px; max-width: 600px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260628-010224.png" alt="LLM description-generation benchmark results" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">LLM description-generation benchmark results</figcaption>  </figure></div><p>A few key findings:</p><p><strong>1. The cost gap is staggering — cheapest to priciest is a 20× spread.</strong> gemini-3.1-flash-lite ¥0.026&#x2F;img vs gpt-5.4 ¥0.567&#x2F;img. For a full 100k-image run, the former is ¥2,607, the latter ¥56,700 — a difference of a dozen-plus RTX 4060 Ti’s worth of money. More surprising, claude-opus is actually cheaper than sonnet (¥0.221 vs ¥0.258) — sonnet got overtaken by opus.</p><p><strong>2. Description length varies hugely, but longer isn’t better.</strong> Claude-haiku averages the longest at 298 chars with the smallest variance (min 186 ~ max 435), and the best structure (the four-part “shape &amp; structure &#x2F; material &amp; texture &#x2F; color &amp; tone &#x2F; fine details”); qwen3.5-plus is the shortest at just 154 chars. But the later embedding retrieval validated a counterintuitive conclusion — <strong>the most human-readable description ≠ the most embedding-friendly description</strong> (see next section).</p><p><strong>3. Qwen — a dagger hidden in a smile: tag leakage directly pollutes the embedding.</strong> qwen3.5-plus leaks <code>&lt;skill&gt;</code> tags (one image output 38 chars of garbage <code>skill\nname visual verdict\n/name\n/skill</code>), and qwen3.6-plus leaks <code>&lt;think chain-of-thought</code> (up to 1,347 chars of reasoning written straight into the output), with stability of only ⭐⭐. These residual markers go straight into the description text and become noise in downstream embedding retrieval. A cleaning step is mandatory before full production (regex tag-stripping + truncation to 800 chars).</p><p><strong>4. Kimi-k2.6 is the top domestic pick — 0 anomalies, fastest.</strong> 10.2s pure query time, the fastest of all, 0 anomalous outputs, full-star stability. If you’re wary of <code>trust_remote_code</code> or need a fully domestic solution, kimi-k2.6 is the most stable choice. Its discounted version kimi-k2.6-yd occasionally has 1 image with a 2,448-char suspected think leak, usable after adding a max-length filter.</p><p><strong>5. The free model is usable but weak.</strong> Mimo-v2.5-free is zero-cost but occasionally times out (120s) and has token-loop anomalies (30K–44K stuck in a loop), with some misidentification on water-body types; stability only ⭐⭐⭐. Good for PoC validation, not for full production.</p><p><strong>6. Description quality correlates positively with render quality.</strong> The higher the render quality, the better the description.</p><table><thead><tr><th>Use case</th><th>Pick</th><th>Reason</th></tr></thead><tbody><tr><td><strong>Ultra-scale</strong> (100k+)</td><td>gemini-3.1-flash-lite</td><td>¥0.026&#x2F;img + 8.6s, cheapest + fastest</td></tr><tr><td><strong>Production default</strong> (quality&#x2F;cost balance)</td><td>kimi-k2.6</td><td>¥0.084&#x2F;img + 10.2s, domestic &amp; stable, 0 anomalies</td></tr><tr><td><strong>Most detailed descriptions</strong></td><td>claude-haiku-4-5</td><td>avg 298 chars longest, smallest variance</td></tr><tr><td><strong>Domestic + Chinese-friendly</strong></td><td>glm-5v-turbo &#x2F; kimi-k2.6</td><td>both 0 anomalies, 0 failures</td></tr><tr><td><strong>Ceiling reference</strong></td><td>claude-opus-4-7</td><td>cheaper than sonnet, 0 anomalies</td></tr><tr><td><strong>Pitfalls to avoid</strong></td><td>qwen3.5&#x2F;3.6-plus</td><td>think&#x2F;skill tag leakage pollutes embedding retrieval</td></tr><tr><td>These generated semantics are meant to feed the embedding model, so let’s invite this round’s winners (gemini-3.1-flash-lite, Kimi-k2.6, glm-5v-turbo, claude-haiku-4-5) into the next chapter.</td><td></td><td></td></tr></tbody></table><h3 id="3-Text-Embedding"><a href="#3-Text-Embedding" class="headerlink" title="3. Text Embedding"></a>3. Text Embedding</h3><p>Visual encoding solved the “search-image-find-image” problem, but that search capability is still too shallow. To make AI deeply understand this game’s assets, the more important thing is to break past surface-level image semantics — to grasp who this asset belongs to, and what it means.</p><p>And this content is what truly needs artists to teach the AI bit by bit, and it’s also the most “grounded.” So how do we teach it?</p><p>My answer is — <strong>have the AI first write a description for each asset, then run semantic retrieval over the descriptions (later, artists can edit that description and re-embed it into the vector store)</strong>. So I invited the previous round’s 4 players to do another cross-benchmark: <strong>7 text-embedding models × 4 description sources &#x3D; 28 combos</strong>, plus an FG-CLIP2 visual baseline + RRF fusion, for 53 experiments total.</p><p>Seven embedding candidates (all runnable in bf16 on my 8GB 4060Ti; Qwen3-Embedding-4B&#x2F;8B excluded because their bf16 weights are ≥8GB):</p><table><thead><tr><th>Model</th><th>Params</th><th>Dim</th><th>Pooling</th><th>C-MTEB</th><th>query&#x2F;doc handling</th></tr></thead><tbody><tr><td>BAAI&#x2F;bge-m3</td><td>0.57B</td><td>1024</td><td>cls</td><td>64.5</td><td>no prefix</td></tr><tr><td>BAAI&#x2F;bge-large-zh-v1.5</td><td>0.33B</td><td>1024</td><td>cls</td><td>64.5</td><td>no prefix</td></tr><tr><td>Qwen&#x2F;Qwen3-Embedding-0.6B</td><td>0.6B</td><td>1024</td><td>last-token</td><td>72.0</td><td>no prefix</td></tr><tr><td>multilingual-e5-large</td><td>0.56B</td><td>1024</td><td>mean</td><td>58.8</td><td>query: <code>query:</code> &#x2F; doc: <code>passage:</code></td></tr><tr><td>stella-mrl-large-zh-v3.5-1792d</td><td>0.56B</td><td>1792</td><td>mean + Dense</td><td>68.6</td><td>mean pool then official <code>2_Dense</code> (1024→1792); no prefix</td></tr><tr><td>jina-embeddings-v3</td><td>0.57B</td><td>1024</td><td>mean + task-LoRA</td><td>~64</td><td><code>retrieval.query</code> &#x2F; <code>retrieval.passage</code> adapter_mask; no text prefix</td></tr><tr><td>gte-Qwen2-1.5B-instruct</td><td>1.5B</td><td>1536</td><td>last-token</td><td>67.7</td><td>⚠️ invalid result (see below)</td></tr></tbody></table><p>The four description sources were picked from the previous section’s 13 models: claude-haiku-4-5 (highest human-readability), kimi-k2.6 (production pick), glm-5v-turbo (domestic &amp; stable), gemini-3.1-flash-lite (the value king).</p><blockquote><p>⚠️ <strong>gte-Qwen2-1.5B results are unusable, excluded from ranking</strong>. Its 2024 custom <code>modeling_qwen.py</code> (bidirectional attention + old KV-cache API) is incompatible with transformers 5.9.0 in three places. I loaded it with native Qwen2Model (causal attention) as a stopgap, but gte’s core is bidirectional — under causal attention, last-token pooling degrades severely (R@10 only 20–60%), which doesn’t represent its true quality. Its C-MTEB of 67.7 suggests its real level should be upper-middle.</p></blockquote><p><strong>The unorthodox blade: the most human-readable description ≠ the most embedding-friendly one</strong></p><p>6 fair models × 4 description sources &#x3D; 24 single-path text retrievals, sorted by R@10 (gte-qwen2 excluded):</p><table><thead><tr><th>Embed</th><th>Description</th><th>P@3</th><th>P@5</th><th>R@10</th></tr></thead><tbody><tr><td><strong>multilingual-e5-large</strong></td><td><strong>gemini-3.1-flash-lite</strong></td><td>60.0%</td><td>44.0%</td><td><strong>93.3%</strong> ⭐</td></tr><tr><td>jina-v3</td><td>claude-haiku-4-5</td><td>33.3%</td><td>32.0%</td><td>86.7%</td></tr><tr><td>bge-m3</td><td>gemini-3.1-flash-lite</td><td>60.0%</td><td>40.0%</td><td>80.0%</td></tr><tr><td>qwen3-0.6b</td><td>gemini-3.1-flash-lite</td><td>60.0%</td><td>44.0%</td><td>80.0%</td></tr><tr><td>bge-large-zh-v1.5</td><td>glm-5v-turbo</td><td>46.7%</td><td>36.0%</td><td>80.0%</td></tr><tr><td>bge-large-zh-v1.5</td><td>kimi-k2.6</td><td>66.7%</td><td>40.0%</td><td>73.3%</td></tr><tr><td>stella-zh-v3.5</td><td>claude-haiku-4-5</td><td>40.0%</td><td>36.0%</td><td>73.3%</td></tr><tr><td>⚠️ gte-qwen2-1.5b</td><td><em>(all 4 sources)</em></td><td>6.7–26.7%</td><td>8–24%</td><td>20–60%</td></tr></tbody></table><p>⭐ <strong>e5-large + gemini single-path R@10&#x3D;93.3%</strong>, tying the FG-CLIP2 image single-path ceiling — the biggest surprise (e5’s C-MTEB is only 58.8, the weakest of the candidates). But this is a single combo over 15 data points, fully within the ±6.7pp noise band — <strong>most likely partly luck</strong>, and it can’t be taken at face value until GT is expanded.</p><p>The most jarring number is in the last row — <strong>bge-m3 + claude-haiku has a P@3 of only 26.7%, the worst of all</strong>. Yet in the previous section, claude-haiku-4-5 was the “highest human-readability” description model — avg 298 chars, smallest variance, most perfectly structured (the four-part “shape &amp; structure &#x2F; material &amp; texture &#x2F; color &amp; tone &#x2F; fine details”).</p><p>Pulling out the average performance of the four description sources across the six embedding models makes it clearer:</p><table><thead><tr><th>Description source</th><th>Human-readability rank</th><th>Avg P@3</th><th>Avg R@10</th><th>Length</th></tr></thead><tbody><tr><td><strong>gemini-3.1-flash-lite</strong></td><td>#4 (shortest)</td><td><strong>58.9%</strong></td><td><strong>76.7%</strong></td><td>avg 165</td></tr><tr><td><strong>kimi-k2.6</strong></td><td>#2</td><td>56.7%</td><td>70.0%</td><td>avg 245</td></tr><tr><td>glm-5v-turbo</td><td>#3</td><td>48.9%</td><td>65.6%</td><td>avg 246</td></tr><tr><td><strong>claude-haiku-4-5</strong></td><td><strong>#1 (most detailed)</strong></td><td><strong>36.7% (last)</strong></td><td>68.9%</td><td>avg 298</td></tr></tbody></table><h4 id="Human-Readable-Doesn’t-Mean-Embedding-Friendly"><a href="#Human-Readable-Doesn’t-Mean-Embedding-Friendly" class="headerlink" title="Human-Readable Doesn’t Mean Embedding-Friendly"></a>Human-Readable Doesn’t Mean Embedding-Friendly</h4><p>The description ranked #1 for human readability ranks #4 (dead last) for embedding-retrieval P@3; the shortest description, ranked #4 for human readability, ranks #1 for embedding retrieval.</p><p>Three likely reasons:</p><ol><li><strong>Too long → key tokens get truncated.</strong> bge-large-zh’s context is only 512 tokens; a 298-char Chinese description likely gets truncated, with the key info lost at the tail.</li><li><strong>Boilerplate dilutes the semantic keywords.</strong> Template words like “shape &amp; structure &#x2F; material &amp; texture &#x2F; color &amp; tone &#x2F; fine details” appear in every description — to the embedding they’re noise: they don’t distinguish asset A from asset B, yet they occupy vector space.</li><li><strong>CLS pooling is sensitive to long text → the gist vector gets averaged out.</strong> The longer the text, the more the CLS token has to encode, which actually dilutes the most critical semantic features (“inn,” “worn-out,” “moss”). gemini-3.1-flash-lite’s descriptions are the shortest (avg 165 chars), letting the embedding focus on the core semantic keywords — <strong>less is more</strong>.</li></ol><h4 id="The-Choice-of-Description-Source-Matters-More-Than-the-Embedding-Model"><a href="#The-Choice-of-Description-Source-Matters-More-Than-the-Embedding-Model" class="headerlink" title="The Choice of Description Source Matters More Than the Embedding Model"></a>The Choice of Description Source Matters More Than the Embedding Model</h4><p>This finding has an important corollary: <strong>swapping the description source on the same embedding model can shift P@3 by 20–40 points; swapping the embedding model on the same description usually differs by only ±7pp.</strong></p><p>In other words, the description source is the lever, the embedding model is the fine-tune. <strong>Investment should go first into iterating the description source (prompt engineering, description cleaning, mixing multiple models’ descriptions)</strong>; which embedding model you pick barely matters — look at the per-model averages:</p><table><thead><tr><th>Embed model</th><th>Avg P@3</th><th>Avg P@5</th><th>Avg R@10</th><th>Verdict</th></tr></thead><tbody><tr><td><strong>qwen3-embedding-0.6b</strong></td><td><strong>55.0%</strong></td><td>40.0%</td><td>71.7%</td><td>⭐ best overall</td></tr><tr><td>bge-large-zh-v1.5</td><td>53.3%</td><td>37.0%</td><td><strong>73.3%</strong></td><td>highest avg R@10</td></tr><tr><td>multilingual-e5-large</td><td>51.7%</td><td>35.0%</td><td>70.0%</td><td>upper-middle</td></tr><tr><td>stella-zh-v3.5</td><td>50.0%</td><td>38.0%</td><td>70.0%</td><td>middle</td></tr><tr><td>jina-v3</td><td>45.0%</td><td>33.0%</td><td>68.3%</td><td>middle</td></tr><tr><td>bge-m3</td><td>46.7%</td><td>35.0%</td><td>68.3%</td><td>middle</td></tr></tbody></table><h4 id="None-of-the-7-Models-Pulls-Clearly-Ahead-on-Average"><a href="#None-of-the-7-Models-Pulls-Clearly-Ahead-on-Average" class="headerlink" title="None of the 7 Models Pulls Clearly Ahead on Average"></a>None of the 7 Models Pulls Clearly Ahead on Average</h4><p>All fall within the GT noise band (±7pp). Note that the C-MTEB ranking (stella 68.6 ≫ e5 58.8) is <strong>badly inconsistent</strong> with the in-domain measurements (e5 ≈ stella ≈ 70%), once again proving “a general benchmark ≠ your domain” — selection must be based on your own in-domain GT.</p><h4 id="Fusion-Twin-Swords-United-1-1-2"><a href="#Fusion-Twin-Swords-United-1-1-2" class="headerlink" title="Fusion: Twin Swords United, 1+1 &gt; 2"></a>Fusion: Twin Swords United, 1+1 &gt; 2</h4><p>The highest single-path text-embedding R@10 reached 93.3% (e5-large + gemini, tying FG-CLIP2), but that’s a single combo over 15 data points, within the noise band — you can’t conclude from it that text has caught up with vision. The more robust judgment: on average, single-path text R@10 sits in the 68–73% range; <strong>text can’t replace vision, but it can supplement it</strong>. After fusing the visual and text paths with RRF (Reciprocal Rank Fusion, k&#x3D;60):</p><table><thead><tr><th>Approach</th><th>P@5</th><th>R@5</th><th>R@10</th></tr></thead><tbody><tr><td>FG-CLIP2 (visual only)</td><td>48.0%</td><td>80.0%</td><td>93.3%</td></tr><tr><td><strong>RRF fusion (best combo)</strong> ⭐</td><td><strong>52.0%</strong></td><td><strong>86.7%</strong></td><td>93.3%</td></tr><tr><td>R@10 is already at the ceiling (5 queries × 3 relevant &#x3D; 15 data points, FG-CLIP2 already hit 14), and fusion couldn’t dig out the last one. But <strong>P@5 went from 48% → 52% (+4pp), R@5 from 80% → 86.7% (+6.7pp)</strong> — correct results got ranked higher.</td><td></td><td></td><td></td></tr><tr><td>For the user experience, this means results that used to sit at positions 6–8 are now pushed into the Top 5 — from “needs a page-flip” to “seen at a glance.”</td><td></td><td></td><td></td></tr><tr><td>The best fusion combo is <strong>FG-CLIP2 + qwen3-0.6b + gemini-flash-lite descriptions</strong>, which is what I’m currently using.</td><td></td><td></td><td></td></tr></tbody></table><h4 id="Selection-Conclusion"><a href="#Selection-Conclusion" class="headerlink" title="Selection Conclusion"></a>Selection Conclusion</h4><table><thead><tr><th>Component</th><th>Choice</th><th>Key reason</th></tr></thead><tbody><tr><td>Visual encoder</td><td>FG-CLIP 2 large</td><td>natively bilingual, R@10&#x3D;93.3%, P@3&#x3D;73.3%, 108ms&#x2F;image</td></tr><tr><td>Text encoder</td><td>Qwen3-Embedding-0.6B</td><td>best overall (avg P@3&#x3D;55.0%), lightweight, 32K context</td></tr><tr><td>Description generation</td><td>gemini-3.1-flash-lite</td><td>¥0.026&#x2F;img, best embedding-retrieval results, ~¥2,000+ for a 100k-scale run</td></tr><tr><td>Fusion strategy</td><td>RRF (k&#x3D;60)</td><td>zero-tuning, ship-ready, P@5 +4pp, R@5 +6.7pp</td></tr></tbody></table><blockquote><p><strong>In one sentence</strong>: Chinese capability is a hard gate, not a bonus; the description model and the retrieval model must be selected separately, with the embedding benchmark as the standard; academic leaderboards are reference-only — you must benchmark on your own data.</p></blockquote><blockquote><p><strong>📒 While we’re at it, let’s tally the one-time landing costs.</strong> Beyond the description generation that keeps coming up (gemini ¥0.026&#x2F;img, ~<strong>¥2,600</strong> for 100k, ~8+ hours), don’t forget a few one-time costs when you actually roll out — <strong>full thumbnail export ≈ 33 hours</strong> (CPU single-thread, a one-time overnight run, incremental afterward), <strong>visual encoding ≈ 3.3 hours</strong>, <strong>text encoding ≈ 20 minutes</strong> (all on a single 4060 Ti). Plus the artists’ later incremental annotation, but that’s optional labor amortized “as you use, you annotate.” All told, the only real money is the bit of API cost for description generation; local encoding is all just electricity — which is why I dared to roll out across 100k-scale assets in one shot.</p></blockquote><h2 id="5-Forging-a-Handy-Weapon-NeuroBrowser-for-Carbon-Based-Lifeforms"><a href="#5-Forging-a-Handy-Weapon-NeuroBrowser-for-Carbon-Based-Lifeforms" class="headerlink" title="5. Forging a Handy Weapon: NeuroBrowser (for Carbon-Based Lifeforms)"></a>5. Forging a Handy Weapon: NeuroBrowser (for Carbon-Based Lifeforms)</h2><p>This weapon is <strong>NeuroBrowser</strong> — an asset-search panel of mine that runs inside the UE4 editor. It embeds directly as a Slate plugin panel, sitting side by side with the Content Browser (mimicking the native Content Browser experience). An out-of-the-box experience for artists.</p><h3 id="Design-Philosophy-Three-Layer-Retrieval-Progressive-Presentation"><a href="#Design-Philosophy-Three-Layer-Retrieval-Progressive-Presentation" class="headerlink" title="Design Philosophy: Three-Layer Retrieval, Progressive Presentation"></a>Design Philosophy: Three-Layer Retrieval, Progressive Presentation</h3><p>Back to the three distances raised in [[#2. The Distance Between Assets and Understanding]], NeuroBrowser’s retrieval architecture is also three-layered:</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></pre></td><td class="code"><pre><span class="line">flowchart TB</span><br><span class="line">    Q[&quot;👤 User query&quot;] --&gt; SQL[&quot;📋 SQL search&lt;br/&gt;FTS5 + tag filter&lt;br/&gt;&amp;lt; 5ms&quot;]</span><br><span class="line">    Q --&gt; VIS[&quot;👁️ Visual search&lt;br/&gt;FG-CLIP-2 large&lt;br/&gt;~30ms&quot;]</span><br><span class="line">    Q --&gt; DESC[&quot;📝 Description search&lt;br/&gt;Qwen3-Embedding-0.6B&lt;br/&gt;~30ms&quot;]</span><br><span class="line">    SQL --&gt; RRF[&quot;🔄 RRF fusion ranking&quot;]</span><br><span class="line">    VIS --&gt; RRF</span><br><span class="line">    DESC --&gt; RRF</span><br><span class="line">    RRF --&gt; RESULT[&quot;⚡ Instant results &amp;lt; 100ms&quot;]</span><br><span class="line">    RRF --&gt; LLM[&quot;🤖 LLM Re-rank&lt;br/&gt;300-600ms&quot;]</span><br><span class="line">    LLM --&gt; REFRESH[&quot;🔄 Re-rank refresh&quot;]</span><br><span class="line"></span><br><span class="line">    style Q fill:#4a90d9,color:#fff</span><br><span class="line">    style RRF fill:#e8793a,color:#fff</span><br><span class="line">    style RESULT fill:#50b86c,color:#fff</span><br><span class="line">    style LLM fill:#9b59b6,color:#fff</span><br></pre></td></tr></table></figure><p><strong>Layer 1: SQL search</strong> (&lt; 5ms). FTS5 full-text + tag filtering + numeric ranges (tri-count, LOD, material count) + logical combinations (AND&#x2F;OR&#x2F;NOT). This layer depends on no AI model and is the safety net — even if the Python backend hasn’t started and no model is loaded, keyword search still works, and it’s 50×+ faster than UE4 native.</p><p><strong>Layer 2: Vector search</strong> (&lt; 30ms). Visual (FG-CLIP-2) and description (Qwen3-Embedding-0.6B) retrieve independently, each returning Top-K, then fused and ranked via RRF. The three path weights are user-adjustable — for “mossy rock” the visual weight is high, for “worn-out object in a corner” the description weight is high, for “big rock with tri-count &lt; 5000” the SQL weight is high.</p><p><strong>Layer 3: LLM re-rank</strong> (300–600ms, async in the background). See the next section.</p><h3 id="About-This-Plugin’s-Design"><a href="#About-This-Plugin’s-Design" class="headerlink" title="About This Plugin’s Design"></a>About This Plugin’s Design</h3><p><strong>1. Heavy local, light server.</strong> All indexes (SQLite + FAISS) and models run on the artist’s local machine. Search works fully offline, even with no network. The Python backend (FastAPI) runs on local localhost, depending on no cloud service. The only thing that goes through the cloud is team sync of descriptions&#x2F;tags — when collaborating, the descriptions you edit are visible to others. Pure-local operation is also supported, so if a user has local data updates, the database can be refreshed quickly.</p><p><strong>2. No engine source modification.</strong> The whole plugin is built on UE 4.27.2’s public Slate APIs — thumbnails via <code>FAssetThumbnailPool</code>, drag-and-drop via <code>FAssetDragDropOp</code>, asset queries via <code>IAssetRegistry</code>. It never touches the engine’s Private directory and never forks the engine, so future upgrades are painless. It asks little of the engine interface, so it can support more engine versions.</p><p><strong>3. Asset-family folding — 100k files collapse to tens of thousands.</strong> The same asset often has a PC version + Mobile version (<code>_ios</code>) + LOD variants + material variants (<code>_01a/_01b</code>). If everything is shown flat, the same logical asset appears 5–8 times in the results — that’s not “found it,” that’s “blew up the search.” NeuroBrowser aggregates by “asset family”: <code>_01a/_01b</code> material variants fold into sub-items, <code>_01/_02</code> different meshes show separately, <code>_ios</code> mobile hides behind the PC version. ~100k asset files → tens of thousands of asset families, results an order of magnitude cleaner.</p><p><strong>4. Tag semantics are config-driven, easy to modify.</strong> Filenames encode a lot of project conventions — region&#x2F;scene markers, technical-variant suffixes, and so on — dozens of scene markers plus a dozen-plus technical-variant suffixes, hundreds of rules in total. This logic is itself semantic, and once aligned with the artists it becomes part of the searchable content. Moreover, all these rules are managed via the <code>tag_rules.json</code> config file, not hard-coded. Artists&#x2F;TAs can add rules themselves, without changing code and recompiling.</p><p><strong>5. Descriptions are alive, not dead.</strong> The LLM-generated description is only an initial value — artists can edit the description and tags directly in the asset detail panel. After editing, the old embedding is automatically marked stale, and a triple background-update mechanism (lazy + scheduled + manual trigger) rebuilds it asynchronously. Descriptions&#x2F;tags sync across the team via the cloud — the description you edit is available to a colleague on their next search.</p><p><strong>6. A usage-analytics loop.</strong> NeuroBrowser has a built-in <code>NeuroBrowserAnalytics</code> module — tab focus, drag-to-viewport, viewport-placement attribution, all logged asynchronously to JSONL. A companion Plotly dashboard shows “which assets are searched most,” “which searches return nothing,” “which assets users drag into scenes most.” This data is the basis for later tuning of search weights and filling in description blind spots.</p><p><strong>7. Multiple search modes.</strong> Users can adjust each path’s retrieval weight, and refine with logic (filter by tags, filter by model poly-count, etc.).</p><p><strong>8. The virtuous cycle — back to the pit we dug at the start.</strong> Remember what I said at the start: for AI to understand assets, someone has to teach it; but artists have no obligation to annotate just for you. My solution isn’t “force artists to annotate,” but to <strong>embed annotation into the search actions they were going to perform anyway</strong> — while searching for and using assets, an artist can casually fix a description or add a tag. These edits sync in real time to the whole team via the <strong>cloud shared database</strong>: the description you correct today directly benefits a colleague’s search tomorrow. The more it’s used → the more accurate the descriptions&#x2F;tags → the more accurate the search → the more people want to use it — that’s the “virtuous cycle” promised at the start, and now it’s truly spinning.</p><h3 id="Feature-Demo"><a href="#Feature-Demo" class="headerlink" title="Feature Demo"></a>Feature Demo</h3><div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); gap: 16px; align-items: start; margin: 24px 0;">  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260628-203526.gif" alt="Standard search" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Standard search</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260628-203527.gif" alt="Image-based search" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Image-based search</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260628-203528.gif" alt="Rule-based search" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Rule-based search</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260628-203529.gif" alt="Cloud-shared description & tag library" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Cloud-shared description / tag library: the description you edit is available to a colleague on their next search</figcaption>  </figure></div><h2 id="6-Forging-a-Less-Handy-Weapon-MCP-CLI-for-Silicon-Based-Lifeforms"><a href="#6-Forging-a-Less-Handy-Weapon-MCP-CLI-for-Silicon-Based-Lifeforms" class="headerlink" title="6. Forging a Less-Handy Weapon: MCP &amp; CLI (for Silicon-Based Lifeforms)"></a>6. Forging a Less-Handy Weapon: MCP &amp; CLI (for Silicon-Based Lifeforms)</h2><p>Remember why we set out to do this?<br>We want to teach AI to recognize and understand assets. Now we have image semantics, text descriptions, and a big pile of semantic tags — enough to let AI understand the specific purpose of each asset. We even expose thumbnails to multimodal models, so if it wants to carefully compare two models, it can even spin up a sub-agent to look at the images and compare two assets in detail. (Of course, more exposed interfaces isn’t always better for the LLM — but that’s another story.)</p><div style="display: flex; flex-wrap: wrap; gap: 16px; justify-content: center; margin: 24px 0;">  <figure style="flex: 1 1 280px; max-width: 600px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260628-020813.png" alt="AI–human collaboration" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Agent invocation</figcaption>  </figure></div><h2 id="Afterword"><a href="#Afterword" class="headerlink" title="Afterword"></a>Afterword</h2><p>To master this kung-fu manual, you must reach the state of “person and weapon as one” with your weapon (AI) — so please download this document and show it to your artificial “idiot” too. Say “get me one of these too,” and you might just get the same tool.</p><blockquote><p><strong>Coming next</strong>: Form Two will focus on «how AI understands scenes» (a pit I dug myself — if I can’t pull it off, I might just switch to a different move).</p></blockquote><hr><p>[^1]: FatemiJahromi, S. A. (2024). <em>Enhancing 3D Asset Retrieval with Semantic Search</em>. Aalto University. <a href="https://aaltodoc.aalto.fi/items/f1f67c23-221b-4d9a-bec3-de68e3da7656">link</a><br>[^2]: Radford, A. et al. (2021). <em>Learning Transferable Visual Models From Natural Language Supervision</em>. OpenAI. CLIP uses contrastive learning to put images and text in a shared vector space — the foundational capability of this approach.</p>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/SoftwareProjects/">SoftwareProjects</category>
      
      <category domain="https://eugenepage.com/tags/AI/">AI</category>
      
      <category domain="https://eugenepage.com/tags/RAG/">RAG</category>
      
      <category domain="https://eugenepage.com/tags/GameDev/">GameDev</category>
      
      <category domain="https://eugenepage.com/tags/CLIP/">CLIP</category>
      
      <category domain="https://eugenepage.com/tags/SemanticSearch/">SemanticSearch</category>
      
      <category domain="https://eugenepage.com/tags/Embedding/">Embedding</category>
      
      <category domain="https://eugenepage.com/tags/AssetManagement/">AssetManagement</category>
      
      <category domain="https://eugenepage.com/tags/UE4/">UE4</category>
      
      <category domain="https://eugenepage.com/tags/Pipeline/">Pipeline</category>
      
      
      <comments>https://eugenepage.com/2026/06/01/20260602.AIGameSeries-Article1/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>游戏生产AI落地武林秘籍（第一式：手到擒来）：让AI理解项目资产</title>
      <link>https://eugenepage.com/zh-CN/2026/06/01/20260602.AIGameSeries-Article1/</link>
      <guid>https://eugenepage.com/zh-CN/2026/06/01/20260602.AIGameSeries-Article1/</guid>
      <pubDate>Mon, 01 Jun 2026 16:00:00 GMT</pubDate>
      
        
        
      <description>&lt;h1 id=&quot;游戏生产AI落地武林秘籍·第一式「手到擒来」：让-AI-理解项目资产&quot;&gt;&lt;a href=&quot;#游戏生产AI落地武林秘籍·第一式「手到擒来」：让-AI-理解项目资产&quot; class=&quot;headerlink&quot; title=&quot;游戏生产AI落地武林秘籍·第一式「手到擒来」：让</description>
        
      
      
      
      <content:encoded><![CDATA[<h1 id="游戏生产AI落地武林秘籍·第一式「手到擒来」：让-AI-理解项目资产"><a href="#游戏生产AI落地武林秘籍·第一式「手到擒来」：让-AI-理解项目资产" class="headerlink" title="游戏生产AI落地武林秘籍·第一式「手到擒来」：让 AI 理解项目资产"></a>游戏生产AI落地武林秘籍·第一式「手到擒来」：让 AI 理解项目资产</h1><blockquote><p><strong>系列导读</strong>：「游戏生产AI落地武林秘籍」是一个面向游戏技术美术（TA）和工具开发者的实践系列，记录我来时路，都是我的血泪史。</p></blockquote><hr><h2 id="一、缘起：不让AI两眼一抹黑"><a href="#一、缘起：不让AI两眼一抹黑" class="headerlink" title="一、缘起：不让AI两眼一抹黑"></a>一、缘起：不让AI两眼一抹黑</h2><p>我们的项目是一个大型开放世界游戏，资产库规模在 <strong>十万级</strong>，涵盖乔木、岩石、灌木、建筑等十多个大类，每个资产还有移动端变体、LOD、Impostor、子资源。</p><p>要让 AI 参与游戏生产，它首先得”认识”这个项目里有什么。但现实是——</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></pre></td><td class="code"><pre><span class="line">&quot;放一棵江南风格的矮灌木&quot;</span><br><span class="line">  → AI 看到的是 shrub_a1_01a、shrub_a1_02b</span><br><span class="line">  → 哪个是矮的？哪个是应该用在这个场景的？不知道。</span><br></pre></td></tr></table></figure><p>人不比 AI 好多少。美术找资产同样靠翻文件夹、猜文件名、问同事，凭借记忆去检索。<br>芬兰 Aalto 大学 2024 年的硕士论文[^1]记录了同样的困境：HypeHype 平台 5000+ 资产库中，搜”forest”找不到描述里写”woods”的资产；</p><p>所以，<strong>让资产变得可被理解</strong>，是 AI 参与生产的前提。<br>但，AI 理解人的东西，是需要人来教的（标注）。<br>这些标注是一个很耗时的事情，美术并没有义务来协助你做这件事情。<br>所以我还需要一个 SOP，让美术边搜东西，边标注资产。试图以此形成一个正向螺旋。</p><hr><h2 id="二、资产与理解的距离"><a href="#二、资产与理解的距离" class="headerlink" title="二、资产与理解的距离"></a>二、资产与理解的距离</h2><p>实际上，不管是人还是AI，要理解一个资产，都存在<strong>三种不同的距离</strong>。</p><ol><li>这个资产代号是什么？（语义）</li><li>这个资产长什么样？（视觉）</li><li>这个资产是干什么用的？用在哪里？怎么用？（逻辑）</li></ol><p><em>BTW：大家用绝大多数优秀的搜索能力，都能检索到和这个文章相关的一切字符，即使这个字符可能只是文档中的一个备注，但是你知道，脑子就是这样，你可能只记得角落里的某个触动你的“刺点（罗兰巴特《明室》）”，可就是想不起这个东西的标题。我所知道的所有游戏引擎的搜索框就是这样，只能搜索到文件名，搜索不到文件里某个蓝图备注，也不支持更复杂的搜索逻辑。</em><br><em>这是产品设计的问题，这还不是“距离”。一个优秀的搜索框，应该像Everything一样（是的，我在实名表扬它），能搜索这个系统里的一切！所以，我们的搜索能力，理论上应该能用印象去模糊地检索到这个资产，这也是我要做的。</em></p><p><strong>第一层：语义距离。</strong> 文件名、路径、标签里的字面匹配——搜”rock”能找到 <code>rock_granite_01</code>，但搜”石头”或是“岩石”就搜索不到。即使这几个字的意思几乎很接近，又或只是中英文的区别。</p><p><strong>第二层：视觉距离。</strong> 想要表达某种特定形式的建筑，但是奈何语文老师去世的早，无法精确地表述出“庑殿”、“闇栔”、“甍”、“甓”这样的词语，但你知道，就是那个东西！此时语言的贫瘠竟是如此的真实。</p><p><strong>第三层：逻辑距离。</strong> 搜”可以放在角落的破旧物件”——这不是在描述外观，而是在描述<strong>用途和状态</strong>。视觉检索帮不了这个忙。这里面是包含一定的推理思考，比如你怎么知道这个家里的角落里可以放一个扫帚，而不是放一把杀猪刀，这是需要理解需求，并从需求出发去思考的。</p><p>三层各有盲区，也各有强项，需要应用在不同的场景。</p><hr><h2 id="三、框架心法"><a href="#三、框架心法" class="headerlink" title="三、框架心法"></a>三、框架心法</h2><div class="callout" data-callout="abstract" style="--callout-color: 0, 176, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/></svg><span class="callout-title-inner">架构总览</span></div><div class="callout-content"><div class="canvas-embed" data-canvas-slug="attachments/Canvas/20260602.AIGameSeries-Architecture"><svg xmlns="http://www.w3.org/2000/svg" class="canvas-svg" data-canvas-revision="7ff889a3b3fe" width="192" height="280" viewBox="-380 -1080 1620 2360" preserveAspectRatio="xMidYMid meet" role="img" aria-label="20260602.AIGameSeries-Architecture"><title>20260602.AIGameSeries-Architecture</title><defs><marker id="canvas-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" /></marker></defs><g class="canvas-groups"><g class="canvas-node canvas-node--group" data-id="a100000000000020" data-x="-320" data-y="200" data-width="1440" data-height="680" data-color="4"><rect class="canvas-group__bg" x="-320" y="200" width="1440" height="680" rx="12" /><text class="canvas-group__label" x="-308" y="222">⚡ 语义检索层（100 - 300ms）— FastAPI 后端</text></g><g class="canvas-node canvas-node--group" data-id="a100000000000010" data-x="-40" data-y="-1040" data-width="500" data-height="540" data-color="6"><rect class="canvas-group__bg" x="-40" y="-1040" width="500" height="540" rx="12" /><text class="canvas-group__label" x="-28" y="-1018">用户入口</text></g><g class="canvas-node canvas-node--group" data-id="2269b9ba802f3f5d" data-x="-340" data-y="-420" data-width="350" data-height="380" data-color="3"><rect class="canvas-group__bg" x="-340" y="-420" width="350" height="380" rx="12" /><text class="canvas-group__label" x="-328" y="-398">基础检索层（&lt; 100ms）</text></g><g class="canvas-node canvas-node--group" data-id="a100000000000030" data-x="100" data-y="1080" data-width="600" data-height="160" data-color="2"><rect class="canvas-group__bg" x="100" y="1080" width="600" height="160" rx="12" /><text class="canvas-group__label" x="112" y="1102">🎯 精排层（300-2000ms） CLI / MCP调用</text></g></g><g class="canvas-edges"><g class="canvas-edge-group" data-id="e1000000000000001" data-from-node="a100000000000011" data-to-node="a100000000000012" data-from-side="right" data-to-side="left" data-color="6"><path class="canvas-edge" d="M 360 -973 C 506.8181036369683 -973, -66.81810363696829 -633, 80 -633" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="220" y="-803" text-anchor="middle">查询</text></g><g class="canvas-edge-group" data-id="e1000000000000002" data-from-node="a100000000000012" data-to-node="a100000000000021" data-from-side="bottom" data-to-side="top" data-color="4"><path class="canvas-edge" d="M 220 -586 C 220 -412.21341312466666, -170 -413.78658687533334, -170 -240" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="25" y="-413" text-anchor="middle">SQL</text></g><g class="canvas-edge-group" data-id="e1000000000000003" data-from-node="a100000000000012" data-to-node="a100000000000022" data-from-side="bottom" data-to-side="top" data-color="4"><path class="canvas-edge" d="M 220 -586 C 220 -279.52977306106874, -140 -46.47022693893126, -140 260" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="40" y="-163" text-anchor="middle">视觉</text></g><g class="canvas-edge-group" data-id="e1000000000000004" data-from-node="a100000000000012" data-to-node="a100000000000023" data-from-side="bottom" data-to-side="top" data-color="4"><path class="canvas-edge" d="M 220 -586 C 220 -222.09891453857966, 910 -103.90108546142034, 910 260" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="565" y="-163" text-anchor="middle">描述</text></g><g class="canvas-edge-group" data-id="e1000000000000005" data-from-node="a100000000000021" data-to-node="a100000000000024" data-from-side="bottom" data-to-side="top" data-color="4"><path class="canvas-edge" d="M -170 -126 C -170 154.7244596080331, 180 359.2755403919669, 180 640" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="5" y="257" text-anchor="middle">SQL 结果</text></g><g class="canvas-edge-group" data-id="e1000000000000006" data-from-node="a100000000000022" data-to-node="a100000000000024" data-from-side="bottom" data-to-side="top" data-color="4"><path class="canvas-edge" d="M -140 354 C -140 497.06020488669174, 180 496.93979511330826, 180 640" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="20" y="497" text-anchor="middle">视觉结果</text></g><g class="canvas-edge-group" data-id="e1000000000000007" data-from-node="a100000000000023" data-to-node="a100000000000024" data-from-side="bottom" data-to-side="top" data-color="4"><path class="canvas-edge" d="M 910 354 C 910 615.3418365963544, 180 378.6581634036457, 180 640" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="545" y="497" text-anchor="middle">描述结果</text></g><g class="canvas-edge-group" data-id="e1000000000000008" data-from-node="a100000000000024" data-to-node="a100000000000012" data-from-side="right" data-to-side="right" data-color="4"><path class="canvas-edge" d="M 380 687 C 820.0505021522467 687, 800.0505021522467 -633, 360 -633" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="370" y="27" text-anchor="middle">&lt; 100ms 即时返回</text></g><g class="canvas-edge-group" data-id="e1000000000000009" data-from-node="a100000000000024" data-to-node="a100000000000031" data-from-side="bottom" data-to-side="top" data-color="2"><path class="canvas-edge" d="M 180 734 C 180 882.0975654387637, 400 971.9024345612363, 400 1120" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="290" y="927" text-anchor="middle">候选送精排</text></g><g class="canvas-edge-group" data-id="e1000000000000010" data-from-node="a100000000000030" data-to-node="a100000000000012" data-from-side="right" data-to-side="right" data-color="2"><path class="canvas-edge" d="M 700 1160 C 1308.3172600616301 1160, 968.3172600616301 -633, 360 -633" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="530" y="263.5" text-anchor="middle">300-600ms 精排刷新</text></g><g class="canvas-edge-group" data-id="e1000000000000011" data-from-node="a100000000000023" data-to-node="a100000000000060" data-from-side="right" data-to-side="left" data-color="5"><path class="canvas-edge" d="M 1060 307 C 1228.9079302131456 307, 751.0920697868544 -180, 920 -180" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="990" y="63.5" text-anchor="middle">cloud_sync 双向同步</text></g><g class="canvas-edge-group" data-id="243036cbd0cb25d2" data-from-node="a100000000000021" data-to-node="a100000000000060" data-from-side="right" data-to-side="left" data-color="5"><path class="canvas-edge" d="M -20 -183 C 293.33492907395083 -183, 606.6650709260491 -180, 920 -180" fill="none" marker-end="url(#canvas-arrow)" /><text class="canvas-edge__label" x="450" y="-181.5" text-anchor="middle">SQL检索描述信息</text></g></g><g class="canvas-nodes"><g class="canvas-node canvas-node--text" data-id="a100000000000011" data-x="80" data-y="-1000" data-width="280" data-height="54" data-color="6"><rect class="canvas-node__bg" x="80" y="-1000" width="280" height="54" rx="8" /><foreignObject x="80" y="-1000" width="280" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#👤-用户查询" class="headerlink" title="👤 用户查询"></a>👤 用户查询</div><p>文字 &#x2F; 图片 &#x2F; 图文混合 &#x2F; 过滤条件</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000021" data-x="-320" data-y="-240" data-width="300" data-height="114" data-color="3"><rect class="canvas-node__bg" x="-320" y="-240" width="300" height="114" rx="8" /><foreignObject x="-320" y="-240" width="300" height="114"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#📋-SQL-检索" class="headerlink" title="📋 SQL 检索"></a>📋 SQL 检索</div><p><strong>sql_capability.py</strong></p><p>FTS5 全文 + 标签过滤<br>数值范围 + 逻辑组合<br>trigram 中文补充召回</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000022" data-x="-290" data-y="260" data-width="300" data-height="94" data-color="4"><rect class="canvas-node__bg" x="-290" y="260" width="300" height="94" rx="8" /><foreignObject x="-290" y="260" width="300" height="94"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#👁️-视觉检索" class="headerlink" title="👁️ 视觉检索"></a>👁️ 视觉检索</div><p><strong>visual_capability.py</strong></p><p>文字→图片 &#x2F; 图片→图片<br>图文联合 α·img+(1-α)·txt</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000012" data-x="80" data-y="-680" data-width="280" data-height="94" data-color="6"><rect class="canvas-node__bg" x="80" y="-680" width="280" height="94" rx="8" /><foreignObject x="80" y="-680" width="280" height="94"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#🖼️-UE4-Slate-插件" class="headerlink" title="🖼️ UE4 Slate 插件"></a>🖼️ UE4 Slate 插件</div><p>搜索框 + 缩略图网格 + 拖拽<br>路径树 + 标签过滤 + 资产族折叠<br>详情面板 + 权重档位 + Analytics</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000060" data-x="920" data-y="-207" data-width="280" data-height="54" data-color="5"><rect class="canvas-node__bg" x="920" y="-207" width="280" height="54" rx="8" /><foreignObject x="920" y="-207" width="280" height="54"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#☁️-云端数据库" class="headerlink" title="☁️ 云端数据库"></a>☁️ 云端数据库</div><p>描述 &#x2F; 标签 &#x2F; 标签规则</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000023" data-x="760" data-y="260" data-width="300" data-height="94" data-color="4"><rect class="canvas-node__bg" x="760" y="260" width="300" height="94" rx="8" /><foreignObject x="760" y="260" width="300" height="94"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#📝-描述词检索" class="headerlink" title="📝 描述词检索"></a>📝 描述词检索</div><p><strong>desc_capability.py</strong></p><p>全量描述 embedding<br>富文本：类别+标签+描述</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000024" data-x="-20" data-y="640" data-width="400" data-height="94" data-color="4"><rect class="canvas-node__bg" x="-20" y="640" width="400" height="94" rx="8" /><foreignObject x="-20" y="640" width="400" height="94"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#🔄-RRF-融合排序" class="headerlink" title="🔄 RRF 融合排序"></a>🔄 RRF 融合排序</div><p><strong>fusion.py</strong> | k&#x3D;60</p><p>SQL + Visual + Desc → 统一排序<br>三路权重用户可调</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a100000000000031" data-x="140" data-y="1120" data-width="520" data-height="100" data-color="2"><rect class="canvas-node__bg" x="140" y="1120" width="520" height="100" rx="8" /><foreignObject x="140" y="1120" width="520" height="100"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><div class="canvas-node__heading canvas-node__heading--h1"><a href="#🤖-LLM-Re-rank" class="headerlink" title="🤖 LLM Re-rank"></a>🤖 LLM Re-rank</div><p><strong>llm_capability.py</strong></p><p>query + 候选 description → relevance score<br>渐进式：先出即时结果，精排后自动刷新</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="06e5336795590aba" data-x="320" data-y="-40" data-width="260" data-height="60"><rect class="canvas-node__bg" x="320" y="-40" width="260" height="60" rx="8" /><foreignObject x="320" y="-40" width="260" height="60"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"></div></foreignObject></g></g></svg><span class="canvas-embed__expand" aria-hidden="true" title="点击放大">⛶</span></div></div></div><p>先讲一条工具设计上的”心法”：<strong>用户愿意为工具等待，但等得越久、期待就越高。</strong> 搜了 1 分钟却给一个糟糕的结果，是用户最不能接受的。所以整个框架是围绕<strong>响应速度分层</strong>搭起来的——快的先出、慢的后补，绝不让用户干等。</p><p>顺着第二节那三种”距离”，我把检索拆成三层，让每一层各去够一种距离：</p><ul><li><strong>第一层·基础检索（最快，&lt; 100ms）</strong>：在 UE 原生检索逻辑上做优化，靠文件名 &#x2F; 路径 &#x2F; 标签做字面与规则匹配。它是”保底”——不依赖任何 AI 模型也能用。</li><li><strong>第二层·向量检索（即时，&lt; 100ms）</strong>：这一层其实跑着<strong>两条并行的路</strong>——<strong>视觉向量</strong>去够「视觉距离」（搜图找图），<strong>文本 &#x2F; 描述向量</strong>去够「语义距离」（同义词、中英、换个说法都搜得到，补上第一层字面匹配跨不过的那道坎）。两路各自召回，再融合排序。</li><li><strong>第三层·逻辑检索（精排，后台异步）</strong>：用 CLI 与 MCP 把外部 LLM 接进来，去够最难的「逻辑距离」——理解用途、状态、意图这类需要推理的查询。</li></ul><blockquote><p>一句话理顺命名：<strong>三层框架对应三种距离，但「语义距离」是被第一层（字面匹配）和第二层（文本向量）接力补上的</strong>——这也是为什么明明叫”三层框架”，技术上却跑着「SQL + 视觉 + 描述 + LLM」四条路。后面第五节的 NeuroBrowser 就是照这套心法落的地。</p></blockquote><h2 id="四、华山论剑·技术选型"><a href="#四、华山论剑·技术选型" class="headerlink" title="四、华山论剑·技术选型"></a>四、华山论剑·技术选型</h2><p>框架设计好了，接下来就是最关键的问题：<strong>用什么模型？</strong></p><p>这不是一个能靠直觉或论文排行榜回答的问题。游戏渲染图和自然图像之间存在 domain gap——我们的缩略图是引擎内渲染的（天空球 + 多方向光 + 风格化材质），和 CLIP[^2] 训练用的 Flickr &#x2F; LAION 数据集长得完全不一样。学术排行榜上的分数肯定不能直接迁移。</p><p>所以我做了两个 benchmark：<strong>视觉编码器选型</strong>%% （对应第1小节） %%和<strong>描述&#x2F;文本 Embedding 选型</strong>%% （对应第3小节） %%。数据集用的是 100 张项目真实缩略图 + 5 组美术标注的 Ground Truth（每组 1 个中文描述 + 3 张正确图片）。</p><p>另外，描述Embedding是不具有初始数据的，这些数据直接让美术去标注也不是非常合适，所以我直接用大语言模型去生成了最初的所有资产的描述，然后再用这个描述去做文本语义的Embedding（未来新资产入库的时候，也可以考虑使用这套方案，然后美术再基于LLM生成的描述数据去修改），所以还对不同的多模态LLM进行了一波评测%% （对应第2小节） %%。</p><h3 id="1-视觉编码器：14-个模型两轮横评"><a href="#1-视觉编码器：14-个模型两轮横评" class="headerlink" title="1. 视觉编码器：14 个模型两轮横评"></a>1. 视觉编码器：14 个模型两轮横评</h3><p>图像 -&gt; 语义向量</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></pre></td><td class="code"><pre><span class="line">flowchart LR</span><br><span class="line">    A[&quot;🖼️ 资产缩略图&quot;] --&gt; B[&quot;视觉模型&quot;]</span><br><span class="line">    B --&gt; C[&quot;1024-dim 向量&quot;]</span><br><span class="line">    C --&gt; D[&quot;FAISS 检索&quot;]</span><br><span class="line"></span><br><span class="line">    style A fill:#4a90d9,color:#fff</span><br><span class="line">    style B fill:#e8793a,color:#fff</span><br><span class="line">    style C fill:#50b86c,color:#fff</span><br><span class="line">    style D fill:#9b59b6,color:#fff</span><br></pre></td></tr></table></figure><p>第一轮从 Aalto 论文推荐的 4 个模型起步（最小测试），核心发现是：<strong>不支持中文的 CLIP 面对中文 query 完全失效</strong>，Recall@10 只有 13.3%，基本等于随机。而加了 xlm-roberta 多语言文本塔的版本以 66.7% 遥遥领先——5 到 10 倍的差距。</p><p>第二轮按这条线索扩展到 10 个新模型，覆盖 4 个 Tier：2025 SOTA（FG-CLIP 2 系列）、中文原生训练（Chinese-CLIP 系列）、多语言新秀（MEXMA &#x2F; NLLB &#x2F; AltCLIP）、以及第一轮最强方案的家族放大版。</p><p>结果出乎意料：<strong>奇虎 360 的 FG-CLIP 2 large</strong>（0.9B 参数，双语原生）以 P@3&#x3D;73.3%、R@10&#x3D;93.3% 双料夺冠。5 个 query 中有 4 个在 Top-2 就全中正确答案——这是工业可用的水平。比第一轮最强方案提升了 26.6 个百分点。</p><p>更意外的是，FG-CLIP 2 large 反而比参数量更大的 so400m（1.0B）在 P@3 上强 20 个百分点——不是越大越好，训练步数和缩略图尺寸适配可能更关键。</p><p>性价比方面，Chinese-CLIP ViT-L&#x2F;14@336 在 R@10 上与 FG-CLIP 2 large 并列第一（93.3%），维度更小（768 vs 1024）、加载最简单、生态最成熟，是稳健备选。</p><p>所以我最终选择了 FG-CLIP 2 large 作为图像语义的编码器。</p><blockquote><p>⚠️ <strong>先泼一盆冷水</strong>：上面这些百分比都建立在 <strong>5 组 GT × 3 张正确图 &#x3D; 15 个数据点</strong> 上，单个结果错位就是 ±6.7pp 的抖动。所以”73.3% 双料夺冠””比第一轮提升 26.6pp”这种话，方向是对的，但<strong>领先幅度大概率含运气成分</strong>，得等 GT 扩到 30+ 组才能当成定论。这条警告对下面第 3 小节的文本 benchmark 同样成立——别被任何单个高分冲昏头。</p></blockquote><div style="display: flex; flex-wrap: wrap; gap: 16px; justify-content: center; margin: 24px 0;">  <figure style="flex: 1 1 280px; max-width: 360px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260624-000308.png" alt="视觉编码器模型对比" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">视觉编码器模型对比</figcaption>  </figure></div><h3 id="2-LLMs生成评测"><a href="#2-LLMs生成评测" class="headerlink" title="2. LLMs生成评测"></a>2. LLMs生成评测</h3><p>缩略图 → 中文描述</p><p>如上文所述，我需要为文字语义准备第一波初始化文字内容，所以我打算直接用大语言模型去生成了最初的所有资产的描述，然后美术再基于这个文字的基础上进行一些修改，来使其更贴合“生产内容”场景。但是，要对资产进行理解，并生成描述也也不是一个简单的事情，不同LLMs在生成时间、效果、稳定性上都存在差异。为此我也做了一个测试：</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></pre></td><td class="code"><pre><span class="line">flowchart LR</span><br><span class="line">    A[&quot;🖼️ 资产缩略图&quot;] --&gt; B[&quot;多模态 LLM&quot;]</span><br><span class="line">    B --&gt; C[&quot;中文描述文本&quot;]</span><br><span class="line">    C --&gt; D[&quot;文本 Embedding&quot;]</span><br><span class="line"></span><br><span class="line">    style A fill:#4a90d9,color:#fff</span><br><span class="line">    style B fill:#e8793a,color:#fff</span><br><span class="line">    style C fill:#50b86c,color:#fff</span><br><span class="line">    style D fill:#9b59b6,color:#fff</span><br></pre></td></tr></table></figure><p>我拉了 13 个主流多模态 LLM （2026 年 5 月末）对同一批 100 张资产缩略图生成（生成这个缩略图也有讲究，这里最好是一个高分辨，有能力的话，甚至可以考虑多角度缩略图。我这里采用的是2048*2048的单缩略图，且最好不要过曝也不要欠曝）描述，从<strong>成本、速度、质量、稳定性</strong>四个维度横评：</p><table><thead><tr><th>模型</th><th>均长（字）</th><th>min~max</th><th>单张成本</th><th>100张总成本</th><th>纯查询时间</th><th>总耗时</th><th>稳定性</th></tr></thead><tbody><tr><td><strong>kimi-k2.6</strong></td><td>245</td><td>138~483</td><td>¥0.084</td><td>¥8.37</td><td>10.2s</td><td>20.4min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td><strong>gemini-3.1-flash-lite</strong></td><td>165</td><td>73~254</td><td><strong>¥0.026</strong></td><td><strong>¥2.62</strong></td><td><strong>8.6s</strong></td><td>17.6min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>kimi-k2.6-yd</td><td>303</td><td>95~2448</td><td>¥0.053</td><td>¥5.28</td><td>14.5s</td><td>27.5min</td><td>⭐⭐⭐⭐</td></tr><tr><td><strong>claude-haiku-4-5</strong></td><td><strong>298</strong></td><td>186~435</td><td>¥0.104</td><td>¥10.40</td><td>13.3s</td><td>25.5min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>glm-5v-turbo</td><td>246</td><td>136~432</td><td>¥0.121</td><td>¥12.45</td><td>12.9s</td><td>24.8min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>qwen3.5-plus</td><td>154</td><td>7~269</td><td>¥0.030</td><td>¥3.00</td><td>10.4s</td><td>20.6min</td><td>⭐⭐</td></tr><tr><td>qwen3.6-plus</td><td>173</td><td>13~1347</td><td>¥0.130</td><td>¥13.00</td><td>16.2s</td><td>30.3min</td><td>⭐⭐</td></tr><tr><td>gpt-5.2</td><td>199</td><td>106~308</td><td>¥0.181</td><td>¥18.06</td><td>20.9s</td><td>38.1min</td><td>⭐⭐⭐⭐</td></tr><tr><td>claude-opus-4-7</td><td>256</td><td>140~417</td><td>¥0.221</td><td>¥22.11</td><td>19.6s</td><td>36.0min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>claude-sonnet-4-6</td><td>248</td><td>140~412</td><td>¥0.258</td><td>¥25.79</td><td>20.4s</td><td>~37min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>gemini-3.5-flash</td><td>212</td><td>106~452</td><td>¥0.303</td><td>¥30.28</td><td>18.3s</td><td>~33min</td><td>⭐⭐⭐⭐</td></tr><tr><td>gpt-5.4</td><td>242</td><td>147~416</td><td>¥0.567</td><td>¥56.74</td><td>15.4s</td><td>29.0min</td><td>⭐⭐⭐⭐⭐</td></tr><tr><td>mimo-v2.5-free</td><td>172</td><td>73~320</td><td>¥0（免费）</td><td>¥0</td><td>20.6s</td><td>37.6min</td><td>⭐⭐⭐</td></tr></tbody></table><ul><li>评测的时间仅供参考，和不同API提供商也有关系。这仅仅是我的环境。</li><li>另外，LLM的生成成本也和提供的提示词有关。复现的成本也会有区别。</li><li>我在测试的时候使用固定提示词，且每一个图片一个独立地上下文进行的测试。</li></ul><div style="display: flex; flex-wrap: wrap; gap: 16px; justify-content: center; margin: 24px 0;">  <figure style="flex: 1 1 280px; max-width: 600px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260628-010224.png" alt="LLM 生成描述模型评测结果" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">LLM 生成描述模型评测结果</figcaption>  </figure></div><p>几个关键发现：</p><p><strong>1. 成本差距惊人——最便宜和最贵差 20 倍。</strong> gemini-3.1-flash-lite ¥0.026&#x2F;张 vs gpt-5.4 ¥0.567&#x2F;张。按 10 万张全量算，前者 ¥2,607，后者 ¥56,700——差十几张 RTX 4060 Ti 的钱。更意外的是 claude-opus 居然比 sonnet 还便宜（¥0.221 vs ¥0.258），sonnet 竟被 opus 反超。</p><p><strong>2. 描述长度差异巨大，但不是越长越好。</strong> Claude-haiku 平均 298 字最长、方差最小（min 186 ~ max 435），结构化最好（”形状结构 &#x2F; 材质纹理 &#x2F; 颜色色调 &#x2F; 细节特征”四段式）；qwen3.5-plus 仅 154 字最短。但后续 Embedding 检索验证了一个反直觉结论——<strong>对人类最可读的描述 ≠ 对 Embedding 最友好的描述</strong>（详见下节）。</p><p><strong>3. 千问——笑里藏刀的暗器：标签泄漏直接污染 Embedding。</strong> qwen3.5-plus 会泄漏 <code>&lt;skill&gt;</code> 标签（某张图输出 38 字垃圾 <code>skill\nname visual verdict\n/name\n/skill</code>），qwen3.6-plus 会泄漏 <code>&lt;think chain-of-thought</code>（最长 1347 字的推理过程直接写进输出），稳定性只有 ⭐⭐。这些残留标记直接进入描述文本，在后续 Embedding 检索中变成噪声。全量生产前必须加清洗步骤（正则去标签 + 截断到 800 字）。</p><p><strong>4. Kimi-k2.6 是国产首选——0 异常、速度最快。</strong> 10.2s 纯查询时间全场最快，0 异常输出，稳定性满星。如果对 <code>trust_remote_code</code> 有顾虑或需要纯国产方案，kimi-k2.6 是最稳选择。其折扣版 kimi-k2.6-yd 偶发 1 张 2448 字疑似 think 泄漏，加最大字数过滤后可用。</p><p><strong>5. 免费模型可用但偏弱。</strong> Mimo-v2.5-free 零成本但偶发 timeout（120s）和 token 循环异常（30K-44K 陷入循环），水体类识别也有失误，稳定性仅 ⭐⭐⭐。适合做 PoC 验证，不适合全量生产。</p><p><strong>6. 描述质量与渲染图质量正相关。</strong> 渲染图质量越高，描述质量越好。</p><table><thead><tr><th>推荐场景</th><th>首选</th><th>理由</th></tr></thead><tbody><tr><td><strong>超大规模</strong>（10万+）</td><td>gemini-3.1-flash-lite</td><td>¥0.026&#x2F;张 + 8.6s，全场最便宜+最快</td></tr><tr><td><strong>生产首选</strong>（质量与成本平衡）</td><td>kimi-k2.6</td><td>¥0.084&#x2F;张 + 10.2s，国产稳定 0 异常</td></tr><tr><td><strong>描述详尽</strong></td><td>claude-haiku-4-5</td><td>avg 298 字最长、方差最小</td></tr><tr><td><strong>国产+中文友好</strong></td><td>glm-5v-turbo &#x2F; kimi-k2.6</td><td>两者 0 异常 0 失败</td></tr><tr><td><strong>天花板对照</strong></td><td>claude-opus-4-7</td><td>比 sonnet 便宜，0 异常</td></tr><tr><td><strong>避坑</strong></td><td>qwen3.5&#x2F;3.6-plus</td><td>think&#x2F;skill 标签泄漏，进 embedding 会污染检索</td></tr><tr><td>这些生成的语义是为了给Embedding模型用的，所以有请本轮的胜者（gemini-3.1-flash-lite、Kimi-k2.6、glm-5v-turbo、claude-haiku-4-5）进下一章节</td><td></td><td></td></tr></tbody></table><h3 id="3-文本-Embedding"><a href="#3-文本-Embedding" class="headerlink" title="3. 文本 Embedding"></a>3. 文本 Embedding</h3><p>视觉编码解决了”搜图找图”的问题，但这种搜索能力还是太浅层了。<br>如果要让AI深度理解这款游戏的资产，更重要的是突破表层的图像语义，了解这个资产属于谁？它意味着什么？</p><p>而这些内容，是真正地需要美术去一点点教会AI的，并且也是最”落地”的，那怎么去教呢？</p><p>我的答案就是——<strong>让 AI 先给每个资产写一段描述，再对描述做语义检索（日后美术可以针对这一段描述进行修改，然后重新embedding进向量库）</strong>。<br>所以我请上轮的4个玩家又做了一个交叉评测：<strong>7 个文本 Embedding 模型 × 4 个描述源 &#x3D; 28 种组合</strong>，加上 FG-CLIP2 视觉 baseline + RRF 融合，总共 53 组实验。</p><p>七个 Embedding 候选（均为我环境里 8GB 4060Ti bf16 跑得动的模型；Qwen3-Embedding-4B&#x2F;8B 因 bf16 权重 ≥8GB，排除）：</p><table><thead><tr><th>模型</th><th>参数</th><th>维度</th><th>pooling</th><th>C-MTEB</th><th>query&#x2F;doc 处理</th></tr></thead><tbody><tr><td>BAAI&#x2F;bge-m3</td><td>0.57B</td><td>1024</td><td>cls</td><td>64.5</td><td>无前缀</td></tr><tr><td>BAAI&#x2F;bge-large-zh-v1.5</td><td>0.33B</td><td>1024</td><td>cls</td><td>64.5</td><td>无前缀</td></tr><tr><td>Qwen&#x2F;Qwen3-Embedding-0.6B</td><td>0.6B</td><td>1024</td><td>last-token</td><td>72.0</td><td>无前缀</td></tr><tr><td>multilingual-e5-large</td><td>0.56B</td><td>1024</td><td>mean</td><td>58.8</td><td>query 加 <code>query:</code> &#x2F; doc 加 <code>passage:</code></td></tr><tr><td>stella-mrl-large-zh-v3.5-1792d</td><td>0.56B</td><td>1792</td><td>mean + Dense</td><td>68.6</td><td>mean pool 后套官方 <code>2_Dense</code>(1024→1792)；无前缀</td></tr><tr><td>jina-embeddings-v3</td><td>0.57B</td><td>1024</td><td>mean + task-LoRA</td><td>~64</td><td><code>retrieval.query</code>&#x2F;<code>retrieval.passage</code> adapter_mask；无文本前缀</td></tr><tr><td>gte-Qwen2-1.5B-instruct</td><td>1.5B</td><td>1536</td><td>last-token</td><td>67.7</td><td>⚠️ 结果无效（见下）</td></tr></tbody></table><p>四个描述源从上节 13 个模型中选出：claude-haiku-4-5（人类阅读质量最高）、kimi-k2.6（生产首选）、glm-5v-turbo（国产稳定）、gemini-3.1-flash-lite（性价比之王）。</p><blockquote><p>⚠️ <strong>gte-Qwen2-1.5B 结果不可用，已从排名排除</strong>。其 2024 版自定义 <code>modeling_qwen.py</code>（bidirectional attention + 旧 KV-cache API）与 transformers 5.9.0 三处不兼容。本次用原生 Qwen2Model（causal attention）应急加载，但 gte 的核心是 bidirectional——causal 下 last-token pooling 严重退化（R@10 仅 20–60%），不代表真实质量。其 C-MTEB 67.7 提示真实水平应是中游偏上。</p></blockquote><p><strong>剑走偏锋：对人类最可读的描述 ≠ 对 Embedding 最友好的描述</strong></p><p>6 个公平模型 × 4 描述源 &#x3D; 24 组单路文本检索，按 R@10 排序（gte-qwen2 排除）：</p><table><thead><tr><th>Embed</th><th>Description</th><th>P@3</th><th>P@5</th><th>R@10</th></tr></thead><tbody><tr><td><strong>multilingual-e5-large</strong></td><td><strong>gemini-3.1-flash-lite</strong></td><td>60.0%</td><td>44.0%</td><td><strong>93.3%</strong> ⭐</td></tr><tr><td>jina-v3</td><td>claude-haiku-4-5</td><td>33.3%</td><td>32.0%</td><td>86.7%</td></tr><tr><td>bge-m3</td><td>gemini-3.1-flash-lite</td><td>60.0%</td><td>40.0%</td><td>80.0%</td></tr><tr><td>qwen3-0.6b</td><td>gemini-3.1-flash-lite</td><td>60.0%</td><td>44.0%</td><td>80.0%</td></tr><tr><td>bge-large-zh-v1.5</td><td>glm-5v-turbo</td><td>46.7%</td><td>36.0%</td><td>80.0%</td></tr><tr><td>bge-large-zh-v1.5</td><td>kimi-k2.6</td><td>66.7%</td><td>40.0%</td><td>73.3%</td></tr><tr><td>stella-zh-v3.5</td><td>claude-haiku-4-5</td><td>40.0%</td><td>36.0%</td><td>73.3%</td></tr><tr><td>⚠️ gte-qwen2-1.5b</td><td><em>(4 源全部)</em></td><td>6.7–26.7%</td><td>8–24%</td><td>20–60%</td></tr></tbody></table><p>⭐ <strong>e5-large + gemini 单路 R@10&#x3D;93.3%</strong>，追平 FG-CLIP2 图像单路天花板——最大意外（e5 的 C-MTEB 仅 58.8，本是候选里最弱的）。但这是 15 个数据点上的单组合，完全落在 ±6.7pp 噪声带内，<strong>大概率部分是运气</strong>，必须扩 GT 复核才能当真。</p><p>最扎眼的数据在最后一行——<strong>bge-m3 + claude-haiku 的 P@3 只有 26.7%，全场最差</strong>。<br>而上节里 claude-haiku-4-5 可是”人类阅读质量最高”的描述模型——avg 298 字、方差最小、结构化最完美（”形状结构 &#x2F; 材质纹理 &#x2F; 颜色色调 &#x2F; 细节特征”四段式）。</p><p>把四个描述源在六个 Embedding 模型上的平均表现拉出来看更清楚：</p><table><thead><tr><th>描述源</th><th>人类阅读排名</th><th>平均 P@3</th><th>平均 R@10</th><th>字数</th></tr></thead><tbody><tr><td><strong>gemini-3.1-flash-lite</strong></td><td>第 4（最短）</td><td><strong>58.9%</strong></td><td><strong>76.7%</strong></td><td>avg 165</td></tr><tr><td><strong>kimi-k2.6</strong></td><td>第 2</td><td>56.7%</td><td>70.0%</td><td>avg 245</td></tr><tr><td>glm-5v-turbo</td><td>第 3</td><td>48.9%</td><td>65.6%</td><td>avg 246</td></tr><tr><td><strong>claude-haiku-4-5</strong></td><td><strong>第 1（最详尽）</strong></td><td><strong>36.7%（垫底）</strong></td><td>68.9%</td><td>avg 298</td></tr></tbody></table><h4 id="人类可读并不意味着Embedding友好"><a href="#人类可读并不意味着Embedding友好" class="headerlink" title="人类可读并不意味着Embedding友好"></a>人类可读并不意味着Embedding友好</h4><p>人类阅读排名第 1 的描述，Embedding 检索 P@3 排名第 4（垫底）；人类阅读排名第 4 的最短描述，Embedding 检索排名第 1。</p><p>推测原因有三个：</p><ol><li><strong>描述太长 → 关键 token 被截断切掉。</strong> bge-large-zh 的上下文只有 512 token，298 字的中文描述大概率被截断，关键信息丢在尾部。</li><li><strong>模板套话稀释了语义关键词。</strong> “形状结构 &#x2F; 材质纹理 &#x2F; 颜色色调 &#x2F; 细节特征”这些模板词在每个描述里都出现，对 Embedding 来说是噪声——它们不区分资产 A 和资产 B，却占据了向量空间。</li><li><strong>CLS pooling 对长文本敏感 → 主旨向量被均化。</strong> 越长的文本，CLS token 需要编码的信息越多，反而把最关键的语义特征（”客栈”、”破旧”、”青苔”）给稀释了。而 gemini-3.1-flash-lite 的描述最短（avg 165 字），反而让 Embedding 聚焦在最核心的语义关键词上——<strong>少即是多</strong>。</li></ol><h4 id="描述源的选择比-Embedding-模型更重要"><a href="#描述源的选择比-Embedding-模型更重要" class="headerlink" title="描述源的选择比 Embedding 模型更重要"></a><strong>描述源的选择比 Embedding 模型更重要</strong></h4><p>这个发现有一个重要的推论：<strong>同一 Embedding 模型上换描述源，P@3 能差 20~40 个百分点；换 Embedding 模型在同一描述上，差距通常只有 ±7pp。</strong></p><p>换句话说，描述源是杠杆，Embedding 模型是微调。<strong>投入应优先放在描述源迭代（prompt 工程、描述清洗、混合多模型描述）</strong>，Embedding 模型选哪个都差距不大——看每模型平均：</p><table><thead><tr><th>Embed 模型</th><th>平均 P@3</th><th>平均 P@5</th><th>平均 R@10</th><th>结论</th></tr></thead><tbody><tr><td><strong>qwen3-embedding-0.6b</strong></td><td><strong>55.0%</strong></td><td>40.0%</td><td>71.7%</td><td>⭐ 综合最优</td></tr><tr><td>bge-large-zh-v1.5</td><td>53.3%</td><td>37.0%</td><td><strong>73.3%</strong></td><td>R@10 平均最高</td></tr><tr><td>multilingual-e5-large</td><td>51.7%</td><td>35.0%</td><td>70.0%</td><td>中游偏上</td></tr><tr><td>stella-zh-v3.5</td><td>50.0%</td><td>38.0%</td><td>70.0%</td><td>中游</td></tr><tr><td>jina-v3</td><td>45.0%</td><td>33.0%</td><td>68.3%</td><td>中游</td></tr><tr><td>bge-m3</td><td>46.7%</td><td>35.0%</td><td>68.3%</td><td>中游</td></tr></tbody></table><h4 id="7-款模型没有一个在平均意义上明显拉开差距"><a href="#7-款模型没有一个在平均意义上明显拉开差距" class="headerlink" title="7 款模型没有一个在平均意义上明显拉开差距"></a>7 款模型<strong>没有一个在平均意义上明显拉开差距</strong></h4><p>全部落在 GT 噪声带（±7pp）内。注意 C-MTEB 排名（stella 68.6 ≫ e5 58.8）与本域实测（e5 ≈ stella ≈ 70%）<strong>严重不一致</strong>，再次印证「通用 benchmark ≠ 你的域」——选型必须以本域 GT 为准。</p><h4 id="融合：双剑合璧，1-1-2"><a href="#融合：双剑合璧，1-1-2" class="headerlink" title="融合：双剑合璧，1+1 &gt; 2"></a>融合：双剑合璧，1+1 &gt; 2</h4><p>单路文本 Embedding 的 R@10 最高冲到 93.3%（e5-large + gemini，追平 FG-CLIP2），但这是 15 个数据点上的单组合、落在噪声带内，不可据此认为文本已追平视觉。更稳健的判断是：平均意义上文本单路 R@10 在 68–73% 区间，<strong>文本不能替代视觉，但可以补充视觉</strong>。<br>用 RRF（Reciprocal Rank Fusion, k&#x3D;60）把视觉和文本两路融合后：</p><table><thead><tr><th>方案</th><th>P@5</th><th>R@5</th><th>R@10</th></tr></thead><tbody><tr><td>FG-CLIP2（仅视觉）</td><td>48.0%</td><td>80.0%</td><td>93.3%</td></tr><tr><td><strong>RRF 融合（最佳组合）</strong> ⭐</td><td><strong>52.0%</strong></td><td><strong>86.7%</strong></td><td>93.3%</td></tr><tr><td>R@10 已经是天花板（5 query × 3 relevant &#x3D; 15 个数据点，FG-CLIP2 已命中 14 个），融合没能把最后一个找出来。但 <strong>P@5 从 48% → 52%（+4pp）、R@5 从 80% → 86.7%（+6.7pp）</strong>——正确结果排得更靠前了。</td><td></td><td></td><td></td></tr><tr><td>对用户体验来说，这意味着：原来排在第 6~8 位的结果，现在被推到了前 5 位——从”需要翻页”变成”一眼看到”。</td><td></td><td></td><td></td></tr><tr><td>最佳融合组合是 <strong>FG-CLIP2 + qwen3-0.6b + gemini-flash-lite 描述</strong>，所以我目前采用了这个组合。</td><td></td><td></td><td></td></tr></tbody></table><h4 id="选型结论"><a href="#选型结论" class="headerlink" title="选型结论"></a>选型结论</h4><table><thead><tr><th>组件</th><th>选择</th><th>核心理由</th></tr></thead><tbody><tr><td>视觉编码器</td><td>FG-CLIP 2 large</td><td>双语原生，R@10&#x3D;93.3%，P@3&#x3D;73.3%，单张 108ms</td></tr><tr><td>文本编码器</td><td>Qwen3-Embedding-0.6B</td><td>综合最优（平均 P@3&#x3D;55.0%），轻量，32K 上下文</td></tr><tr><td>描述生成</td><td>gemini-3.1-flash-lite</td><td>¥0.026&#x2F;张，Embedding 检索效果最佳，十万级别跑下来 ¥2000+即可。</td></tr><tr><td>融合策略</td><td>RRF (k&#x3D;60)</td><td>零调参可上线，P@5 +4pp、R@5 +6.7pp</td></tr></tbody></table><blockquote><p><strong>一句话总结</strong>：中文能力是硬门槛不是加分项；描述模型和检索模型必须分开选型，以 Embedding benchmark 为准；学术排行榜只能作参考，必须用自己的数据跑分。</p></blockquote><blockquote><p><strong>📒 顺便把一次性落地账也算清楚。</strong> 除了上面反复出现的描述生成（gemini ¥0.026&#x2F;张，十万张约 <strong>¥2,600</strong>、跑了 8 个多小时），真正铺开还有几笔一次性成本别忘了——<strong>缩略图全量导出 ≈ 33 小时</strong>（CPU 单线程，一次性夜里跑，后续只走增量）、<strong>视觉编码 ≈ 3.3 小时</strong>、<strong>文本编码 ≈ 20 分钟</strong>（都在一张 4060 Ti 上）。再加上美术后续的增量标注，但那是”边用边标”摊薄掉的、可选的人力。算下来真正花钱的就是描述生成那点 API 费，本地编码全是电费——这也是为什么我敢在十万级资产上一次性全量铺开。</p></blockquote><h2 id="五、造一把趁手的兵器：NeuroBrowser（给碳基生物用的）"><a href="#五、造一把趁手的兵器：NeuroBrowser（给碳基生物用的）" class="headerlink" title="五、造一把趁手的兵器：NeuroBrowser（给碳基生物用的）"></a>五、造一把趁手的兵器：NeuroBrowser（给碳基生物用的）</h2><p>这把兵器就是 <strong>NeuroBrowser</strong>——我的一个跑在 UE4 编辑器里的资产搜索面板。它直接嵌入编辑器的 Slate 插件面板，和 Content Browser 并排着（仿照UE原生内容浏览器的体验）。美术开箱即用的体验。</p><h3 id="设计哲学：三层检索、渐进呈现"><a href="#设计哲学：三层检索、渐进呈现" class="headerlink" title="设计哲学：三层检索、渐进呈现"></a>设计哲学：三层检索、渐进呈现</h3><p>回到 [[#二、资产与理解的距离]] 里提出的三层距离，NeuroBrowser 的检索架构也是三层：</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></pre></td><td class="code"><pre><span class="line">flowchart TB</span><br><span class="line">    Q[&quot;👤 用户查询&quot;] --&gt; SQL[&quot;📋 SQL 检索&lt;br/&gt;FTS5 + 标签过滤&lt;br/&gt;&amp;lt; 5ms&quot;]</span><br><span class="line">    Q --&gt; VIS[&quot;👁️ 视觉检索&lt;br/&gt;FG-CLIP-2 large&lt;br/&gt;~30ms&quot;]</span><br><span class="line">    Q --&gt; DESC[&quot;📝 描述词检索&lt;br/&gt;Qwen3-Embedding-0.6B&lt;br/&gt;~30ms&quot;]</span><br><span class="line">    SQL --&gt; RRF[&quot;🔄 RRF 融合排序&quot;]</span><br><span class="line">    VIS --&gt; RRF</span><br><span class="line">    DESC --&gt; RRF</span><br><span class="line">    RRF --&gt; RESULT[&quot;⚡ 即时结果 &amp;lt; 100ms&quot;]</span><br><span class="line">    RRF --&gt; LLM[&quot;🤖 LLM Re-rank&lt;br/&gt;300-600ms&quot;]</span><br><span class="line">    LLM --&gt; REFRESH[&quot;🔄 精排刷新&quot;]</span><br><span class="line"></span><br><span class="line">    style Q fill:#4a90d9,color:#fff</span><br><span class="line">    style RRF fill:#e8793a,color:#fff</span><br><span class="line">    style RESULT fill:#50b86c,color:#fff</span><br><span class="line">    style LLM fill:#9b59b6,color:#fff</span><br></pre></td></tr></table></figure><p><strong>第一层：SQL 检索</strong>（&lt; 5ms）。FTS5 全文检索 + 标签过滤 + 数值范围（三角面、LOD、材质数）+ 逻辑组合（AND&#x2F;OR&#x2F;NOT）。这一层不依赖任何 AI 模型，是保底——即使 Python 后端没启动、模型没加载，关键词搜索依然可用，而且比 UE4 原生快 50 倍以上。</p><p><strong>第二层：向量检索</strong>（&lt; 30ms）。视觉（FG-CLIP-2）和描述词（Qwen3-Embedding-0.6B）两路独立检索，各自返回 Top-K，然后通过 RRF 融合排序。三路权重用户可调——搜”青苔石头”时视觉权重高，搜”角落破旧物件”时描述权重高，搜”三角面 &lt; 5000 的大石头”时 SQL 权重高。</p><p><strong>第三层：LLM 精排</strong>（300-600ms，后台异步）。见下一个段落。</p><h3 id="关于这个插件的设计"><a href="#关于这个插件的设计" class="headerlink" title="关于这个插件的设计"></a>关于这个插件的设计</h3><p><strong>1. 重本地、轻服务器。</strong> 所有索引（SQLite + FAISS）和模型都跑在美术本地机器上。搜索完全离线可用，断网也能正常工作。Python 后端（FastAPI）跑在本地 localhost，不依赖任何云端服务。唯一走云端的是描述&#x2F;标签的团队同步——多人协作时，你改的描述别人也能看到。也支持纯本地运行，这样如果用户本地有数据更新，也可以快速更新数据库。</p><p><strong>2. 不修改引擎源码。</strong> 整个插件基于 UE 4.27.2 的公开 Slate API 实现——缩略图用 <code>FAssetThumbnailPool</code>，拖拽用 <code>FAssetDragDropOp</code>，资产查询用 <code>IAssetRegistry</code>。不碰引擎 Private 目录，不 fork 引擎，后续升级无负担。对引擎接口要求少，可以支持更多引擎版本。</p><p><strong>3. 资产族折叠——10 万变 3 万。</strong> 同一个资产往往有 PC 版 + Mobile 版（<code>_ios</code>）+ LOD 变体 + 材质变体（<code>_01a/_01b</code>）。如果全部平铺展示，搜索结果里同一个逻辑资产会出现 5-8 条——这不是”搜到了”，这是”搜炸了”。NeuroBrowser 按”资产族”聚合：<code>_01a/_01b</code> 材质变体折叠为子项，<code>_01/_02</code> 不同 Mesh 分开显示，<code>_ios</code> 移动端隐藏在 PC 端后面。十万级资产文件 → 数万个资产族，搜索结果干净一个数量级。</p><p><strong>4. 标签语义配置化，方便修改。</strong> 文件名里编码了大量的项目约定——用简写区分不同区域 &#x2F; 场景风格、建筑与资源类别，再配上技术变体后缀（积雪版本、移动端版本等）……30 多个场景标记 + 10 多个技术变体后缀，累计上百种规则，这些逻辑本身是带有语义的，和美术对齐了这些语义后也成为了搜索内容的一部分。另外，这些规则全部通过 <code>tag_rules.json</code> 配置文件管理，不写死在代码里。美术&#x2F;TA 可以自己加规则，不需要改代码重新编译。</p><p><strong>5. 描述是活的，不是死的。</strong> LLM 生成的描述只是初始值——美术可以在资产详情面板里直接修改描述和标签。改完之后，旧的 Embedding 自动标记为 stale，后台三重更新机制（惰性 + 定时 + 手动触发）会异步重建。描述&#x2F;标签通过云端在团队间同步，你改的描述，同事下次搜索就能用到。</p><p><strong>6. 使用分析闭环。</strong> NeuroBrowser 内置了 <code>NeuroBrowserAnalytics</code> 模块——Tab 焦点、拖拽到视口、Viewport 放置归因，全部异步记录到 JSONL。配套的数据分析仪表板可以看”哪些资产被搜得最多”、”哪些搜索没找到结果”、”用户最常拖哪些资产到场景里”。这些数据是后续优化搜索权重、补全描述盲区的依据。</p><p><strong>7. 支持多种搜索模式。</strong> 用户可调整各路检索权重，支持逻辑精筛选（按标签精筛、按模型面数大小筛选等）。</p><p><strong>8. 正向螺旋——回到开篇挖的那个坑。</strong> 还记得开篇说的吗：要让 AI 理解资产，得有人来教；但美术没义务专门替你做标注。我的解法不是”逼美术标注”，而是把标注<strong>嵌进他们本来就要做的搜索动作里</strong>——美术搜资产、用资产的过程中，顺手就能改一条描述、补一个标签。这些改动通过<strong>云端共享数据库</strong>实时同步给全队：你今天纠正的一条描述，同事明天搜索时就直接受益。用得越多 → 描述&#x2F;标签越准 → 搜得越准 → 越多人愿意用——这就是开篇许诺的那个”正向螺旋”，现在它真的转起来了。</p><h3 id="功能Demo演示"><a href="#功能Demo演示" class="headerlink" title="功能Demo演示"></a>功能Demo演示</h3><div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); gap: 16px; align-items: start; margin: 24px 0;">  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260628-203526.gif" alt="常规搜索功能" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">常规语意搜索功能</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260628-203527.gif" alt="基于图像的搜索能力" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">基于图像的搜索能力</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260628-203528.gif" alt="基于规则的搜索能力" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">基于规则的搜索能力</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260628-203529.gif" alt="云端共享的描述与标签库" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">云端共享的描述 / 标签库：你改的描述，同事下次搜索就能用到</figcaption>  </figure></div><h2 id="六、再做一把不趁手的兵器：MCP、CLI（给硅基生物用的）"><a href="#六、再做一把不趁手的兵器：MCP、CLI（给硅基生物用的）" class="headerlink" title="六、再做一把不趁手的兵器：MCP、CLI（给硅基生物用的）"></a>六、再做一把不趁手的兵器：MCP、CLI（给硅基生物用的）</h2><p>还记得我们做这件事情的初衷吗？<br>我们要教会 AI 去认识、理解资产。现在我们有了图像语义、文本描述，还有一大堆的语义标签，我们足以让AI理解每一个资产的具体用途，甚至开放了缩略图给多模态，它如果想仔细对比两个模型，甚至可以开一个子Agent去看图仔细对比两个资产。（当然，并不是开放的接口越多，对于LLM越好，而这就是另外一个故事了）。</p><div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); gap: 16px; align-items: start; margin: 24px 0;">  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260628-020813.png" alt="AI 与人类的协作关系" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">Agent调用</figcaption>  </figure>  <figure style="margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260717-185021.png" alt="在编辑器里面和你一起工作" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">在编辑器里面和你一起工作</figcaption>  </figure></div><h2 id="七、落地之后：把「行为构成」摊开看"><a href="#七、落地之后：把「行为构成」摊开看" class="headerlink" title="七、落地之后：把「行为构成」摊开看"></a>七、落地之后：把「行为构成」摊开看</h2><p>兵器造好了，得看它真上了战场是什么成色。第五节里吹过的那个「使用分析闭环」现在真转起来了——NeuroBrowser 自带的埋点把上线以来每一次操作都记成了 JSONL，下面这张「行为构成」就是这段时间的体检报告：横轴是次数，每一根条都是一个动作。</p><div style="display: flex; flex-wrap: wrap; gap: 16px; justify-content: center; margin: 24px 0;">  <figure style="flex: 1 1 280px; max-width: 620px; margin: 0; text-align: center;">    <img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260624-114108.png" alt="用户行为构成" style="width: 100%; height: auto; display: block; border-radius: 8px;" />    <figcaption style="margin-top: 8px; font-size: 14px; color: #666;">用户行为构成：搜、选、开、拖越多，说明用得越深</figcaption>  </figure></div><p>在部署的第一周时间，测试的6 个用户开了 130 个会话，发起 1403 次搜索、选中资产 681 次。从数据上说，NeuroBrowser和UE的Content Browser目前在使用量上几乎平分秋色，虽然UE Content Browser使用数据更好看一点。从使用习惯上看，<strong>它没变成「替代品」，而是变成了「发现层」。</strong> 原生内容浏览器面板被打开了 1814 次，NeuroBrowser 被打开 1517 次，老的搜索习惯还在；</p><p>另外，「真正把资产放进场景」这一步：从原生 Content Browser 放进去 892 个，从 NeuroBrowser 拖进去的只有 118 个，差了将近 8 倍。也就是说——<strong>大家在 NeuroBrowser 里「找」，找到之后还是切回原生 CB 去「放」。</strong> 它现在更像一把放大镜，还不是那把能单挑的兵器。我觉得这可能是逻辑设计上的问题，因为我还没有完全去模仿UE原生的路径（目前不能完全地看到除了选定资产以外的资产导致的），这个可能后面还要调优。</p><p>目前，作为刚刚上线的功能，能有这水平，我个人还是比较满意的。后面再根据数据一点点调整吧。</p><hr><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>本武功秘籍，想要学会，必须和你的武器（AI）达成人剑合一的状态，所以请把本文档下载下来，给你的人工智障也看看。 并说“给俺也整一个”，你或许可以获取就同款工具。</p><blockquote><p><strong>预告</strong>：第二式将聚焦「AI 如何去理解场景」（自己挖的坑，如果没做出来，可能会换个招式），敬请期待。</p></blockquote><hr><p>[^1]: FatemiJahromi, S. A. (2024). <em>Enhancing 3D Asset Retrieval with Semantic Search</em>. Aalto University. <a href="https://aaltodoc.aalto.fi/items/f1f67c23-221b-4d9a-bec3-de68e3da7656">链接</a><br>[^2]: Radford, A. et al. (2021). <em>Learning Transferable Visual Models From Natural Language Supervision</em>. OpenAI. CLIP 通过对比学习让图文共享同一向量空间，是本次方案的基础能力。</p>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/SoftwareProjects/">SoftwareProjects</category>
      
      <category domain="https://eugenepage.com/tags/AI/">AI</category>
      
      <category domain="https://eugenepage.com/tags/RAG/">RAG</category>
      
      <category domain="https://eugenepage.com/tags/GameDev/">GameDev</category>
      
      <category domain="https://eugenepage.com/tags/CLIP/">CLIP</category>
      
      <category domain="https://eugenepage.com/tags/SemanticSearch/">SemanticSearch</category>
      
      <category domain="https://eugenepage.com/tags/Embedding/">Embedding</category>
      
      <category domain="https://eugenepage.com/tags/AssetManagement/">AssetManagement</category>
      
      <category domain="https://eugenepage.com/tags/UE4/">UE4</category>
      
      <category domain="https://eugenepage.com/tags/Pipeline/">Pipeline</category>
      
      
      <comments>https://eugenepage.com/zh-CN/2026/06/01/20260602.AIGameSeries-Article1/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>RAG Deep Learning Notes</title>
      <link>https://eugenepage.com/2026/05/23/20260524.RAGDeepLearning/</link>
      <guid>https://eugenepage.com/2026/05/23/20260524.RAGDeepLearning/</guid>
      <pubDate>Sat, 23 May 2026 16:00:00 GMT</pubDate>
      
        
        
      <description>&lt;h1 id=&quot;RAG-Beginner-Learning-Notes&quot;&gt;&lt;a href=&quot;#RAG-Beginner-Learning-Notes&quot; class=&quot;headerlink&quot; title=&quot;RAG Beginner Learning Notes&quot;&gt;&lt;/a&gt;RAG B</description>
        
      
      
      
      <content:encoded><![CDATA[<h1 id="RAG-Beginner-Learning-Notes"><a href="#RAG-Beginner-Learning-Notes" class="headerlink" title="RAG Beginner Learning Notes"></a>RAG Beginner Learning Notes</h1><blockquote><p>Starting point: the asset semantic-search project from [[20260522.SearchToolDesign（Private）]]<br>Goal: understand the principles of RAG → run a minimal demo against local image data</p></blockquote><hr><h2 id="1-What-Is-RAG"><a href="#1-What-Is-RAG" class="headerlink" title="1. What Is RAG?"></a>1. What Is RAG?</h2><p><strong>RAG &#x3D; Retrieval-Augmented Generation</strong></p><p>In one sentence: <strong>first find relevant content from a knowledge base, then use that content to help the AI answer the question.</strong></p><h3 id="1-1-Why-Do-We-Need-RAG"><a href="#1-1-Why-Do-We-Need-RAG" class="headerlink" title="1.1 Why Do We Need RAG?"></a>1.1 Why Do We Need RAG?</h3><table><thead><tr><th>Problem</th><th>How RAG Solves It</th></tr></thead><tbody><tr><td>LLM training data has a cutoff date and misses new information</td><td>The retrieval stage can query the latest data in real time</td></tr><tr><td>LLMs don’t know about private data (e.g. a company’s internal asset library)</td><td>Index the private data so it can be retrieved and injected into the LLM</td></tr><tr><td>LLMs “hallucinate” — they make up facts out of thin air</td><td>Provide real retrieved documents as the basis for answers</td></tr><tr><td>LLMs have a limited token window and can’t fit all the material</td><td>Only retrieve the few most relevant document chunks</td></tr></tbody></table><hr><h2 id="2-The-Complete-RAG-Pipeline"><a href="#2-The-Complete-RAG-Pipeline" class="headerlink" title="2. The Complete RAG Pipeline"></a>2. The Complete RAG Pipeline</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">╔══════════════════════════════════════════════════════════╗</span><br><span class="line">║              Indexing Stage (offline, one-time build)     ║</span><br><span class="line">║                                                          ║</span><br><span class="line">║  Raw data (docs / images / asset descriptions)            ║</span><br><span class="line">║       ↓                                                  ║</span><br><span class="line">║  Embedding model  →  content → fixed-length vectors       ║</span><br><span class="line">║       ↓                                                  ║</span><br><span class="line">║  Vector database (FAISS / ChromaDB) stores vectors,       ║</span><br><span class="line">║  enabling fast retrieval                                 ║</span><br><span class="line">╚══════════════════════════════════════════════════════════╝</span><br><span class="line">                          ↕</span><br><span class="line">╔══════════════════════════════════════════════════════════╗</span><br><span class="line">║              Retrieval Stage (online, per query)          ║</span><br><span class="line">║                                                          ║</span><br><span class="line">║  User question  →  Embedding model  →  question vector    ║</span><br><span class="line">║       ↓                                                  ║</span><br><span class="line">║  Vector DB: compute similarity, find Top-K nearest        ║</span><br><span class="line">║       ↓                                                  ║</span><br><span class="line">║  Return corresponding raw content (doc chunks /           ║</span><br><span class="line">║  image paths / asset info)                               ║</span><br><span class="line">╚══════════════════════════════════════════════════════════╝</span><br><span class="line">                          ↕ (optional)</span><br><span class="line">╔══════════════════════════════════════════════════════════╗</span><br><span class="line">║              Generation Stage (optional, classical RAG)   ║</span><br><span class="line">║                                                          ║</span><br><span class="line">║  [User question] + [retrieved content]  →  LLM  →         ║</span><br><span class="line">║  grounded answer                                         ║</span><br><span class="line">╚══════════════════════════════════════════════════════════╝</span><br></pre></td></tr></table></figure><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">Our project is &quot;retrieve-only, no generation&quot;</span></div><div class="callout-content"><p>The asset-search tool only does the first two stages — it turns an artist’s query into a vector, finds the most similar assets, and returns them directly. This is the retrieval subset of RAG, academically called <strong>Semantic Search</strong> or <strong>Dense Retrieval</strong>.</p></div></div><hr><h2 id="3-Core-Concept-Explanations"><a href="#3-Core-Concept-Explanations" class="headerlink" title="3. Core Concept Explanations"></a>3. Core Concept Explanations</h2><h3 id="3-1-Embedding-Vectorization"><a href="#3-1-Embedding-Vectorization" class="headerlink" title="3.1 Embedding (Vectorization)"></a>3.1 Embedding (Vectorization)</h3><p>Compress any content (text, images) into a <strong>fixed-length list of numbers (a vector)</strong>, such that:</p><ul><li>Semantically <strong>similar</strong> content → vectors that are <strong>close</strong> in space</li><li>Semantically <strong>different</strong> content → vectors that are <strong>far apart</strong> in space</li></ul><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></pre></td><td class="code"><pre><span class="line">&quot;a willow tree&quot;     → [0.12, -0.34, 0.87, ..., 0.05]  (768-dim)</span><br><span class="line">&quot;willows by the river&quot; → [0.11, -0.31, 0.89, ..., 0.06] ← very close!</span><br><span class="line">&quot;a rock&quot;             → [-0.45, 0.72, -0.13, ..., 0.91] ← far away</span><br></pre></td></tr></table></figure><p><strong>Common Embedding Models:</strong></p><table><thead><tr><th>Model</th><th>Input Type</th><th>Dim</th><th>Highlights</th></tr></thead><tbody><tr><td>BGE-M3 (BAAI)</td><td>text</td><td>1024</td><td>Strongest for Chinese, friendly to local deployment</td></tr><tr><td>CLIP (OpenAI)</td><td>image + text</td><td>512</td><td>Images and text share the same vector space ⭐</td></tr><tr><td>SigLIP (Google)</td><td>image + text</td><td>768</td><td>Improved version of CLIP, better zero-shot</td></tr><tr><td>GTE-Qwen2 (Alibaba)</td><td>text</td><td>768</td><td>Multilingual, commercial Apache 2.0</td></tr></tbody></table><h3 id="3-2-FAISS-Vector-Similarity-Search-Library"><a href="#3-2-FAISS-Vector-Similarity-Search-Library" class="headerlink" title="3.2 FAISS (Vector Similarity Search Library)"></a>3.2 FAISS (Vector Similarity Search Library)</h3><p><strong>FAISS &#x3D; Facebook AI Similarity Search</strong> (open-sourced by Meta)</p><p><strong>Why do we need it?</strong><br>When you have 20,000 vectors and want to find the Top-10 most similar to a query vector, comparing them one by one needs 20,000 computations. FAISS uses special data structures to speed this up by tens to thousands of times.</p><p><strong>An analogy:</strong></p><ul><li>Brute-force search &#x3D; flip through every book in a library to find the one closest to your interests</li><li>FAISS &#x3D; the library is partitioned by topic, and you go straight to the “Botany” shelf</li></ul><p><strong>Three Main Index Types:</strong></p><table><thead><tr><th>Type</th><th>Principle</th><th>Accuracy</th><th>Speed</th><th>Suitable Scale</th><th>Our Choice</th></tr></thead><tbody><tr><td><code>IndexFlatL2</code></td><td>Exact; compare one by one</td><td>100%</td><td>medium (but more than enough)</td><td><strong>&lt; 100K</strong></td><td>✅ preferred</td></tr><tr><td><code>IndexIVFFlat</code></td><td>Cluster first, then search</td><td>~98%</td><td>fast</td><td>100K–10M</td><td>future expansion</td></tr><tr><td><code>IndexHNSWFlat</code></td><td>Graph-structure navigation</td><td>~99%</td><td>fastest</td><td>any</td><td>over-engineered</td></tr></tbody></table><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">`IndexFlatL2` is enough for 20,000 assets</span></div><div class="callout-content"><p>It’s exact, the code is simplest, single-query latency &lt; 1ms, fully meets the need.</p></div></div><h3 id="3-3-Similarity-Metrics"><a href="#3-3-Similarity-Metrics" class="headerlink" title="3.3 Similarity Metrics"></a>3.3 Similarity Metrics</h3><table><thead><tr><th>Metric</th><th>Formula Meaning</th><th>When to Use</th></tr></thead><tbody><tr><td>L2 distance (Euclidean)</td><td>Straight-line distance in vector space; smaller &#x3D; more similar</td><td>FAISS default; image retrieval</td></tr><tr><td>Cosine similarity</td><td>Cosine of the angle between two vectors; closer to 1 &#x3D; more similar</td><td>More common for text retrieval</td></tr><tr><td>Inner product (dot product)</td><td>Direction + magnitude together</td><td>OpenAI CLIP’s official recommendation</td></tr></tbody></table><hr><h2 id="4-Image-RAG-The-Special-Power-of-CLIP"><a href="#4-Image-RAG-The-Special-Power-of-CLIP" class="headerlink" title="4. Image RAG (The Special Power of CLIP)"></a>4. Image RAG (The Special Power of CLIP)</h2><p>The core innovation of CLIP: <strong>make images and text share the same vector space</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></pre></td><td class="code"><pre><span class="line">&quot;a willow tree&quot; (text)  → CLIP text encoder  → vector A</span><br><span class="line"> 🌳 (willow image)       → CLIP image encoder → vector B</span><br><span class="line"></span><br><span class="line">Vector A ≈ Vector B  ← this is CLIP&#x27;s training objective!</span><br></pre></td></tr></table></figure><p><strong>This means you can:</strong></p><ul><li><strong>text → image</strong>: input “give me a flowchart” and find the most similar images in the library</li><li><strong>image → image</strong>: upload a reference image to find assets with similar style</li><li><strong>image → text</strong>: input an image to find the most relevant descriptive documents</li></ul><p>This is exactly the principle behind the “text-to-image search” feature of the asset-search tool.</p><hr><h2 id="5-Local-Demo-Plan"><a href="#5-Local-Demo-Plan" class="headerlink" title="5. Local Demo Plan"></a>5. Local Demo Plan</h2><h3 id="5-1-Goal"><a href="#5-1-Goal" class="headerlink" title="5.1 Goal"></a>5.1 Goal</h3><p>Run a minimal <strong>image semantic-retrieval demo</strong> using the blog images (PNG&#x2F;JPG) in <code>D:\Project\UGit\MyPicGo\Images\</code>:</p><ul><li><strong>Input</strong>: a piece of text description (e.g. “code screenshot”, “flowchart”, “UI interface”)</li><li><strong>Output</strong>: paths to the Top-5 most similar images in the library</li></ul><h3 id="5-2-Tech-Stack"><a href="#5-2-Tech-Stack" class="headerlink" title="5.2 Tech Stack"></a>5.2 Tech Stack</h3><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></pre></td><td class="code"><pre><span class="line">D:\Project\UGit\MyPicGo\Images\  (blog images, ~100+ PNGs/JPGs)</span><br><span class="line">    ↓ batch read</span><br><span class="line">CLIP ViT-B/32  →  visual encoder, each image → 512-dim vector</span><br><span class="line">    ↓ build index</span><br><span class="line">FAISS IndexFlatL2  →  in-memory index (no GPU, no server needed)</span><br><span class="line">    ↓ query</span><br><span class="line">user text → CLIP text encoder → 512-dim vector → Top-K retrieval</span><br></pre></td></tr></table></figure><h3 id="5-3-Hands-on-Process"><a href="#5-3-Hands-on-Process" class="headerlink" title="5.3 Hands-on Process"></a>5.3 Hands-on Process</h3><ol><li>Create project directory <code>D:\Project\UGit\PicGoRAGDemo\</code> + Python venv</li><li>Install deps: <code>torch / transformers / faiss-cpu / modelscope / Pillow / numpy</code></li><li>Write <code>build_index.py</code> (indexing stage) + <code>search.py</code> (query stage)</li><li>Run <code>python build_index.py</code>: encode 225 images into 225 × 512-dim vectors, build FAISS index and dump to disk (~15s on CPU)</li><li>Run <code>python search.py &quot;query&quot;</code> to validate retrieval (single query &lt; 100ms)</li></ol><p><strong>Three key pitfalls:</strong></p><ul><li><strong>transformers 5.x changed the CLIP API</strong>: <code>get_image_features(...)</code> now returns <code>BaseModelOutputWithPooling</code> (not a bare Tensor anymore); you must call <code>.pooler_output</code> to get the 512-dim vector.</li><li><strong>HuggingFace is unreachable from mainland China</strong>: direct connection times out and mirrors are unstable; switch to <strong>ModelScope</strong> (Alibaba’s ModelScope — domestic servers, no obstacles).</li><li><strong>ModelScope uses different naming</strong>: HF’s <code>OFA-Sys/chinese-clip-vit-base-patch16</code> corresponds to <code>AI-ModelScope/chinese-clip-vit-base-patch16</code> on ModelScope (<code>AI-ModelScope</code> is the HF-format mirror namespace on ModelScope; the file structure is identical to HF and <code>transformers.AutoModel</code> can load it directly).</li></ul><h3 id="5-4-Test-Findings-Comparing-Chinese-vs-English-Query-Effectiveness"><a href="#5-4-Test-Findings-Comparing-Chinese-vs-English-Query-Effectiveness" class="headerlink" title="5.4 Test Findings: Comparing Chinese vs English Query Effectiveness"></a>5.4 Test Findings: Comparing Chinese vs English Query Effectiveness</h3><p>Real testing found that <strong>English queries are slightly more accurate</strong>:</p><table><thead><tr><th>Query</th><th>Model</th><th>Retrieval Result</th></tr></thead><tbody><tr><td><code>&quot;code screenshot&quot;</code></td><td>OpenAI CLIP</td><td>✅ Top-3 are all code screenshots</td></tr><tr><td><code>&quot;flowchart&quot;</code></td><td>OpenAI CLIP</td><td>✅ Top-3 are all flowcharts</td></tr><tr><td><code>&quot;代码截图&quot;</code> (code screenshot)</td><td>Chinese-CLIP</td><td>✅ Top-3 are all code screenshots</td></tr><tr><td><code>&quot;流程图&quot;</code> (flowchart)</td><td>Chinese-CLIP</td><td>⚠️ Top-3 contains flowcharts but mixes in a few unrelated images</td></tr></tbody></table><p><strong>Possible reason</strong>: the blog images are mostly English-language technical screenshots (code, terminal, UI text are mostly in English). OpenAI CLIP simultaneously recognizes English text + visual features, so its hits are more reliable; Chinese-CLIP’s training data leans toward Chinese everyday&#x2F;news content and isn’t sharp enough on the boundaries of technical visual concepts like “flowchart”.</p><p>This validates the core point from §6.1: <strong>the performance of an embedding model on your domain depends on the training data distribution — choose models based on the scenario</strong>.</p><h3 id="5-5-Complete-Code"><a href="#5-5-Complete-Code" class="headerlink" title="5.5 Complete Code"></a>5.5 Complete Code</h3><h4 id="build-index-py-indexing-stage"><a href="#build-index-py-indexing-stage" class="headerlink" title="build_index.py (indexing stage)"></a><code>build_index.py</code> (indexing stage)</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;build_index.py — encode N images into N 512-dim vectors and build the FAISS index</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Outputs:</span></span><br><span class="line"><span class="string">  index.faiss  FAISS index (row i = 512-dim vector of the i-th image)</span></span><br><span class="line"><span class="string">  paths.json   row number → original image path</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">from</span> __future__ <span class="keyword">import</span> annotations</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"><span class="keyword">import</span> faiss, numpy <span class="keyword">as</span> np, torch</span><br><span class="line"><span class="keyword">from</span> PIL <span class="keyword">import</span> Image</span><br><span class="line"><span class="keyword">from</span> modelscope <span class="keyword">import</span> snapshot_download</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> AutoModel, AutoProcessor</span><br><span class="line"></span><br><span class="line"><span class="comment"># ===== Config =====</span></span><br><span class="line">IMAGE_DIR  = Path(<span class="string">r&quot;D:\Project\UGit\MyPicGo\Images&quot;</span>)</span><br><span class="line">INDEX_PATH = Path(__file__).parent / <span class="string">&quot;index.faiss&quot;</span></span><br><span class="line">PATHS_PATH = Path(__file__).parent / <span class="string">&quot;paths.json&quot;</span></span><br><span class="line"><span class="comment"># AI-ModelScope is the HF-format mirror namespace on ModelScope, directly reachable in China</span></span><br><span class="line">MODEL_ID   = <span class="string">&quot;AI-ModelScope/chinese-clip-vit-base-patch16&quot;</span></span><br><span class="line">BATCH_SIZE = <span class="number">16</span></span><br><span class="line">IMG_EXTS   = &#123;<span class="string">&quot;.png&quot;</span>, <span class="string">&quot;.jpg&quot;</span>, <span class="string">&quot;.jpeg&quot;</span>, <span class="string">&quot;.webp&quot;</span>, <span class="string">&quot;.bmp&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">collect_images</span>(<span class="params">root: Path</span>) -&gt; <span class="built_in">list</span>[Path]:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Recursively scan all images.&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">sorted</span>(p <span class="keyword">for</span> p <span class="keyword">in</span> root.rglob(<span class="string">&quot;*&quot;</span>) <span class="keyword">if</span> p.suffix.lower() <span class="keyword">in</span> IMG_EXTS)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>() -&gt; <span class="literal">None</span>:</span><br><span class="line">    device = <span class="string">&quot;cuda&quot;</span> <span class="keyword">if</span> torch.cuda.is_available() <span class="keyword">else</span> <span class="string">&quot;cpu&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 1. Load model ===</span></span><br><span class="line">    <span class="comment"># snapshot_download downloads ~700MB on first run to ~/.cache/modelscope/,</span></span><br><span class="line">    <span class="comment"># then returns the local path directly</span></span><br><span class="line">    model_dir = snapshot_download(MODEL_ID)</span><br><span class="line">    model     = AutoModel.from_pretrained(model_dir).to(device).<span class="built_in">eval</span>()</span><br><span class="line">    processor = AutoProcessor.from_pretrained(model_dir)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 2. Scan image library ===</span></span><br><span class="line">    paths = collect_images(IMAGE_DIR)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> paths:</span><br><span class="line">        <span class="keyword">raise</span> SystemExit(<span class="string">f&quot;no images found under <span class="subst">&#123;IMAGE_DIR&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 3. Batch encoding ===</span></span><br><span class="line">    embeddings, ok_paths = [], []</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="built_in">len</span>(paths), BATCH_SIZE):</span><br><span class="line">        batch = paths[i:i + BATCH_SIZE]</span><br><span class="line">        imgs, ok = [], []</span><br><span class="line">        <span class="keyword">for</span> p <span class="keyword">in</span> batch:</span><br><span class="line">            <span class="keyword">try</span>:</span><br><span class="line">                imgs.append(Image.<span class="built_in">open</span>(p).convert(<span class="string">&quot;RGB&quot;</span>))</span><br><span class="line">                ok.append(p)</span><br><span class="line">            <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">f&quot;  skip <span class="subst">&#123;p.name&#125;</span>: <span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> imgs:</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        inputs = processor(images=imgs, return_tensors=<span class="string">&quot;pt&quot;</span>).to(device)</span><br><span class="line">        <span class="keyword">with</span> torch.no_grad():</span><br><span class="line">            <span class="comment"># transformers 5.x: get_image_features returns BaseModelOutputWithPooling;</span></span><br><span class="line">            <span class="comment"># the 512-dim projection vector is in the .pooler_output field</span></span><br><span class="line">            feats = model.get_image_features(**inputs).pooler_output</span><br><span class="line">        <span class="comment"># L2 normalize: after normalization, L2 distance ranking ≡ cosine similarity ranking</span></span><br><span class="line">        feats = feats / feats.norm(dim=-<span class="number">1</span>, keepdim=<span class="literal">True</span>)</span><br><span class="line">        embeddings.append(feats.cpu().numpy().astype(<span class="string">&quot;float32&quot;</span>))</span><br><span class="line">        ok_paths.extend(ok)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 4. Build FAISS index and dump to disk ===</span></span><br><span class="line">    mat = np.vstack(embeddings)</span><br><span class="line">    <span class="comment"># IndexFlatL2: exact brute-force retrieval; sufficient for &lt; 100K vectors,</span></span><br><span class="line">    <span class="comment"># single-query latency &lt; 1ms</span></span><br><span class="line">    index = faiss.IndexFlatL2(mat.shape[<span class="number">1</span>])</span><br><span class="line">    index.add(mat)</span><br><span class="line">    faiss.write_index(index, <span class="built_in">str</span>(INDEX_PATH))</span><br><span class="line">    PATHS_PATH.write_text(</span><br><span class="line">        json.dumps([<span class="built_in">str</span>(p) <span class="keyword">for</span> p <span class="keyword">in</span> ok_paths], ensure_ascii=<span class="literal">False</span>, indent=<span class="number">2</span>),</span><br><span class="line">        encoding=<span class="string">&quot;utf-8&quot;</span>,</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><h4 id="search-py-query-stage"><a href="#search-py-query-stage" class="headerlink" title="search.py (query stage)"></a><code>search.py</code> (query stage)</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;search.py — text query → CLIP text encoding → FAISS Top-K → print paths</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Usage:</span></span><br><span class="line"><span class="string">  python search.py &quot;代码截图&quot;</span></span><br><span class="line"><span class="string">  python search.py &quot;flowchart&quot; --topk 10</span></span><br><span class="line"><span class="string">  python search.py &quot;blue UI&quot; --open    # also open the Top-1 result</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">from</span> __future__ <span class="keyword">import</span> annotations</span><br><span class="line"><span class="keyword">import</span> argparse, json, os</span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"><span class="keyword">import</span> faiss, torch</span><br><span class="line"><span class="keyword">from</span> modelscope <span class="keyword">import</span> snapshot_download</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> AutoModel, AutoProcessor</span><br><span class="line"></span><br><span class="line">INDEX_PATH = Path(__file__).parent / <span class="string">&quot;index.faiss&quot;</span></span><br><span class="line">PATHS_PATH = Path(__file__).parent / <span class="string">&quot;paths.json&quot;</span></span><br><span class="line"><span class="comment"># Must match build_index.py — different models&#x27; vector spaces are incompatible</span></span><br><span class="line">MODEL_ID   = <span class="string">&quot;AI-ModelScope/chinese-clip-vit-base-patch16&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>() -&gt; <span class="literal">None</span>:</span><br><span class="line">    parser = argparse.ArgumentParser()</span><br><span class="line">    parser.add_argument(<span class="string">&quot;query&quot;</span>, <span class="built_in">type</span>=<span class="built_in">str</span>, <span class="built_in">help</span>=<span class="string">&quot;Natural-language query&quot;</span>)</span><br><span class="line">    parser.add_argument(<span class="string">&quot;--topk&quot;</span>, <span class="built_in">type</span>=<span class="built_in">int</span>, default=<span class="number">5</span>)</span><br><span class="line">    parser.add_argument(<span class="string">&quot;--open&quot;</span>, action=<span class="string">&quot;store_true&quot;</span>, <span class="built_in">help</span>=<span class="string">&quot;Open the Top-1 result&quot;</span>)</span><br><span class="line">    args = parser.parse_args()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> INDEX_PATH.exists():</span><br><span class="line">        <span class="keyword">raise</span> SystemExit(<span class="string">f&quot;Cannot find <span class="subst">&#123;INDEX_PATH.name&#125;</span>; run python build_index.py first&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 1. Load model + index ===</span></span><br><span class="line">    device = <span class="string">&quot;cuda&quot;</span> <span class="keyword">if</span> torch.cuda.is_available() <span class="keyword">else</span> <span class="string">&quot;cpu&quot;</span></span><br><span class="line">    model_dir = snapshot_download(MODEL_ID)</span><br><span class="line">    model     = AutoModel.from_pretrained(model_dir).to(device).<span class="built_in">eval</span>()</span><br><span class="line">    processor = AutoProcessor.from_pretrained(model_dir)</span><br><span class="line">    index = faiss.read_index(<span class="built_in">str</span>(INDEX_PATH))</span><br><span class="line">    paths = json.loads(PATHS_PATH.read_text(encoding=<span class="string">&quot;utf-8&quot;</span>))</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 2. Encode text into a vector ===</span></span><br><span class="line">    inputs = processor(text=[args.query], return_tensors=<span class="string">&quot;pt&quot;</span>, padding=<span class="literal">True</span>).to(device)</span><br><span class="line">    <span class="keyword">with</span> torch.no_grad():</span><br><span class="line">        <span class="comment"># Also take .pooler_output to get the 512-dim projection vector</span></span><br><span class="line">        feat = model.get_text_features(**inputs).pooler_output</span><br><span class="line">    feat = feat / feat.norm(dim=-<span class="number">1</span>, keepdim=<span class="literal">True</span>)</span><br><span class="line">    query_vec = feat.cpu().numpy().astype(<span class="string">&quot;float32&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 3. FAISS Top-K retrieval ===</span></span><br><span class="line">    distances, indices = index.search(query_vec, args.topk)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 4. Print results ===</span></span><br><span class="line">    <span class="keyword">for</span> rank, (idx, dist) <span class="keyword">in</span> <span class="built_in">enumerate</span>(<span class="built_in">zip</span>(indices[<span class="number">0</span>], distances[<span class="number">0</span>]), <span class="number">1</span>):</span><br><span class="line">        <span class="comment"># For L2-normalized vectors: cos_sim = 1 - L2_dist^2 / 2</span></span><br><span class="line">        sim = <span class="number">1.0</span> - dist / <span class="number">2.0</span></span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;  #<span class="subst">&#123;rank&#125;</span>  sim=<span class="subst">&#123;sim:<span class="number">.3</span>f&#125;</span>  <span class="subst">&#123;paths[idx]&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> args.<span class="built_in">open</span> <span class="keyword">and</span> <span class="built_in">len</span>(indices[<span class="number">0</span>]) &gt; <span class="number">0</span>:</span><br><span class="line">        os.startfile(paths[indices[<span class="number">0</span>][<span class="number">0</span>]])</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><hr><h2 id="6-Learning-Resources"><a href="#6-Learning-Resources" class="headerlink" title="6. Learning Resources"></a>6. Learning Resources</h2><h3 id="6-1-Introductory-Articles-Easy-→-Deep"><a href="#6-1-Introductory-Articles-Easy-→-Deep" class="headerlink" title="6.1 Introductory Articles (Easy → Deep)"></a>6.1 Introductory Articles (Easy → Deep)</h3><table><thead><tr><th>Resource</th><th>Type</th><th>Stage</th></tr></thead><tbody><tr><td>IBM Technology: <em>What is RAG?</em></td><td>5-min video</td><td>Absolute beginner; concepts explained clearly</td></tr><tr><td><em>Retrieval-Augmented Generation</em> — LangChain official docs</td><td>illustrated tutorial</td><td>understand the pipeline, code samples</td></tr><tr><td><em>Building RAG from Scratch</em> — Towards Data Science</td><td>blog post</td><td>no framework needed; raw FAISS + Python</td></tr><tr><td>LlamaIndex official tutorial</td><td>hands-on</td><td>engineering framework, production-grade RAG</td></tr><tr><td>OpenAI Cookbook — RAG</td><td>code samples</td><td>advanced, includes evaluation metrics</td></tr></tbody></table><h3 id="6-2-Must-Read-Papers-Optional"><a href="#6-2-Must-Read-Papers-Optional" class="headerlink" title="6.2 Must-Read Papers (Optional)"></a>6.2 Must-Read Papers (Optional)</h3><table><thead><tr><th>Paper</th><th>Year</th><th>Why Read</th></tr></thead><tbody><tr><td><em>RAG for Knowledge-Intensive NLP Tasks</em> (Lewis et al.)</td><td>2020</td><td>The conceptual origin of RAG</td></tr><tr><td><em>CLIP: Learning Transferable Visual Models</em> (Radford et al.)</td><td>2021</td><td>Foundation of image-text alignment</td></tr><tr><td><em>SigLIP: Sigmoid Loss for Language Image Pre-Training</em></td><td>2023</td><td>Improved version of CLIP; follow-up progress</td></tr></tbody></table><h3 id="6-3-Chinese-Resources"><a href="#6-3-Chinese-Resources" class="headerlink" title="6.3 Chinese Resources"></a>6.3 Chinese Resources</h3><ul><li><strong>BAAI BGE series documentation</strong>: official docs for Chinese embedding models; directly guides model selection</li><li><strong>Zhihu “RAG Practice”</strong>: notes on pitfalls by Chinese engineers; very practical</li><li><strong>Bilibili @跟李沐学AI</strong>: deep-learning fundamentals; necessary background for understanding embeddings</li></ul><hr><h2 id="7-Notes"><a href="#7-Notes" class="headerlink" title="7. Notes"></a>7. Notes</h2><h3 id="Module-1-RAG-Overview"><a href="#Module-1-RAG-Overview" class="headerlink" title="Module 1: RAG Overview"></a>Module 1: RAG Overview</h3><h4 id="1-RAG-Architecture"><a href="#1-RAG-Architecture" class="headerlink" title="1. RAG Architecture"></a>1. RAG Architecture</h4><p><img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260524-032448.png" alt="image.png"></p><p>Inside a RAG system, there is first a <strong>retriever</strong> that has access to the database and fires off a query (similar to a database). The retriever then receives the result of the query (the information deemed most relevant). It then uses this written, possibly most relevant information to generate an augmented prompt.</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></pre></td><td class="code"><pre><span class="line"># ============================================================</span><br><span class="line"># Stage 1: Define the user question (the original prompt)</span><br><span class="line"># ============================================================</span><br><span class="line"># This is the user&#x27;s original question, containing time-sensitive</span><br><span class="line"># information (&quot;this weekend&quot;) that the large model&#x27;s own training</span><br><span class="line"># data cannot cover in real time.</span><br><span class="line">prompt = &quot;Why are hotel prices in Vancouver super expensive this weekend?&quot;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># ============================================================</span><br><span class="line"># Stage 2: Naive Generation (baseline for comparison)</span><br><span class="line"># ============================================================</span><br><span class="line"># Throw the original prompt directly to the LLM to generate an answer.</span><br><span class="line"># Downside: the model doesn&#x27;t know what events are happening in</span><br><span class="line"># Vancouver &quot;this weekend&quot;, so it can only offer generic guesses</span><br><span class="line"># (tourist season, exchange rate, etc.) and may hallucinate.</span><br><span class="line">generate(prompt)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># ============================================================</span><br><span class="line"># Stage 3: Retrieve relevant documents</span><br><span class="line"># ============================================================</span><br><span class="line"># Use the prompt to query external knowledge bases (vector DB,</span><br><span class="line"># search engine, internal docs, etc.) and retrieve real-time /</span><br><span class="line"># authoritative information related to the question, such as:</span><br><span class="line">#   - concerts, conventions, sports events happening this weekend</span><br><span class="line">#   - hotel-industry supply/demand news</span><br><span class="line"># These documents are key material for the next &quot;augmentation&quot; step.</span><br><span class="line">retrieved_documents = retrieve(prompt)</span><br><span class="line">print(retrieved_documents)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># ============================================================</span><br><span class="line"># Stage 4: Construct the augmented prompt</span><br><span class="line"># ============================================================</span><br><span class="line"># Stitch &quot;original question&quot; + &quot;retrieved documents&quot; into a new</span><br><span class="line"># prompt, explicitly feeding external knowledge to the model so</span><br><span class="line"># it answers with something to rely on.</span><br><span class="line">augmented_prompt = f&quot;&quot;&quot;Respond to the following prompt: &#123;prompt&#125;</span><br><span class="line"></span><br><span class="line">using the following retrieved information to help you answer &#123;retrieved_documents&#125;&quot;&quot;&quot;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># ============================================================</span><br><span class="line"># Stage 5: Regenerate based on the augmented prompt</span><br><span class="line"># ============================================================</span><br><span class="line"># The LLM now receives &quot;question + contextual evidence&quot;; the</span><br><span class="line"># output is more accurate, more timely, and reduces hallucination.</span><br><span class="line"># This is the complete closed loop of RAG</span><br><span class="line"># (Retrieval-Augmented Generation):</span><br><span class="line">#   Retrieve → Augment → Generate</span><br><span class="line">generate(augmented_prompt)</span><br></pre></td></tr></table></figure><h4 id="2-Understanding-RAG-Through-LLMs"><a href="#2-Understanding-RAG-Through-LLMs" class="headerlink" title="2. Understanding RAG Through LLMs"></a>2. Understanding RAG Through LLMs</h4><p>LLMs are essentially constantly predicting the probability distribution of the next value to appear; RAG changes that distribution.</p><h4 id="3-Information-Retrieval-RAG-Retrieve-vs-Search-Engine-vs-Database-Query"><a href="#3-Information-Retrieval-RAG-Retrieve-vs-Search-Engine-vs-Database-Query" class="headerlink" title="3. Information Retrieval: RAG Retrieve vs Search Engine vs Database Query"></a>3. Information Retrieval: RAG Retrieve vs Search Engine vs Database Query</h4><p>All three are “looking for things”, but the underlying matching logic is completely different:</p><table><thead><tr><th>Dimension</th><th>Search Engine (BM25&#x2F;TF-IDF)</th><th>Database Query (SQL)</th><th>RAG Retrieve (Vector Search)</th></tr></thead><tbody><tr><td><strong>Matching method</strong></td><td>keyword term-frequency stats</td><td>exact field matching</td><td>semantic similarity (vector distance)</td></tr><tr><td><strong>Query language</strong></td><td>natural-language bag of words</td><td>structured SQL</td><td>any content (text &#x2F; image &#x2F; audio)</td></tr><tr><td><strong>Understands synonyms?</strong></td><td>❌ partially (needs dictionary)</td><td>❌ not at all</td><td>✅ natively supported</td></tr><tr><td><strong>Cross-modal?</strong></td><td>❌ text only finds text</td><td>❌</td><td>✅ text finds images (CLIP)</td></tr><tr><td><strong>Result ranking basis</strong></td><td>TF-IDF score</td><td>no ranking (exact match &#x2F; filter)</td><td>vector-space distance (cosine &#x2F; L2)</td></tr><tr><td><strong>Data-structure requirement</strong></td><td>needs inverted index</td><td>needs strict schema</td><td>only needs vectors; raw structure unrestricted</td></tr><tr><td><strong>Suited for</strong></td><td>“find docs containing these words”</td><td>“find records matching these conditions”</td><td>“find semantically most relevant content”</td></tr></tbody></table><p><strong>A one-line distinction of essence:</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></pre></td><td class="code"><pre><span class="line">Database query — &quot;Are they exactly equal?&quot;   (exact)</span><br><span class="line">Search engine — &quot;Does it have this word?&quot;   (frequency)</span><br><span class="line">RAG retrieve   — &quot;Are they alike in meaning?&quot; (semantic)</span><br></pre></td></tr></table></figure><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">Engineering practice: Hybrid Search</span></div><div class="callout-content"><p>RAG is not a “perfect replacement” for the first two; in production, <strong>vector retrieval + BM25</strong> are often combined, then re-ranked with a Re-ranker. Vector retrieval handles semantic generalization; BM25 handles keyword anchoring; the two complement each other.</p></div></div><h3 id="Module-2-Information-Retrieval-Search-Techniques"><a href="#Module-2-Information-Retrieval-Search-Techniques" class="headerlink" title="Module 2: Information Retrieval &amp; Search Techniques"></a>Module 2: Information Retrieval &amp; Search Techniques</h3><h4 id="1-Retriever-Architecture-Overview"><a href="#1-Retriever-Architecture-Overview" class="headerlink" title="1. Retriever Architecture Overview"></a>1. Retriever Architecture Overview</h4><h5 id="Metadata-Filtering"><a href="#Metadata-Filtering" class="headerlink" title="Metadata Filtering"></a>Metadata Filtering</h5><p>Before or after vector retrieval, use structured fields to <strong>hard-filter the candidate set</strong>, shrinking the retrieval scope.</p><ul><li><strong>Principle</strong>: documents are ingested with metadata fields (e.g. source, date, category); during query, filter first, then rank by vector similarity.</li><li><strong>Advantage</strong>: dramatically reduces interference from irrelevant documents, improves both precision and retrieval efficiency.</li><li><strong>Limitation</strong>: relies on metadata quality at ingestion time; missing or misclassified fields can wrongly filter out effective documents. It doesn’t understand content and is almost never used alone.</li></ul><h5 id="Keyword-Searching"><a href="#Keyword-Searching" class="headerlink" title="Keyword Searching"></a>Keyword Searching</h5><p>Searches documents by exact-word matching; the most classical search method. Mainly includes TF-IDF and BM25.</p><ul><li><strong>Principle</strong>: split the query into tokens, compute term frequency (e.g. BM25), return documents containing those words.</li><li><strong>Advantage</strong>: fast, results explainable, accurate hits on proper nouns (codes, IDs, model numbers).</li><li><strong>Limitation</strong>: doesn’t understand synonyms or paraphrases — say it differently and you might not find it.</li></ul><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">TF-IDF vs BM25</span></div><div class="callout-content"><p>Both score by term frequency, but BM25 improves on TF-IDF: TF-IDF grows linearly with term frequency; BM25 adds <strong>term-frequency saturation</strong> (diminishing returns for high-frequency words) and <strong>document-length normalization</strong>, making weight more reasonable whether a term appears once in a short doc or many times in a long one. In practice BM25 is better.</p></div></div><h5 id="Semantic-Searching"><a href="#Semantic-Searching" class="headerlink" title="Semantic Searching"></a>Semantic Searching</h5><p>Encode text into vectors and compute vector similarity to match “similarly-meaningful” content.</p><ul><li><strong>Principle</strong>: use an Embedding model to map both query and documents into a high-dimensional vector space; measure distance using cosine similarity, etc.</li><li><strong>Advantage</strong>: captures semantic generalization; finds related content even when phrased differently.</li><li><strong>Limitation</strong>: higher compute cost; less reliable than keyword search for exact terms (e.g. specific IDs).</li></ul><h4 id="2-Two-Common-Keyword-Retrieval-Algorithms-in-Detail"><a href="#2-Two-Common-Keyword-Retrieval-Algorithms-in-Detail" class="headerlink" title="2. Two Common Keyword-Retrieval Algorithms in Detail"></a>2. Two Common Keyword-Retrieval Algorithms in Detail</h4><p><strong>TF-IDF</strong></p><p>Core formula: <code>Score = TF(term, doc) × log(total docs / docs containing term)</code></p><ul><li><strong>TF</strong> (term frequency): the more a term appears in the current doc, the more relevant</li><li><strong>IDF</strong> (inverse document frequency): the more docs contain a term, the more “filler” it is — weight drops; rarer terms get higher weight</li><li><strong>Result</strong>: high-frequency function words like “the”, “is” score near zero; proper nouns like “quantum entanglement” or “BM25” score high</li></ul><p><strong>BM25</strong> (the industry-grade improvement of TF-IDF; Elasticsearch’s default algorithm)</p><p>Core improvements:</p><table><thead><tr><th>Problem</th><th>TF-IDF</th><th>BM25</th></tr></thead><tbody><tr><td>Term-frequency stacking</td><td>100 occurrences &#x3D; 100× score</td><td>term frequency <strong>saturates</strong>; diminishing returns (parameter <code>k1</code>, default 1.2–2.0)</td></tr><tr><td>Long documents naturally favored</td><td>over-penalizes</td><td><strong>normalized by average doc length</strong> (parameter <code>b</code>, default 0.75)</td></tr></tbody></table><blockquote><p><strong><code>k1</code></strong>: controls how fast term frequency saturates; higher means “saying it more makes it more important”; lower means “3 times and 100 times are nearly the same”.<br><strong><code>b</code></strong>: controls length penalty strength; <code>b=0</code> ignores length, <code>b=1</code> strictly scores by density; 0.75 is an industry empirical default.</p></blockquote><h4 id="3-Similarities-and-Differences-Between-Semantic-Search-and-Keyword-Search"><a href="#3-Similarities-and-Differences-Between-Semantic-Search-and-Keyword-Search" class="headerlink" title="3. Similarities and Differences Between Semantic Search and Keyword Search"></a>3. Similarities and Differences Between Semantic Search and Keyword Search</h4><h5 id="1-Common-ground-Digitization"><a href="#1-Common-ground-Digitization" class="headerlink" title="1. Common ground: Digitization"></a>1. Common ground: Digitization</h5><ul><li><strong>Prompt and documents each get a vector</strong>:<br>  No matter the search type, the computer can’t directly “read” text. The first step is always turning both the user’s question and the documents in the database into a list of numbers, called a vector.</li><li><strong>Vectors compared to generate scores</strong>:<br>  Once converted into numbers, the computer can use math formulas (e.g. cosine similarity) to compute the “distance” between two vectors. The closer, the higher the score, the more relevant.</li></ul><h5 id="2-Core-difference-How-is-the-vector-generated"><a href="#2-Core-difference-How-is-the-vector-generated" class="headerlink" title="2. Core difference: How is the vector generated?"></a>2. Core difference: How is the vector generated?</h5><h6 id="Keyword-Search-Count-word-occurrences"><a href="#Keyword-Search-Count-word-occurrences" class="headerlink" title="Keyword Search: Count word occurrences"></a><strong>Keyword Search: Count word occurrences</strong></h6><ul><li><strong>Principle</strong>: the result is called a <strong>sparse vector</strong>.</li><li><strong>Logic</strong>: each position of the vector represents a specific word. If the document contains that word, the corresponding position gets a score (based on term frequency, e.g. BM25).</li><li><strong>Characteristic</strong>: literal matching; only recognizes “identical-looking” words.<br>  <strong>Limitation</strong>: searching for “doctor” won’t find a document containing “physician” but not “doctor”, because it doesn’t understand word meaning.</li></ul><h6 id="Semantic-Search-Use-an-embedding-model"><a href="#Semantic-Search-Use-an-embedding-model" class="headerlink" title="Semantic Search: Use an embedding model"></a><strong>Semantic Search: Use an embedding model</strong></h6><ul><li><strong>Principle</strong>: the result is called a <strong>dense vector</strong>.</li><li><strong>Logic</strong>: instead of counting words directly, text is fed into a pre-trained <strong>deep learning model (Embedding Model, e.g. BERT)</strong>. The model “maps” the text into a multidimensional semantic space.</li><li><strong>Characteristic</strong>: understands meaning; the numbers in the vector represent abstract “features” or “concepts”.<br>  <strong>Advantage</strong>: recognizes synonyms; even if words don’t match, as long as the “meaning” is close (e.g. “cat” and “kitten”, “doctor” and “physician”), their vectors are mathematically close in space.</li></ul><h4 id="3-RRF-Algorithm-Balancing-Keyword-and-Semantic"><a href="#3-RRF-Algorithm-Balancing-Keyword-and-Semantic" class="headerlink" title="3. RRF Algorithm (Balancing Keyword and Semantic)"></a>3. RRF Algorithm (Balancing Keyword and Semantic)</h4><p>It is the core technique in <strong>hybrid retrieval</strong>. When you run both “keyword retrieval” and “semantic retrieval” in your RAG system, you get two completely different scoring lists. RRF (Reciprocal Rank Fusion) merges these two lists <strong>fairly into one final ranking list</strong>.</p><h5 id="1-Core-problem-The-“apples-and-oranges”-comparison-dilemma"><a href="#1-Core-problem-The-“apples-and-oranges”-comparison-dilemma" class="headerlink" title="1. Core problem: The “apples and oranges” comparison dilemma"></a>1. Core problem: The “apples and oranges” comparison dilemma</h5><p>Hybrid retrieval faces a fundamental difficulty:</p><ul><li>Keyword (BM25) scores may be <code>15.4</code>, <code>12.8</code>, etc.</li><li>Semantic (vector-search) scores (cosine similarity) typically range between <code>0.8</code> and <code>0.9</code>.</li><li><strong>Problem</strong>: these two scores have different units and cannot be directly summed or compared.</li></ul><p><strong>RRF’s solution: completely ignore raw scores; only look at documents’ positions (ranks) in the lists.</strong></p><h5 id="2-Core-mechanism"><a href="#2-Core-mechanism" class="headerlink" title="2. Core mechanism"></a>2. Core mechanism</h5><ul><li><strong>Reward “consensus” documents</strong>: if a document ranks high in both keyword search and semantic search, RRF gives it an extremely high final score.</li><li><strong>Normalize weights across searches</strong>: provides a fair comparison across strategies; neither algorithm can dominate just because its score range is larger.</li><li><strong>Score &#x3D; reciprocal of rank (the origin of the algorithm’s name)</strong>:<ul><li>Rank 1 → <code>1/1 = 1.0</code> score</li><li>Rank 2 → <code>1/2 = 0.5</code> score</li><li>Rank 10 → <code>1/10 = 0.1</code> score</li><li>Logic: higher rank &#x3D; higher score; the score drops faster as rank drops.</li></ul></li><li><strong>Aggregate scores</strong>: sum each document’s reciprocal score across all lists; the one with the highest total wins the final ranking.</li></ul><h5 id="3-Formula"><a href="#3-Formula" class="headerlink" title="3. Formula"></a>3. Formula</h5><p>$$RRF(d) &#x3D; \sum_{i&#x3D;1}^{n} \frac{1}{k + rank_i(d)}$$</p><ul><li><code>rank_i</code>: document <code>d</code>‘s ranking in the <code>i</code>-th retrieval list (starting from 1).</li><li><code>k</code>: <strong>smoothing constant</strong>, the industry default is usually <strong>60</strong>.</li></ul><p><strong>Why is <code>k</code> needed?</strong></p><p>Without <code>k</code>, the gap between rank 1 (1.0 score) and rank 100 (0.01 score) is enormous, giving the first-ranked document overwhelming power. <code>k</code> acts as a “shock absorber” — compressing extreme differences and weakening the influence of noisy documents that happen to land at rank 1.</p><table><thead><tr><th>Parameter Value</th><th>Effect</th><th>Risk</th></tr></thead><tbody><tr><td><code>k = 0</code> (extremely sensitive)</td><td>Rank 1 has absolute dominance</td><td>If one algorithm accidentally puts a noisy doc at rank 1, it disrupts the whole RAG result</td></tr><tr><td><code>k = 60</code> (smooth and robust, industry default)</td><td>High rank in a single list no longer monopolizes</td><td>No obvious risk; requires multiple retrieval strategies to agree for a doc to win</td></tr></tbody></table><h5 id="4-RRF’s-core-advantage-Only-cares-about-rank"><a href="#4-RRF’s-core-advantage-Only-cares-about-rank" class="headerlink" title="4. RRF’s core advantage: Only cares about rank"></a>4. RRF’s core advantage: Only cares about rank</h5><ul><li><strong>No score normalization needed</strong>: no need to convert BM25’s 20 score and vector search’s 0.9 score — compare positions directly.</li><li><strong>Seamless cross-strategy merging</strong>: with 2 or 5 different retrieval techniques, as long as each gives a rank list, RRF can fuse fairly.</li></ul><h5 id="5-Parameters-for-tuning-semantic-vs-keyword-weights"><a href="#5-Parameters-for-tuning-semantic-vs-keyword-weights" class="headerlink" title="5. Parameters for tuning semantic vs keyword weights"></a>5. Parameters for tuning semantic vs keyword weights</h5><p>Standard RRF itself has no direct “semantic vs keyword” weight parameter — <code>k</code> is just a smoothing constant, not a control over their ratio.<br>But <strong>Weighted RRF</strong> introduces an independent weight <code>w</code> for each retrieval strategy in the formula:</p><p>$$RRF(d) &#x3D; \sum_{i&#x3D;1}^{n} \frac{w_i}{k + rank_i(d)}$$</p><ul><li>Increase <code>w_semantic</code> → results skew toward semantic understanding (synonyms, conceptual relevance)</li><li>Increase <code>w_keyword</code> → results skew toward exact literal matching</li></ul><p><strong>Where do you tune this weight in an actual implementation?</strong><br>Take <strong>LangChain</strong>‘s <code>EnsembleRetriever</code> as the most intuitive example:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> langchain.retrievers <span class="keyword">import</span> BM25Retriever, EnsembleRetriever</span><br><span class="line"><span class="keyword">from</span> langchain_community.vectorstores <span class="keyword">import</span> FAISS</span><br><span class="line"></span><br><span class="line"><span class="comment"># Two retrievers</span></span><br><span class="line">bm25_retriever = BM25Retriever.from_documents(docs)</span><br><span class="line">vector_retriever = FAISS.from_documents(docs, embedding_model).as_retriever()</span><br><span class="line"></span><br><span class="line"><span class="comment"># weights correspond to w_i in the formula; the two values usually sum to 1.0</span></span><br><span class="line">ensemble_retriever = EnsembleRetriever(</span><br><span class="line">    retrievers=[bm25_retriever, vector_retriever],</span><br><span class="line">    weights=[<span class="number">0.3</span>, <span class="number">0.7</span>]  <span class="comment"># keyword 30%, semantic 70%</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><blockquote><p><code>weights=[0.3, 0.7]</code> are <code>w_keyword</code> and <code>w_semantic</code> from the formula. Increase semantic → better at understanding intent; increase keyword → better at exact proper-noun matching.</p></blockquote><h4 id="4-Evaluation-Metrics"><a href="#4-Evaluation-Metrics" class="headerlink" title="4. Evaluation Metrics"></a>4. Evaluation Metrics</h4><p>Quantifying how good a retriever is with numbers is the scientific basis of system tuning.</p><h5 id="Three-core-metrics"><a href="#Three-core-metrics" class="headerlink" title="Three core metrics"></a>Three core metrics</h5><table><thead><tr><th>Metric</th><th>Core Goal</th><th>One-Line Explanation</th></tr></thead><tbody><tr><td><strong>Recall@K</strong></td><td>“find all of them”</td><td>Among the top-K results, how many genuinely relevant docs were found</td></tr><tr><td><strong>Precision &amp; MAP</strong></td><td>“find precisely + rank well”</td><td>Precision measures how much noise is in what you return; MAP further evaluates whether relevant docs are at the top</td></tr><tr><td><strong>MRR (Mean Reciprocal Rank)</strong></td><td>“first hit”</td><td>The earlier the first relevant doc appears, the higher the score</td></tr></tbody></table><h5 id="A-concrete-example-to-understand-Precision-and-Recall"><a href="#A-concrete-example-to-understand-Precision-and-Recall" class="headerlink" title="A concrete example to understand Precision and Recall"></a>A concrete example to understand Precision and Recall</h5><p><strong>Scenario</strong>: the knowledge base has 100 docs, of which <strong>10 are truly relevant (Ground Truth)</strong>. The retriever returns <strong>8</strong>, of which <strong>6 are truly relevant</strong>.</p><p>$$Precision &#x3D; \frac{\text{returned and relevant}}{\text{total returned}} &#x3D; \frac{6}{8} &#x3D; 75%$$</p><p>$$Recall &#x3D; \frac{\text{returned and relevant}}{\text{all relevant docs in the library}} &#x3D; \frac{6}{10} &#x3D; 60%$$</p><blockquote><p><strong>Precision</strong>: from the perspective of “the docs you returned” — how many are real, how much is noise?<br><strong>Recall</strong>: from the perspective of “the knowledge base” — of the 10 correct answers, how many did you find?</p></blockquote><h5 id="The-intrinsic-tension-between-Precision-and-Recall"><a href="#The-intrinsic-tension-between-Precision-and-Recall" class="headerlink" title="The intrinsic tension between Precision and Recall"></a>The intrinsic tension between Precision and Recall</h5><p>The two naturally pull against each other:</p><table><thead><tr><th>Action</th><th>Precision</th><th>Recall</th><th>Reason</th></tr></thead><tbody><tr><td>Larger K (e.g. 8 → 50)</td><td>⬇️ drops</td><td>⬆️ rises</td><td>catch more; more noise, fewer misses</td></tr><tr><td>Smaller K (e.g. 8 → 3)</td><td>⬆️ rises</td><td>⬇️ drops</td><td>only the surest picks; precise but incomplete</td></tr></tbody></table><h5 id="Practical-uses-of-metrics"><a href="#Practical-uses-of-metrics" class="headerlink" title="Practical uses of metrics"></a>Practical uses of metrics</h5><ul><li><strong>Establish baseline performance</strong>: score your current system and make clear where you are</li><li><strong>Validate optimization impact</strong>: when changing Embedding model, adjusting Chunk size, or modifying hybrid-retrieval weights, compare before&#x2F;after metrics to confirm whether the change actually worked</li></ul><h5 id="The-most-critical-prerequisite-Ground-Truth"><a href="#The-most-critical-prerequisite-Ground-Truth" class="headerlink" title="The most critical prerequisite: Ground Truth"></a>The most critical prerequisite: Ground Truth</h5><blockquote><p><strong>All metrics depend on a “ground truth” dataset</strong></p></blockquote><ul><li><strong>What is Ground Truth?</strong> A human-annotated dataset. For example, for question A, pre-annotate that “doc 1” and “doc 5” are the only correct answers in the knowledge base.</li><li><strong>Why does it matter?</strong> Recall, Precision, MAP, MRR all require comparing “system’s answers” against “standard answers”. Without Ground Truth, scientific tuning is impossible.</li></ul><h4 id="5-Embedding-Model-In-Depth"><a href="#5-Embedding-Model-In-Depth" class="headerlink" title="5. Embedding Model In-Depth"></a>5. Embedding Model In-Depth</h4><h5 id="Contrastive-Training-Process"><a href="#Contrastive-Training-Process" class="headerlink" title="Contrastive Training Process"></a>Contrastive Training Process</h5><p>Training objective: pull vectors of <strong>similar content closer</strong>, push vectors of <strong>dissimilar content apart</strong>.</p><h5 id="Positive-Samples-From-“Natural-Pairings-on-the-Internet”"><a href="#Positive-Samples-From-“Natural-Pairings-on-the-Internet”" class="headerlink" title="Positive Samples: From “Natural Pairings on the Internet”"></a>Positive Samples: From “Natural Pairings on the Internet”</h5><p>Humans’ natural behavior on the internet inherently produces massive paired data:</p><table><thead><tr><th>Data Source</th><th>Positive Sample Pairing</th><th>Who “labels” it?</th></tr></thead><tbody><tr><td>Image <code>alt</code> attributes on web pages</td><td><code>&lt;img alt=&quot;willow tree at sunset&quot;&gt;</code> → (image, “willow tree at sunset”)</td><td>Web authors writing alt text unconsciously create the pairing</td></tr><tr><td>Forum Q&amp;A (StackOverflow, Zhihu)</td><td>(question, best answer)</td><td>Users asking and answering unconsciously create the pairing</td></tr><tr><td>Wikipedia</td><td>(article title, first paragraph)</td><td>Humans writing encyclopedias — the structure naturally pairs</td></tr><tr><td>News images</td><td>(photo, caption)</td><td>Editors writing captions unconsciously create the pairing</td></tr></tbody></table><blockquote><p><strong>Core idea: the internet itself is a huge “implicit-annotation dataset”</strong>. When humans create content in daily life, they are unconsciously labeling data for AI.</p></blockquote><h5 id="Negative-Samples-Automatically-Generated-by-Algorithms"><a href="#Negative-Samples-Automatically-Generated-by-Algorithms" class="headerlink" title="Negative Samples: Automatically Generated by Algorithms"></a>Negative Samples: Automatically Generated by Algorithms</h5><table><thead><tr><th>Negative Type</th><th>Source</th><th>Needs Human?</th></tr></thead><tbody><tr><td><strong>In-batch negatives</strong></td><td>other samples in the batch automatically play the role</td><td>❌ fully automatic</td></tr><tr><td><strong>Random negatives</strong></td><td>sampled at random from the dataset</td><td>❌ fully automatic</td></tr><tr><td><strong>Hard negatives</strong></td><td>use a weak model to first retrieve “close but wrong” samples</td><td>⚠️ partly needs human verification</td></tr><tr><td><strong>LLM-generated hard negatives</strong></td><td>let GPT-4 generate semantically similar but different sentences</td><td>❌ AI-generated</td></tr></tbody></table><h5 id="Rare-Scenarios-That-Need-Human-Annotation"><a href="#Rare-Scenarios-That-Need-Human-Annotation" class="headerlink" title="Rare Scenarios That Need Human Annotation"></a>Rare Scenarios That Need Human Annotation</h5><p>Only when you need a <strong>high-precision Benchmark</strong> (used to test how good a model is) do you bring in human annotation:</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></pre></td><td class="code"><pre><span class="line">STS (Semantic Textual Similarity) dataset:</span><br><span class="line">  Humans score the similarity of two sentences from 1 to 5</span><br><span class="line">  But this is used to &quot;evaluate&quot; the model, not the main data used to &quot;train&quot; it</span><br></pre></td></tr></table></figure><p><strong>Training process</strong>: forward pass generates vectors → build similarity matrix → InfoNCE loss penalizes cases where “positive samples don’t rank high” → backprop updates weights.</p><blockquote><p>CLIP’s 400M training pairs are almost entirely auto-acquired. The internet itself is the implicit-annotation dataset; human annotation is only used for evaluation benchmarks, not as the main training data.</p><p>I also feel that this training process is quite similar to the node graph on my personal website — the node graph has repulsive and attractive forces, the entire node-graph system needs to maintain a stable state. Therefore, to maintain the balance of repulsion and attraction, it needs an adjustment process. And I think this adjustment process may be exactly what embedding is.</p><p>A common misconception — <strong>LLM parameters vs Embedding dimensions</strong>: parameters are the total weights inside the neural network (CLIP has ~150M), i.e. the knowledge learned during training; the dimension is the length of the output vector (e.g. 512), a fixed spec at design time. Parameters are the total knowledge the model learned (the more, the “smarter”); the dimension is the length of the output vector (it bounds the upper limit of expressiveness). They are not the same thing.</p></blockquote><h3 id="Module-3-Information-Retrieval-with-Vector-Data"><a href="#Module-3-Information-Retrieval-with-Vector-Data" class="headerlink" title="Module 3: Information Retrieval with Vector Data"></a>Module 3: Information Retrieval with Vector Data</h3><h4 id="1-From-Brute-Force-Search-to-ANN"><a href="#1-From-Brute-Force-Search-to-ANN" class="headerlink" title="1. From Brute-Force Search to ANN"></a>1. From Brute-Force Search to ANN</h4><p>All vector-retrieval algorithms share one key variable — <strong>K</strong>: the number of vectors most similar to the query (Top-K) to return. Around “how to efficiently find these K vectors”, two major schools have evolved: <strong>exact retrieval</strong> and <strong>approximate retrieval (ANN)</strong>.</p><h5 id="The-most-basic-algorithm-brute-force-search-Brute-Force-Flat-Search-Linear-Scan"><a href="#The-most-basic-algorithm-brute-force-search-Brute-Force-Flat-Search-Linear-Scan" class="headerlink" title="The most basic algorithm: brute-force search (Brute Force &#x2F; Flat Search &#x2F; Linear Scan)"></a>The most basic algorithm: brute-force search (Brute Force &#x2F; Flat Search &#x2F; Linear Scan)</h5><p>The simplest direct approach — compute the distance (cosine &#x2F; Euclidean) between the query vector and <strong>every</strong> vector in the database, then sort and take the Top-K.</p><blockquote><p>In FAISS it’s called <code>IndexFlatL2</code>; in academia it’s called <strong>Exact KNN</strong>; in engineering it’s often called <strong>Brute Force</strong> or <strong>Linear Scan</strong>. They’re all the same thing: build no index, brute force to the end.</p></blockquote><ul><li><strong>Pros</strong>: trivial to implement (one line of numpy); <strong>Recall &#x3D; 100%</strong>, the accuracy ceiling and evaluation baseline of all ANN algorithms</li><li><strong>Fatal problem</strong>: complexity is <strong>O(N×D)</strong> (N &#x3D; number of vectors, D &#x3D; dimension). The bigger the database, the slower a single query — millions of vectors is already hard; billions are outright unusable</li><li><strong>Suitable for</strong>: small datasets (&lt; 100K), offline evaluation, providing ground truth for ANN</li></ul><h5 id="The-solution-ANN-Approximate-Nearest-Neighbor-search"><a href="#The-solution-ANN-Approximate-Nearest-Neighbor-search" class="headerlink" title="The solution: ANN (Approximate Nearest Neighbor) search"></a>The solution: ANN (Approximate Nearest Neighbor) search</h5><p><strong>Core idea</strong>: pre-build an index, skip most vectors that can’t possibly be relevant, only do fine-grained comparison in a small range — sacrificing a tiny bit of accuracy in exchange for orders-of-magnitude faster queries.</p><p>Mainstream implementations:</p><ul><li><strong>HNSW</strong> (Hierarchical Navigable Small World graphs): based on “skip-list + small-world networks”; complexity drops to O(log N); high accuracy and fast queries — industry mainstream</li><li><strong>IVF</strong> (Inverted File Index): first K-means clustering; at query time only scan the nearest few clusters — suited for very large scale</li><li><strong>PQ</strong> (Product Quantization): splits high-dim vectors into segments then replaces with cluster codes — compresses vectors 64× or more, dramatically saving memory</li></ul><p>In engineering you must trade off between <strong>accuracy (Recall) ↔ speed (QPS) ↔ memory</strong>; common combinations are IVF+PQ, HNSW+PQ.</p><h4 id="2-Vector-Databases"><a href="#2-Vector-Databases" class="headerlink" title="2. Vector Databases"></a>2. Vector Databases</h4><p>A vector database is a database system designed specifically for <strong>storing, managing, and retrieving high-dimensional vectors</strong>. Ordinary databases store structured rows and columns; vector databases store Embedding vectors — and have ANN indexes built in, making semantic retrieval a first-class citizen.</p><h5 id="Core-differences-from-traditional-databases"><a href="#Core-differences-from-traditional-databases" class="headerlink" title="Core differences from traditional databases"></a>Core differences from traditional databases</h5><table><thead><tr><th>Dimension</th><th>Relational DB (MySQL)</th><th>Vector DB (Qdrant &#x2F; Milvus)</th></tr></thead><tbody><tr><td>Core data</td><td>structured rows &amp; columns</td><td>high-dim float vectors</td></tr><tr><td>Query method</td><td>SQL exact matching</td><td>ANN approximate-similarity retrieval</td></tr><tr><td>Index type</td><td>B-Tree, Hash</td><td>HNSW, IVF, PQ</td></tr><tr><td>Typical question</td><td>“find record with id&#x3D;42”</td><td>“find Top-10 semantically most similar”</td></tr></tbody></table><blockquote><p>Vector databases usually <strong>store vectors + metadata together</strong>, supporting “first hard-filter by metadata, then do vector retrieval” — i.e. the combination of Metadata Filtering and semantic retrieval mentioned earlier.</p></blockquote><h5 id="Mainstream-Vector-Database-Comparison"><a href="#Mainstream-Vector-Database-Comparison" class="headerlink" title="Mainstream Vector Database Comparison"></a>Mainstream Vector Database Comparison</h5><ul><li><strong>Qdrant</strong>: written in Rust; strong performance; REST&#x2F;gRPC API; supports payload filtering; open source, self-hostable</li><li><strong>Milvus</strong>: designed for very large scale (billions); cloud-native architecture; suited for production distributed scenarios</li><li><strong>ChromaDB</strong>: lightest, starts in a few lines of code; first pick for dev&#x2F;debugging, not suited for large-scale production</li><li><strong>pgvector</strong>: PostgreSQL extension; add vector retrieval to an existing PG database with no new system</li><li><strong>FAISS</strong>: strictly speaking a library, not a database — no persistence, no CRUD, but the source of all vector DB algorithms underneath</li></ul><h5 id="Basic-Process-for-Creating-a-Vector-Database"><a href="#Basic-Process-for-Creating-a-Vector-Database" class="headerlink" title="Basic Process for Creating a Vector Database"></a>Basic Process for Creating a Vector Database</h5><p><strong>Step 1 — Database Setup</strong><br>Create a collection and define its schema — specifying which fields to store, the vector dimension, and which distance metric to use (cosine &#x2F; L2 &#x2F; inner product). This is the container for all subsequent operations, equivalent to creating a table.</p><p><strong>Step 2 — Loading Documents</strong><br>Read raw data (text, PDFs, images, etc.) into memory, chunking as needed — splitting long documents into small chunks suitable for embedding, to avoid semantic dilution when a chunk is too long.</p><p><strong>Step 3 — Sparse Vectors (for keyword retrieval)</strong><br>Use algorithms like BM25 to generate sparse vectors for each document chunk — most positions are zero; only positions for terms that appear have weights. Designed for exact keyword matching; this is the keyword side of hybrid retrieval.</p><p><strong>Step 4 — Dense Vectors (for semantic retrieval)</strong><br>Pass document chunks through an Embedding model (e.g. BGE, CLIP), outputting a dense vector for each chunk — every dimension has a value, carrying semantic information. The core of semantic search; understands synonyms and intent.</p><p><strong>Step 5 — Build the HNSW Index</strong><br>Build an HNSW (Hierarchical Navigable Small World) index on top of the dense vectors. Pre-weave a “navigation network” among the vectors; at query time you follow graph jumps to locate, reducing complexity from O(N) to O(log N) and achieving millisecond-level ANN search. <em>This step can be skipped; if skipped, you have brute-force retrieval.</em></p><h4 id="3-Chunk-Chunking-Techniques"><a href="#3-Chunk-Chunking-Techniques" class="headerlink" title="3. Chunk (Chunking Techniques)"></a>3. Chunk (Chunking Techniques)</h4><p>Splitting long documents into small chunks suitable for embedding is the key pre-processing step of the RAG indexing stage. Too-large a chunk dilutes semantics; too-small a chunk loses context; whether you split well directly affects retrieval quality.</p><h5 id="Why-do-we-need-chunking"><a href="#Why-do-we-need-chunking" class="headerlink" title="Why do we need chunking?"></a>Why do we need chunking?</h5><p>Embedding models have a <strong>token limit</strong> (e.g. BERT series 512 tokens, BGE-M3 8192 tokens). Stuffing an entire book in only yields one fuzzy “average semantic”, and retrieval will struggle to hit specific passages. After chunking, each chunk has its own independent vector, and retrieval precision improves dramatically.</p><h5 id="Main-chunking-strategies"><a href="#Main-chunking-strategies" class="headerlink" title="Main chunking strategies"></a>Main chunking strategies</h5><ul><li><strong>Fixed-size Chunking</strong>: hard-cut by character or token count; crude. Downside: may cut mid-sentence and lose context. Usually paired with <strong>Overlap</strong> — adjacent chunks share some tokens — to mitigate the truncation issue.</li><li><strong>Semantic Chunking</strong>: cuts along sentence boundaries, paragraphs, heading hierarchy; preserves natural semantic integrity. Suited for structured documents (Markdown, PDF).</li><li><strong>Recursive Character Splitting</strong>: LangChain’s default strategy; tries priorities like <code>\n\n → \n → period → space</code> in order, cutting on natural boundaries as much as possible; balances simplicity with semantic completeness.</li></ul><h4 id="4-Some-More-Advanced-Chunking-Techniques"><a href="#4-Some-More-Advanced-Chunking-Techniques" class="headerlink" title="4. Some More Advanced Chunking Techniques"></a>4. Some More Advanced Chunking Techniques</h4><h5 id="Using-LLMs-for-Semantic-Chunking"><a href="#Using-LLMs-for-Semantic-Chunking" class="headerlink" title="Using LLMs for Semantic Chunking"></a>Using LLMs for Semantic Chunking</h5><p>Traditional chunking relies on rules (paragraphs, headings, fixed size); “LLM semantic chunking” lets the model truly <strong>understand the semantic boundaries of content</strong> before deciding how to cut — higher quality but also higher cost.</p><hr><p><strong>① Embedding-similarity-based Semantic Chunking (SemanticChunker)</strong></p><p>Principle: split text into sentences first → compute embedding for each sentence → compute <strong>cosine similarity of adjacent sentences</strong> → when similarity drops sharply, the topic has shifted; cut there.</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></pre></td><td class="code"><pre><span class="line">sentence1 → sentence2 → sentence3 ↘↘sharp drop↘↘ sentence4 → sentence5</span><br><span class="line">                                          ↑</span><br><span class="line">                                      cut point (semantic break)</span><br></pre></td></tr></table></figure><ul><li><strong>Tool</strong>: LangChain’s <code>SemanticChunker</code> directly wraps this logic</li><li><strong>Pros</strong>: no LLM inference needed; low cost; truly cuts by semantic breakpoints, not by character count</li><li><strong>Cons</strong>: needs a similarity threshold; a badly-chosen threshold cuts too finely or too coarsely</li></ul><hr><p><strong>② Proposition Chunking</strong></p><p>Principle: use an LLM to further distill each text segment into several <strong>atomic propositions</strong> — each proposition is an <strong>independent, complete, self-contained</strong> minimal fact unit.</p><blockquote><p>Example: original text “Einstein published the special theory of relativity in 1905 and won the Nobel Prize for the photoelectric effect”<br>→ Proposition 1: “Einstein published the special theory of relativity in 1905”<br>→ Proposition 2: “Einstein won the Nobel Prize for his research on the photoelectric effect”</p></blockquote><ul><li><strong>Pros</strong>: at retrieval time, query and proposition map one-to-one; precision is extremely high; each proposition is self-contained, understandable without context</li><li><strong>Cons</strong>: every chunk must invoke an LLM to distill — <strong>token-heavy and slow</strong>; indexing is expensive</li><li><strong>Suited for</strong>: scenarios that demand the highest knowledge-base retrieval quality (medical, legal, precision Q&amp;A)</li></ul><hr><p><strong>③ Agentic Chunking</strong></p><p>Hand the whole document directly to the LLM, letting it autonomously decide semantic boundaries, output split points, or output the chunks directly. Most flexible; can handle complex unstructured documents (e.g. dialogue records, mixed-format docs) but <strong>highest cost</strong>; generally only used in offline preprocessing pipelines.</p><hr><h5 id="Comparison-of-the-three-approaches"><a href="#Comparison-of-the-three-approaches" class="headerlink" title="Comparison of the three approaches"></a>Comparison of the three approaches</h5><table><thead><tr><th>Approach</th><th>Calls LLM?</th><th>Cost</th><th>Precision</th><th>Suited Scenarios</th></tr></thead><tbody><tr><td>Embedding similarity</td><td>only Embedding model</td><td>low</td><td>medium</td><td>general scenarios; quick build</td></tr><tr><td>Proposition chunking</td><td>yes (distill propositions)</td><td>high</td><td>high</td><td>precision Q&amp;A, knowledge bases</td></tr><tr><td>Agentic chunking</td><td>yes (understand + cut)</td><td>highest</td><td>highest</td><td>complex unstructured docs</td></tr></tbody></table><blockquote><p>The most common pragmatic compromise: first use <strong>recursive character splitting</strong> for rough cuts, then use <strong>embedding similarity</strong> for semantic-boundary correction; only enable <strong>proposition chunking</strong> on core knowledge-base passages.</p></blockquote><h4 id="5-Query-Parsing"><a href="#5-Query-Parsing" class="headerlink" title="5. Query Parsing"></a>5. Query Parsing</h4><p>User questions are often <strong>colloquial, vague, and contain multiple sub-intents</strong> — using them directly for retrieval, vector similarity will drift, missing genuinely relevant documents. The goal of “query parsing” is to use an LLM to <strong>make the question more retrieval-friendly before retrieval</strong>.</p><p>Core idea: <strong>before Retrieval, use one (or more) LLM calls to transform the Query, then retrieve.</strong></p><hr><h5 id="Query-Rewriting"><a href="#Query-Rewriting" class="headerlink" title="Query Rewriting"></a>Query Rewriting</h5><p><strong>Principle</strong>: directly use an LLM to rewrite the user’s original question into one (or more) new queries that are more precise and closer to the language of the knowledge-base documents.</p><blockquote><p>Example: user asks “how to fix this bug” → rewritten: “how to fix an IndexError array-out-of-bounds exception?”</p></blockquote><p><strong>Why it works</strong>: Embedding models use symmetric similarity — the more alike the user’s words and the document’s words, the closer the vectors. Rewriting bridges the “colloquial ↔ document language” vocabulary gap.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Rewrite prompt sketch</span></span><br><span class="line">system = <span class="string">&quot;You are a professional search assistant. Rewrite the user&#x27;s question into a precise query suited for document retrieval. Output only the rewritten sentence.&quot;</span></span><br><span class="line">rewritten = llm.chat(system, user_query)</span><br><span class="line">results = vector_db.search(embed(rewritten), top_k=<span class="number">5</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>Pros</strong>: simple to implement; one LLM call; significantly improves vocabulary-mismatch</li><li><strong>Cons</strong>: the rewrite may drift from the original meaning; adds one LLM’s latency</li></ul><hr><h5 id="Query-Decomposition"><a href="#Query-Decomposition" class="headerlink" title="Query Decomposition"></a>Query Decomposition</h5><p><strong>Principle</strong>: faced with a complex query containing multiple sub-questions, let the LLM <strong>break it into several independent sub-questions</strong>; each sub-question retrieves separately; finally aggregate the answers.</p><blockquote><p>Example: user asks “What are the pros and cons of Python vs JavaScript in web development, and which should I choose?”<br>→ sub-question 1: “What are the pros of Python for web development?”<br>→ sub-question 2: “What are the cons of Python for web development?”<br>→ sub-question 3: “What are the pros of JavaScript for web development?”<br>→ sub-question 4: “What are the cons of JavaScript for web development?”</p></blockquote><p>Each sub-question retrieves separately; the per-sub-question retrieval results are merged and handed to the LLM for the final answer.</p><p><strong>Why it works</strong>: a complex question often corresponds to multiple scattered pieces of information in the knowledge base. Without decomposition, it’s hard for a single query to hit all relevant passages at once; with decomposition each query is more focused, and retrieval precision improves dramatically.</p><ul><li><strong>Pros</strong>: especially suited for multi-hop reasoning and comparison questions</li><li><strong>Cons</strong>: number of sub-questions is uncontrolled; multiple retrievals multiply cost; aggregation logic is complex</li></ul><hr><h5 id="HyDE-—-Hypothetical-Document-Embeddings"><a href="#HyDE-—-Hypothetical-Document-Embeddings" class="headerlink" title="HyDE — Hypothetical Document Embeddings"></a>HyDE — Hypothetical Document Embeddings</h5><p><strong>Principle</strong>: instead of embedding the Query directly, first let the LLM <strong>generate a hypothetical “ideal answer”</strong>, then embed that hypothetical answer and use it for retrieval.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">User Query → LLM generates &quot;hypothetical answer&quot; → Embed(hypothetical answer) → vector retrieval</span><br></pre></td></tr></table></figure><blockquote><p>Example: user asks “Why is HNSW fast to query?”<br>→ LLM generates a hypothetical answer: “HNSW is based on a hierarchical graph structure; at query time, it quickly locates a general area from the high-level sparse graph, then does fine-grained search layer by layer…”<br>→ Embedding this hypothetical answer → compared to embedding the question directly, the vector is closer to the real documents in the knowledge base</p></blockquote><p><strong>Intuition</strong>: question embeddings and answer embeddings are not in the same place in semantic space. A hypothetical answer is more like a “real document”; therefore its vector is closer to documents in the knowledge base, so retrieval Recall is higher.</p><ul><li><strong>Pros</strong>: very effective for “knowledge-intensive Q&amp;A”; doesn’t depend on keywords; purely semantically driven</li><li><strong>Cons</strong>: the hypothetical answer may contain hallucinations, but <strong>the retrieval stage doesn’t rely on the answer’s correctness, only its vector</strong>, so hallucinations don’t directly affect results</li><li><strong>Suited for</strong>: specialized-domain Q&A; scenarios where document phrasing differs greatly from question phrasing</li></ul><hr><h5 id="Multi-Query"><a href="#Multi-Query" class="headerlink" title="Multi-Query"></a>Multi-Query</h5><p><strong>Principle</strong>: let an LLM <strong>generate N variants of the same question from multiple angles</strong>, retrieve each separately, then deduplicate and merge the results (often combined with RRF).</p><blockquote><p>Example: original question “the limitations of RAG”<br>→ variant 1: “In what scenarios does RAG perform poorly?”<br>→ variant 2: “What are the drawbacks of Retrieval-Augmented Generation?”<br>→ variant 3: “Failure cases of RAG systems”</p></blockquote><p>Each of the three queries retrieves separately; after merging, re-rank with RRF; final Top-K coverage far exceeds any single query.</p><ul><li><strong>Pros</strong>: fills coverage blind spots of a single query; naturally pairs with RRF</li><li><strong>Cons</strong>: N embeddings + LLM call; latency and cost scale linearly</li></ul><hr><h5 id="Summary-Comparison-of-Approaches"><a href="#Summary-Comparison-of-Approaches" class="headerlink" title="Summary &#x2F; Comparison of Approaches"></a>Summary &#x2F; Comparison of Approaches</h5><table><thead><tr><th>Approach</th><th>Core Idea</th><th>Suited Scenarios</th><th>Extra LLM Calls</th><th>Risk</th></tr></thead><tbody><tr><td>Query Rewriting</td><td>swap words; sound more like doc language</td><td>colloquial &#x2F; technical vocab mismatch</td><td>1</td><td>drift from original meaning</td></tr><tr><td>Query Decomposition</td><td>split into sub-questions; retrieve separately</td><td>multi-hop &#x2F; comparison complex questions</td><td>1 (split) + N retrievals</td><td>sub-question explosion</td></tr><tr><td>HyDE</td><td>generate hypothetical answer then retrieve</td><td>question phrasing differs a lot from docs</td><td>1</td><td>hallucination shifts the vector</td></tr><tr><td>Multi-Query</td><td>generate multiple variants for blind-spot coverage</td><td>low recall; narrow coverage</td><td>1 (generate) + N retrievals</td><td>high cost</td></tr></tbody></table><blockquote><p><strong>In engineering practice</strong>: the most common lightweight recipe is <strong>Query Rewriting + Multi-Query (N&#x3D;3) + RRF</strong> — only one extra LLM call, and retrieval quality improves noticeably. HyDE and Decomposition are reserved for scenarios with extreme quality requirements (e.g. legal-doc Q&amp;A).</p></blockquote><h4 id="6-Reranker-Re-ranking-Strategy"><a href="#6-Reranker-Re-ranking-Strategy" class="headerlink" title="6. Reranker &amp; Re-ranking Strategy"></a>6. Reranker &amp; Re-ranking Strategy</h4><blockquote><p><strong>Background</strong>: vector retrieval (the regular two-encoding kind called Bi-Encoder) is fast but coarse — it compresses the Query and Document into one vector each and takes the dot product, losing lots of fine-grained interaction information. A Reranker is the second-stage module that does fine-grained scoring and re-ranking on Top-N candidates after coarse retrieval.</p></blockquote><h5 id="Two-stage-retrieval-Bi-Encoder-pipeline"><a href="#Two-stage-retrieval-Bi-Encoder-pipeline" class="headerlink" title="Two-stage retrieval (Bi-Encoder) pipeline"></a>Two-stage retrieval (Bi-Encoder) pipeline</h5><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><span class="line">User Query</span><br><span class="line">    ↓</span><br><span class="line">[Stage 1] Vector retrieval (Bi-Encoder + FAISS) → Top-100 candidates   ← fast but coarse</span><br><span class="line">    ↓</span><br><span class="line">[Stage 2] (Cross-Encoder) Reranker fine-grained scoring → Top-10 final results   ← slow but accurate</span><br></pre></td></tr></table></figure><h5 id="Cross-Encoder"><a href="#Cross-Encoder" class="headerlink" title="Cross-Encoder"></a>Cross-Encoder</h5><p><strong>Principle</strong>: concatenate the Query and Document and input them to the same Transformer, letting all tokens of the two text segments attend to each other — directly output a relevance score.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Input:  [CLS] Query tokens [SEP] Document tokens [SEP]</span><br><span class="line">Output: a relevance score between 0 and 1</span><br></pre></td></tr></table></figure><ul><li><strong>Pros</strong>: full attention; most accurate scoring; the precision ceiling</li><li><strong>Cons</strong>: Document vectors <strong>can’t be precomputed</strong> — every query has to rerun each candidate document with the Query; 100 candidates &#x3D; 100 model inferences; high latency</li><li><strong>Suited for</strong>: small candidate sets (within Top-100), scenarios that demand the highest precision</li></ul><h5 id="ColBERT-Contextualized-Late-Interaction-over-BERT"><a href="#ColBERT-Contextualized-Late-Interaction-over-BERT" class="headerlink" title="ColBERT (Contextualized Late Interaction over BERT)"></a>ColBERT (Contextualized Late Interaction over BERT)</h5><p><img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260606-220900.png" alt="image.png"><br>As shown in the image, each word in the question will resonate with every word in the article, yielding more precise results.</p><p><strong>Principle</strong>: Query and Document are still <strong>encoded separately</strong> (so Documents can be precomputed), but instead of compressing to a single vector, the vector of each token is preserved. The relevance score is computed via <strong>MaxSim</strong>: for each token of the Query (split by token), find the most similar token across all Document tokens, sum across.</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></pre></td><td class="code"><pre><span class="line">Query    → [q₁, q₂, q₃, ...]     one vector per token</span><br><span class="line">Document → [d₁, d₂, d₃, ...]    one vector per token (can be stored offline)</span><br><span class="line"></span><br><span class="line">Score = Σ max_j sim(qᵢ, dⱼ)     ← &quot;Late Interaction&quot;</span><br></pre></td></tr></table></figure><ul><li><strong>Pros</strong>: Documents can be precomputed offline and stored; interaction is much richer than a single-vector Bi-Encoder</li><li><strong>Cons</strong>: large storage (one vector per token, longer docs cost more); slower than Bi-Encoder, faster than Cross-Encoder</li><li><strong>Position</strong>: the <strong>middle tier</strong> between precision and speed; suited for larger candidate sets (Top-1000)</li></ul><h5 id="Side-by-side-comparison"><a href="#Side-by-side-comparison" class="headerlink" title="Side-by-side comparison"></a>Side-by-side comparison</h5><table><thead><tr><th></th><th>Bi-Encoder</th><th>ColBERT</th><th>Cross-Encoder</th></tr></thead><tbody><tr><td>Encoding</td><td>one vector per Query&#x2F;Doc</td><td>token-level vectors per Query&#x2F;Doc</td><td>concatenated then jointly encoded</td></tr><tr><td>Doc precompute</td><td>✅</td><td>✅</td><td>❌</td></tr><tr><td>Interaction granularity</td><td>coarse (single-vector dot product)</td><td>medium (token-level MaxSim)</td><td>fine (full attention)</td></tr><tr><td>Speed</td><td>fastest</td><td>medium</td><td>slowest</td></tr><tr><td>Precision</td><td>lowest</td><td>medium</td><td>highest</td></tr><tr><td>Typical use</td><td>stage-1 coarse retrieval</td><td>medium-scale reranking</td><td>small candidate set for precise ranking</td></tr></tbody></table><h4 id="7-ReRanking"><a href="#7-ReRanking" class="headerlink" title="7. ReRanking"></a>7. ReRanking</h4><blockquote><p>Re-ranking is not a specific model; it’s a <strong>pipeline-position</strong> concept — re-scoring and re-ordering the candidate set after coarse retrieval.<br>There are multiple implementations; §6 already detailed Cross-Encoder and ColBERT; here are two more paths.</p></blockquote><h5 id="Three-paths-for-re-ranking-overview"><a href="#Three-paths-for-re-ranking-overview" class="headerlink" title="Three paths for re-ranking (overview)"></a>Three paths for re-ranking (overview)</h5><table><thead><tr><th>Path</th><th>Core Mechanism</th><th>See</th></tr></thead><tbody><tr><td>Cross-Encoder</td><td>Query+Doc concatenated and jointly encoded; full token-level interaction</td><td>§6 ①</td></tr><tr><td>ColBERT</td><td>Keep token vectors; MaxSim late interaction</td><td>§6 ②</td></tr><tr><td><strong>RRF</strong></td><td>Merge ranks from multiple retrieval sources; no scoring required</td><td>below ↓</td></tr><tr><td><strong>Direct LLM scoring</strong></td><td>Let a large model judge relevance</td><td>below ↓</td></tr></tbody></table><hr><h5 id="①-RRF-Reciprocal-Rank-Fusion"><a href="#①-RRF-Reciprocal-Rank-Fusion" class="headerlink" title="① RRF (Reciprocal Rank Fusion)"></a>① RRF (Reciprocal Rank Fusion)</h5><p><strong>Suited when</strong>: you’ve run multiple retrieval sources in parallel (e.g. sparse BM25 + dense CLIP) and need to merge two rank lists into one.</p><p><strong>Core formula</strong>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">RRF_score(doc) = Σ  1 / (k + rank_i)       k usually taken as 60</span><br><span class="line">                 i</span><br></pre></td></tr></table></figure><p>For each retrieval source, compute each document’s contribution from its rank position (not its score), then sum up.</p><p><strong>Concrete example</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></pre></td><td class="code"><pre><span class="line">Query: &quot;stone covered with moss&quot;</span><br><span class="line"></span><br><span class="line">BM25 ranking (keyword):        CLIP ranking (visual):</span><br><span class="line">  stone_moss_03  → rank 1        stone_moss_03  → rank 1</span><br><span class="line">  stone_moss_01  → rank 2        rock_wet_01    → rank 2</span><br><span class="line">  rock_wet_01    → rank 8        stone_moss_01  → rank 5</span><br><span class="line"></span><br><span class="line">RRF fusion:</span><br><span class="line">  stone_moss_03 = 1/(60+1) + 1/(60+1) = 0.0328  ← both sides agree ✅</span><br><span class="line">  stone_moss_01 = 1/(60+2) + 1/(60+5) = 0.0315</span><br><span class="line">  rock_wet_01   = 1/(60+8) + 1/(60+2) = 0.0308</span><br></pre></td></tr></table></figure><p><strong>Why not just sum the two raw scores?</strong></p><p>BM25 scores (term-frequency stats) and CLIP cosine similarities have <strong>completely different units</strong>; summing directly is meaningless. RRF only cares about rank position, not absolute values, naturally sidestepping the unit-alignment problem.</p><hr><h5 id="②-Direct-LLM-scoring"><a href="#②-Direct-LLM-scoring" class="headerlink" title="② Direct LLM scoring"></a>② Direct LLM scoring</h5><p>Feed the candidate result’s text description to an LLM, letting the model judge relevance to the Query directly:</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><span class="line">Prompt example:</span><br><span class="line">  query: &quot;stone covered with moss&quot;</span><br><span class="line">  candidate asset: &quot;grayish-brown stone, surface densely covered with green moss,</span><br><span class="line">                    obvious weathered texture, suited to ancient-style scenes&quot;</span><br><span class="line"></span><br><span class="line">  Please score this asset&#x27;s relevance to the query (0-10) and give a one-sentence reason.</span><br><span class="line"></span><br><span class="line">LLM output:</span><br><span class="line">  Score: 9</span><br><span class="line">  Reason: the two core elements — stone and moss — match exactly; the weathered</span><br><span class="line">  texture further reinforces ancient-style relevance</span><br></pre></td></tr></table></figure><table><thead><tr><th></th><th>Notes</th></tr></thead><tbody><tr><td><strong>Pros</strong></td><td>strongest semantic understanding; customizable scoring criteria (style, scene fit, etc.)</td></tr><tr><td><strong>Cons</strong></td><td>each candidate requires an LLM call; high cost and latency</td></tr><tr><td><strong>Suited for</strong></td><td>very small candidate sets (within Top-5), or when explainable scoring rationale is needed</td></tr></tbody></table><hr><h5 id="Quick-selection-guide"><a href="#Quick-selection-guide" class="headerlink" title="Quick selection guide"></a>Quick selection guide</h5><table><thead><tr><th>Candidate Set Size</th><th>Recommended Strategy</th></tr></thead><tbody><tr><td>All 100K+</td><td>only Bi-Encoder (vector retrieval)</td></tr><tr><td>Top-1000</td><td>ColBERT or RRF fusing multiple sources</td></tr><tr><td>Top-100</td><td><strong>Cross-Encoder</strong> (recommended; best precision&#x2F;latency balance)</td></tr><tr><td>Top-10</td><td>Direct LLM scoring (optional; when maximum precision or explainability is needed)</td></tr></tbody></table><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">Mapping to the game asset library project</span></div><div class="callout-content"><ul><li><strong>Done (M1)</strong>: FTS5 keyword retrieval (sparse)</li><li><strong>Done (M4)</strong>: CLIP FAISS visual retrieval (dense)</li><li><strong>In progress (M5)</strong>: Cross-Encoder re-ranking, Top-100 → Top-10</li><li><strong>Optional upgrade</strong>: first fuse FTS5 and CLIP results with RRF, then send them to a Cross-Encoder —<br>sparse covers exact keyword hits; dense covers semantic territory; complementary.</li></ul></div></div><h3 id="Module-4-LLMs-Text-Generation"><a href="#Module-4-LLMs-Text-Generation" class="headerlink" title="Module 4: LLMs &amp; Text Generation"></a>Module 4: LLMs &amp; Text Generation</h3><p>The first three modules all covered <strong>Retrieve</strong> — finding the most relevant content. This module enters the final RAG stage — <strong>Generate</strong>: hand the retrieved content to a large language model (LLM) and generate a grounded answer.</p><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">Where this module sits in the RAG pipeline</span></div><div class="callout-content"><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">User question → [Retrieve] Top-K relevant chunks → [Assemble Prompt] → [LLM Generate] → answer</span><br><span class="line">                     ↑ Module 1–3                          ↑ Module 4</span><br></pre></td></tr></table></figure><p>Our asset-search tool is “retrieve-only, no generation”, so Module 4 is <strong>optional</strong> for it; but the local <code>starting-ragchatbot-codebase</code> (Q&amp;A bot) is full RAG, with Claude handling the generation step.</p></div></div><h4 id="Transformer-Architecture-Overview"><a href="#Transformer-Architecture-Overview" class="headerlink" title="Transformer Architecture Overview"></a>Transformer Architecture Overview</h4><blockquote><p>In one sentence: Transformer is the <strong>common foundation</strong> of nearly all modern LLMs, Embedding models, and Rerankers. Throughout these notes: BERT, CLIP, BGE, Cross-Encoder, GPT&#x2F;Claude — all are Transformer variants.</p></blockquote><h5 id="1-Why-Transformer-vs-RNN"><a href="#1-Why-Transformer-vs-RNN" class="headerlink" title="1. Why Transformer? (vs RNN)"></a>1. Why Transformer? (vs RNN)</h5><p><strong>First, the naming: why “Transformer” instead of “Attention”?</strong></p><ul><li><strong>Attention is a component, Transformer is the whole machine.</strong> Attention appeared as early as 2014 (Bahdanau) as an RNN accessory for machine translation; by 2017 it was a common technique — if the new architecture had also been called Attention, it would have collided with “RNN+attention”.</li><li><strong>The paper title is the argument, the architecture name is the product name.</strong> “Attention Is All You Need” reads between the lines: “previously it was RNN plus attention; drop RNN and <strong>keep only attention</strong> is enough.” The title shouts the slogan, the new architecture chose a new name — <strong>Transformer</strong> — to cut ties with the RNN era.</li><li><strong>“Transform” &#x3D; transforming representation layer by layer.</strong> The model rewrites each token’s vector layer by layer through stacked layers, ultimately turning “isolated word vectors” into “context-rich semantic vectors”. Attention only describes an operation at one layer (Q·K→weighted V); Transformer describes the whole machine — different abstraction levels.</li></ul><p><strong>So why does it matter?</strong> Before “Attention Is All You Need” (2017), text was handled mainly by RNN&#x2F;LSTM (<strong>RNN, Recurrent Neural Network</strong>), which <strong>reads one word at a time in sequence</strong>, with two hard flaws:</p><table><thead><tr><th>Problem</th><th>RNN&#x2F;LSTM</th><th>Transformer</th></tr></thead><tbody><tr><td>Parallelism</td><td>must be sequential; cannot parallelize</td><td>processes the entire sentence <strong>in one parallel pass</strong> ⭐</td></tr><tr><td>Long-range dependencies</td><td>info decays with distance</td><td>any two words can <strong>directly</strong> establish connection</td></tr><tr><td>Training speed</td><td>slow</td><td>fast (can fully utilize the GPU)</td></tr></tbody></table><p>Transformer solves both at once with <strong>self-attention</strong>, the prerequisite for LLMs scaling to hundreds of billions of parameters.</p><h5 id="2-Core-mechanism-Self-Attention"><a href="#2-Core-mechanism-Self-Attention" class="headerlink" title="2. Core mechanism: Self-Attention"></a>2. Core mechanism: Self-Attention</h5><p>Self-attention makes every word in a sentence “look at” every other word, absorbing information weighted by relevance.</p><p><strong>The key triad Q &#x2F; K &#x2F; V:</strong></p><table><thead><tr><th>Symbol</th><th>Meaning</th><th>Analogy (like retrieval?)</th></tr></thead><tbody><tr><td><strong>Query</strong></td><td>what I’m looking for</td><td>the user’s search term</td></tr><tr><td><strong>Key</strong></td><td>what “label” I offer</td><td>the document’s index</td></tr><tr><td><strong>Value</strong></td><td>what I actually contain</td><td>the document body</td></tr></tbody></table><p>Computation flow:</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></pre></td><td class="code"><pre><span class="line">For each word:</span><br><span class="line">  1. Dot-product the Query with all words&#x27; Keys → relevance scores</span><br><span class="line">  2. softmax normalization → attention weights (sum = 1)</span><br><span class="line">  3. Use the weights to take a weighted sum of all Values → the word&#x27;s new representation</span><br></pre></td></tr></table></figure><p><strong>Concrete example</strong> — understanding what “it” refers to in a sentence:</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></pre></td><td class="code"><pre><span class="line">Sentence: This stone is covered with moss because it is very damp</span><br><span class="line">                                 ↑</span><br><span class="line">The &quot;it&quot; word&#x27;s Query matches all words&#x27; Keys:</span><br><span class="line">  &quot;stone&quot;  → attention 0.7   ← highest weight; the model infers &quot;it = stone&quot;</span><br><span class="line">  &quot;moss&quot;   → attention 0.2</span><br><span class="line">  &quot;damp&quot;   → attention 0.1</span><br></pre></td></tr></table></figure><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">Key insight: attention is essentially a &quot;soft retrieval&quot;</span></div><div class="callout-content"><p>Query·Key → softmax → weighted sum of Values — this mechanism <strong>is the same math idea</strong> as the dense retrieval covered in Modules 2 and 3 (vector similarity → Top-K → fetch content), just happening <strong>inside</strong> the model, performed in real time on every token. Once you understand vector retrieval, you’ve understood half of attention.</p></div></div><h5 id="3-Multi-Head-Attention-Positional-Encoding-Overall-Structure"><a href="#3-Multi-Head-Attention-Positional-Encoding-Overall-Structure" class="headerlink" title="3. Multi-Head Attention + Positional Encoding + Overall Structure"></a>3. Multi-Head Attention + Positional Encoding + Overall Structure</h5><ul><li><strong>Multi-Head Attention</strong>: run multiple Q&#x2F;K&#x2F;V groups in parallel; each “head” focuses on a different angle (one head looks at syntax, one at coreference, one at semantics…). Then concatenate. Analogy: multiple experts reviewing the same sentence from different dimensions simultaneously.</li><li><strong>Positional Encoding</strong>: self-attention itself doesn’t distinguish word order (“dog bites man” and “man bites dog” look the same), so position info must be injected for each word.</li><li><strong>Residual connections + Layer Normalization + Feed-Forward Networks (FFN)</strong>: the standard accessories of every layer, ensuring that deep networks (tens to hundreds of layers) train stably.</li></ul><h5 id="4-Three-Architecture-Variants-Decide-the-model’s-use"><a href="#4-Three-Architecture-Variants-Decide-the-model’s-use" class="headerlink" title="4. Three Architecture Variants (Decide the model’s use)"></a>4. Three Architecture Variants (Decide the model’s use)</h5><table><thead><tr><th>Architecture</th><th>Representative Models</th><th>Strength</th><th>Role in these Notes</th></tr></thead><tbody><tr><td><strong>Encoder-only</strong></td><td>BERT, BGE-M3</td><td>understanding, vectorization</td><td><strong>Embedding models</strong> (§3.1), Cross-Encoder (§6)</td></tr><tr><td><strong>Decoder-only</strong></td><td>GPT, Claude, Qwen, Llama</td><td>text generation</td><td><strong>this module’s protagonist</strong>, responsible for “generation”</td></tr><tr><td><strong>Encoder-Decoder</strong></td><td>T5, BART</td><td>translation, summarization</td><td>less used in chat</td></tr></tbody></table><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">One image connecting all of these notes</span></div><div class="callout-content"><ul><li>You use <strong>CLIP&#x2F;BGE</strong> (Encoder) to vectorize assets → this is Transformer</li><li>You use <strong>Cross-Encoder</strong> (Encoder) for re-ranking → this is Transformer</li><li>You use <strong>Claude&#x2F;Qwen</strong> (Decoder) to generate answers → still Transformer<br>All three share the same lineage; the difference is only “how it’s wired, which half is used, how it’s trained”.</li></ul></div></div><hr><h4 id="LLM-Sampling-Strategies"><a href="#LLM-Sampling-Strategies" class="headerlink" title="LLM Sampling Strategies"></a>LLM Sampling Strategies</h4><p>Retrieval decides “what content to feed”; <strong>sampling strategy decides “how the model says the content”</strong> — with the same context and different parameters, the output can be rigorous or wide-ranging. For RAG this section directly affects <strong>hallucination rate</strong>.</p><h5 id="1-How-does-the-LLM-“spit-out”-words"><a href="#1-How-does-the-LLM-“spit-out”-words" class="headerlink" title="1. How does the LLM “spit out” words?"></a>1. How does the LLM “spit out” words?</h5><p>At each step, the Decoder model does only one thing: <strong>predict the probability distribution of the next word</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></pre></td><td class="code"><pre><span class="line">Input: &quot;Stones covered with moss usually appear in&quot;</span><br><span class="line">Model output (next-word probabilities):</span><br><span class="line">  damp    0.45</span><br><span class="line">  shade   0.25</span><br><span class="line">  forest  0.15</span><br><span class="line">  desert  0.02</span><br><span class="line">  ... (tens of thousands of words in the vocab, each with a probability)</span><br></pre></td></tr></table></figure><p>The “sampling strategy” is the rule for <strong>picking a word from this distribution</strong>. After picking, append it to the input, predict the next, and loop.</p><h5 id="2-Deterministic-decoding"><a href="#2-Deterministic-decoding" class="headerlink" title="2. Deterministic decoding"></a>2. Deterministic decoding</h5><table><thead><tr><th>Strategy</th><th>How</th><th>Characteristics</th></tr></thead><tbody><tr><td><strong>Greedy decoding</strong></td><td>always pick the highest-probability word</td><td>stable but bland; easily gets stuck in repetition</td></tr><tr><td><strong>Beam Search</strong></td><td>keep N candidate paths simultaneously; final pick the globally optimal</td><td>high quality but slow; mostly used in translation&#x2F;summarization</td></tr></tbody></table><h5 id="3-Stochastic-sampling-mainstream-for-chat-creative-writing"><a href="#3-Stochastic-sampling-mainstream-for-chat-creative-writing" class="headerlink" title="3. Stochastic sampling (mainstream for chat&#x2F;creative writing)"></a>3. Stochastic sampling (mainstream for chat&#x2F;creative writing)</h5><p><strong>① Temperature</strong> — adjusts how “peaked” or “flat” the distribution is:</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><span class="line">logits are divided by T before softmax:</span><br><span class="line"></span><br><span class="line">T = 0 (extremely cold): only the top score remains; equivalent to greedy; most conservative</span><br><span class="line">T = 0.7 (warm): high-score words are more likely, but occasional surprises  ← general default</span><br><span class="line">T = 1.5 (hot): distribution flattens; low-score words also have a chance; more random / creative</span><br></pre></td></tr></table></figure><p><strong>② Top-k</strong>: sample from only the highest-probability <strong>k</strong> words (e.g. k&#x3D;40); cut off the long tail.</p><p><strong>③ Top-p &#x2F; Nucleus sampling</strong>: accumulate probabilities from high to low; stop once <strong>p</strong> (e.g. 0.9) is reached; only sample within this dynamic set. Smarter than Top-k — fewer candidates when distribution is steep, more when flat.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Candidates (sorted by probability): damp 0.45  shade 0.25  forest 0.15  moss 0.08 ...</span><br><span class="line">Top-p=0.9: accumulate 0.45+0.25+0.15+0.08=0.93 ≥ 0.9 → sample only from these 4</span><br></pre></td></tr></table></figure><p><strong>④ min-p (relatively new)</strong>: uses the highest-probability word as the baseline; sets a dynamic minimum ratio; more robust than Top-p.</p><p><strong>⑤ Repetition &#x2F; frequency penalty</strong>: downweight words that have already appeared; avoid the “repeater” effect.</p><h5 id="4-How-to-tune-for-RAG"><a href="#4-How-to-tune-for-RAG" class="headerlink" title="4. How to tune for RAG?"></a>4. How to tune for RAG?</h5><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">RAG's core demand is &quot;faithful to retrieved content&quot;, not &quot;creativity&quot;</span></div><div class="callout-content"><p>If temperature is too high during generation, the model will easily <strong>detach from retrieved facts and improvise → hallucination</strong>.</p></div></div><table><thead><tr><th>Scenario</th><th>Temperature</th><th>Top-p</th><th>Notes</th></tr></thead><tbody><tr><td><strong>RAG factual Q&amp;A</strong> ⭐</td><td><strong>0 ~ 0.3</strong></td><td>0.9</td><td>maximize faithfulness; rarely fabricate</td></tr><tr><td>Summarization &#x2F; rewriting</td><td>0.3 ~ 0.5</td><td>0.9</td><td>a little flexibility without going off</td></tr><tr><td>Creative writing &#x2F; naming</td><td>0.8 ~ 1.2</td><td>0.95</td><td>encourage diversity</td></tr><tr><td>Code generation</td><td>0 ~ 0.2</td><td>—</td><td>needs determinism</td></tr></tbody></table><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">Implication for the asset-library project</span></div><div class="callout-content"><p>If we ever add a feature to the asset search that “explains in natural language why an asset is recommended”, temperature should be set to around <strong>0.2</strong> — making the model speak faithfully based on the asset’s metadata description, rather than inventing properties that don’t exist.</p></div></div><hr><h4 id="Choosing-a-Suitable-LLM"><a href="#Choosing-a-Suitable-LLM" class="headerlink" title="Choosing a Suitable LLM"></a>Choosing a Suitable LLM</h4><p>There’s no “best” model — only “most suited to the current task + budget + compliance requirements”. Here’s a reusable selection framework.</p><h5 id="1-Seven-evaluation-dimensions"><a href="#1-Seven-evaluation-dimensions" class="headerlink" title="1. Seven evaluation dimensions"></a>1. Seven evaluation dimensions</h5><table><thead><tr><th>Dimension</th><th>Key Question</th><th>Impact on RAG</th></tr></thead><tbody><tr><td><strong>Capability tier</strong></td><td>are reasoning and complex instruction following strong enough</td><td>sets the upper bound on answer quality</td></tr><tr><td><strong>Context window</strong></td><td>how many retrieved chunks can fit</td><td>directly relevant to RAG, see below ↓</td></tr><tr><td><strong>Cost</strong></td><td>input&#x2F;output price per million tokens</td><td>main expense at high call rates</td></tr><tr><td><strong>Latency &#x2F; throughput</strong></td><td>time to first token, tokens per second</td><td>affects user experience</td></tr><tr><td><strong>Privacy &#x2F; compliance</strong></td><td>can data leave the local environment</td><td>red line for private assets &#x2F; code ⭐</td></tr><tr><td><strong>Chinese ability</strong></td><td>Chinese understanding and generation</td><td>a must in Chinese scenarios</td></tr><tr><td><strong>Structured output &#x2F; tool use</strong></td><td>can stably output JSON, call functions</td><td>required for Agentic RAG</td></tr></tbody></table><h5 id="2-Closed-source-API-vs-open-source-local"><a href="#2-Closed-source-API-vs-open-source-local" class="headerlink" title="2. Closed-source API vs open-source local"></a>2. Closed-source API vs open-source local</h5><table><thead><tr><th></th><th>Closed-source API (Claude &#x2F; GPT &#x2F; Gemini)</th><th>Open-source local (Qwen &#x2F; Llama &#x2F; DeepSeek…)</th></tr></thead><tbody><tr><td>Capability</td><td>usually strongest</td><td>top open-source is approaching closed-source</td></tr><tr><td>Deployment</td><td>API call; zero ops</td><td>you run it on your own GPU; ops required</td></tr><tr><td>Cost</td><td>pay-as-you-go; no upfront investment</td><td>hardware upfront; marginal cost near zero</td></tr><tr><td>Privacy</td><td>data leaves local ⚠️</td><td><strong>data stays local</strong> ✅</td></tr><tr><td>Customization</td><td>limited</td><td>finetune &#x2F; quantize; fully controllable</td></tr></tbody></table><h5 id="3-Mid-2026-mainline-model-quick-view"><a href="#3-Mid-2026-mainline-model-quick-view" class="headerlink" title="3. Mid-2026 mainline model quick view"></a>3. Mid-2026 mainline model quick view</h5><div class="callout" data-callout="quote" style="--callout-color: 158, 158, 158;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/></svg><span class="callout-title-inner">Source and timeliness</span></div><div class="callout-content"><p>The table below is cross-verified against Anthropic’s official <code>claude-api</code> reference (cached 2026-06-04) and public pricing comparison sites (<a href="https://www.morphllm.com/llm-api">morphllm LLM API, verified 2026-06-09</a>, <a href="https://www.tldl.io/resources/llm-api-pricing-2026">LLM API Pricing 2026</a>). <strong>Models and prices change extremely fast — refer to the official site as truth.</strong></p></div></div><p><strong>Closed-source APIs (price &#x3D; input&#x2F;output, per million tokens):</strong></p><table><thead><tr><th>Model</th><th>Position</th><th>Context</th><th>Price (in&#x2F;out)</th></tr></thead><tbody><tr><td><strong>Claude Fable 5</strong></td><td>strongest; long-horizon agents</td><td>1M</td><td>$10 &#x2F; $50</td></tr><tr><td><strong>Claude Opus 4.8</strong> ⭐</td><td>flagship; strong coding&#x2F;agents</td><td>1M</td><td>$5 &#x2F; $25</td></tr><tr><td><strong>Claude Sonnet 4.6</strong></td><td>speed&#x2F;intelligence balance</td><td>1M</td><td>$3 &#x2F; $15</td></tr><tr><td><strong>Claude Haiku 4.5</strong></td><td>fast, cheap</td><td>200K</td><td>$1 &#x2F; $5</td></tr><tr><td>GPT-5.5 (OpenAI)</td><td>strong reasoning &#x2F; math</td><td>~256K</td><td>~$5 &#x2F; $30</td></tr><tr><td>Gemini 3.1 Pro (Google)</td><td>ultra-long context, multimodal, cost-effective</td><td>huge (≥1M)</td><td>~$2 &#x2F; $12</td></tr><tr><td>DeepSeek V4</td><td>extreme cost-performance</td><td>large</td><td>~$0.14 from</td></tr></tbody></table><p><strong>Open-source &#x2F; locally deployable (suited to private data):</strong></p><table><thead><tr><th>Model Family</th><th>Highlights</th><th>Notes</th></tr></thead><tbody><tr><td><strong>Qwen 3.5 (Tongyi Qianwen)</strong></td><td>top tier for Chinese; full size range</td><td>small sizes run on 8GB; first pick for local ⭐</td></tr><tr><td>Llama 4 (Meta)</td><td>largest ecosystem</td><td>medium-to-large sizes</td></tr><tr><td>DeepSeek V4</td><td>strong reasoning, open source</td><td>full version needs large VRAM</td></tr><tr><td>GLM-5 (Zhipu)</td><td>Chinese-friendly</td><td></td></tr><tr><td>Mistral &#x2F; Gemma &#x2F; Phi</td><td>Europe &#x2F; Google &#x2F; Microsoft small models</td><td>Phi suited for edge</td></tr></tbody></table><h5 id="4-Context-window-vs-RAG"><a href="#4-Context-window-vs-RAG" class="headerlink" title="4. Context window vs RAG"></a>4. Context window vs RAG</h5><p>RAG must stuff <strong>[system prompt] + [Top-K retrieved chunks] + [conversation history] + [user question]</strong> into the context all at once. The larger the window, the more retrieval evidence you can include.</p><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">But &quot;large window&quot; ≠ &quot;stuff it blindly&quot;</span></div><div class="callout-content"><ul><li>Stuffing too many irrelevant chunks <strong>dilutes</strong> useful info, raises cost, and may trigger “lost in the middle” (see next section).</li><li>The right move remains: <strong>first re-rank to Top-5~10 high-quality chunks</strong> (Module 3 §6&#x2F;§7 Reranker), rather than dumping Top-100 in. <strong>Good retrieval &gt; large windows.</strong></li></ul></div></div><h5 id="5-Quick-selection-guide"><a href="#5-Quick-selection-guide" class="headerlink" title="5. Quick selection guide"></a>5. Quick selection guide</h5><table><thead><tr><th>Your Situation</th><th>Recommendation</th></tr></thead><tbody><tr><td>Private art assets &#x2F; private code, <strong>data must not leave local</strong></td><td>Local <strong>Qwen 3.5</strong> series (run on your DGX Spark node; 128GB unified memory can load quantized medium-large models) ⭐</td></tr><tr><td>Want the highest answer quality and can accept API</td><td><strong>Claude Opus 4.8</strong> (<code>claude-opus-4-8</code>) or Fable 5</td></tr><tr><td>High frequency, cost-sensitive</td><td>Claude Haiku 4.5 &#x2F; Gemini Flash &#x2F; DeepSeek</td></tr><tr><td>Need ultra-long context</td><td>Gemini 3.1 Pro &#x2F; Claude (1M window)</td></tr></tbody></table><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">Choices for the two projects</span></div><div class="callout-content"><ul><li><strong>Asset-library project</strong> (private art assets) → privacy first → local Qwen on DGX Spark</li><li><strong><code>starting-ragchatbot-codebase</code></strong> (course Q&amp;A bot) → already uses <strong>Claude</strong> (Anthropic’s official default recommended <code>claude-opus-4-8</code> + adaptive thinking <code>thinking:&#123;type:&quot;adaptive&quot;&#125;</code>); just call the API, no ops needed</li></ul></div></div><hr><h4 id="Prompt-Engineering"><a href="#Prompt-Engineering" class="headerlink" title="Prompt Engineering"></a>Prompt Engineering</h4><p>Once the model is chosen and retrieval is done, <strong>the prompt is your only control lever at inference time</strong> — without retraining, you can dramatically change output quality just by organizing the input. For RAG, the core goal of prompt engineering is: <strong>make the model answer strictly based on retrieved content and refuse to hallucinate</strong>.</p><h5 id="1-Basic-structure-of-a-prompt"><a href="#1-Basic-structure-of-a-prompt" class="headerlink" title="1. Basic structure of a prompt"></a>1. Basic structure of a prompt</h5><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></pre></td><td class="code"><pre><span class="line">┌── System: set role, rules, tone, boundaries</span><br><span class="line">│      &quot;You are an asset-library assistant; answer only from the provided asset info...&quot;</span><br><span class="line">├── Context: inject retrieved chunks</span><br><span class="line">│      &quot;[Asset 1] grayish-brown stone, covered with moss... [Asset 2] ...&quot;</span><br><span class="line">├── User: this turn&#x27;s question</span><br><span class="line">│      &quot;Recommend stone assets suited for ancient-style scenes&quot;</span><br><span class="line">└── (optional) Few-shot examples / output-format constraints</span><br></pre></td></tr></table></figure><h5 id="2-General-techniques"><a href="#2-General-techniques" class="headerlink" title="2. General techniques"></a>2. General techniques</h5><table><thead><tr><th>Technique</th><th>How</th><th>When to Use</th></tr></thead><tbody><tr><td><strong>Role setting</strong></td><td>“You are a senior art director…”</td><td>almost always</td></tr><tr><td><strong>Few-shot</strong></td><td>provide 1–3 “input → ideal output” examples</td><td>when output format&#x2F;style must be stable</td></tr><tr><td><strong>Chain-of-Thought (CoT)</strong></td><td>“Reason step by step before concluding”</td><td>complex reasoning (note: RAG factual Q&amp;A usually doesn’t need much divergence)</td></tr><tr><td><strong>Structured output</strong></td><td>require JSON output &#x2F; specified fields</td><td>downstream code parses; Agentic RAG</td></tr></tbody></table><h5 id="3-RAG-specific-prompt-template-key"><a href="#3-RAG-specific-prompt-template-key" class="headerlink" title="3. RAG-specific prompt template (key)"></a>3. RAG-specific prompt template (key)</h5><p>A qualified RAG prompt must include three “anti-hallucination” instructions: <strong>① use only provided content ② if not found, say so ③ cite sources</strong>.</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">You are the retrieval assistant for the game asset library. Strictly observe:</span><br><span class="line">1. Answer only from the information in the [Retrieved Results] below; do not use your own knowledge.</span><br><span class="line">2. If the retrieval results contain no answer, respond directly with &quot;No matching assets found&quot;; do not fabricate.</span><br><span class="line">3. When answering, tag the asset you relied on with [asset ID].</span><br><span class="line"></span><br><span class="line">[Retrieved Results]</span><br><span class="line">[stone_moss_03] Grayish-brown stone, densely covered with green moss, with obvious weathered texture, suitable for ancient-style scenes</span><br><span class="line">[rock_wet_01]   Damp rock, dark color, no moss</span><br><span class="line"></span><br><span class="line">[User Question]</span><br><span class="line">Any stone with moss, suitable for an ancient-style scene?</span><br><span class="line"></span><br><span class="line">[Expected Output]</span><br><span class="line">Recommend [stone_moss_03]: the grayish-brown stone densely covered with moss and weathered texture fits the ancient-style scene.</span><br><span class="line">([rock_wet_01] has no moss, low relevance and not recommended.)</span><br></pre></td></tr></table></figure><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">These three instructions directly determine the trustworthiness of a RAG system</span></div><div class="callout-content"><p>Without instruction 2, the model “confidently makes stuff up” when retrieval is empty; without instruction 3, users can’t verify — answers aren’t traceable.</p></div></div><h5 id="4-“Lost-in-the-Middle”-phenomenon"><a href="#4-“Lost-in-the-Middle”-phenomenon" class="headerlink" title="4. “Lost in the Middle” phenomenon"></a>4. “Lost in the Middle” phenomenon</h5><p>Research shows: when the context is very long, the model remembers <strong>the beginning and end</strong> most firmly, while <strong>the middle</strong> is most easily ignored — recall curves take a U-shape.</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><span class="line">Model attention ▲</span><br><span class="line">              │＼            ／</span><br><span class="line">              │  ＼ ____ ／      ← middle sag</span><br><span class="line">              └──────────────▶ chunk position</span><br><span class="line">               beginning  middle  end</span><br></pre></td></tr></table></figure><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">Direct implication for RAG</span></div><div class="callout-content"><p>Place <strong>the most relevant retrieved chunks at the very beginning or very end of the prompt</strong>, not buried in a pile of chunks in the middle. This again confirms the value of <strong>re-ranking</strong>: instead of stuffing 50 chunks and letting key info drown in the middle, re-rank to 5 chunks and put them in the most visible positions.</p></div></div><h5 id="5-Good-vs-bad-prompt-comparison"><a href="#5-Good-vs-bad-prompt-comparison" class="headerlink" title="5. Good vs bad prompt comparison"></a>5. Good vs bad prompt comparison</h5><table><thead><tr><th></th><th>❌ Bad</th><th>✅ Good</th></tr></thead><tbody><tr><td>Role</td><td>(none)</td><td>“You are an asset-library assistant”</td></tr><tr><td>Anti-hallucination</td><td>(none; let the model ramble)</td><td>“Use only retrieved results; if not found, say so”</td></tr><tr><td>Source</td><td>(not required)</td><td>“Tag the basis with [asset ID]”</td></tr><tr><td>Chunk order</td><td>randomly piled</td><td>most relevant at the very beginning&#x2F;end</td></tr><tr><td>Output</td><td>“answer freely”</td><td>specify format &#x2F; fields</td></tr></tbody></table><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">Prompt engineering vs fine-tuning vs RAG — three ways to &quot;feed knowledge&quot;</span></div><div class="callout-content"><ul><li><strong>Prompt engineering</strong>: temporary, zero-cost; guides via the current input → this section</li><li><strong>RAG</strong>: dynamically injects external knowledge; knowledge updates in real time → this whole set of notes</li><li><strong>Fine-tuning</strong>: bake knowledge &#x2F; style into model weights; high cost; slow to update<br>The three are often combined: <strong>RAG supplies facts + prompts constrain behavior + (optional) fine-tuning unifies style</strong>.</li></ul></div></div><hr><h3 id="Module-5-RAG-Systems-in-Production"><a href="#Module-5-RAG-Systems-in-Production" class="headerlink" title="Module 5: RAG Systems in Production"></a>Module 5: RAG Systems in Production</h3><h4 id="1-What-challenges-does-production-face"><a href="#1-What-challenges-does-production-face" class="headerlink" title="1. What challenges does production face?"></a>1. What challenges does production face?</h4><p>When RAG leaves the demo, it hits a string of “walls” that demos don’t see:</p><table><thead><tr><th>Dimension</th><th>Demo State</th><th>Production Reality</th></tr></thead><tbody><tr><td>Data</td><td>dozens of clean documents</td><td>millions of dirty documents (duplicates, outdated, permission-sensitive)</td></tr><tr><td>Users</td><td>internal testers</td><td>real users with wildly varied phrasing, typos, colloquialisms</td></tr><tr><td>Recall</td><td>“good enough”</td><td>must be traceable, explainable, SLA-bound</td></tr><tr><td>Cost</td><td>runs on one card</td><td>QPS, cost, latency pulling against each other</td></tr><tr><td>Feedback</td><td>none</td><td>needs logging, monitoring, A&#x2F;B, observability</td></tr></tbody></table><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">The essential shift</span></div><div class="callout-content"><p>Demo is “<strong>can it answer</strong>“; production is “<strong>does it answer stably, cheaply, quickly, safely, and is the improvement measurable</strong>“.</p></div></div><h4 id="2-Implement-an-RAG-Evaluation-Strategy"><a href="#2-Implement-an-RAG-Evaluation-Strategy" class="headerlink" title="2. Implement an RAG Evaluation Strategy"></a>2. Implement an RAG Evaluation Strategy</h4><p>Evaluation &#x3D; answering “how good is my RAG really right now?”. Three steps:</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></pre></td><td class="code"><pre><span class="line">Offline eval set (gold set) → Automated scoring → Regression comparison</span><br><span class="line">       ↑                              ↓</span><br><span class="line">       └──── Collect online feedback, expand the set ──────┘</span><br></pre></td></tr></table></figure><ul><li><strong>Gold set</strong>: human-annotated question + expected answer &#x2F; relevant chunks</li><li><strong>Metrics</strong>: retrieval (Recall@k, MRR), generation (faithfulness, answer relevance), end-to-end (hit rate)</li><li><strong>Tools</strong>: RAGAS, ARES, LangSmith Evaluation, DeepEval</li></ul><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">Evaluation must be &quot;reproducible&quot;</span></div><div class="callout-content"><p>Same data + same code &#x3D; same score. Otherwise one version change and you can’t tell whether “the model improved” or “the prompt changed”.</p></div></div><h4 id="3-Logging-Monitoring-and-Observability"><a href="#3-Logging-Monitoring-and-Observability" class="headerlink" title="3. Logging, Monitoring, and Observability"></a>3. Logging, Monitoring, and Observability</h4><p>Production RAG is a <strong>black-box pipeline</strong>; no logging &#x3D; no debuggability. Three layers:</p><table><thead><tr><th>Layer</th><th>What to Record</th><th>Used For</th></tr></thead><tbody><tr><td><strong>Trace</strong></td><td>every query’s retriever &#x2F; reranker &#x2F; LLM call, inputs and outputs, timings</td><td>reproduce problems</td></tr><tr><td><strong>Metric</strong></td><td>QPS, latency percentiles, token usage, hit rate, error rate</td><td>dashboard alerting</td></tr><tr><td><strong>Feedback</strong></td><td>user 👍&#x2F;👎, manual audits, explicit ratings</td><td>feed back into eval set, iterate the model</td></tr></tbody></table><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">The three kits</span></div><div class="callout-content"><p>LangSmith &#x2F; Langfuse &#x2F; Arize Phoenix &#x2F; MLflow — pick any one, don’t run naked.</p></div></div><h4 id="4-Customized-Evaluation"><a href="#4-Customized-Evaluation" class="headerlink" title="4. Customized Evaluation"></a>4. Customized Evaluation</h4><p>Generic metrics (e.g. “semantic similarity”) often don’t align with the business. <strong>Business says it’s good, then it’s actually good.</strong></p><ul><li><strong>Domain-expert scoring</strong>: legal &#x2F; medical &#x2F; customer-service leads score 1–5 by business criteria</li><li><strong>Rule-based validation</strong>: must include a certain field, must cite a certain type of source, must not contain certain words</li><li><strong>Behavioral proxy metrics</strong>: like rate, copy rate, follow-up rate (user keeps asking &#x3D; wasn’t answered clearly)</li><li><strong>LLM-as-a-Judge</strong>: use a strong model as judge, but <strong>must calibrate against a gold set</strong>, otherwise the judge itself drifts.</li></ul><h4 id="5-Quantization"><a href="#5-Quantization" class="headerlink" title="5. Quantization"></a>5. Quantization</h4><p>Swap a model’s&#x2F;vector’s “high-precision numbers” for “low-precision representation” to <strong>save VRAM, save money, save latency</strong>.</p><table><thead><tr><th>Object</th><th>Method</th><th>Effect</th></tr></thead><tbody><tr><td>Embedding model</td><td>int8 &#x2F; binary</td><td>vector-DB memory ↓ 4–32×, recall slightly drops</td></tr><tr><td>LLM</td><td>GPTQ &#x2F; AWQ &#x2F; GGUF</td><td>VRAM ↓ 2–4×, speed ↑, quality slight loss</td></tr><tr><td>Vector storage</td><td>Product Quantization (PQ)</td><td>tens of millions of vectors runnable on one machine</td></tr></tbody></table><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">The cost of quantization</span></div><div class="callout-content"><p>Recall &#x2F; generation quality drops 1–3%; <strong>must regress on the gold set</strong> — don’t push to production on gut feel.</p></div></div><h4 id="6-Cost-vs-Response-Quality"><a href="#6-Cost-vs-Response-Quality" class="headerlink" title="6. Cost vs Response Quality"></a>6. Cost vs Response Quality</h4><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></pre></td><td class="code"><pre><span class="line">Quality ▲</span><br><span class="line">       │      ╭──── Large model (expensive)</span><br><span class="line">       │    ╱</span><br><span class="line">       │  ╱</span><br><span class="line">       │╱</span><br><span class="line">       └──────────────▶ Cost</span><br><span class="line">  Small model  Routing</span><br></pre></td></tr></table></figure><p>Common cost-saving strategies:</p><ul><li><strong>Model routing</strong>: simple questions → small model &#x2F; rules; complex questions → large model</li><li><strong>Caching</strong>: same query hits the cache, return directly, save one LLM</li><li><strong>Token reduction</strong>: re-rank to Top-3, truncate long docs, use smaller context</li><li><strong>Batching &#x2F; async</strong>: non-real-time tasks batch requests</li></ul><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">Not the more expensive the better</span></div><div class="callout-content"><p>Finding “where 1% quality improvement costs 10× the money” is the ROI key for engineering optimization.</p></div></div><h4 id="7-Latency-vs-Response-Quality"><a href="#7-Latency-vs-Response-Quality" class="headerlink" title="7. Latency vs Response Quality"></a>7. Latency vs Response Quality</h4><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><span class="line">Time to first token ▲</span><br><span class="line">                   │   ╭─ Long context + large model</span><br><span class="line">                   │ ╱</span><br><span class="line">                   │╱──── Streaming + small model + re-ranking</span><br><span class="line">                   └──────────────▶ Quality</span><br></pre></td></tr></table></figure><p>Hard production latency requirements:</p><table><thead><tr><th>Scenario</th><th>Target TTFT</th></tr></thead><tbody><tr><td>Chat &#x2F; search</td><td>&lt; 1s</td></tr><tr><td>Customer-service auto-reply</td><td>&lt; 2s</td></tr><tr><td>Offline analysis</td><td>not sensitive</td></tr></tbody></table><p>Means: <strong>streaming output (SSE), parallel retrieval (vector + BM25 in parallel), re-ranking first, speculative decoding</strong>.</p><h4 id="8-Security"><a href="#8-Security" class="headerlink" title="8. Security"></a>8. Security</h4><p>RAG’s attack surface &#x3D; retriever + LLM; defend at both ends:</p><table><thead><tr><th>Risk</th><th>Example</th><th>Defense</th></tr></thead><tbody><tr><td><strong>Prompt injection</strong></td><td>user question contains “ignore all previous instructions”</td><td>input sanitization + strict segmenting of instructions vs data</td></tr><tr><td><strong>Data leakage</strong></td><td>retrieval returns someone else’s private document</td><td>metadata ACL filter + tenant isolation</td></tr><tr><td><strong>Unauthorized answers</strong></td><td>internal wiki answered to an external user</td><td>permission gateway + source watermarking</td></tr><tr><td><strong>Hallucination</strong></td><td>fabricates non-existent assets</td><td>force citation + “say ‘not found’ when none”</td></tr><tr><td><strong>Toxic output</strong></td><td>LLM parrots retrieved malicious text</td><td>content moderation + output filtering</td></tr></tbody></table><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">No silver bullet for prompt injection</span></div><div class="callout-content"><p>Only by combining <strong>input sanitization + output review + permission isolation</strong> across the three layers; single-point defense will always be bypassed.</p></div></div><h4 id="9-Multimodal-Retrieval-Augmented-Generation"><a href="#9-Multimodal-Retrieval-Augmented-Generation" class="headerlink" title="9. Multimodal Retrieval-Augmented Generation"></a>9. Multimodal Retrieval-Augmented Generation</h4><p>RAG isn’t limited to feeding text. The more modalities you can retrieve&#x2F;generate, the wider your application scope:</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></pre></td><td class="code"><pre><span class="line">text    ←─────► text   (most common; Q&amp;A / search)</span><br><span class="line">image   ←─────► text   (image-to-text search; CLIP)</span><br><span class="line">text    ←─────► image  (text-to-image / text-to-image-search)</span><br><span class="line">image   ←─────► image  (image-to-image search)</span><br><span class="line">audio   ←─────► text   (meeting recordings → summaries)</span><br><span class="line">video   ←─────► text   (long video → key frames + subtitle retrieval)</span><br></pre></td></tr></table></figure><p>Key techniques:</p><ul><li><strong>Unified embedding</strong>: CLIP &#x2F; SigLIP &#x2F; BGE-M3 map multiple modalities into one vector space</li><li><strong>Multimodal LLM</strong>: GPT-4o, Gemini, Qwen-VL — can see images, hear audio, read docs</li><li><strong>Structured parsing</strong>: PDF tables, scans, charts → specialized models convert to Markdown&#x2F;JSON before feeding RAG</li></ul><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">A common landing posture</span></div><div class="callout-content"><p>First OCR + structure-parse all PDFs &#x2F; PPTs &#x2F; screenshots → convert to text + image blocks → push into one vector DB. A single RAG system consumes an enterprise’s entire knowledge assets.</p></div></div><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">Module 5 recap</span></div><div class="callout-content"><p>Production RAG &#x3D; <strong>evaluation system + observability + engineering trade-offs + security&#x2F;compliance + multimodal extension</strong>. Beyond the technology itself, engineering capability is what decides whether it can really “live”.</p></div></div><h2 id="8-References"><a href="#8-References" class="headerlink" title="8. References"></a>8. References</h2><hr><div class="callout" data-callout="quote" style="--callout-color: 158, 158, 158;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/></svg><span class="callout-title-inner">Related documents</span></div><div class="callout-content"><ul><li>[[20260522.SearchToolDesign（Private）]]: complete design doc for the asset-search tool</li><li>[[20260521.ArtPipelineAIIntegration（Private）]]: upstream AI pipeline integration doc</li><li><a href="https://www.bilibili.com/video/BV1ECQ9B5EKe/">Andrew Ng’s RAG Course — Instructor Zain</a></li><li><a href="https://learn.deeplearning.ai/courses/retrieval-augmented-generation/lesson/rrngb/a-conversation-with-andrew-ng?startTime=1">Zain’s RAG course official link</a></li></ul></div></div><p>The local repo path is D:\Project\UGit\starting-ragchatbot-codebase</p>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/AI/">AI</category>
      
      <category domain="https://eugenepage.com/tags/RAG/">RAG</category>
      
      <category domain="https://eugenepage.com/tags/embedding/">embedding</category>
      
      <category domain="https://eugenepage.com/tags/vectorSearch/">vectorSearch</category>
      
      <category domain="https://eugenepage.com/tags/CLIP/">CLIP</category>
      
      <category domain="https://eugenepage.com/tags/FAISS/">FAISS</category>
      
      <category domain="https://eugenepage.com/tags/learning/">learning</category>
      
      
      <comments>https://eugenepage.com/2026/05/23/20260524.RAGDeepLearning/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>RAG 深度学习笔记</title>
      <link>https://eugenepage.com/zh-CN/2026/05/23/20260524.RAGDeepLearning/</link>
      <guid>https://eugenepage.com/zh-CN/2026/05/23/20260524.RAGDeepLearning/</guid>
      <pubDate>Sat, 23 May 2026 16:00:00 GMT</pubDate>
      
        
        
      <description>&lt;h1 id=&quot;RAG-入门学习笔记&quot;&gt;&lt;a href=&quot;#RAG-入门学习笔记&quot; class=&quot;headerlink&quot; title=&quot;RAG 入门学习笔记&quot;&gt;&lt;/a&gt;RAG 入门学习笔记&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;学习起点：[[20260522.SearchToo</description>
        
      
      
      
      <content:encoded><![CDATA[<h1 id="RAG-入门学习笔记"><a href="#RAG-入门学习笔记" class="headerlink" title="RAG 入门学习笔记"></a>RAG 入门学习笔记</h1><blockquote><p>学习起点：[[20260522.SearchToolDesign（Private）]] 中的资产语义搜索项目<br>目标：理解 RAG 原理 → 在本地图片数据上跑通最小 Demo</p></blockquote><hr><h2 id="一、什么是-RAG？"><a href="#一、什么是-RAG？" class="headerlink" title="一、什么是 RAG？"></a>一、什么是 RAG？</h2><p><strong>RAG &#x3D; Retrieval-Augmented Generation（检索增强生成）</strong></p><p>用一句话说：<strong>先从知识库里找到相关内容，再用这些内容辅助 AI 回答问题。</strong></p><h3 id="1-1-为什么需要-RAG？"><a href="#1-1-为什么需要-RAG？" class="headerlink" title="1.1 为什么需要 RAG？"></a>1.1 为什么需要 RAG？</h3><table><thead><tr><th>问题</th><th>RAG 如何解决</th></tr></thead><tbody><tr><td>LLM 训练数据有截止日期，不知道新信息</td><td>检索阶段可以实时查询最新数据</td></tr><tr><td>LLM 不知道私有数据（公司内部资产库）</td><td>把私有数据做成索引，检索后注入给 LLM</td></tr><tr><td>LLM 会”幻觉”，凭空捏造事实</td><td>提供检索到的真实文档作为依据</td></tr><tr><td>LLM token 窗口有限，无法塞入全部资料</td><td>只检索最相关的少数文档片段</td></tr></tbody></table><hr><h2 id="二、RAG-的完整流程"><a href="#二、RAG-的完整流程" class="headerlink" title="二、RAG 的完整流程"></a>二、RAG 的完整流程</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">╔══════════════════════════════════════════════════════════╗</span><br><span class="line">║              索引阶段（离线，一次性构建）                   ║</span><br><span class="line">║                                                          ║</span><br><span class="line">║  原始数据（文档 / 图片 / 资产描述）                        ║</span><br><span class="line">║       ↓                                                  ║</span><br><span class="line">║  Embedding 模型  →  将内容转成固定长度的向量               ║</span><br><span class="line">║       ↓                                                  ║</span><br><span class="line">║  向量数据库（FAISS / ChromaDB）存储向量，支持快速检索        ║</span><br><span class="line">╚══════════════════════════════════════════════════════════╝</span><br><span class="line">                          ↕</span><br><span class="line">╔══════════════════════════════════════════════════════════╗</span><br><span class="line">║              检索阶段（在线，每次查询触发）                  ║</span><br><span class="line">║                                                          ║</span><br><span class="line">║  用户问题  →  Embedding 模型  →  问题向量                 ║</span><br><span class="line">║       ↓                                                  ║</span><br><span class="line">║  向量数据库：计算相似度，找 Top-K 个最近邻                   ║</span><br><span class="line">║       ↓                                                  ║</span><br><span class="line">║  返回对应原始内容（文档片段 / 图片路径 / 资产信息）           ║</span><br><span class="line">╚══════════════════════════════════════════════════════════╝</span><br><span class="line">                          ↕（可选）</span><br><span class="line">╔══════════════════════════════════════════════════════════╗</span><br><span class="line">║              生成阶段（可选，传统 RAG 才需要）               ║</span><br><span class="line">║                                                          ║</span><br><span class="line">║  [用户问题] + [检索到的内容]  →  LLM  →  有据可查的回答    ║</span><br><span class="line">╚══════════════════════════════════════════════════════════╝</span><br></pre></td></tr></table></figure><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">我们的项目是&quot;只检索，不生成&quot;</span></div><div class="callout-content"><p>资产搜索工具只做前两个阶段——把美术的查询转成向量，找最相似的资产，直接返回结果。这是 RAG 的检索子集，学术上叫 <strong>语义检索（Semantic Search）</strong> 或 <strong>Dense Retrieval</strong>。</p></div></div><hr><h2 id="三、核心概念解释"><a href="#三、核心概念解释" class="headerlink" title="三、核心概念解释"></a>三、核心概念解释</h2><h3 id="3-1-Embedding（向量化）"><a href="#3-1-Embedding（向量化）" class="headerlink" title="3.1 Embedding（向量化）"></a>3.1 Embedding（向量化）</h3><p>把任意内容（文字、图片）压缩成一个<strong>固定长度的数字列表（向量）</strong>，使得：</p><ul><li>语义<strong>相似</strong>的内容 → 向量在空间中<strong>相近</strong></li><li>语义<strong>不同</strong>的内容 → 向量在空间中<strong>远离</strong></li></ul><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></pre></td><td class="code"><pre><span class="line">&quot;一棵柳树&quot;   → [0.12, -0.34, 0.87, ..., 0.05]  （768 维）</span><br><span class="line">&quot;江南垂柳&quot;   → [0.11, -0.31, 0.89, ..., 0.06]  ← 很接近！</span><br><span class="line">&quot;一块岩石&quot;   → [-0.45, 0.72, -0.13, ..., 0.91] ← 差很远</span><br></pre></td></tr></table></figure><p><strong>常用 Embedding 模型：</strong></p><table><thead><tr><th>模型</th><th>输入类型</th><th>维度</th><th>特点</th></tr></thead><tbody><tr><td>BGE-M3 (BAAI)</td><td>文字</td><td>1024</td><td>中文最强，本地部署友好</td></tr><tr><td>CLIP (OpenAI)</td><td>图片 + 文字</td><td>512</td><td>图文共享同一向量空间 ⭐</td></tr><tr><td>SigLIP (Google)</td><td>图片 + 文字</td><td>768</td><td>CLIP 改进版，零样本更好</td></tr><tr><td>GTE-Qwen2 (阿里)</td><td>文字</td><td>768</td><td>多语言，商用 Apache 2.0</td></tr></tbody></table><h3 id="3-2-FAISS（向量相似度搜索库）"><a href="#3-2-FAISS（向量相似度搜索库）" class="headerlink" title="3.2 FAISS（向量相似度搜索库）"></a>3.2 FAISS（向量相似度搜索库）</h3><p><strong>FAISS &#x3D; Facebook AI Similarity Search</strong>（Meta 开源）</p><p><strong>为什么需要它？</strong><br>当你有 20000 个向量，要找和查询向量最相似的 Top-10，如果逐一比较需要 20000 次计算。FAISS 用特殊的数据结构，把这个过程加速几十到几千倍。</p><p><strong>类比：</strong></p><ul><li>暴力搜索 &#x3D; 图书馆里一本一本翻，找和你兴趣最接近的书</li><li>FAISS &#x3D; 图书馆按主题分区，直接去”植物类”书架精确查找</li></ul><p><strong>三种主要索引类型：</strong></p><table><thead><tr><th>类型</th><th>原理</th><th>精度</th><th>速度</th><th>适合规模</th><th>我们的选择</th></tr></thead><tbody><tr><td><code>IndexFlatL2</code></td><td>精确，逐一比较</td><td>100%</td><td>中（但绝对够用）</td><td><strong>&lt; 100K</strong></td><td>✅ 首选</td></tr><tr><td><code>IndexIVFFlat</code></td><td>先分群，再搜索</td><td>~98%</td><td>快</td><td>100K–10M</td><td>将来扩展用</td></tr><tr><td><code>IndexHNSWFlat</code></td><td>图结构导航</td><td>~99%</td><td>最快</td><td>任意</td><td>过度设计</td></tr></tbody></table><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">20000 资产用 `IndexFlatL2` 即可</span></div><div class="callout-content"><p>精确、代码最简单，单次查询 &lt;1ms，完全满足需求。</p></div></div><h3 id="3-3-相似度度量"><a href="#3-3-相似度度量" class="headerlink" title="3.3 相似度度量"></a>3.3 相似度度量</h3><table><thead><tr><th>度量方式</th><th>公式含义</th><th>何时用</th></tr></thead><tbody><tr><td>L2 距离（欧式）</td><td>向量空间中的直线距离，越小越相似</td><td>FAISS 默认，图片检索</td></tr><tr><td>余弦相似度</td><td>两向量夹角的余弦值，越接近 1 越相似</td><td>文本检索更常用</td></tr><tr><td>内积（点积）</td><td>方向 + 长度共同决定</td><td>CLIP 官方推荐</td></tr></tbody></table><hr><h2 id="四、图片-RAG（CLIP-的特殊能力）"><a href="#四、图片-RAG（CLIP-的特殊能力）" class="headerlink" title="四、图片 RAG（CLIP 的特殊能力）"></a>四、图片 RAG（CLIP 的特殊能力）</h2><p>CLIP 的核心创新：<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></pre></td><td class="code"><pre><span class="line">&quot;一棵柳树&quot;（文字）→ CLIP 文本编码器 → 向量 A</span><br><span class="line"> 🌳（柳树图片）   → CLIP 图片编码器 → 向量 B</span><br><span class="line"></span><br><span class="line">向量 A ≈ 向量 B  ← 这是 CLIP 的训练目标！</span><br></pre></td></tr></table></figure><p><strong>这意味着你可以：</strong></p><ul><li><strong>文字 → 图片</strong>：输入”给我一个流程图”，在图片库里找最相似的</li><li><strong>图片 → 图片</strong>：上传参考图，找风格相近的资产</li><li><strong>图片 → 文字</strong>：输入图片，找最相关的描述文档</li></ul><p>这正是资产搜索工具「以文搜图」功能的核心原理。</p><hr><h2 id="五、本地-Demo-计划"><a href="#五、本地-Demo-计划" class="headerlink" title="五、本地 Demo 计划"></a>五、本地 Demo 计划</h2><h3 id="5-1-目标"><a href="#5-1-目标" class="headerlink" title="5.1 目标"></a>5.1 目标</h3><p>用 <code>D:\Project\UGit\MyPicGo\Images\</code> 里的博客图片（PNG&#x2F;JPG），跑通一个最小的<strong>图片语义检索 Demo</strong>：</p><ul><li><strong>输入</strong>：一段文字描述（如”代码截图”、”流程图”、”UI 界面”）</li><li><strong>输出</strong>：图片库里最相似的 Top-5 图片路径</li></ul><h3 id="5-2-技术栈"><a href="#5-2-技术栈" class="headerlink" title="5.2 技术栈"></a>5.2 技术栈</h3><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></pre></td><td class="code"><pre><span class="line">D:\Project\UGit\MyPicGo\Images\  （博客图片，约 100+ 张 PNG/JPG）</span><br><span class="line">    ↓ 批量读取</span><br><span class="line">CLIP ViT-B/32  →  视觉编码器，每张图 → 512 维向量</span><br><span class="line">    ↓ 建索引</span><br><span class="line">FAISS IndexFlatL2  →  内存索引（不需要 GPU，不需要服务器）</span><br><span class="line">    ↓ 查询</span><br><span class="line">用户文字 → CLIP 文本编码器 → 512 维向量 → Top-K 检索</span><br></pre></td></tr></table></figure><h3 id="5-3-实战过程"><a href="#5-3-实战过程" class="headerlink" title="5.3 实战过程"></a>5.3 实战过程</h3><ol><li>建项目目录 <code>D:\Project\UGit\PicGoRAGDemo\</code> + Python venv</li><li>装依赖：<code>torch / transformers / faiss-cpu / modelscope / Pillow / numpy</code></li><li>写 <code>build_index.py</code>（索引阶段）+ <code>search.py</code>（查询阶段）</li><li>跑 <code>python build_index.py</code>：225 张图编码成 225 × 512 维向量，建 FAISS 索引落盘（CPU 上 ~15s）</li><li>跑 <code>python search.py &quot;query&quot;</code> 验证检索（单次查询 &lt;100ms）</li></ol><p><strong>3 个关键踩坑：</strong></p><ul><li><strong>transformers 5.x 改了 CLIP API</strong>：<code>get_image_features(...)</code> 现在返回 <code>BaseModelOutputWithPooling</code>（不再是裸 Tensor），要 <code>.pooler_output</code> 取 512 维向量</li><li><strong>HuggingFace 国内连不上</strong>：直连超时、镜像也不稳，换 <strong>ModelScope</strong>（阿里魔搭，国内服务器无障碍）</li><li><strong>ModelScope 命名不同</strong>：HF 上的 <code>OFA-Sys/chinese-clip-vit-base-patch16</code> 在 MS 上对应 <code>AI-ModelScope/chinese-clip-vit-base-patch16</code>（<code>AI-ModelScope</code> 是 HF 格式镜像专用命名空间，文件结构和 HF 完全一致，<code>transformers.AutoModel</code> 可以直接加载）</li></ul><h3 id="5-4-测试发现：中英文-query-效果对比"><a href="#5-4-测试发现：中英文-query-效果对比" class="headerlink" title="5.4 测试发现：中英文 query 效果对比"></a>5.4 测试发现：中英文 query 效果对比</h3><p>实测发现<strong>英文 query 略准</strong>：</p><table><thead><tr><th>Query</th><th>模型</th><th>检索效果</th></tr></thead><tbody><tr><td><code>&quot;code screenshot&quot;</code></td><td>OpenAI CLIP</td><td>✅ Top-3 全是代码截图</td></tr><tr><td><code>&quot;flowchart&quot;</code></td><td>OpenAI CLIP</td><td>✅ Top-3 全是流程图</td></tr><tr><td><code>&quot;代码截图&quot;</code></td><td>Chinese-CLIP</td><td>✅ Top-3 全是代码截图</td></tr><tr><td><code>&quot;流程图&quot;</code></td><td>Chinese-CLIP</td><td>⚠️ Top-3 有流程图但混入少量无关图</td></tr></tbody></table><p><strong>可能原因</strong>：博客图片以英文技术截图为主（代码、终端、UI 文字大多为英文）。OpenAI CLIP 同时识别英文文字 + 视觉特征，命中更稳；Chinese-CLIP 训练数据偏中文生活 &#x2F; 资讯，对”流程图”这种偏技术的视觉概念边界不够锐利。</p><p>这正印证 §6.1 的核心观点：<strong>embedding 模型在你领域的表现取决于训练数据分布——选型要看场景</strong>。</p><h3 id="5-5-完整代码"><a href="#5-5-完整代码" class="headerlink" title="5.5 完整代码"></a>5.5 完整代码</h3><h4 id="build-index-py（索引阶段）"><a href="#build-index-py（索引阶段）" class="headerlink" title="build_index.py（索引阶段）"></a><code>build_index.py</code>（索引阶段）</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><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><span class="line"><span class="string">&quot;&quot;&quot;build_index.py — 把 N 张图编成 N 个 512 维向量，建 FAISS 索引</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">产物:</span></span><br><span class="line"><span class="string">  index.faiss  FAISS 索引（行 i = 第 i 张图的 512 维向量）</span></span><br><span class="line"><span class="string">  paths.json   行号 → 原图路径</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">from</span> __future__ <span class="keyword">import</span> annotations</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"><span class="keyword">import</span> faiss, numpy <span class="keyword">as</span> np, torch</span><br><span class="line"><span class="keyword">from</span> PIL <span class="keyword">import</span> Image</span><br><span class="line"><span class="keyword">from</span> modelscope <span class="keyword">import</span> snapshot_download</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> AutoModel, AutoProcessor</span><br><span class="line"></span><br><span class="line"><span class="comment"># ===== 配置 =====</span></span><br><span class="line">IMAGE_DIR  = Path(<span class="string">r&quot;D:\Project\UGit\MyPicGo\Images&quot;</span>)</span><br><span class="line">INDEX_PATH = Path(__file__).parent / <span class="string">&quot;index.faiss&quot;</span></span><br><span class="line">PATHS_PATH = Path(__file__).parent / <span class="string">&quot;paths.json&quot;</span></span><br><span class="line"><span class="comment"># AI-ModelScope 是 ModelScope 上的 HF 格式镜像命名空间，国内可直连</span></span><br><span class="line">MODEL_ID   = <span class="string">&quot;AI-ModelScope/chinese-clip-vit-base-patch16&quot;</span></span><br><span class="line">BATCH_SIZE = <span class="number">16</span></span><br><span class="line">IMG_EXTS   = &#123;<span class="string">&quot;.png&quot;</span>, <span class="string">&quot;.jpg&quot;</span>, <span class="string">&quot;.jpeg&quot;</span>, <span class="string">&quot;.webp&quot;</span>, <span class="string">&quot;.bmp&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">collect_images</span>(<span class="params">root: Path</span>) -&gt; <span class="built_in">list</span>[Path]:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;递归扫描所有图片&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">sorted</span>(p <span class="keyword">for</span> p <span class="keyword">in</span> root.rglob(<span class="string">&quot;*&quot;</span>) <span class="keyword">if</span> p.suffix.lower() <span class="keyword">in</span> IMG_EXTS)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>() -&gt; <span class="literal">None</span>:</span><br><span class="line">    device = <span class="string">&quot;cuda&quot;</span> <span class="keyword">if</span> torch.cuda.is_available() <span class="keyword">else</span> <span class="string">&quot;cpu&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 1. 加载模型 ===</span></span><br><span class="line">    <span class="comment"># snapshot_download 首次下载 ~700MB 到 ~/.cache/modelscope/，之后直接返回本地路径</span></span><br><span class="line">    model_dir = snapshot_download(MODEL_ID)</span><br><span class="line">    model     = AutoModel.from_pretrained(model_dir).to(device).<span class="built_in">eval</span>()</span><br><span class="line">    processor = AutoProcessor.from_pretrained(model_dir)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 2. 扫描图片库 ===</span></span><br><span class="line">    paths = collect_images(IMAGE_DIR)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> paths:</span><br><span class="line">        <span class="keyword">raise</span> SystemExit(<span class="string">f&quot;no images found under <span class="subst">&#123;IMAGE_DIR&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 3. 批量编码 ===</span></span><br><span class="line">    embeddings, ok_paths = [], []</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="built_in">len</span>(paths), BATCH_SIZE):</span><br><span class="line">        batch = paths[i:i + BATCH_SIZE]</span><br><span class="line">        imgs, ok = [], []</span><br><span class="line">        <span class="keyword">for</span> p <span class="keyword">in</span> batch:</span><br><span class="line">            <span class="keyword">try</span>:</span><br><span class="line">                imgs.append(Image.<span class="built_in">open</span>(p).convert(<span class="string">&quot;RGB&quot;</span>))</span><br><span class="line">                ok.append(p)</span><br><span class="line">            <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">f&quot;  skip <span class="subst">&#123;p.name&#125;</span>: <span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> imgs:</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        inputs = processor(images=imgs, return_tensors=<span class="string">&quot;pt&quot;</span>).to(device)</span><br><span class="line">        <span class="keyword">with</span> torch.no_grad():</span><br><span class="line">            <span class="comment"># transformers 5.x: get_image_features 返回 BaseModelOutputWithPooling</span></span><br><span class="line">            <span class="comment"># 512 维投影向量在 .pooler_output 字段里</span></span><br><span class="line">            feats = model.get_image_features(**inputs).pooler_output</span><br><span class="line">        <span class="comment"># L2 归一化：归一化后 L2 距离 ↔ cosine 相似度排名等价</span></span><br><span class="line">        feats = feats / feats.norm(dim=-<span class="number">1</span>, keepdim=<span class="literal">True</span>)</span><br><span class="line">        embeddings.append(feats.cpu().numpy().astype(<span class="string">&quot;float32&quot;</span>))</span><br><span class="line">        ok_paths.extend(ok)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 4. 建 FAISS 索引并落盘 ===</span></span><br><span class="line">    mat = np.vstack(embeddings)</span><br><span class="line">    <span class="comment"># IndexFlatL2: 精确暴力检索；&lt; 100K 向量足够，单次查询 &lt;1ms</span></span><br><span class="line">    index = faiss.IndexFlatL2(mat.shape[<span class="number">1</span>])</span><br><span class="line">    index.add(mat)</span><br><span class="line">    faiss.write_index(index, <span class="built_in">str</span>(INDEX_PATH))</span><br><span class="line">    PATHS_PATH.write_text(</span><br><span class="line">        json.dumps([<span class="built_in">str</span>(p) <span class="keyword">for</span> p <span class="keyword">in</span> ok_paths], ensure_ascii=<span class="literal">False</span>, indent=<span class="number">2</span>),</span><br><span class="line">        encoding=<span class="string">&quot;utf-8&quot;</span>,</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><h4 id="search-py（查询阶段）"><a href="#search-py（查询阶段）" class="headerlink" title="search.py（查询阶段）"></a><code>search.py</code>（查询阶段）</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;search.py — 文字 query → CLIP 文本编码 → FAISS Top-K → 打印路径</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">用法:</span></span><br><span class="line"><span class="string">  python search.py &quot;代码截图&quot;</span></span><br><span class="line"><span class="string">  python search.py &quot;flowchart&quot; --topk 10</span></span><br><span class="line"><span class="string">  python search.py &quot;blue UI&quot; --open    # 顺便打开 Top-1</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">from</span> __future__ <span class="keyword">import</span> annotations</span><br><span class="line"><span class="keyword">import</span> argparse, json, os</span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"><span class="keyword">import</span> faiss, torch</span><br><span class="line"><span class="keyword">from</span> modelscope <span class="keyword">import</span> snapshot_download</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> AutoModel, AutoProcessor</span><br><span class="line"></span><br><span class="line">INDEX_PATH = Path(__file__).parent / <span class="string">&quot;index.faiss&quot;</span></span><br><span class="line">PATHS_PATH = Path(__file__).parent / <span class="string">&quot;paths.json&quot;</span></span><br><span class="line"><span class="comment"># 必须和 build_index.py 一致——不同模型的向量空间不通用</span></span><br><span class="line">MODEL_ID   = <span class="string">&quot;AI-ModelScope/chinese-clip-vit-base-patch16&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>() -&gt; <span class="literal">None</span>:</span><br><span class="line">    parser = argparse.ArgumentParser()</span><br><span class="line">    parser.add_argument(<span class="string">&quot;query&quot;</span>, <span class="built_in">type</span>=<span class="built_in">str</span>, <span class="built_in">help</span>=<span class="string">&quot;自然语言查询&quot;</span>)</span><br><span class="line">    parser.add_argument(<span class="string">&quot;--topk&quot;</span>, <span class="built_in">type</span>=<span class="built_in">int</span>, default=<span class="number">5</span>)</span><br><span class="line">    parser.add_argument(<span class="string">&quot;--open&quot;</span>, action=<span class="string">&quot;store_true&quot;</span>, <span class="built_in">help</span>=<span class="string">&quot;打开 Top-1&quot;</span>)</span><br><span class="line">    args = parser.parse_args()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> INDEX_PATH.exists():</span><br><span class="line">        <span class="keyword">raise</span> SystemExit(<span class="string">f&quot;找不到 <span class="subst">&#123;INDEX_PATH.name&#125;</span>，先跑 python build_index.py&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 1. 加载模型 + 索引 ===</span></span><br><span class="line">    device = <span class="string">&quot;cuda&quot;</span> <span class="keyword">if</span> torch.cuda.is_available() <span class="keyword">else</span> <span class="string">&quot;cpu&quot;</span></span><br><span class="line">    model_dir = snapshot_download(MODEL_ID)</span><br><span class="line">    model     = AutoModel.from_pretrained(model_dir).to(device).<span class="built_in">eval</span>()</span><br><span class="line">    processor = AutoProcessor.from_pretrained(model_dir)</span><br><span class="line">    index = faiss.read_index(<span class="built_in">str</span>(INDEX_PATH))</span><br><span class="line">    paths = json.loads(PATHS_PATH.read_text(encoding=<span class="string">&quot;utf-8&quot;</span>))</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 2. 文字编码为向量 ===</span></span><br><span class="line">    inputs = processor(text=[args.query], return_tensors=<span class="string">&quot;pt&quot;</span>, padding=<span class="literal">True</span>).to(device)</span><br><span class="line">    <span class="keyword">with</span> torch.no_grad():</span><br><span class="line">        <span class="comment"># 同样取 .pooler_output 拿 512 维投影向量</span></span><br><span class="line">        feat = model.get_text_features(**inputs).pooler_output</span><br><span class="line">    feat = feat / feat.norm(dim=-<span class="number">1</span>, keepdim=<span class="literal">True</span>)</span><br><span class="line">    query_vec = feat.cpu().numpy().astype(<span class="string">&quot;float32&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 3. FAISS Top-K 检索 ===</span></span><br><span class="line">    distances, indices = index.search(query_vec, args.topk)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># === 4. 打印结果 ===</span></span><br><span class="line">    <span class="keyword">for</span> rank, (idx, dist) <span class="keyword">in</span> <span class="built_in">enumerate</span>(<span class="built_in">zip</span>(indices[<span class="number">0</span>], distances[<span class="number">0</span>]), <span class="number">1</span>):</span><br><span class="line">        <span class="comment"># 对 L2-normalized 向量：cos_sim = 1 - L2_dist^2 / 2</span></span><br><span class="line">        sim = <span class="number">1.0</span> - dist / <span class="number">2.0</span></span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;  #<span class="subst">&#123;rank&#125;</span>  sim=<span class="subst">&#123;sim:<span class="number">.3</span>f&#125;</span>  <span class="subst">&#123;paths[idx]&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> args.<span class="built_in">open</span> <span class="keyword">and</span> <span class="built_in">len</span>(indices[<span class="number">0</span>]) &gt; <span class="number">0</span>:</span><br><span class="line">        os.startfile(paths[indices[<span class="number">0</span>][<span class="number">0</span>]])</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><hr><h2 id="六、学习资源"><a href="#六、学习资源" class="headerlink" title="六、学习资源"></a>六、学习资源</h2><h3 id="6-1-入门文章（由浅入深）"><a href="#6-1-入门文章（由浅入深）" class="headerlink" title="6.1 入门文章（由浅入深）"></a>6.1 入门文章（由浅入深）</h3><table><thead><tr><th>资源</th><th>类型</th><th>适合阶段</th></tr></thead><tbody><tr><td>IBM Technology: <em>What is RAG?</em></td><td>5分钟视频</td><td>零基础入门，概念最清晰</td></tr><tr><td><em>Retrieval-Augmented Generation</em> - LangChain 官方文档</td><td>图文教程</td><td>理解流程，有代码示例</td></tr><tr><td><em>Building RAG from Scratch</em> - Towards Data Science</td><td>博客</td><td>不依赖框架，直接用 FAISS + Python</td></tr><tr><td>LlamaIndex 官方教程</td><td>实践</td><td>工程化框架，生产级 RAG</td></tr><tr><td>OpenAI Cookbook - RAG</td><td>代码示例</td><td>进阶，含评估指标</td></tr></tbody></table><h3 id="6-2-必读论文（可选）"><a href="#6-2-必读论文（可选）" class="headerlink" title="6.2 必读论文（可选）"></a>6.2 必读论文（可选）</h3><table><thead><tr><th>论文</th><th>年份</th><th>为什么读</th></tr></thead><tbody><tr><td><em>RAG for Knowledge-Intensive NLP Tasks</em>（Lewis et al.）</td><td>2020</td><td>RAG 概念起源</td></tr><tr><td><em>CLIP: Learning Transferable Visual Models</em>（Radford et al.）</td><td>2021</td><td>图文对齐的基础原理</td></tr><tr><td><em>SigLIP: Sigmoid Loss for Language Image Pre-Training</em></td><td>2023</td><td>CLIP 改进版，了解进展</td></tr></tbody></table><h3 id="6-3-中文资源"><a href="#6-3-中文资源" class="headerlink" title="6.3 中文资源"></a>6.3 中文资源</h3><ul><li><strong>BAAI BGE 系列文档</strong>：中文 embedding 模型的官方说明，直接指导模型选型</li><li><strong>知乎「RAG 实践」</strong>：国内工程师踩坑笔记，实用</li><li><strong>B站 @跟李沐学AI</strong>：深度学习基础，理解 embedding 原理的必要背景</li></ul><hr><h2 id="七、笔记"><a href="#七、笔记" class="headerlink" title="七、笔记"></a>七、笔记</h2><h3 id="模块一：RAG-Overview"><a href="#模块一：RAG-Overview" class="headerlink" title="模块一：RAG Overview"></a>模块一：RAG Overview</h3><h4 id="1-RAG体系结构："><a href="#1-RAG体系结构：" class="headerlink" title="1. RAG体系结构："></a>1. RAG体系结构：</h4><p><img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260524-032448.png" alt="image.png"><br>在 RAG 系统内部，首先会有可以访问数据库的 retriever，它会发动一次查询（类似数据库）。然后 retriever 会得到一个查询后的 （被认为最相关的一些信息）。接着，它会把这写得到的可能最相关的信息，生成一个增强的提示词。</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></pre></td><td class="code"><pre><span class="line"># ============================================================</span><br><span class="line"># 阶段 1：定义用户提问（原始 Prompt）</span><br><span class="line"># ============================================================</span><br><span class="line"># 这是用户最初提出的问题，包含时效性信息（&quot;this weekend&quot;），</span><br><span class="line"># 大模型自身的训练数据无法覆盖实时情况。</span><br><span class="line">prompt = &quot;Why are hotel prices in Vancouver super expensive this weekend?&quot;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># ============================================================</span><br><span class="line"># 阶段 2：朴素生成（Naive Generation，作为对照基线）</span><br><span class="line"># ============================================================</span><br><span class="line"># 直接把原始 prompt 丢给 LLM 生成答案。</span><br><span class="line"># 缺点：模型不知道&quot;本周末&quot;温哥华正在发生什么事件，</span><br><span class="line"># 只能凭训练数据给出泛泛的猜测（旅游旺季、汇率等），容易出现幻觉。</span><br><span class="line">generate(prompt)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># ============================================================</span><br><span class="line"># 阶段 3：检索相关文档（Retrieval）</span><br><span class="line"># ============================================================</span><br><span class="line"># 用 prompt 去外部知识库（向量数据库、搜索引擎、内部文档等）</span><br><span class="line"># 检索与问题相关的实时/权威信息，例如：</span><br><span class="line">#   - 本周末温哥华正在举办的演唱会、展会、体育赛事</span><br><span class="line">#   - 酒店行业的供需新闻</span><br><span class="line"># 这些文档是后续&quot;增强&quot;环节的关键素材。</span><br><span class="line">retrieved_documents = retrieve(prompt)</span><br><span class="line">print(retrieved_documents)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># ============================================================</span><br><span class="line"># 阶段 4：构造增强提示（Augmented Prompt）</span><br><span class="line"># ============================================================</span><br><span class="line"># 把&quot;原始问题&quot; + &quot;检索到的文档&quot;拼接成一个新的 prompt，</span><br><span class="line"># 显式地把外部知识喂给模型，让它在有据可依的前提下作答。</span><br><span class="line">augmented_prompt = f&quot;&quot;&quot;Respond to the following prompt: &#123;prompt&#125;</span><br><span class="line"></span><br><span class="line">using the following retrieved information to help you answer &#123;retrieved_documents&#125;&quot;&quot;&quot;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># ============================================================</span><br><span class="line"># 阶段 5：基于增强 Prompt 重新生成（Augmented Generation）</span><br><span class="line"># ============================================================</span><br><span class="line"># 此时 LLM 拿到的是&quot;问题 + 上下文证据&quot;，</span><br><span class="line"># 输出会更准确、更具时效性，也能减少幻觉。</span><br><span class="line"># 这就是 RAG（Retrieval-Augmented Generation）的完整闭环：</span><br><span class="line">#   Retrieve → Augment → Generate</span><br><span class="line">generate(augmented_prompt)</span><br></pre></td></tr></table></figure><h4 id="2-LLMs上理解RAG"><a href="#2-LLMs上理解RAG" class="headerlink" title="2. LLMs上理解RAG"></a>2. LLMs上理解RAG</h4><p>LLMs 本质上是在不停地预测下一个即将出现的值的可能性，RAG 是改变了这个可能性的分布。</p><h4 id="3-信息检索：RAG-Retrieve-vs-搜索引擎-vs-数据库查询"><a href="#3-信息检索：RAG-Retrieve-vs-搜索引擎-vs-数据库查询" class="headerlink" title="3. 信息检索：RAG Retrieve vs 搜索引擎 vs 数据库查询"></a>3. 信息检索：RAG Retrieve vs 搜索引擎 vs 数据库查询</h4><p>三者都在”找东西”，但底层匹配逻辑完全不同：</p><table><thead><tr><th>维度</th><th>搜索引擎（BM25&#x2F;TF-IDF）</th><th>数据库查询（SQL）</th><th>RAG Retrieve（向量检索）</th></tr></thead><tbody><tr><td><strong>匹配方式</strong></td><td>关键词词频统计</td><td>精确字段匹配</td><td>语义相似度（向量距离）</td></tr><tr><td><strong>查询语言</strong></td><td>自然语言词袋</td><td>结构化 SQL</td><td>任意内容（文字 &#x2F; 图片 &#x2F; 音频）</td></tr><tr><td><strong>能否理解同义词</strong></td><td>❌ 部分（需要词典）</td><td>❌ 完全不能</td><td>✅ 天然支持</td></tr><tr><td><strong>能否跨模态</strong></td><td>❌ 文字只能找文字</td><td>❌</td><td>✅ 文字找图片（CLIP）</td></tr><tr><td><strong>结果排序依据</strong></td><td>TF-IDF 分数</td><td>无排序（精确匹配 &#x2F; 过滤）</td><td>向量空间距离（余弦 &#x2F; L2）</td></tr><tr><td><strong>数据结构要求</strong></td><td>需要倒排索引</td><td>需要严格 Schema</td><td>只需向量，原始结构无要求</td></tr><tr><td><strong>适合的问题</strong></td><td>“找包含这些词的文档”</td><td>“找符合这些条件的记录”</td><td>“找语义上最相关的内容”</td></tr></tbody></table><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></pre></td><td class="code"><pre><span class="line">数据库查询  ── &quot;完全相等吗？&quot;   （精确）</span><br><span class="line">搜索引擎    ── &quot;有没有这个词？&quot;  （词频）</span><br><span class="line">RAG Retrieve── &quot;意思像不像？&quot;   （语义）</span><br></pre></td></tr></table></figure><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">实际工程：混合检索（Hybrid Search）</span></div><div class="callout-content"><p>RAG 并非”完美替代”前两者，生产环境常将<strong>向量检索 + BM25</strong>结合使用，取并集后再用 Re-ranker 重新排序——向量检索负责语义泛化，BM25 负责关键词锚定，两者互补。</p></div></div><h3 id="模块二：信息检索和搜索技术"><a href="#模块二：信息检索和搜索技术" class="headerlink" title="模块二：信息检索和搜索技术"></a>模块二：信息检索和搜索技术</h3><h4 id="1-检索器架构概述"><a href="#1-检索器架构概述" class="headerlink" title="1. 检索器架构概述"></a>1. 检索器架构概述</h4><h5 id="Metadata-Filtering（元数据过滤）"><a href="#Metadata-Filtering（元数据过滤）" class="headerlink" title="Metadata Filtering（元数据过滤）"></a>Metadata Filtering（元数据过滤）</h5><p>在向量检索前&#x2F;后，用结构化字段对候选集进行硬性筛选，<strong>缩小检索范围</strong>。</p><ul><li><strong>原理</strong>：文档在入库时附带元数据字段（如来源、日期、类别），查询时先过滤再做向量相似度排序</li><li><strong>优势</strong>：大幅减少无关文档的干扰，提升精度和检索效率。</li><li><strong>局限</strong>：依赖入库时的元数据质量，字段缺失或分类不准会导致有效文档被误过滤。不会理解内容，且几乎不会单独使用。</li></ul><h5 id="Keyword-Searching（关键词搜索）"><a href="#Keyword-Searching（关键词搜索）" class="headerlink" title="Keyword Searching（关键词搜索）"></a>Keyword Searching（关键词搜索）</h5><p>基于精确词语匹配来查找文档，是最经典的搜索方式。主要包含 TF-IDF 和 BM25 两种算法。</p><ul><li><strong>原理</strong>：将查询拆成词条，统计词频（如 BM25 算法），返回包含这些词的文档  </li><li><strong>优势</strong>：速度快、结果可解释、对专有名词（代码、ID、型号）命中精准</li><li><strong>局限</strong>：无法理解同义词或语义近似表达，换个说法就可能找不到</li></ul><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">TF-IDF vs BM25</span></div><div class="callout-content"><p>两者都用词频打分，但 BM25 是 TF-IDF 的改进版：TF-IDF 词频越高得分线性增长；BM25 加入<strong>词频饱和</strong>（高频词收益递减）和<strong>文档长度归一化</strong>，短文档中出现一次与长文档出现多次权重更合理，实际检索效果更好。</p></div></div><h5 id="Semantic-Searching（语义搜索）"><a href="#Semantic-Searching（语义搜索）" class="headerlink" title="Semantic Searching（语义搜索）"></a>Semantic Searching（语义搜索）</h5><p>将文本编码为向量，通过计算向量相似度来匹配”意思相近”的内容。</p><ul><li><strong>原理</strong>：用 Embedding 模型把查询和文档都映射到高维向量空间，用余弦相似度等方式度量距离</li><li><strong>优势</strong>：能捕捉语义泛化，即使措辞不同也能找到相关内容</li><li><strong>局限</strong>：计算开销更高，对精确词语（如特定 ID）反而不如关键词搜索可靠</li></ul><h4 id="2-两种常见的关键词检索算法详解"><a href="#2-两种常见的关键词检索算法详解" class="headerlink" title="2. 两种常见的关键词检索算法详解"></a>2. 两种常见的关键词检索算法详解</h4><p><strong>TF-IDF</strong></p><p>核心公式：<code>Score = TF(词, 文档) × log(文档总数 / 含该词的文档数)</code></p><ul><li><strong>TF</strong>（词频）：词在当前文档出现越多，说明越相关</li><li><strong>IDF</strong>（逆文档频率）：词在越多文档里出现，说明越”废话”，权重越低；罕见词权重越高</li><li>结果：<code>&quot;的&quot; &quot;is&quot;</code> 等高频虚词接近 0 分，<code>&quot;量子纠缠&quot; &quot;BM25&quot;</code> 等专有词得高分</li></ul><p><strong>BM25</strong>（TF-IDF 的工业级改进版，Elasticsearch 默认算法）</p><p>核心改进点：</p><table><thead><tr><th>问题</th><th>TF-IDF</th><th>BM25</th></tr></thead><tbody><tr><td>词频线性叠加</td><td>出现 100 次 &#x3D; 1 次的 100 倍</td><td>词频<strong>饱和</strong>，多了收益递减（参数 <code>k1</code>，默认 1.2–2.0）</td></tr><tr><td>长文档天然优势</td><td>惩罚过重</td><td>按平均文档长度<strong>归一化</strong>（参数 <code>b</code>，默认 0.75）</td></tr></tbody></table><blockquote><p><strong><code>k1</code></strong>：控制词频饱和速度，越高代表”多说几遍越重要”；越低代表”说 3 遍和说 100 遍差不多”<br><strong><code>b</code></strong>：控制长度惩罚力度，<code>b=0</code> 忽略长度，<code>b=1</code> 严格按密度打分，0.75 是工业经验值</p></blockquote><h4 id="3-语义搜索与关键词搜索的异同"><a href="#3-语义搜索与关键词搜索的异同" class="headerlink" title="3. 语义搜索与关键词搜索的异同"></a>3. 语义搜索与关键词搜索的异同</h4><h5 id="1-共同点：数字化"><a href="#1-共同点：数字化" class="headerlink" title="1. 共同点：数字化"></a>1. 共同点：数字化</h5><ul><li><strong>Prompt and documents each get a vector（提示词和文档各获得一个向量）：</strong><br>  无论是哪种搜索，计算机都无法直接“读懂”文字。第一步都是将用户的提问和数据库里的文档转化成一串数字列表，这串数字就叫向量。</li><li><strong>Vectors compared to generate scores（对比向量以生成评分）：</strong><br>  一旦变成了数字，计算机就可以通过数学公式（如余弦相似度）计算两个向量之间的“距离”。距离越近，分值越高，代表两者越相关。</li></ul><h5 id="2-核心区别：向量是如何生成的？"><a href="#2-核心区别：向量是如何生成的？" class="headerlink" title="2. 核心区别：向量是如何生成的？"></a>2. 核心区别：向量是如何生成的？</h5><h6 id="关键词搜索：统计单词数量"><a href="#关键词搜索：统计单词数量" class="headerlink" title="关键词搜索：统计单词数量"></a><strong>关键词搜索：统计单词数量</strong></h6><ul><li><strong>原理：</strong> 这种方式生成的叫<strong>稀疏向量</strong>。</li><li><strong>逻辑：</strong> 向量里的每一个位置代表一个特定的单词。如果文档里出现了这个词，就在对应位置标上分数（基于词频，如 BM25）。</li><li><strong>特点：</strong> 字面匹配，它只认“长得一样”的词。<br>  <strong>局限：</strong> 如果你搜“医生”，它找不到包含“医师”但没写“医生”的文档，因为它不理解词义。</li></ul><h6 id="语义搜索：使用嵌入模型"><a href="#语义搜索：使用嵌入模型" class="headerlink" title="语义搜索：使用嵌入模型"></a><strong>语义搜索：使用嵌入模型</strong></h6><ul><li><strong>原理：</strong> 这种方式生成的叫<strong>稠密向量 (Dense Vector)</strong>。</li><li><strong>逻辑：</strong> 它不直接数单词，而是把文本输入到一个预训练好的<strong>深度学习模型（Embedding Model，如 BERT）</strong>。模型会把文本“映射”到一个多维的语义空间里。</li><li><strong>特点：</strong> 理解含义，向量里的数字代表的是抽象的“特征”或“概念”。<br>  <strong>优势：</strong> 它能识别同义词。即使单词不匹配，只要“意思”接近（比如“猫”和“小猫”，“医生”和“医师”），它们的向量在数学空间里的距离就会非常近。</li></ul><h4 id="3-RRF-算法（平衡关键词和语义）"><a href="#3-RRF-算法（平衡关键词和语义）" class="headerlink" title="3.  RRF 算法（平衡关键词和语义）"></a>3.  RRF 算法（平衡关键词和语义）</h4><p>它是 <strong>混合检索</strong> 中的核心技术。当你在 RAG 系统中同时运行”关键词检索”和”语义检索”时，会得到两份完全不同的评分列表。RRF（Reciprocal Rank Fusion，倒数排名融合）的作用就是<strong>将这两份列表公正地合并成一份最终的排名列表</strong>。</p><h5 id="1-核心问题：”苹果和橙子”的对比困境"><a href="#1-核心问题：”苹果和橙子”的对比困境" class="headerlink" title="1. 核心问题：”苹果和橙子”的对比困境"></a>1. 核心问题：”苹果和橙子”的对比困境</h5><p>混合检索面临一个根本性困难：</p><ul><li><strong>关键词检索（BM25）</strong> 的分数可能是 <code>15.4</code>、<code>12.8</code> 等。</li><li><strong>语义检索（向量搜索）</strong> 的分数（余弦相似度）通常在 <code>0.8</code> 到 <code>0.9</code> 之间。</li><li><strong>问题</strong>：这两种分数单位不同，无法直接相加比较。</li></ul><p><strong>RRF 的解法：完全忽略原始分数，只看文档在列表中的位次（排名）。</strong></p><h5 id="2-核心机制"><a href="#2-核心机制" class="headerlink" title="2. 核心机制"></a>2. 核心机制</h5><ul><li><strong>奖励”共识”文档：</strong> 如果一个文档在关键词搜索和语义搜索中都排名靠前，RRF 会给它极高的最终得分。</li><li><strong>标准化两种搜索的权重：</strong> 提供了一种跨策略的公平比较方式，不让任何一种算法因分值范围大而主导结果。</li><li><strong>得分 &#x3D; 排名的倒数（算法名称由来）：</strong><ul><li>第 1 名 → <code>1/1 = 1.0</code> 分</li><li>第 2 名 → <code>1/2 = 0.5</code> 分</li><li>第 10 名 → <code>1/10 = 0.1</code> 分</li><li>逻辑：名次越靠前分值越高，且名次越往后分值下降越快。</li></ul></li><li><strong>汇总得分：</strong> 把同一文档在所有列表中的倒数分数相加，总分最高者赢得最终排名。</li></ul><h5 id="3-公式"><a href="#3-公式" class="headerlink" title="3. 公式"></a>3. 公式</h5><p>$$RRF(d) &#x3D; \sum_{i&#x3D;1}^{n} \frac{1}{k + rank_i(d)}$$</p><ul><li><code>rank_i</code>：文档 <code>d</code> 在第 <code>i</code> 个检索列表中的排名（从 1 开始）。</li><li><code>k</code>：<strong>平滑常数</strong>，工业界通常默认设为 <strong>60</strong>。</li></ul><p><strong>为什么需要 <code>k</code>？</strong></p><p>若不加 <code>k</code>，第 1 名（1.0 分）与第 100 名（0.01 分）差距极大，会导致排名第一的文档拥有统治级权力。<code>k</code> 作为”减震器”，能压缩极端差异，削弱偶然排第一的噪声文档的影响。</p><table><thead><tr><th>参数值</th><th>效果</th><th>风险</th></tr></thead><tbody><tr><td><code>k = 0</code>（极度敏感）</td><td>第一名拥有绝对统治力</td><td>某个算法偶然将噪声文档排第一，会干扰整个 RAG 结果</td></tr><tr><td><code>k = 60</code>（平滑稳健，工业默认）</td><td>单列表的高排名不再独占优势</td><td>无明显风险；要求多个检索策略共同认可才能胜出</td></tr></tbody></table><h5 id="4-RRF-的核心优势：只在乎排名"><a href="#4-RRF-的核心优势：只在乎排名" class="headerlink" title="4. RRF 的核心优势：只在乎排名"></a>4. RRF 的核心优势：只在乎排名</h5><ul><li><strong>无需分数归一化：</strong> 不需要考虑 BM25 的 20 分和向量搜索的 0.9 分如何换算，直接比较位次即可。</li><li><strong>跨策略无缝合并：</strong> 无论是 2 种还是 5 种不同的检索技术，只要各自给出一份排名列表，RRF 就能公平融合。</li></ul><h5 id="5-调节语义-vs-关键词权重的参数"><a href="#5-调节语义-vs-关键词权重的参数" class="headerlink" title="5. 调节语义 vs 关键词权重的参数"></a>5. 调节语义 vs 关键词权重的参数</h5><p>标准 RRF 本身没有直接的”语义 vs 关键词”权重参数——<code>k</code> 只是平滑常数，不控制两者比重。<br>但<strong>加权 RRF（Weighted RRF）</strong> 在公式中为每个检索策略引入独立权重 <code>w</code>：</p><p>$$RRF(d) &#x3D; \sum_{i&#x3D;1}^{n} \frac{w_i}{k + rank_i(d)}$$</p><ul><li>提高 <code>w_semantic</code> → 结果更偏向语义理解（找近义、概念相关）</li><li>提高 <code>w_keyword</code> → 结果更偏向精确字面匹配</li></ul><p><strong>在工程实现中，这个权重在哪里调？</strong><br>以 <strong>LangChain</strong> 的 <code>EnsembleRetriever</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><span class="line"><span class="keyword">from</span> langchain.retrievers <span class="keyword">import</span> BM25Retriever, EnsembleRetriever</span><br><span class="line"><span class="keyword">from</span> langchain_community.vectorstores <span class="keyword">import</span> FAISS</span><br><span class="line"></span><br><span class="line"><span class="comment"># 两种检索器</span></span><br><span class="line">bm25_retriever = BM25Retriever.from_documents(docs)</span><br><span class="line">vector_retriever = FAISS.from_documents(docs, embedding_model).as_retriever()</span><br><span class="line"></span><br><span class="line"><span class="comment"># weights 就是公式里的 w_i，两个值相加通常为 1.0</span></span><br><span class="line">ensemble_retriever = EnsembleRetriever(</span><br><span class="line">    retrievers=[bm25_retriever, vector_retriever],</span><br><span class="line">    weights=[<span class="number">0.3</span>, <span class="number">0.7</span>]  <span class="comment"># 关键词 30%，语义 70%</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><blockquote><p><code>weights=[0.3, 0.7]</code> 即公式中的 <code>w_keyword</code> 和 <code>w_semantic</code>。调大语义权重 → 更擅长理解意图；调大关键词权重 → 更擅长精确匹配专有名词。</p></blockquote><h4 id="4-评估指标"><a href="#4-评估指标" class="headerlink" title="4. 评估指标"></a>4. 评估指标</h4><p>用量化数字衡量检索器好不好用，是系统调优的科学基础。</p><h5 id="三大核心指标"><a href="#三大核心指标" class="headerlink" title="三大核心指标"></a>三大核心指标</h5><table><thead><tr><th>指标</th><th>核心目标</th><th>一句话解释</th></tr></thead><tbody><tr><td><strong>召回率 Recall@K</strong></td><td>“找得全”</td><td>前 K 个结果里，成功找到了多少个真正相关的文档</td></tr><tr><td><strong>精确率 Precision &amp; MAP</strong></td><td>“找得准 + 排得好”</td><td>Precision 衡量返回结果中有多少是噪音；MAP 进一步评估相关文档是否排在最前面</td></tr><tr><td><strong>平均倒数排名 MRR</strong></td><td>“首位命中”</td><td>第一个相关文档出现的位置越靠前，得分越高</td></tr></tbody></table><h5 id="用一个具体例子理解-Precision-和-Recall"><a href="#用一个具体例子理解-Precision-和-Recall" class="headerlink" title="用一个具体例子理解 Precision 和 Recall"></a>用一个具体例子理解 Precision 和 Recall</h5><p><strong>场景：</strong> 知识库共 100 个文档，其中 <strong>10 个</strong>真正相关（Ground Truth），检索器返回了 <strong>8 个</strong>，其中 <strong>6 个</strong>真正相关。</p><p>$$Precision &#x3D; \frac{\text{返回的且相关的}}{\text{总返回数}} &#x3D; \frac{6}{8} &#x3D; 75%$$</p><p>$$Recall &#x3D; \frac{\text{返回的且相关的}}{\text{库中所有相关文档数}} &#x3D; \frac{6}{10} &#x3D; 60%$$</p><blockquote><p><strong>Precision</strong>：站在”你返回的文档”角度 —— 有多少是真货，有多少是噪音？<br><strong>Recall</strong>：站在”知识库”角度 —— 10 个正确答案，你找到了几个？</p></blockquote><h5 id="Precision-与-Recall-的内在张力"><a href="#Precision-与-Recall-的内在张力" class="headerlink" title="Precision 与 Recall 的内在张力"></a>Precision 与 Recall 的内在张力</h5><p>两者天然互相拉扯：</p><table><thead><tr><th>操作</th><th>Precision</th><th>Recall</th><th>原因</th></tr></thead><tbody><tr><td>K 调大（如 8 → 50）</td><td>⬇️ 下降</td><td>⬆️ 上升</td><td>捞得多，噪音增多，但遗漏减少</td></tr><tr><td>K 调小（如 8 → 3）</td><td>⬆️ 上升</td><td>⬇️ 下降</td><td>只选最有把握的，精准但遗漏多</td></tr></tbody></table><h5 id="指标的实际用途"><a href="#指标的实际用途" class="headerlink" title="指标的实际用途"></a>指标的实际用途</h5><ul><li><strong>评估基准表现</strong>：给当前系统打分，明确现状水平</li><li><strong>验证优化效果</strong>：换 Embedding 模型、调整 Chunking 大小、修改混合检索权重时，通过对比指标前后变化来确认改动是否真的有效</li></ul><h5 id="最关键的前提：Ground-Truth"><a href="#最关键的前提：Ground-Truth" class="headerlink" title="最关键的前提：Ground Truth"></a>最关键的前提：Ground Truth</h5><blockquote><p><strong>所有指标都依赖于”基准真相”数据集</strong></p></blockquote><ul><li><strong>什么是 Ground Truth？</strong> 人工标注好的数据集。例如：针对问题 A，提前标注知识库里”文档 1”和”文档 5”是唯一正确答案。</li><li><strong>为什么重要？</strong> Recall、Precision、MAP、MRR 的所有计算都需要对比”系统找出的答案”与”标准答案”。没有 Ground Truth，就无法进行科学调优。</li></ul><h4 id="5-Embedding-model深入探讨"><a href="#5-Embedding-model深入探讨" class="headerlink" title="5. Embedding model深入探讨"></a>5. Embedding model深入探讨</h4><h5 id="对比训练（Contrastive-Training-Process）"><a href="#对比训练（Contrastive-Training-Process）" class="headerlink" title="对比训练（Contrastive Training Process）"></a>对比训练（Contrastive Training Process）</h5><p>训练目标：让<strong>相似内容的向量靠近，不相似内容的向量远离</strong>。</p><h5 id="正样本：来自”互联网的自然配对”"><a href="#正样本：来自”互联网的自然配对”" class="headerlink" title="正样本：来自”互联网的自然配对”"></a>正样本：来自”互联网的自然配对”</h5><p>人类在互联网上的自然行为，天然就产生了海量配对数据：</p><table><thead><tr><th>数据源</th><th>正样本配对方式</th><th>谁”标注”的？</th></tr></thead><tbody><tr><td>网页图片的 <code>alt</code> 属性</td><td><code>&lt;img alt=&quot;夕阳下的柳树&quot;&gt;</code> → (图片, “夕阳下的柳树”)</td><td>网页作者写 alt 文字时，无意识地创造了配对</td></tr><tr><td>论坛问答（StackOverflow、知乎）</td><td>(问题, 最佳答案)</td><td>用户提问和回答时，无意识地创造了配对</td></tr><tr><td>维基百科</td><td>(文章标题, 正文第一段)</td><td>人类写百科，结构天然就是配对</td></tr><tr><td>新闻图片</td><td>(配图, 图说文字)</td><td>编辑写图说时，无意识地创造了配对</td></tr></tbody></table><blockquote><p><strong>核心思想：互联网本身就是一个巨大的”隐式标注数据集”</strong>。人类日常创作内容时，已经在无意识地为 AI 标注数据了。</p></blockquote><h5 id="负样本：由算法自动生成"><a href="#负样本：由算法自动生成" class="headerlink" title="负样本：由算法自动生成"></a>负样本：由算法自动生成</h5><table><thead><tr><th>负样本类型</th><th>来源</th><th>需要人工吗？</th></tr></thead><tbody><tr><td><strong>In-batch 负样本</strong></td><td>Batch 内其他样本自动充当</td><td>❌ 完全自动</td></tr><tr><td><strong>随机负样本</strong></td><td>从数据集随机采样</td><td>❌ 完全自动</td></tr><tr><td><strong>Hard Negatives</strong></td><td>用弱模型先检索”接近但错误”的样本</td><td>⚠️ 部分需要人工验证</td></tr><tr><td><strong>LLM 生成的 Hard Negatives</strong></td><td>让 GPT-4 生成语义相近但不同的句子</td><td>❌ AI 生成</td></tr></tbody></table><h5 id="极少数需要人工标注的场景"><a href="#极少数需要人工标注的场景" class="headerlink" title="极少数需要人工标注的场景"></a>极少数需要人工标注的场景</h5><p>只有在需要<strong>高精度 Benchmark</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></pre></td><td class="code"><pre><span class="line">STS（Semantic Textual Similarity）数据集：</span><br><span class="line">  人类给两个句子的相似度打 1-5 分</span><br><span class="line">  但这是用来&quot;评测&quot;模型的，不是&quot;训练&quot;模型的主力数据</span><br></pre></td></tr></table></figure><p><strong>训练过程</strong>：前向传播生成向量 → 构建相似度矩阵 → InfoNCE Loss 惩罚”正样本排名不靠前”的情况 → 反向传播更新权重。</p><blockquote><p>CLIP 的 4 亿训练对几乎全部自动获取。互联网本身就是隐式标注数据集，人工标注只用于评测 Benchmark，不是主力训练数据。</p><p>另外我感觉这个训练的过程很像我个人网站上的节点图，这个节点图会有斥力和吸力，整个节点图系统需要保持一个稳定的状态。因此，需要保持斥力和吸力的稳定，它需要有一个调整的过程。而这个调整的过程，我觉得可能就是 embedding 这个过程。</p><p>一个常见的误区——<strong>大模型参数 vs Embedding维度</strong>：参数是神经网络内部的权重总量（CLIP 约 1.5 亿个），是训练中学到的知识本身；维度是输出向量的长度（如 512 维），是设计时固定的规格。参数是模型学到的知识总量（越多越”聪明”），维度是输出向量的长度（影响表达能力的上限）。两者不是同一回事。</p></blockquote><h3 id="模块三：使用向量数据进行信息检索"><a href="#模块三：使用向量数据进行信息检索" class="headerlink" title="模块三：使用向量数据进行信息检索"></a>模块三：使用向量数据进行信息检索</h3><h4 id="1-从暴力检索到-ANN"><a href="#1-从暴力检索到-ANN" class="headerlink" title="1. 从暴力检索到 ANN"></a>1. 从暴力检索到 ANN</h4><p>向量检索的所有算法，都有一个关键变量 <strong>K</strong>——指定返回与 query 最相近的向量数量（Top-K）。围绕”如何高效找到这 K 个向量”，演化出了两大流派：<strong>精确检索</strong> 与 <strong>近似检索（ANN）</strong>。</p><h5 id="最基础的算法：暴力检索（Brute-Force-Flat-Search-Linear-Scan）"><a href="#最基础的算法：暴力检索（Brute-Force-Flat-Search-Linear-Scan）" class="headerlink" title="最基础的算法：暴力检索（Brute Force &#x2F; Flat Search &#x2F; Linear Scan）"></a>最基础的算法：暴力检索（Brute Force &#x2F; Flat Search &#x2F; Linear Scan）</h5><p>最朴素直观的做法——计算 query 向量与数据库中<strong>每一个</strong>向量的距离（余弦&#x2F;欧氏），全排序后取 Top-K。</p><blockquote><p>在 FAISS 里它叫 <code>IndexFlatL2</code>，在学术界叫 <strong>Exact KNN</strong>，在工程上常被称为 <strong>Brute Force</strong> 或 <strong>Linear Scan</strong>。本质都是同一件事：不建任何索引，硬算到底。</p></blockquote><ul><li><strong>优点</strong>：实现极简（一行 numpy 就能写完）；<strong>Recall &#x3D; 100%</strong>，是所有 ANN 算法的精度天花板与评测基准</li><li><strong>致命问题</strong>：复杂度为 <strong>O(N×D)</strong>（N&#x3D;向量数，D&#x3D;维度）。数据库越大，单次查询越慢——百万向量已经吃力，亿级规模直接不可用</li><li><strong>适用场景</strong>：小数据集（&lt;10万）、离线评测、为 ANN 提供 ground truth</li></ul><h5 id="解决方案：ANN（Approximate-Nearest-Neighbor，近似最近邻搜索）"><a href="#解决方案：ANN（Approximate-Nearest-Neighbor，近似最近邻搜索）" class="headerlink" title="解决方案：ANN（Approximate Nearest Neighbor，近似最近邻搜索）"></a>解决方案：ANN（Approximate Nearest Neighbor，近似最近邻搜索）</h5><p><strong>核心思想</strong>：预先建索引，跳过大部分不可能相关的向量，只在小范围内精细比对——通过牺牲极小的精度，换取查询速度数个数量级的提升。</p><p>主流实现：</p><ul><li><strong>HNSW</strong>（分层可导航小世界图）：基于”跳表+小世界网络”，复杂度降至 O(log N)，精度高、查询快，工业界主流</li><li><strong>IVF</strong>（倒排文件索引）：先用 K-means 聚类，查询时只扫描最近的几个簇，适合超大规模</li><li><strong>PQ</strong>（乘积量化）：把高维向量切段后用聚类编号代替，向量压缩 64 倍以上，显著节省内存</li></ul><p>工程上需在 <strong>精度（Recall）↔ 速度（QPS）↔ 内存</strong> 三者间权衡，常见组合如 IVF+PQ、HNSW+PQ。</p><h4 id="2-向量数据库"><a href="#2-向量数据库" class="headerlink" title="2. 向量数据库"></a>2. 向量数据库</h4><p>向量数据库是专门为<strong>存储、管理和检索高维向量</strong>设计的数据库系统。普通数据库存的是结构化行列，向量数据库存的是 Embedding 向量——并在其上内置 ANN 索引，让语义检索变成一等公民。</p><h5 id="与传统数据库的核心区别"><a href="#与传统数据库的核心区别" class="headerlink" title="与传统数据库的核心区别"></a>与传统数据库的核心区别</h5><table><thead><tr><th>维度</th><th>关系型数据库（MySQL）</th><th>向量数据库（Qdrant&#x2F;Milvus）</th></tr></thead><tbody><tr><td>核心数据</td><td>结构化行列</td><td>高维浮点向量</td></tr><tr><td>查询方式</td><td>SQL 精确匹配</td><td>ANN 近似相似度检索</td></tr><tr><td>索引类型</td><td>B-Tree、Hash</td><td>HNSW、IVF、PQ</td></tr><tr><td>典型问题</td><td>“找 id&#x3D;42 的记录”</td><td>“找最语义相似的 Top-10”</td></tr></tbody></table><blockquote><p>向量数据库通常<strong>同时存向量 + 元数据</strong>，支持先用元数据硬过滤，再做向量检索——即上文提到的 Metadata Filtering 与语义检索的结合。</p></blockquote><h5 id="主流向量数据库对比"><a href="#主流向量数据库对比" class="headerlink" title="主流向量数据库对比"></a>主流向量数据库对比</h5><ul><li><strong>Qdrant</strong>：Rust 编写，性能强，REST&#x2F;gRPC 接口，支持 payload 过滤，开源可自部署</li><li><strong>Milvus</strong>：专为超大规模设计（亿级），云原生架构，适合生产级分布式场景</li><li><strong>ChromaDB</strong>：最轻量，几行代码启动，开发调试首选，不适合大规模生产</li><li><strong>pgvector</strong>：PostgreSQL 插件，已有 PG 数据库直接加向量检索，无需引入新系统</li><li><strong>FAISS</strong>：严格来说是库而非数据库——无持久化、无 CRUD，但是所有向量数据库的底层算法来源</li></ul><h5 id="创建向量数据库的基础流程"><a href="#创建向量数据库的基础流程" class="headerlink" title="创建向量数据库的基础流程"></a>创建向量数据库的基础流程</h5><p><strong>Step 1 — 数据库初始化（Database Setup）</strong><br>创建集合（Collection）并定义 Schema——指定存哪些字段、向量维度是多少、用什么距离度量（余弦 &#x2F; L2 &#x2F; 内积）。这是后续所有操作的容器，相当于建表。</p><p><strong>Step 2 — 文档加载（Loading Documents）</strong><br>将原始数据（文本、PDF、图片等）读入内存，按需做分块（Chunking）——把长文档切成适合 Embedding 的小段，避免单段过长导致语义稀释。</p><p><strong>Step 3 — 稀疏向量（Sparse Vectors，关键词检索用）</strong><br>用 BM25 等算法为每个文档块生成稀疏向量——向量大多数位置为 0，只有出现过的词对应位置有权重值。专为关键词精确匹配设计，是混合检索（Hybrid Search）的关键词侧。</p><p><strong>Step 4 — 稠密向量（Dense Vectors，语义检索用）</strong><br>将文档块送入 Embedding 模型（如 BGE、CLIP），输出每个块的稠密向量——每一维都有值，承载语义信息。这是语义搜索的核心，能理解同义词和意图。</p><p><strong>Step 5 — 构建 HNSW 索引（HNSW Index）</strong><br>在稠密向量上建立 HNSW（分层可导航小世界图）索引。预先在向量之间织好”导航网络”，查询时沿图跳跃定位，将检索复杂度从 O(N) 降至 O(log N)，实现毫秒级 ANN 搜索。<em>这个也可以不做，如果不做的话就是暴力检索。</em></p><h4 id="3-Chunk（分块技术）"><a href="#3-Chunk（分块技术）" class="headerlink" title="3. Chunk（分块技术）"></a>3. Chunk（分块技术）</h4><p>将长文档切成适合 Embedding 的小段，是 RAG 索引阶段的关键预处理步骤。块太大语义稀释、块太小上下文丢失，切得好不好直接影响检索质量。</p><h5 id="为什么要分块？"><a href="#为什么要分块？" class="headerlink" title="为什么要分块？"></a>为什么要分块？</h5><p>Embedding 模型有 <strong>Token 上限</strong>（如 BERT 系列 512 token、BGE-M3 8192 token）。把整本书塞进去只会得到一个模糊的”平均语义”，检索时很难命中具体段落。分块后每段各自有独立向量，检索精度大幅提升。</p><h5 id="主要分块策略"><a href="#主要分块策略" class="headerlink" title="主要分块策略"></a>主要分块策略</h5><ul><li><strong>固定大小（Fixed-size Chunking）</strong>：按字数 &#x2F; token 数硬切，简单粗暴。缺点是可能从句子中间截断，损失上下文。常配合 <strong>Overlap（重叠）</strong> 使用——相邻块共享若干 token，缓解截断问题。</li><li><strong>语义分块（Semantic Chunking）</strong>：按句子边界、段落、标题层级切分，保留自然语义完整性。适合结构化文档（Markdown、PDF）。</li><li><strong>递归字符分割（Recursive Character Splitting）</strong>：LangChain 默认策略，按 <code>\n\n → \n → 句号 → 空格</code> 优先级依次尝试，尽量在自然边界切割，兼顾简单与语义完整。</li></ul><h4 id="4-一些更高级的-chunk-技术"><a href="#4-一些更高级的-chunk-技术" class="headerlink" title="4. 一些更高级的 chunk 技术"></a>4. 一些更高级的 chunk 技术</h4><h5 id="利用-LLM-进行语义分块"><a href="#利用-LLM-进行语义分块" class="headerlink" title="利用 LLM 进行语义分块"></a>利用 LLM 进行语义分块</h5><p>传统分块靠规则（段落、标题、固定大小），而”LLM 语义分块”让模型真正<strong>理解内容的语义边界</strong>后再决定怎么切，质量更高但成本也更高。</p><hr><p><strong>① 基于 Embedding 相似度的语义分块（SemanticChunker）</strong></p><p>原理：先按句子拆开文本 → 对每个句子计算 Embedding → 计算<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></pre></td><td class="code"><pre><span class="line">句1 → 句2 → 句3 ↘↘骤降↘↘ 句4 → 句5</span><br><span class="line">                      ↑</span><br><span class="line">                   切割点（语义断裂）</span><br></pre></td></tr></table></figure><ul><li><strong>工具</strong>：LangChain 的 <code>SemanticChunker</code> 直接封装了这个逻辑</li><li><strong>优点</strong>：无需调用 LLM 推理，成本低；真正按语义断点切，而不是按字数</li><li><strong>缺点</strong>：需要设定相似度阈值，阈值选不好会切太碎或切太少</li></ul><hr><p><strong>② 命题分块（Proposition Chunking）</strong></p><p>原理：用 LLM 将每个文本段进一步提炼为若干<strong>原子命题</strong>——每条命题是一个<strong>独立、完整、可单独理解</strong>的最小事实单元。</p><blockquote><p>例：原文”爱因斯坦于 1905 年发表了狭义相对论，并因光电效应获诺贝尔奖”<br>→ 命题1：「爱因斯坦于 1905 年发表了狭义相对论」<br>→ 命题2：「爱因斯坦因光电效应研究获得了诺贝尔奖」</p></blockquote><ul><li><strong>优点</strong>：检索时 query 与命题一一对应，精准度极高；每条命题自包含，不依赖上下文也能理解</li><li><strong>缺点</strong>：每个 chunk 都要调用 LLM 提炼，<strong>token 消耗大、速度慢</strong>，索引构建成本高</li><li><strong>适用</strong>：对知识库检索质量要求极高的场景（医疗、法律、精确问答）</li></ul><hr><p><strong>③ Agentic 分块</strong></p><p>直接将整段文档交给 LLM，让它自主判断语义边界、输出分割点或直接输出分好的块。最灵活，能处理复杂非结构化文档（如对话记录、混排文档），但<strong>成本最高</strong>，一般只用在离线预处理管道中。</p><hr><h5 id="三种方式对比"><a href="#三种方式对比" class="headerlink" title="三种方式对比"></a>三种方式对比</h5><table><thead><tr><th>方式</th><th>是否调用 LLM</th><th>成本</th><th>精度</th><th>适用场景</th></tr></thead><tbody><tr><td>Embedding 相似度</td><td>只用 Embedding 模型</td><td>低</td><td>中</td><td>通用场景，快速构建</td></tr><tr><td>命题分块</td><td>是（提炼命题）</td><td>高</td><td>高</td><td>精确问答、知识库</td></tr><tr><td>Agentic 分块</td><td>是（理解+切割）</td><td>最高</td><td>最高</td><td>复杂非结构化文档</td></tr></tbody></table><blockquote><p>实际项目中最常见的折中方案：先用<strong>递归字符分割</strong>做粗切，再用<strong>Embedding 相似度</strong>做语义边界校正，只在核心知识库段落上启用<strong>命题分块</strong>。</p></blockquote><h4 id="5-查询解析"><a href="#5-查询解析" class="headerlink" title="5.查询解析"></a>5.查询解析</h4><p>用户的原始问题往往<strong>口语化、模糊、包含多个子意图</strong>——直接用来检索，向量相似度会偏移，漏掉真正相关的文档。”查询解析”这一步的目标，就是在检索前用 LLM <strong>把问题变得更适合检索</strong>。</p><p>核心思路：<strong>在 Retrieval 之前，先用一次（或多次）LLM 调用对 Query 做变换，再去检索。</strong></p><hr><h5 id="Query-Rewriting-——-查询改写"><a href="#Query-Rewriting-——-查询改写" class="headerlink" title="Query Rewriting —— 查询改写"></a>Query Rewriting —— 查询改写</h5><p><strong>原理</strong>：直接用 LLM 将用户的原始问题改写为一个（或多个）措辞更精准、更接近知识库文档语言的新 Query。</p><blockquote><p>例：用户问”这个 bug 咋修” → 改写后：”如何修复 IndexError 数组越界异常？”</p></blockquote><p><strong>为什么有效</strong>：Embedding 模型是对称相似度——用户说的词和文档里写的词越像，向量越接近。改写填补了”口语 ↔ 文档语言”的词汇鸿沟（Vocabulary Mismatch）。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 改写 Prompt 示意</span></span><br><span class="line">system = <span class="string">&quot;你是一个专业搜索助手，将用户问题改写为适合文档检索的精准查询语句，只输出改写后的句子。&quot;</span></span><br><span class="line">rewritten = llm.chat(system, user_query)</span><br><span class="line">results = vector_db.search(embed(rewritten), top_k=<span class="number">5</span>)</span><br></pre></td></tr></table></figure><ul><li><strong>优点</strong>：实现简单，一次 LLM 调用；显著改善词汇不匹配问题</li><li><strong>缺点</strong>：改写有可能偏离原意；增加一次 LLM 延迟</li></ul><hr><h5 id="Query-Decomposition-——-查询分解"><a href="#Query-Decomposition-——-查询分解" class="headerlink" title="Query Decomposition —— 查询分解"></a>Query Decomposition —— 查询分解</h5><p><strong>原理</strong>：遇到包含多个子问题的复杂 Query，让 LLM 将其<strong>拆解为若干独立子问题</strong>，每个子问题单独去检索，最后汇总答案。</p><blockquote><p>例：用户问 “Python 和 JavaScript 在 Web 开发中各有什么优缺点，该选哪个？”<br>→ 子问题 1：「Python Web 开发的优点是什么？」<br>→ 子问题 2：「Python Web 开发的缺点是什么？」<br>→ 子问题 3：「JavaScript Web 开发的优点是什么？」<br>→ 子问题 4：「JavaScript Web 开发的缺点是什么？」</p></blockquote><p>每条子问题分别检索，检索结果合并后一起交给 LLM 生成最终答案。</p><p><strong>为什么有效</strong>：一个复杂问题往往在知识库里对应多处分散的信息。不分解则很难用一条 Query 同时命中所有相关段落；分解后每条 Query 更聚焦，检索精度大幅提升。</p><ul><li><strong>优点</strong>：特别适合多跳推理（Multi-hop）和比较类问题</li><li><strong>缺点</strong>：子问题数量不可控；多次检索成本倍增；汇总逻辑复杂</li></ul><hr><h5 id="HyDE-——-假设性文档嵌入（Hypothetical-Document-Embeddings）"><a href="#HyDE-——-假设性文档嵌入（Hypothetical-Document-Embeddings）" class="headerlink" title="HyDE —— 假设性文档嵌入（Hypothetical Document Embeddings）"></a>HyDE —— 假设性文档嵌入（Hypothetical Document Embeddings）</h5><p><strong>原理</strong>：不直接对 Query 做 Embedding，而是先让 LLM <strong>生成一段假设性的”理想答案”</strong>，再对这段假设答案做 Embedding 去检索。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">用户 Query → LLM 生成&quot;假设答案&quot; → Embed(假设答案) → 向量检索</span><br></pre></td></tr></table></figure><blockquote><p>例：用户问”HNSW 为什么查询速度快？”<br>→ LLM 生成假设答案：”HNSW 基于分层图结构，查询时从高层稀疏图快速定位大致区域，再逐层精细搜索……”<br>→ 对这段假设答案做 Embedding → 比直接对问题句做 Embedding，向量更接近知识库里真实文档</p></blockquote><p><strong>直觉理解</strong>：问题的 Embedding 和答案的 Embedding 在语义空间里本就不在同一个位置。假设答案更像一段”真实文档”，因此和知识库里的文档向量距离更近，检索 Recall 更高。</p><ul><li><strong>优点</strong>：对”知识密集型问答”效果显著；不依赖关键词，纯语义驱动</li><li><strong>缺点</strong>：假设答案可能包含幻觉，但<strong>检索阶段不依赖答案的正确性，只用它的向量</strong>，所以幻觉不直接影响结果</li><li><strong>适用</strong>：专业领域问答、文档措辞与问题措辞差异大的场景</li></ul><hr><h5 id="Multi-Query-——-多角度提问"><a href="#Multi-Query-——-多角度提问" class="headerlink" title="Multi-Query —— 多角度提问"></a>Multi-Query —— 多角度提问</h5><p><strong>原理</strong>：让 LLM 从多个角度<strong>生成同一问题的 N 种变体</strong>，分别检索，再对检索结果去重合并（常与 RRF 联用）。</p><blockquote><p>例：原问题”RAG 的局限性”<br>→ 变体 1：「RAG 在什么场景下效果不好？」<br>→ 变体 2：「Retrieval-Augmented Generation 的缺点有哪些？」<br>→ 变体 3：「RAG 系统的失败案例」</p></blockquote><p>三条 Query 各自检索，合并结果后用 RRF 重排序，最终 Top-K 覆盖面远超单一 Query。</p><ul><li><strong>优点</strong>：弥补单条 Query 的覆盖盲区；与 RRF 天然搭配</li><li><strong>缺点</strong>：N 次 Embedding 检索 + LLM 调用，延迟和成本线性增长</li></ul><hr><h5 id="总结-各方案对比"><a href="#总结-各方案对比" class="headerlink" title="总结&#x2F;各方案对比"></a>总结&#x2F;各方案对比</h5><table><thead><tr><th>方式</th><th>核心思路</th><th>适用场景</th><th>额外 LLM 调用</th><th>风险</th></tr></thead><tbody><tr><td>Query Rewriting</td><td>改词，更像文档语言</td><td>口语&#x2F;专业词汇不匹配</td><td>1次</td><td>改偏原意</td></tr><tr><td>Query Decomposition</td><td>拆子问，分别检索</td><td>多跳&#x2F;比较类复杂问题</td><td>1次（拆分）+ N次检索</td><td>子问题爆炸</td></tr><tr><td>HyDE</td><td>先生成假设答案再检索</td><td>问题与文档措辞差异大</td><td>1次</td><td>幻觉影响向量方向</td></tr><tr><td>Multi-Query</td><td>生成多变体覆盖盲区</td><td>召回率不足、覆盖面窄</td><td>1次（生成）+ N次检索</td><td>成本高</td></tr></tbody></table><blockquote><p><strong>工程实践中</strong>：最常见的轻量方案是 <strong>Query Rewriting + Multi-Query（N&#x3D;3）+ RRF</strong>——只多 1 次 LLM 调用，检索质量提升明显。HyDE 和 Decomposition 留给对质量要求极高的场景（如法律文书问答）。</p></blockquote><h4 id="6-Reranker-与精排策略"><a href="#6-Reranker-与精排策略" class="headerlink" title="6. Reranker 与精排策略"></a>6. Reranker 与精排策略</h4><blockquote><p><strong>背景</strong>：向量检索（常规的编码两次的成为Bi-Encoder）快但粗糙——它把 Query 和 Document 分别压成单个向量再做点积，损失了大量细粒度的交互信息。Reranker 就是在粗检索之后，对 Top-N 候选做精细打分、重新排序的第二阶段模块。</p></blockquote><h5 id="两阶段检（Bi-Encoder）索管道"><a href="#两阶段检（Bi-Encoder）索管道" class="headerlink" title="两阶段检（Bi-Encoder）索管道"></a>两阶段检（Bi-Encoder）索管道</h5><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><span class="line">用户 Query</span><br><span class="line">    ↓</span><br><span class="line">[第一阶段] 向量检索（Bi-Encoder + FAISS） → Top-100 候选   ← 快，但粗</span><br><span class="line">    ↓</span><br><span class="line">[第二阶段] （Cross-Encoder）Reranker 精细打分 → Top-10 最终结果             ← 慢，但准</span><br></pre></td></tr></table></figure><h5 id="交叉编码器（Cross-Encoder）"><a href="#交叉编码器（Cross-Encoder）" class="headerlink" title="交叉编码器（Cross-Encoder）"></a>交叉编码器（Cross-Encoder）</h5><p><strong>原理</strong>：把 Query 和 Document <strong>拼在一起</strong>输入同一个 Transformer，让两段文字的所有 token 互相做注意力——直接输出一个相关性分数。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入: [CLS] Query tokens [SEP] Document tokens [SEP]</span><br><span class="line">输出: 一个 0~1 的相关性分数</span><br></pre></td></tr></table></figure><ul><li><strong>优点</strong>：全量交互，打分最准确，是精度天花板</li><li><strong>缺点</strong>：Document 向量<strong>无法预计算</strong>——每次查询都要把每个候选文档和 Query 重新跑一遍，100 个候选 &#x3D; 100 次模型推理，延迟高</li><li><strong>适用</strong>：候选集不大（Top-100 以内）、对精度要求极高的场景</li></ul><h5 id="ColBERT（Contextualized-Late-Interaction-over-BERT）"><a href="#ColBERT（Contextualized-Late-Interaction-over-BERT）" class="headerlink" title="ColBERT（Contextualized Late Interaction over BERT）"></a>ColBERT（Contextualized Late Interaction over BERT）</h5><p><img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260606-220900.png" alt="image.png"><br>如上图，问题的每一个词都会和文章里的每一个词产生呼应，从而获得更精确的结果。</p><p><strong>原理</strong>：Query 和 Document 依然<strong>分开编码</strong>（所以 Document 可预计算），但不压缩成单个向量——保留每个 token 的向量。相关性分数用 <strong>MaxSim</strong> 计算：对 Query 的每个 token（逐字切分），找 Document 所有 token 里最相似的那个，累加求和。</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></pre></td><td class="code"><pre><span class="line">Query   → [q₁, q₂, q₃, ...]     每个 token（逐字切分） 一个向量</span><br><span class="line">Document → [d₁, d₂, d₃, ...]    每个 token（逐字切分） 一个向量（可离线存储）</span><br><span class="line"></span><br><span class="line">Score = Σ max_j sim(qᵢ, dⱼ)     ← &quot;Late Interaction&quot;</span><br></pre></td></tr></table></figure><ul><li><strong>优点</strong>：Document 可以离线预计算并存储；交互比单向量 Bi-Encoder 丰富得多</li><li><strong>缺点</strong>：存储开销大（每个 token 存一个向量，文档越长越占空间）；比 Bi-Encoder 慢，比 Cross-Encoder 快</li><li><strong>定位</strong>：精度与速度的<strong>中间档</strong>，适合候选集较大（Top-1000）的场景</li></ul><h5 id="三者横向对比"><a href="#三者横向对比" class="headerlink" title="三者横向对比"></a>三者横向对比</h5><table><thead><tr><th></th><th>Bi-Encoder</th><th>ColBERT</th><th>Cross-Encoder</th></tr></thead><tbody><tr><td>编码方式</td><td>Query&#x2F;Doc 各一个向量</td><td>Query&#x2F;Doc 各保留 token 向量</td><td>拼接后联合编码</td></tr><tr><td>Doc 预计算</td><td>✅</td><td>✅</td><td>❌</td></tr><tr><td>交互粒度</td><td>粗（单向量点积）</td><td>中（token-level MaxSim）</td><td>细（全量注意力）</td></tr><tr><td>速度</td><td>最快</td><td>中</td><td>最慢</td></tr><tr><td>精度</td><td>最低</td><td>中</td><td>最高</td></tr><tr><td>典型用途</td><td>第一阶段粗检索</td><td>中等规模 Reranking</td><td>小候选集精排</td></tr></tbody></table><h4 id="7-ReRanking"><a href="#7-ReRanking" class="headerlink" title="7. ReRanking"></a>7. ReRanking</h4><blockquote><p>精排不是一种具体模型，而是一个<strong>管道位置</strong>的概念——在粗检索之后，对候选集重新打分排序。<br>具体实现方式有多种，§6 已详解 Cross-Encoder 与 ColBERT，这里补充另外两条路线。</p></blockquote><h5 id="精排的三条路线（概览）"><a href="#精排的三条路线（概览）" class="headerlink" title="精排的三条路线（概览）"></a>精排的三条路线（概览）</h5><table><thead><tr><th>路线</th><th>核心机制</th><th>详见</th></tr></thead><tbody><tr><td>Cross-Encoder</td><td>Query+Doc 拼接联合编码，token 全量交互</td><td>§6 ①</td></tr><tr><td>ColBERT</td><td>保留 token 向量，MaxSim 延迟交互</td><td>§6 ②</td></tr><tr><td><strong>RRF</strong></td><td>合并多路检索的排名，无需打分</td><td>下方 ↓</td></tr><tr><td><strong>LLM 直接评分</strong></td><td>让大模型判断相关性</td><td>下方 ↓</td></tr></tbody></table><hr><h5 id="①-RRF（互惠排名融合，Reciprocal-Rank-Fusion）"><a href="#①-RRF（互惠排名融合，Reciprocal-Rank-Fusion）" class="headerlink" title="① RRF（互惠排名融合，Reciprocal Rank Fusion）"></a>① RRF（互惠排名融合，Reciprocal Rank Fusion）</h5><p><strong>适用场景</strong>：你同时运行了多路检索（如稀疏 BM25 + 稠密 CLIP），需要把两份排名列表合并成一份。</p><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></pre></td><td class="code"><pre><span class="line">RRF_score(doc) = Σ  1 / (k + rank_i)       k 通常取 60</span><br><span class="line">                 i</span><br></pre></td></tr></table></figure><p>对每路检索，用文档的排名位置（而非分数）计算贡献，再累加。</p><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></pre></td><td class="code"><pre><span class="line">Query: &quot;覆有青苔的石头&quot;</span><br><span class="line"></span><br><span class="line">BM25 排名（关键词）：        CLIP 排名（视觉）：</span><br><span class="line">  stone_moss_03  → rank 1     stone_moss_03  → rank 1</span><br><span class="line">  stone_moss_01  → rank 2     rock_wet_01    → rank 2</span><br><span class="line">  rock_wet_01    → rank 8     stone_moss_01  → rank 5</span><br><span class="line"></span><br><span class="line">RRF 合并：</span><br><span class="line">  stone_moss_03 = 1/(60+1) + 1/(60+1) = 0.0328  ← 两路都认可 ✅</span><br><span class="line">  stone_moss_01 = 1/(60+2) + 1/(60+5) = 0.0315</span><br><span class="line">  rock_wet_01   = 1/(60+8) + 1/(60+2) = 0.0308</span><br></pre></td></tr></table></figure><p><strong>为什么不直接把两路分数相加？</strong></p><p>BM25 分数（词频统计）和 CLIP 余弦相似度的<strong>量纲完全不同</strong>，直接相加没有意义。<br>RRF 只关心排名位置，不看具体数值，天然绕开了量纲对齐问题。</p><hr><h5 id="②-LLM-直接评分"><a href="#②-LLM-直接评分" class="headerlink" title="② LLM 直接评分"></a>② LLM 直接评分</h5><p>将候选结果的文本描述喂给 LLM，让模型直接判断与 Query 的相关性：</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></pre></td><td class="code"><pre><span class="line">Prompt 示例：</span><br><span class="line">  查询需求：&quot;覆有青苔的石头&quot;</span><br><span class="line">  候选资产：&quot;灰褐色石块，表面密布绿色苔藓，风化纹理明显，适合古风场景&quot;</span><br><span class="line"></span><br><span class="line">  请给这个资产与查询需求的相关程度打分（0-10），并给出一句理由。</span><br><span class="line"></span><br><span class="line">LLM 输出：</span><br><span class="line">  分数：9</span><br><span class="line">  理由：石块与青苔两个核心要素完全吻合，风化纹理进一步强化古风相关性</span><br></pre></td></tr></table></figure><table><thead><tr><th></th><th>说明</th></tr></thead><tbody><tr><td><strong>优点</strong></td><td>语义理解能力最强；可自定义评分标准（风格、场景适配度等）</td></tr><tr><td><strong>缺点</strong></td><td>每条候选都要调一次 LLM，成本高、延迟高</td></tr><tr><td><strong>适用</strong></td><td>候选集极小（Top-5 以内）、或需要可解释的评分理由</td></tr></tbody></table><hr><h5 id="选型速查"><a href="#选型速查" class="headerlink" title="选型速查"></a>选型速查</h5><table><thead><tr><th>候选集规模</th><th>推荐策略</th></tr></thead><tbody><tr><td>全量 10 万+</td><td>只能 Bi-Encoder（向量检索）</td></tr><tr><td>Top-1000</td><td>ColBERT 或 RRF 合并多路结果</td></tr><tr><td>Top-100</td><td><strong>Cross-Encoder</strong>（推荐，精度&#x2F;延迟最佳平衡）</td></tr><tr><td>Top-10</td><td>LLM 直接评分（可选，需要极致精度或可解释性时）</td></tr></tbody></table><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">游戏资产库项目对应策略</span></div><div class="callout-content"><ul><li><strong>已有（M1）</strong>：FTS5 关键词检索（稀疏）</li><li><strong>已有（M4）</strong>：CLIP FAISS 视觉检索（稠密）</li><li><strong>进行中（M5）</strong>：Cross-Encoder 精排，Top-100 → Top-10</li><li><strong>可选升级</strong>：将 FTS5 与 CLIP 的结果先用 RRF 合并，再送入 Cross-Encoder——<br>稀疏路线补精确关键词命中，稠密路线补语义覆盖，两者互补</li></ul></div></div><h3 id="模块四：大语言模型与文本生成"><a href="#模块四：大语言模型与文本生成" class="headerlink" title="模块四：大语言模型与文本生成"></a>模块四：大语言模型与文本生成</h3><p>前三个模块都在讲 <strong>Retrieve（检索）</strong>——把最相关的内容找出来。本模块进入 RAG 的最后一环 <strong>Generate（生成）</strong>：把检索到的内容交给大语言模型（LLM），生成有据可查的回答。</p><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">本模块在 RAG 流程中的位置</span></div><div class="callout-content"><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">用户问题 →【检索】Top-K 相关片段 →【拼装 Prompt】→【LLM 生成】→ 回答</span><br><span class="line">                 ↑ 模块一~三                      ↑ 模块四</span><br></pre></td></tr></table></figure><p>我们的资产搜索工具是”只检索不生成”，所以模块四对它是<strong>可选项</strong>；但本地的 <code>starting-ragchatbot-codebase</code>（问答机器人）是完整 RAG，生成环节由 Claude 承担。</p></div></div><h4 id="Transformer架构简介"><a href="#Transformer架构简介" class="headerlink" title="Transformer架构简介"></a>Transformer架构简介</h4><blockquote><p>一句话：Transformer 是当今几乎所有 LLM、Embedding 模型、Reranker 的<strong>共同底座</strong>。本笔记反复出现的 BERT、CLIP、BGE、Cross-Encoder、GPT&#x2F;Claude——全部是 Transformer 的变体。</p></blockquote><h5 id="1-为什么是-Transformer？（对比-RNN）"><a href="#1-为什么是-Transformer？（对比-RNN）" class="headerlink" title="1. 为什么是 Transformer？（对比 RNN）"></a>1. 为什么是 Transformer？（对比 RNN）</h5><p><strong>先说命名：为什么叫 Transformer，而不叫 Attention？</strong></p><ul><li><strong>Attention 是零件，Transformer 是整机。</strong> Attention 早在 2014 年（Bahdanau）就作为 RNN 的配件用于机器翻译，到 2017 年已是通用技术——新架构若也叫 Attention，会和”RNN+attention”撞名。</li><li><strong>论文标题是论点，架构名是产品名。</strong>《Attention Is All You Need》的潜台词是”以前 RNN 配 attention，把 RNN 拿掉、<strong>只留 attention</strong> 就够”；标题负责喊口号，新架构另起名 <strong>Transformer</strong> 与 RNN 时代切割。</li><li><strong>“Transform” &#x3D; 逐层变换表示。</strong> 模型把 token 的向量表示通过堆叠多层一层层重写，最终把”孤立词向量”变成”饱含上下文语义的向量”。Attention 只描述单层的某个操作（Q·K→加权 V），Transformer 描述整机在做什么——抽象层级不同。</li></ul><p><strong>那为什么需要它？</strong> 2017 年论文《Attention Is All You Need》之前，处理文本主要靠 RNN&#x2F;LSTM （<strong>RNN（Recurrent Neural Network，循环神经网络）</strong>），它<strong>按顺序</strong>一个词一个词读，有两个硬伤：</p><table><thead><tr><th>问题</th><th>RNN&#x2F;LSTM</th><th>Transformer</th></tr></thead><tbody><tr><td>并行能力</td><td>必须顺序计算，无法并行</td><td>整句<strong>一次性并行</strong>处理 ⭐</td></tr><tr><td>长距离依赖</td><td>距离越远信息衰减越严重</td><td>任意两词<strong>直接</strong>建立联系</td></tr><tr><td>训练速度</td><td>慢</td><td>快（能吃满 GPU）</td></tr></tbody></table><p>Transformer 用**自注意力（Self-Attention）**一举解决了这两点，这也是 LLM 能”规模化”到千亿参数的前提。</p><h5 id="2-核心机制：自注意力（Self-Attention）"><a href="#2-核心机制：自注意力（Self-Attention）" class="headerlink" title="2. 核心机制：自注意力（Self-Attention）"></a>2. 核心机制：自注意力（Self-Attention）</h5><p>自注意力让句子里的每个词，都去”看”句子里所有其他词，按相关性加权吸收信息。</p><p><strong>关键三元组 Q &#x2F; K &#x2F; V：</strong></p><table><thead><tr><th>符号</th><th>含义</th><th>类比（像不像检索？）</th></tr></thead><tbody><tr><td><strong>Query（查询）</strong></td><td>我现在想找什么</td><td>用户的搜索词</td></tr><tr><td><strong>Key（键）</strong></td><td>我能提供什么”标签”</td><td>文档的索引</td></tr><tr><td><strong>Value（值）</strong></td><td>我实际的内容</td><td>文档正文</td></tr></tbody></table><p>计算流程：</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></pre></td><td class="code"><pre><span class="line">对每个词：</span><br><span class="line">  1. 用 Query 和所有词的 Key 做点积 → 相关性分数</span><br><span class="line">  2. softmax 归一化 → 注意力权重（加起来=1）</span><br><span class="line">  3. 用权重对所有 Value 加权求和 → 该词的新表示</span><br></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></pre></td><td class="code"><pre><span class="line">句子： 这块石头覆满青苔，因为它很潮湿</span><br><span class="line">                              ↑</span><br><span class="line">&quot;它&quot;这个词的 Query 去匹配全句的 Key：</span><br><span class="line">  &quot;石头&quot; → 注意力 0.7   ← 权重最高，模型据此判断&quot;它=石头&quot;</span><br><span class="line">  &quot;青苔&quot; → 注意力 0.2</span><br><span class="line">  &quot;潮湿&quot; → 注意力 0.1</span><br></pre></td></tr></table></figure><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">关键洞察：注意力本质上就是一次&quot;软检索&quot;</span></div><div class="callout-content"><p>Query·Key→softmax→对 Value 加权求和——这套机制<strong>和模块二、三讲的稠密检索（向量相似度→Top-K→取回内容）是同一个数学思想</strong>，只不过它发生在模型<strong>内部</strong>，对每个 token 实时进行。理解了向量检索，你已经理解了注意力的一半。</p></div></div><h5 id="3-多头注意力-位置编码-整体结构"><a href="#3-多头注意力-位置编码-整体结构" class="headerlink" title="3. 多头注意力 + 位置编码 + 整体结构"></a>3. 多头注意力 + 位置编码 + 整体结构</h5><ul><li><strong>多头注意力（Multi-Head）</strong>：并行跑多组 Q&#x2F;K&#x2F;V，每个”头”关注不同角度（一个头看语法、一个头看指代、一个头看语义……），最后拼接。类比：多个专家从不同维度同时审阅同一句话。</li><li><strong>位置编码（Positional Encoding）</strong>：自注意力本身不区分词序（”狗咬人”和”人咬狗”会被看成一样），因此要额外注入每个词的位置信息。</li><li><strong>残差连接 + 层归一化 + 前馈网络（FFN）</strong>：每一层的标准配件，保证深层网络（几十上百层）能稳定训练。</li></ul><h5 id="4-三种架构变体（决定模型用途）"><a href="#4-三种架构变体（决定模型用途）" class="headerlink" title="4. 三种架构变体（决定模型用途）"></a>4. 三种架构变体（决定模型用途）</h5><table><thead><tr><th>架构</th><th>代表模型</th><th>擅长</th><th>在本笔记中的角色</th></tr></thead><tbody><tr><td><strong>Encoder-only</strong>（编码器）</td><td>BERT、BGE-M3</td><td>理解、向量化</td><td><strong>Embedding 模型</strong>（§3.1）、Cross-Encoder（§6）</td></tr><tr><td><strong>Decoder-only</strong>（解码器）</td><td>GPT、Claude、Qwen、Llama</td><td>生成文本</td><td><strong>本模块的主角</strong>，负责”生成”</td></tr><tr><td><strong>Encoder-Decoder</strong></td><td>T5、BART</td><td>翻译、摘要</td><td>较少用于聊天</td></tr></tbody></table><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">一张图串起整本笔记</span></div><div class="callout-content"><ul><li>你用 <strong>CLIP&#x2F;BGE</strong>（Encoder）把资产转成向量 → 这是 Transformer</li><li>你用 <strong>Cross-Encoder</strong>（Encoder）做精排 → 这是 Transformer</li><li>你用 <strong>Claude&#x2F;Qwen</strong>（Decoder）生成答案 → 还是 Transformer<br>三者血缘相同，区别只在”怎么搭、用哪半边、怎么训练”。</li></ul></div></div><hr><h4 id="大语言模型采样策略"><a href="#大语言模型采样策略" class="headerlink" title="大语言模型采样策略"></a>大语言模型采样策略</h4><p>检索决定”喂什么内容”，<strong>采样策略决定”模型怎么把内容说出来”</strong>——同样的上下文，参数不同，输出可以严谨也可以天马行空。对 RAG 来说这一节直接关系到<strong>幻觉率</strong>。</p><h5 id="1-LLM-是怎么”吐字”的？"><a href="#1-LLM-是怎么”吐字”的？" class="headerlink" title="1. LLM 是怎么”吐字”的？"></a>1. LLM 是怎么”吐字”的？</h5><p>Decoder 模型每一步只做一件事：<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></pre></td><td class="code"><pre><span class="line">输入：&quot;覆有青苔的石头通常出现在&quot;</span><br><span class="line">模型输出（下一个词的概率）：</span><br><span class="line">  潮湿   0.45</span><br><span class="line">  阴暗   0.25</span><br><span class="line">  森林   0.15</span><br><span class="line">  沙漠   0.02</span><br><span class="line">  ...（词表里几万个词，每个都有概率）</span><br></pre></td></tr></table></figure><p>“采样策略”就是<strong>从这个概率分布里挑一个词</strong>的规则。挑完接到输入后面，再预测下一个，循环往复。</p><h5 id="2-确定性解码"><a href="#2-确定性解码" class="headerlink" title="2. 确定性解码"></a>2. 确定性解码</h5><table><thead><tr><th>策略</th><th>做法</th><th>特点</th></tr></thead><tbody><tr><td><strong>贪心解码 Greedy</strong></td><td>每步都选概率最高的词</td><td>稳定但呆板，易陷入重复</td></tr><tr><td><strong>Beam Search</strong></td><td>同时保留 N 条候选路径，最后选整体最优</td><td>质量高但慢，多用于翻译&#x2F;摘要</td></tr></tbody></table><h5 id="3-随机采样（聊天-创作主流）"><a href="#3-随机采样（聊天-创作主流）" class="headerlink" title="3. 随机采样（聊天&#x2F;创作主流）"></a>3. 随机采样（聊天&#x2F;创作主流）</h5><p><strong>① Temperature（温度）</strong>——调节分布的”陡峭&#x2F;平缓”：</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><span class="line">logits 经过 softmax 前先除以 T：</span><br><span class="line"></span><br><span class="line">T = 0（极冷）：只剩最高分，等价于贪心，最保守</span><br><span class="line">T = 0.7（温）： 高分词更可能，但偶有惊喜  ← 通用默认</span><br><span class="line">T = 1.5（热）： 分布被拉平，低分词也有机会，更随机/有创意</span><br></pre></td></tr></table></figure><p><strong>② Top-k</strong>：只在概率最高的 <strong>k</strong> 个词里采样（如 k&#x3D;40），砍掉长尾。</p><p><strong>③ Top-p &#x2F; 核采样（Nucleus）</strong>：从高到低累加概率，凑够 <strong>p</strong>（如 0.9）就停，只在这个动态集合里采样。比 Top-k 更聪明——分布陡时候选少、分布平时候选多。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">候选（按概率排序）： 潮湿0.45 阴暗0.25 森林0.15 苔藓0.08 ...</span><br><span class="line">Top-p=0.9：累加 0.45+0.25+0.15+0.08=0.93 ≥0.9 → 只在这4个里采样</span><br></pre></td></tr></table></figure><p><strong>④ min-p（较新）</strong>：以最高概率词为基准，按比例设一个动态下限，鲁棒性比 Top-p 更好。</p><p><strong>⑤ 重复惩罚（repetition &#x2F; frequency penalty）</strong>：对已经出现过的词降权，避免”复读机”。</p><h5 id="4-RAG-场景该怎么调？"><a href="#4-RAG-场景该怎么调？" class="headerlink" title="4. RAG 场景该怎么调？"></a>4. RAG 场景该怎么调？</h5><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">RAG 的核心诉求是&quot;忠于检索内容&quot;，不是&quot;创意&quot;</span></div><div class="callout-content"><p>生成阶段如果温度太高，模型容易<strong>脱离检索到的事实、自由发挥 → 幻觉</strong>。</p></div></div><table><thead><tr><th>场景</th><th>Temperature</th><th>Top-p</th><th>说明</th></tr></thead><tbody><tr><td><strong>RAG 事实问答</strong> ⭐</td><td><strong>0 ~ 0.3</strong></td><td>0.9</td><td>最大化忠实度，少编造</td></tr><tr><td>摘要&#x2F;改写</td><td>0.3 ~ 0.5</td><td>0.9</td><td>略有灵活但不离谱</td></tr><tr><td>创意写作&#x2F;起名</td><td>0.8 ~ 1.2</td><td>0.95</td><td>鼓励多样性</td></tr><tr><td>代码生成</td><td>0 ~ 0.2</td><td>—</td><td>要确定性</td></tr></tbody></table><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">资产库项目的启示</span></div><div class="callout-content"><p>如果将来给资产搜索加一个”自然语言解释为什么推荐这个资产”的生成功能，温度应设到 <strong>0.2 左右</strong>——让它老老实实根据资产的 metadata 描述说话，而不是脑补不存在的属性。</p></div></div><hr><h4 id="选择适合的大语言模型"><a href="#选择适合的大语言模型" class="headerlink" title="选择适合的大语言模型"></a>选择适合的大语言模型</h4><p>没有”最好”的模型，只有”最适合当前任务+预算+合规要求”的模型。下面是一套可复用的选型框架。</p><h5 id="1-七个评估维度"><a href="#1-七个评估维度" class="headerlink" title="1. 七个评估维度"></a>1. 七个评估维度</h5><table><thead><tr><th>维度</th><th>关键问题</th><th>对 RAG 的影响</th></tr></thead><tbody><tr><td><strong>能力等级</strong></td><td>推理&#x2F;复杂指令够不够强</td><td>决定答案质量上限</td></tr><tr><td><strong>上下文窗口</strong></td><td>能塞下多少检索片段</td><td>RAG 直接相关，见下 ↓</td></tr><tr><td><strong>成本</strong></td><td>输入&#x2F;输出每百万 token 价格</td><td>高频调用时是主要开销</td></tr><tr><td><strong>延迟&#x2F;吞吐</strong></td><td>首字时间、每秒 token 数</td><td>影响用户体验</td></tr><tr><td><strong>隐私&#x2F;合规</strong></td><td>数据能否出本地</td><td>私有资产&#x2F;代码的红线 ⭐</td></tr><tr><td><strong>中文能力</strong></td><td>中文理解与生成</td><td>中文场景必看</td></tr><tr><td><strong>结构化输出&#x2F;工具调用</strong></td><td>能否稳定输出 JSON、调函数</td><td>Agentic RAG 必备</td></tr></tbody></table><h5 id="2-闭源-API-vs-开源本地"><a href="#2-闭源-API-vs-开源本地" class="headerlink" title="2. 闭源 API vs 开源本地"></a>2. 闭源 API vs 开源本地</h5><table><thead><tr><th></th><th>闭源 API（Claude &#x2F; GPT &#x2F; Gemini）</th><th>开源本地（Qwen &#x2F; Llama &#x2F; DeepSeek …）</th></tr></thead><tbody><tr><td>能力</td><td>通常最强</td><td>头部开源已逼近闭源</td></tr><tr><td>部署</td><td>调 API，零运维</td><td>自己用 GPU 跑，需运维</td></tr><tr><td>成本</td><td>按量付费，无前期投入</td><td>硬件前期投入，边际成本近乎 0</td></tr><tr><td>隐私</td><td>数据出本地 ⚠️</td><td><strong>数据不出本地</strong> ✅</td></tr><tr><td>定制</td><td>有限</td><td>可微调&#x2F;量化，完全可控</td></tr></tbody></table><h5 id="3-2026-年中主流模型速览"><a href="#3-2026-年中主流模型速览" class="headerlink" title="3. 2026 年中主流模型速览"></a>3. 2026 年中主流模型速览</h5><div class="callout" data-callout="quote" style="--callout-color: 158, 158, 158;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/></svg><span class="callout-title-inner">数据来源与时效</span></div><div class="callout-content"><p>下表数据交叉核对自 Anthropic 官方 <code>claude-api</code> 参考（缓存 2026-06-04）与公开比价站（<a href="https://www.morphllm.com/llm-api">morphllm LLM API，验证于 2026-06-09</a>、<a href="https://www.tldl.io/resources/llm-api-pricing-2026">LLM API Pricing 2026</a>）。<strong>模型与价格变动极快，以官网为准。</strong></p></div></div><p><strong>闭源 API（价格＝输入&#x2F;输出，每百万 token）：</strong></p><table><thead><tr><th>模型</th><th>定位</th><th>上下文</th><th>价格(in&#x2F;out)</th></tr></thead><tbody><tr><td><strong>Claude Fable 5</strong></td><td>最强、长程 Agent</td><td>1M</td><td>$10 &#x2F; $50</td></tr><tr><td><strong>Claude Opus 4.8</strong> ⭐</td><td>旗舰、编码&#x2F;Agent 强</td><td>1M</td><td>$5 &#x2F; $25</td></tr><tr><td><strong>Claude Sonnet 4.6</strong></td><td>速度&#x2F;智能平衡</td><td>1M</td><td>$3 &#x2F; $15</td></tr><tr><td><strong>Claude Haiku 4.5</strong></td><td>快而省</td><td>200K</td><td>$1 &#x2F; $5</td></tr><tr><td>GPT-5.5（OpenAI）</td><td>推理&#x2F;数学强</td><td>~256K</td><td>~$5 &#x2F; $30</td></tr><tr><td>Gemini 3.1 Pro（Google）</td><td>超长上下文、多模态、性价比</td><td>巨大(≥1M)</td><td>~$2 &#x2F; $12</td></tr><tr><td>DeepSeek V4</td><td>极致性价比</td><td>大</td><td>~$0.14 起</td></tr></tbody></table><p><strong>开源&#x2F;可本地部署（适合私有数据）：</strong></p><table><thead><tr><th>模型族</th><th>特点</th><th>备注</th></tr></thead><tbody><tr><td><strong>Qwen 3.5（通义千问）</strong></td><td>中文最强梯队、尺寸齐全</td><td>小尺寸 8GB 可跑，本地首选 ⭐</td></tr><tr><td>Llama 4（Meta）</td><td>生态最大</td><td>中大尺寸</td></tr><tr><td>DeepSeek V4</td><td>推理强、开源</td><td>满血版需大显存</td></tr><tr><td>GLM-5（智谱）</td><td>中文友好</td><td></td></tr><tr><td>Mistral &#x2F; Gemma &#x2F; Phi</td><td>欧系&#x2F;谷歌&#x2F;微软小模型</td><td>Phi 适合边缘端</td></tr></tbody></table><h5 id="4-上下文窗口与-RAG-的关系"><a href="#4-上下文窗口与-RAG-的关系" class="headerlink" title="4. 上下文窗口与 RAG 的关系"></a>4. 上下文窗口与 RAG 的关系</h5><p>RAG 要把 <strong>[系统提示] + [Top-K 检索片段] + [对话历史] + [用户问题]</strong> 一起塞进上下文。窗口越大，能放的检索证据越多。</p><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">但&quot;窗口大&quot;不等于&quot;可以无脑塞&quot;</span></div><div class="callout-content"><ul><li>塞太多无关片段会<strong>稀释</strong>有用信息、抬高成本、还可能触发”中间遗忘”（见下一节）。</li><li>正解仍是：<strong>先精排取 Top-5~10 高质量片段</strong>（模块三 §6&#x2F;§7 的 Reranker），而不是把 Top-100 全丢进去。<strong>好的检索 &gt; 大的窗口。</strong></li></ul></div></div><h5 id="5-选型速查"><a href="#5-选型速查" class="headerlink" title="5. 选型速查"></a>5. 选型速查</h5><table><thead><tr><th>你的情况</th><th>推荐</th></tr></thead><tbody><tr><td>私有美术资产 &#x2F; 私有代码，<strong>数据不能出本地</strong></td><td>本地 <strong>Qwen3</strong> 系列（用你的 DGX Spark 节点跑，128GB 统一内存可加载量化后的中大模型） ⭐</td></tr><tr><td>追求最高答案质量、可接受 API</td><td><strong>Claude Opus 4.8</strong>（<code>claude-opus-4-8</code>）或 Fable 5</td></tr><tr><td>高频、对成本敏感</td><td>Claude Haiku 4.5 &#x2F; Gemini Flash &#x2F; DeepSeek</td></tr><tr><td>需要超长上下文</td><td>Gemini 3.1 Pro &#x2F; Claude（1M 窗口）</td></tr></tbody></table><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">两个项目的对应选择</span></div><div class="callout-content"><ul><li><strong>资产库项目</strong>（私有美术资产）→ 隐私优先 → 本地 Qwen on DGX Spark</li><li><strong><code>starting-ragchatbot-codebase</code></strong>（课程问答机器人）→ 已用 <strong>Claude</strong>（Anthropic 官方推荐默认 <code>claude-opus-4-8</code> + 自适应思考 <code>thinking:&#123;type:&quot;adaptive&quot;&#125;</code>），调 API 即可，无需运维</li></ul></div></div><hr><h4 id="提示词工程"><a href="#提示词工程" class="headerlink" title="提示词工程"></a>提示词工程</h4><p>模型选定、检索完成后，<strong>提示词（Prompt）是你在推理阶段唯一的控制杆</strong>——不重新训练，仅靠组织输入就能大幅改变输出质量。对 RAG 而言，提示词工程的核心目标是：<strong>让模型严格基于检索内容作答，拒绝幻觉</strong>。</p><h5 id="1-提示词的基本结构"><a href="#1-提示词的基本结构" class="headerlink" title="1. 提示词的基本结构"></a>1. 提示词的基本结构</h5><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></pre></td><td class="code"><pre><span class="line">┌── System（系统提示）：设定角色、规则、语气、边界</span><br><span class="line">│      &quot;你是资产库助手，只依据提供的资产信息回答...&quot;</span><br><span class="line">├── Context（上下文）：注入检索到的片段</span><br><span class="line">│      &quot;【资产1】灰褐色石块，密布青苔... 【资产2】...&quot;</span><br><span class="line">├── User（用户提问）：本轮问题</span><br><span class="line">│      &quot;推荐适合古风场景的石头类资产&quot;</span><br><span class="line">└──（可选）Few-shot 示例 / 输出格式约束</span><br></pre></td></tr></table></figure><h5 id="2-通用技巧"><a href="#2-通用技巧" class="headerlink" title="2. 通用技巧"></a>2. 通用技巧</h5><table><thead><tr><th>技巧</th><th>做法</th><th>何时用</th></tr></thead><tbody><tr><td><strong>角色设定</strong></td><td>“你是资深美术指导……”</td><td>几乎总是</td></tr><tr><td><strong>Few-shot 少样本</strong></td><td>给 1~3 个”输入→理想输出”示例</td><td>输出格式&#x2F;风格要稳定</td></tr><tr><td><strong>思维链 CoT</strong></td><td>“请一步步推理后再给结论”</td><td>复杂推理（注：RAG 事实问答通常不需要太多发散）</td></tr><tr><td><strong>结构化输出</strong></td><td>要求输出 JSON &#x2F; 指定字段</td><td>下游程序要解析、Agentic RAG</td></tr></tbody></table><h5 id="3-RAG-专用提示词模板（重点）"><a href="#3-RAG-专用提示词模板（重点）" class="headerlink" title="3. RAG 专用提示词模板（重点）"></a>3. RAG 专用提示词模板（重点）</h5><p>一个合格的 RAG 提示词必须包含三道”防幻觉”指令：<strong>①只用提供的内容 ②找不到就说不知道 ③标注来源</strong>。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">你是游戏资产库的检索助手。请严格遵守：</span><br><span class="line">1. 只根据下面【检索结果】中的信息回答，不要使用你自己的知识。</span><br><span class="line">2. 如果检索结果里没有答案，直接回答&quot;未找到相关资产&quot;，不要编造。</span><br><span class="line">3. 回答时用 [资产ID] 标注你依据的是哪条资产。</span><br><span class="line"></span><br><span class="line">【检索结果】</span><br><span class="line">[stone_moss_03] 灰褐色石块，表面密布绿色苔藓，风化纹理明显，适合古风场景</span><br><span class="line">[rock_wet_01]   湿润岩石，深色，无苔藓</span><br><span class="line"></span><br><span class="line">【用户问题】</span><br><span class="line">有没有适合古风场景、带青苔的石头？</span><br><span class="line"></span><br><span class="line">【期望输出】</span><br><span class="line">推荐 [stone_moss_03]：灰褐色石块且密布青苔、风化纹理，契合古风场景。</span><br><span class="line">（[rock_wet_01] 无苔藓，相关性较低未推荐。）</span><br></pre></td></tr></table></figure><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">这三条指令直接决定 RAG 系统的可信度</span></div><div class="callout-content"><p>缺了第 2 条，模型在检索为空时会”自信地胡说”；缺了第 3 条，用户无法核查，答案不可追溯。</p></div></div><h5 id="4-“中间遗忘”现象（Lost-in-the-Middle）"><a href="#4-“中间遗忘”现象（Lost-in-the-Middle）" class="headerlink" title="4. “中间遗忘”现象（Lost in the Middle）"></a>4. “中间遗忘”现象（Lost in the Middle）</h5><p>研究发现：当上下文很长时，模型对<strong>开头和结尾</strong>的信息记得最牢，<strong>中间</strong>的内容最容易被忽略——召回率曲线呈 U 形。</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><span class="line">模型注意力 ▲</span><br><span class="line">          │＼            ／</span><br><span class="line">          │  ＼ ____ ／      ← 中间塌陷</span><br><span class="line">          └──────────────▶ 片段位置</span><br><span class="line">           开头   中间   结尾</span><br></pre></td></tr></table></figure><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">对 RAG 的直接启示</span></div><div class="callout-content"><p>把 <strong>最相关的检索片段放在 Prompt 的最前面或最后面</strong>，不要埋在中间一大堆片段里。这也再次印证了<strong>精排（Reranking）的价值</strong>：与其塞 50 条让关键信息淹没在中间，不如精排出 5 条放在显眼位置。</p></div></div><h5 id="5-好-vs-坏-提示词对比"><a href="#5-好-vs-坏-提示词对比" class="headerlink" title="5. 好 vs 坏 提示词对比"></a>5. 好 vs 坏 提示词对比</h5><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>“用 [资产ID] 标注依据”</td></tr><tr><td>片段顺序</td><td>随便堆</td><td>最相关的放首&#x2F;尾</td></tr><tr><td>输出</td><td>“随便答”</td><td>指定格式&#x2F;字段</td></tr></tbody></table><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">提示词工程 vs 微调 vs RAG——三种&quot;喂知识&quot;的方式</span></div><div class="callout-content"><ul><li><strong>提示词工程</strong>：临时、零成本，靠当前输入引导 → 本节</li><li><strong>RAG</strong>：动态注入外部知识，知识可实时更新 → 本笔记全篇</li><li><strong>微调（Fine-tuning）</strong>：把知识&#x2F;风格”焊进”模型权重，成本高、更新慢<br>三者常组合使用：<strong>RAG 提供事实 + 提示词约束行为 + （可选）微调统一风格</strong>。</li></ul></div></div><h4 id="幻觉抑制处理（Hallucination-Suppression）"><a href="#幻觉抑制处理（Hallucination-Suppression）" class="headerlink" title="幻觉抑制处理（Hallucination Suppression）"></a>幻觉抑制处理（Hallucination Suppression）</h4><p>前三节讲的是”如何让模型少胡说”，但当幻觉不可避免地出现时，我们还需要<strong>事后</strong>手段来检测、定位、评估、归因。下面四个概念构成 RAG 系统幻觉治理的完整闭环。</p><h6 id="1-Citation-Context-Cite（上下文引用）"><a href="#1-Citation-Context-Cite（上下文引用）" class="headerlink" title="(1) Citation &#x2F; Context Cite（上下文引用）"></a>(1) Citation &#x2F; Context Cite（上下文引用）</h6><p><strong>Citation</strong> 直译”引用”，在 RAG 语境里特指：<strong>让模型把答案中每一个事实”钉”回原文的具体片段</strong>，让用户能一键追溯到出处。</p><p><strong>两种实现层次：</strong></p><table><thead><tr><th>层次</th><th>做法</th><th>代表</th></tr></thead><tbody><tr><td><strong>事后归因</strong> (Attribution)</td><td>用 LLM 给答案中的每个事实反查”出处是哪个片段”</td><td>Citation Generation &#x2F; Cite-and-Explain</td></tr><tr><td><strong>原生内嵌</strong> (Native Citations)</td><td>让模型在生成的同时直接产出引用，无需二次加工</td><td>Anthropic Citations API、LongCite</td></tr></tbody></table><p><strong>Anthropic Citations API 示例</strong>（<a href="https://platform.claude.com/docs/en/build-with-claude/citations">官方文档</a>）——把文档标记为 <code>citations: &#123;&quot;enabled&quot;: True&#125;</code>，模型就会<strong>自动</strong>返回带 <code>char_start</code> &#x2F; <code>char_end</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></pre></td><td class="code"><pre><span class="line">response = client.messages.create(</span><br><span class="line">    model=<span class="string">&quot;claude-opus-4-8&quot;</span>,</span><br><span class="line">    max_tokens=<span class="number">1024</span>,</span><br><span class="line">    messages=[&#123;</span><br><span class="line">        <span class="string">&quot;role&quot;</span>: <span class="string">&quot;user&quot;</span>,</span><br><span class="line">        <span class="string">&quot;content&quot;</span>: [</span><br><span class="line">            &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;document&quot;</span>,</span><br><span class="line">             <span class="string">&quot;source&quot;</span>: &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;text&quot;</span>, <span class="string">&quot;media_type&quot;</span>: <span class="string">&quot;text/plain&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;data&quot;</span>: <span class="string">&quot;草地是绿色的。天空是蓝色的。&quot;</span>&#125;,</span><br><span class="line">             <span class="string">&quot;title&quot;</span>: <span class="string">&quot;我的文档&quot;</span>,</span><br><span class="line">             <span class="string">&quot;citations&quot;</span>: &#123;<span class="string">&quot;enabled&quot;</span>: <span class="literal">True</span>&#125;&#125;,</span><br><span class="line">            &#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;text&quot;</span>, <span class="string">&quot;text&quot;</span>: <span class="string">&quot;草地和天空是什么颜色？&quot;</span>&#125;</span><br><span class="line">        ]</span><br><span class="line">    &#125;]</span><br><span class="line">)</span><br><span class="line"><span class="comment"># response.content 里会带 citations 字段，</span></span><br><span class="line"><span class="comment"># 直接告诉用户&quot;天空是蓝色的&quot;出自原文第 13~24 个字符</span></span><br></pre></td></tr></table></figure><p><strong>它解决什么痛点？</strong></p><table><thead><tr><th>没引用</th><th>有引用</th></tr></thead><tbody><tr><td>“草是绿的”</td><td>“草是绿的”〔引用自《我的文档》§1, char 0–6〕</td></tr><tr><td>模型编造 → 用户无法察觉</td><td>引用对照 → 用户可秒判真假</td></tr></tbody></table><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">对资产库项目的启示</span></div><div class="callout-content"><p>即便现在只做检索不做生成，将来若加 LLM 总结，把引用机制从一开始就塞进 prompt 是几乎零成本的”防御工事”。</p></div></div><hr><h6 id="2-Citation-Generation（引用生成）"><a href="#2-Citation-Generation（引用生成）" class="headerlink" title="(2) Citation Generation（引用生成）"></a>(2) Citation Generation（引用生成）</h6><blockquote><p><strong>Citation Generation &#x3D; 让 LLM 主动”产出”带引用的答案。</strong></p></blockquote><p>它是上面 Citation 的”生成端”实现策略，强调<strong>训练&#x2F;提示</strong>模型把”输出 + 引用”打包在一起写出来。</p><p><strong>主流做法分两路：</strong></p><table><thead><tr><th>路线</th><th>代表</th><th>思路</th></tr></thead><tbody><tr><td><strong>训练式</strong></td><td>LongCite（THU, 2024）</td><td>构造长文 QA 训练对，让模型学会”答到哪句、引到哪段”，产出<strong>细粒度句级引用</strong></td></tr><tr><td><strong>提示式</strong></td><td>Anthropic-style Citations</td><td>改用提示词约束输出格式，例如：「请输出 JSON: <code>&#123;answer, citations: [&#123;doc_id, span&#125;]&#125;</code>」</td></tr></tbody></table><p><strong>LongCite 例子（虚构示意）：</strong></p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">问：CLIP 是什么时候、由谁发布的？</span><br><span class="line">答：CLIP 由 OpenAI 于 2021 年 1 月发布。</span><br><span class="line">引用：</span><br><span class="line">  [1] 文档《CLIP 概述》§1.1 &quot;OpenAI 于 2021 年 1 月公开发布 CLIP...&quot;</span><br><span class="line">  [2] 文档《多模态模型发展史》§3 &quot;2021 年 1 月：CLIP、ALIGN...&quot;</span><br></pre></td></tr></table></figure><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">Citation 生成 ≠ 完全消除幻觉</span></div><div class="callout-content"><p><a href="https://arxiv.org/html/2510.17853v1">CiteGuard（2025）</a> 指出：模型可能<strong>生成”长得像引用”但实际错位”的引用</strong>（recall 只有 16–17%）。所以生成引用本身仍需要被验证——它比无引用强，但<strong>不是银弹</strong>。</p></div></div><hr><h6 id="3-Evaluating-Criterion-Quality-in-LLMs"><a href="#3-Evaluating-Criterion-Quality-in-LLMs" class="headerlink" title="(3) Evaluating Criterion Quality in LLMs"></a>(3) Evaluating Criterion Quality in LLMs</h6><blockquote><p><strong>直译：评估 LLM 中”评估标准”的质量。</strong></p><p>这一节讲的不是”评估 LLM”，而是<strong>评估”用来评估 LLM 的那些打分标准（criterion &#x2F; rubric）本身靠不靠谱”</strong>。</p></blockquote><p><strong>为什么重要？</strong> 当我们用 <strong>LLM-as-a-Judge</strong>（让 LLM 当裁判打分）评价 RAG 输出时，裁判其实只看到了<strong>你写的 rubric</strong>（评分标准）。rubric 写得糊，裁判打得就糊。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">✅ 好 rubric（一条一维度 + 锚点 + justify）：</span><br><span class="line">  &quot;答案忠实性 (1–5)：</span><br><span class="line">    1=完全编造  3=部分基于上下文  5=完全基于上下文</span><br><span class="line">   请基于检索片段逐句核对，引用支持该分数的句子。&quot;</span><br><span class="line"></span><br><span class="line">❌ 差 rubric：</span><br><span class="line">  &quot;答案是否好？(1–5)&quot;</span><br></pre></td></tr></table></figure><p><strong>量化”rubric 质量”要看几件事：</strong></p><table><thead><tr><th>维度</th><th>含义</th><th>如何测</th></tr></thead><tbody><tr><td><strong>判别力</strong> Discriminability</td><td>不同质量的答案能不能拉开档次？</td><td>同 query 多份候选，看分数分布</td></tr><tr><td><strong>一致性</strong> Inter-judge Agreement</td><td>同一份答案，不同裁判（人&#x2F;LLM）打分是否一致</td><td>Cohen’s κ、% 一致率</td></tr><tr><td><strong>与人类对齐</strong> Human Alignment</td><td>裁判分是否接近真人打分</td><td>跟人工标注集求 Pearson&#x2F;Spearman</td></tr><tr><td><strong>稳定性</strong> Rubric Drift</td><td>同一 rubric 用久了分数是否漂移</td><td>定期回测金标集</td></tr><tr><td><strong>抗偏性</strong> Bias Resistance</td><td>不受位置偏差 &#x2F; 长度偏差 &#x2F; 自家偏好影响</td><td>ABX 配对测试</td></tr></tbody></table><p><strong>实操做法（行业主流）：</strong></p><ol><li><strong>先建一个金标集</strong>（几百条问题 + 人工标注的”标准答案 + 标准分数”）。</li><li><strong>多版 rubric 对照打</strong>同一份金标，看哪版跟人类分数最接近。</li><li><strong>定期回测</strong>——模型升级、领域变化时重跑金标，看分数曲线是否漂移（rubric drift）。</li><li><strong>记录 justification</strong>——每个分数要求裁判给出理由，方便事后复盘”为什么这个答案拿 4 分不是 3 分”。</li></ol><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">对 RAG 评估的直接影响</span></div><div class="callout-content"><p>模块五要讲的 RAG 评估三大指标——<strong>Context Relevance &#x2F; Groundedness &#x2F; Answer Relevance</strong>（即 TruLens 提出的 <a href="https://www.snowflake.com/en/engineering-blog/benchmarking-LLM-as-a-judge-RAG-triad-metrics/">RAG Triad</a>）——能否真的衡量质量，<strong>完全取决于为这三个指标写的 rubric 写得清不清楚</strong>。这一节就是为那块内容做铺垫。</p></div></div><hr><h6 id="4-四个概念如何拧成”幻觉治理闭环”"><a href="#4-四个概念如何拧成”幻觉治理闭环”" class="headerlink" title="(4) 四个概念如何拧成”幻觉治理闭环”"></a>(4) 四个概念如何拧成”幻觉治理闭环”</h6><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></pre></td><td class="code"><pre><span class="line">                ┌─────────────────────────┐</span><br><span class="line">                │  Citation / Context Cite │  ← &quot;答案能溯源&quot;</span><br><span class="line">                │  （生成时内嵌 / 事后归因）│</span><br><span class="line">                └────────────┬────────────┘</span><br><span class="line">                             ↓</span><br><span class="line">Citation Generation ────→ 训练/提示 LLM 主动产出引用</span><br><span class="line">                             ↓</span><br><span class="line">                ┌─────────────────────────┐</span><br><span class="line">                │ Evaluating Criterion     │</span><br><span class="line">                │ Quality in LLMs          │  ← &quot;裁判标准本身要可靠&quot;</span><br><span class="line">                │ （rubric 质量评估）      │</span><br><span class="line">                └────────────┬────────────┘</span><br><span class="line">                             ↓</span><br><span class="line">                       RAG 评估打分</span><br><span class="line">                       （更准的分数）</span><br><span class="line">                             ↓</span><br><span class="line">              上线后持续监控 + 反馈回检索 / 生成</span><br></pre></td></tr></table></figure><p><strong>对 RAG 的总结性建议：</strong></p><table><thead><tr><th>阶段</th><th>用上哪些手段</th></tr></thead><tbody><tr><td><strong>离线开发</strong></td><td>Good prompt（§3）+ 精排 Top-K（§模块三）+ Citation Generation（训练&#x2F;提示）</td></tr><tr><td><strong>上线前</strong></td><td>RAG Triad 评估 + Evaluating Criterion Quality（人评对齐）</td></tr><tr><td><strong>线上</strong></td><td>原生 Citations（如 Anthropic API）+ 用户反馈回流</td></tr><tr><td><strong>事后归因</strong></td><td>Context Cite 反查 + 监控 Groundedness 分数</td></tr></tbody></table><div class="callout" data-callout="quote" style="--callout-color: 158, 158, 158;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/></svg><span class="callout-title-inner">一句话记住</span></div><div class="callout-content"><p><strong>好的 RAG 不只”答得对”，还要”答得可以查”——生成（Citation）+ 评估（Criterion）才是完整的幻觉治理。</strong></p></div></div><h4 id="RAG-vs-微调（Fine-tuning）"><a href="#RAG-vs-微调（Fine-tuning）" class="headerlink" title="RAG vs 微调（Fine tuning）"></a>RAG vs 微调（Fine tuning）</h4><p>RAG 与微调经常被一起讨论，但其实是<strong>两条不同的路线</strong>，解决的问题不一样：</p><table><thead><tr><th>维度</th><th>RAG（检索增强生成）</th><th>Fine-tuning（微调）</th></tr></thead><tbody><tr><td><strong>做什么</strong></td><td>检索外部知识，拼进 prompt</td><td>改模型权重，让模型”记住”知识</td></tr><tr><td><strong>知识更新</strong></td><td>改知识库即可，<strong>实时生效</strong></td><td>必须重新训练，耗时耗钱</td></tr><tr><td><strong>适用场景</strong></td><td>私有数据 &#x2F; 时效性强 &#x2F; 要引出处</td><td>学特定风格 &#x2F; 固定输出格式 &#x2F; 注入专业表达</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>仍可能幻觉（遗忘 &#x2F; 编造）</td></tr><tr><td><strong>数据需求</strong></td><td>文档 + 嵌入索引</td><td>高质量标注问答对（通常上千条起步）</td></tr></tbody></table><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">怎么选？</span></div><div class="callout-content"><ul><li><strong>数据在变、要溯源、合规要求高</strong> → 优先 RAG</li><li><strong>要改风格、固定输出格式、学特定表达</strong> → 优先 Fine-tuning</li><li><strong>最难的任务</strong> → RAG + 微调组合（先精调基底，再外挂知识）</li></ul></div></div><div class="callout" data-callout="quote" style="--callout-color: 158, 158, 158;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/></svg><span class="callout-title-inner">一句话记住</span></div><div class="callout-content"><p><strong>RAG 是给模型”开卷考试”，Fine-tuning 是让模型”在家背书”。开卷永远比背书保鲜。</strong></p></div></div><h4 id="常见微调方式"><a href="#常见微调方式" class="headerlink" title="常见微调方式"></a>常见微调方式</h4><table><thead><tr><th>方式</th><th>一句话说明</th><th>适合场景</th><th>数据量级</th></tr></thead><tbody><tr><td><strong>指令微调（SFT）</strong></td><td>用”指令-回答”对训练，让模型听懂人话、学格式</td><td>教新任务、改语气风格</td><td>几千～几万条</td></tr><tr><td><strong>持续预训练（Continued Pre-training）</strong></td><td>在专业语料上继续预训练，让模型”懂行”</td><td>法律、医疗、代码、金融等垂直领域</td><td>大规模无标注语料</td></tr><tr><td><strong>LoRA &#x2F; QLoRA（PEFT）</strong></td><td>只训练少量参数（低秩适配器），冻结原权重</td><td>资源不够 &#x2F; 多任务并行 &#x2F; 快速迭代</td><td>几百～几千条</td></tr><tr><td><strong>DPO（直接偏好优化）</strong></td><td>用”好回答 vs 坏回答”对直接优化策略</td><td>想要 RLHF 效果但嫌流程复杂</td><td>几千条偏好对</td></tr><tr><td><strong>RLHF</strong></td><td>先训奖励模型，再用 PPO 强化学习对齐人类偏好</td><td>ChatGPT 式的”听话 + 有用 + 安全”对齐</td><td>几万条人类标注</td></tr><tr><td><strong>Prefix &#x2F; Prompt &#x2F; Adapter Tuning</strong></td><td>在模型上加一个可训练的小模块&#x2F;前缀</td><td>一个基座服务多任务</td><td>几百条</td></tr></tbody></table><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">工程上最常见的&quot;三段式&quot;路径</span></div><div class="callout-content"><p><strong>持续预训练（学领域知识）→ 指令微调 SFT（学格式&#x2F;风格）→ DPO 或 RLHF（对齐偏好）</strong>——这也是 Llama、Qwen、DeepSeek 这类开源模型的标准训练流程。</p><p>但是课程的老师也说，微调并不是让AI去学习新知识的一种好办法。AI通常经过微调之后，通常它在提示词上会有比较大的变化。但是对于信息面的影响会比较小。<br>所以这就引出了 RAG 和微调的适合场景：<strong>RAG 比较适合于注入新知识，而微调比较适合于为特定领域做适配</strong>。</p></div></div><div class="callout" data-callout="quote" style="--callout-color: 158, 158, 158;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/></svg><span class="callout-title-inner">一句话记住</span></div><div class="callout-content"><p><strong>不是所有微调都要”改全量”——LoRA&#x2F;QLoRA 这类 PEFT 用 1% 的参数就能拿到 90% 的效果，才是工程界的性价比之王。</strong></p></div></div><hr><h3 id="模块五-生产环境中的RAG系统"><a href="#模块五-生产环境中的RAG系统" class="headerlink" title="模块五 生产环境中的RAG系统"></a>模块五 生产环境中的RAG系统</h3><h4 id="1-生产环境面临哪些挑战？"><a href="#1-生产环境面临哪些挑战？" class="headerlink" title="1. 生产环境面临哪些挑战？"></a>1. 生产环境面临哪些挑战？</h4><p>RAG 走出 demo 后，会撞上一连串 demo 里看不到的”墙”：</p><table><thead><tr><th>维度</th><th>Demo 状态</th><th>生产现实</th></tr></thead><tbody><tr><td>数据</td><td>几十条干净文档</td><td>百万级脏文档（重复、过期、权限敏感）</td></tr><tr><td>用户</td><td>自己人测试</td><td>真实用户问法千奇百怪、夹带错别字&#x2F;口语</td></tr><tr><td>召回</td><td>“差不多就行”</td><td>必须可追溯、可解释、有 SLA</td></tr><tr><td>成本</td><td>一张卡跑跑</td><td>QPS、成本、延迟三者拉扯</td></tr><tr><td>反馈</td><td>没有</td><td>需要日志、监控、A&#x2F;B、可观测性</td></tr><tr><td></td><td></td><td></td></tr></tbody></table><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">本质变化</span></div><div class="callout-content"><p>Demo 是”<strong>能不能答出来</strong>“，生产是”<strong>答得稳不稳、贵不贵、快不快、安不安全、能不能量化改进</strong>“。</p></div></div><h4 id="2-实施-RAG-评估策略"><a href="#2-实施-RAG-评估策略" class="headerlink" title="2. 实施 RAG 评估策略"></a>2. 实施 RAG 评估策略</h4><p>评估 &#x3D; 回答”我的 RAG 现在到底有多好？”。三步走：</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></pre></td><td class="code"><pre><span class="line">离线评估集（黄金集） → 自动化打分 → 回归对比</span><br><span class="line">       ↑                              ↓</span><br><span class="line">       └──── 收集线上反馈，扩充集 ──────┘</span><br></pre></td></tr></table></figure><ul><li><strong>黄金集</strong>：人工标注 question + 期望答案 &#x2F; 相关片段</li><li><strong>指标</strong>：检索（Recall@k、MRR）、生成（忠实度、答案相关性）、端到端（命中率）</li><li><strong>工具</strong>：RAGAS、ARES、LangSmith Evaluation、DeepEval</li></ul><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">评估要&quot;可重复&quot;</span></div><div class="callout-content"><p>同一份数据 + 同一份代码 &#x3D; 同一份分数。否则版本一改，谁也说不清是”模型进步”还是”prompt 改了”。</p></div></div><h4 id="3-日志记录、监控与可观测性"><a href="#3-日志记录、监控与可观测性" class="headerlink" title="3. 日志记录、监控与可观测性"></a>3. 日志记录、监控与可观测性</h4><p>生产 RAG 是<strong>黑盒流水线</strong>，没有日志 &#x3D; 没有调试能力。三个层面：</p><table><thead><tr><th>层面</th><th>记录什么</th><th>用来干嘛</th></tr></thead><tbody><tr><td><strong>Trace（链路）</strong></td><td>每次 query 的 retriever &#x2F; reranker &#x2F; LLM 调用、入参出参、耗时</td><td>复现问题</td></tr><tr><td><strong>Metric（指标）</strong></td><td>QPS、延迟分位、token 用量、命中率、错误率</td><td>大盘告警</td></tr><tr><td><strong>Feedback（反馈）</strong></td><td>用户 👍&#x2F;👎、人工抽检、显式评分</td><td>灌回评估集，迭代模型</td></tr></tbody></table><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">三件套参考</span></div><div class="callout-content"><p>LangSmith &#x2F; Langfuse &#x2F; Arize Phoenix &#x2F; MLflow —— 任选一套，别裸跑。</p></div></div><h4 id="4-定制化评估"><a href="#4-定制化评估" class="headerlink" title="4. 定制化评估"></a>4. 定制化评估</h4><p>通用指标（如”语义相似度”）往往对不上业务。<strong>业务说答得好，才算真的好</strong>。</p><ul><li><strong>领域专家打分</strong>：法务&#x2F;医生&#x2F;客服主管按业务标准 1~5 分</li><li><strong>规则化校验</strong>：必须出现某字段、必须引用某类来源、不能出现某些词</li><li><strong>行为代理指标</strong>：点赞率、复制率、追问率（用户继续问 &#x3D; 没答清）</li><li><strong>LLM-as-a-Judge</strong>：用强模型当裁判，但<strong>必须用黄金集校准</strong>，否则裁判自己也在飘</li></ul><h4 id="5-量化（Quantization）"><a href="#5-量化（Quantization）" class="headerlink" title="5. 量化（Quantization）"></a>5. 量化（Quantization）</h4><p>把模型&#x2F;向量的”高精度数值”换成”低精度表示”，<strong>省显存、省钱、省延迟</strong>。</p><table><thead><tr><th>对象</th><th>方法</th><th>效果</th></tr></thead><tbody><tr><td>Embedding 模型</td><td>int8 &#x2F; binary</td><td>向量库内存 ↓ 4~32x，召回略降</td></tr><tr><td>LLM</td><td>GPTQ &#x2F; AWQ &#x2F; GGUF</td><td>显存 ↓ 2~4x，速度 ↑，质量微损</td></tr><tr><td>向量存储</td><td>Product Quantization (PQ)</td><td>千万级向量单机可跑</td></tr></tbody></table><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">量化的代价</span></div><div class="callout-content"><p>召回率&#x2F;生成质量会有 1~3% 的下降，<strong>必须在黄金集上回归</strong>，别拍脑袋上生产。</p></div></div><h4 id="6-成本-VS-响应质量"><a href="#6-成本-VS-响应质量" class="headerlink" title="6. 成本 VS 响应质量"></a>6. 成本 VS 响应质量</h4><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></pre></td><td class="code"><pre><span class="line">质量 ▲</span><br><span class="line">     │      ╭──── 大模型（贵）</span><br><span class="line">     │    ╱</span><br><span class="line">     │  ╱</span><br><span class="line">     │╱</span><br><span class="line">     └──────────────▶ 成本</span><br><span class="line">  小模型    路由</span><br></pre></td></tr></table></figure><p>常见省钱策略：</p><ul><li><strong>模型路由</strong>：简单问题 → 小模型&#x2F;规则，复杂问题 → 大模型</li><li><strong>缓存</strong>：相同 query 命中直接返回，省一次 LLM</li><li><strong>减 token</strong>：精排压到 Top-3、截断长文档、用更小 context</li><li><strong>批处理 &#x2F; 异步</strong>：非实时任务合并请求</li></ul><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">不是越贵越好</span></div><div class="callout-content"><p>把”质量提升 1% 要多花 10 倍钱”这种点找到，就是工程优化的 ROI 关键。</p></div></div><h4 id="7-延迟-VS-响应质量"><a href="#7-延迟-VS-响应质量" class="headerlink" title="7. 延迟 VS 响应质量"></a>7. 延迟 VS 响应质量</h4><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><span class="line">首字延迟 ▲</span><br><span class="line">         │   ╭─ 长上下文 + 大模型</span><br><span class="line">         │ ╱</span><br><span class="line">         │╱───── 流式 + 小模型 + 精排</span><br><span class="line">         └──────────────▶ 质量</span><br></pre></td></tr></table></figure><p>生产对延迟的硬要求：</p><table><thead><tr><th>场景</th><th>目标首字延迟</th></tr></thead><tbody><tr><td>聊天&#x2F;搜索</td><td>&lt; 1s</td></tr><tr><td>客服自动回复</td><td>&lt; 2s</td></tr><tr><td>离线分析</td><td>不敏感</td></tr></tbody></table><p>手段：<strong>流式输出（SSE）、并行检索（向量+BM25 并发）、精排前置、speculative decoding</strong>。</p><h4 id="8-安全性"><a href="#8-安全性" class="headerlink" title="8. 安全性"></a>8. 安全性</h4><p>RAG 的攻击面 &#x3D; 检索器 + LLM，两头都要防：</p><table><thead><tr><th>风险</th><th>例子</th><th>防御</th></tr></thead><tbody><tr><td><strong>Prompt Injection</strong></td><td>用户问题里塞”忽略上面所有指令”</td><td>输入清洗 + 指令&#x2F;数据严格分段</td></tr><tr><td><strong>数据泄露</strong></td><td>检索返回了别人私有文档</td><td>元数据 ACL 过滤 + 租户隔离</td></tr><tr><td><strong>越权回答</strong></td><td>内部 wiki 答给了外部用户</td><td>权限网关 + 来源水印</td></tr><tr><td><strong>幻觉</strong></td><td>编造不存在的资产</td><td>强制引用 + “找不到就说没有”</td></tr><tr><td><strong>有毒输出</strong></td><td>检索到恶意文本被 LLM 复述</td><td>内容审核 + 输出过滤</td></tr></tbody></table><div class="callout" data-callout="warning" style="--callout-color: 255, 145, 0;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg><span class="callout-title-inner">提示词注入没有银弹</span></div><div class="callout-content"><p>只能在 <strong>输入清洗 + 输出审查 + 权限隔离</strong> 三层一起做，单点防御都会被绕。</p></div></div><h4 id="9-多模态检索增强生成"><a href="#9-多模态检索增强生成" class="headerlink" title="9. 多模态检索增强生成"></a>9. 多模态检索增强生成</h4><p>RAG 不止能喂文字。能检索&#x2F;生成的模态越多，应用场景越广：</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></pre></td><td class="code"><pre><span class="line">文本 ←─────► 文本   （最常见，问答/搜索）</span><br><span class="line">图片 ←─────► 文本   （以图搜文，CLIP）</span><br><span class="line">文本 ←─────► 图片   （文生图 / 文搜图）</span><br><span class="line">图片 ←─────► 图片   （以图搜图）</span><br><span class="line">音频 ←─────► 文本   （会议录音 → 摘要）</span><br><span class="line">视频 ←─────► 文本   （长视频 → 关键帧 + 字幕检索）</span><br></pre></td></tr></table></figure><p>关键技术：</p><ul><li><strong>统一 Embedding</strong>：CLIP &#x2F; SigLIP &#x2F; BGE-M3，把多模态映射到同一向量空间</li><li><strong>多模态 LLM</strong>：GPT-4o、Gemini、Qwen-VL，能看图、能听音、能读文档</li><li><strong>结构化解析</strong>：PDF 表格、扫描件、Chart → 用专用模型转 Markdown&#x2F;JSON 再进 RAG</li></ul><div class="callout" data-callout="tip" style="--callout-color: 0, 191, 165;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span class="callout-title-inner">一个常见落地姿势</span></div><div class="callout-content"><p>先把 PDF&#x2F;PPT&#x2F;截图全部 OCR + 结构化 → 转成文本 + 图块 → 进同一个向量库。一个 RAG 系统吃下企业全部知识资产。</p></div></div><div class="callout" data-callout="note" style="--callout-color: 68, 138, 255;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg><span class="callout-title-inner">模块五小结</span></div><div class="callout-content"><p>生产 RAG &#x3D; <strong>评估体系 + 可观测性 + 工程权衡 + 安全合规 + 多模态扩展</strong>。技术之外，工程化能力决定了它能不能真正”活下去”。</p></div></div><h2 id="八、参考资料"><a href="#八、参考资料" class="headerlink" title="八、参考资料"></a>八、参考资料</h2><hr><div class="callout" data-callout="quote" style="--callout-color: 158, 158, 158;"><div class="callout-title"><svg class="callout-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/></svg><span class="callout-title-inner">关联文档</span></div><div class="callout-content"><ul><li>[[20260522.SearchToolDesign（Private）]]：资产搜索工具完整设计方案</li><li>[[20260521.ArtPipelineAIIntegration（Private）]]：上层 AI Pipeline 集成文档</li><li><a href="https://www.bilibili.com/video/BV1ECQ9B5EKe/">吴恩达Rag课程 导师Zain</a></li><li><a href="https://learn.deeplearning.ai/courses/retrieval-augmented-generation/lesson/rrngb/a-conversation-with-andrew-ng?startTime=1">Zain Rag课程正版链接</a></li></ul></div></div><p>我本地的仓库位置 D:\Project\UGit\starting-ragchatbot-codebase</p>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/AI/">AI</category>
      
      <category domain="https://eugenepage.com/tags/RAG/">RAG</category>
      
      <category domain="https://eugenepage.com/tags/embedding/">embedding</category>
      
      <category domain="https://eugenepage.com/tags/vectorSearch/">vectorSearch</category>
      
      <category domain="https://eugenepage.com/tags/CLIP/">CLIP</category>
      
      <category domain="https://eugenepage.com/tags/FAISS/">FAISS</category>
      
      <category domain="https://eugenepage.com/tags/learning/">learning</category>
      
      
      <comments>https://eugenepage.com/zh-CN/2026/05/23/20260524.RAGDeepLearning/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Obsidian 学习路径与功能笔记</title>
      <link>https://eugenepage.com/zh-CN/2026/05/08/20260509.ObsidianFunctionLearning/</link>
      <guid>https://eugenepage.com/zh-CN/2026/05/08/20260509.ObsidianFunctionLearning/</guid>
      <pubDate>Fri, 08 May 2026 16:00:00 GMT</pubDate>
      
        
        
      <description>&lt;h1 id=&quot;Obsidian-学习路径与功能笔记&quot;&gt;&lt;a href=&quot;#Obsidian-学习路径与功能笔记&quot; class=&quot;headerlink&quot; title=&quot;Obsidian 学习路径与功能笔记&quot;&gt;&lt;/a&gt;Obsidian 学习路径与功能笔记&lt;/h1&gt;&lt;blockquo</description>
        
      
      
      
      <content:encoded><![CDATA[<h1 id="Obsidian-学习路径与功能笔记"><a href="#Obsidian-学习路径与功能笔记" class="headerlink" title="Obsidian 学习路径与功能笔记"></a>Obsidian 学习路径与功能笔记</h1><blockquote><p>目标：以最少的折腾时间，把 Obsidian 用成”长期可复利”的知识库；先稳住基本功，再按需扩展插件与方法论。</p></blockquote><hr><h2 id="0-为什么是-Obsidian"><a href="#0-为什么是-Obsidian" class="headerlink" title="0. 为什么是 Obsidian"></a>0. 为什么是 Obsidian</h2><ul><li><strong>本地优先</strong>：所有笔记是 <code>.md</code> 纯文本，跟随 Git&#x2F;网盘随便同步；与本仓库 Hexo 博客天然兼容（<code>notes/_posts/**/*.md</code> 可直接被博客引擎消费）。</li><li><strong>链接驱动</strong>：用 <code>[[wikilink]]</code> 把碎片连成网，长期沉淀越久越值钱。</li><li><strong>插件生态</strong>：核心插件 + 社区插件 ≈ “可编程的笔记系统”。</li><li><strong>零锁定</strong>：随时可以离开，文件即数据。</li></ul><h2 id="1-我自己的文件目录路径"><a href="#1-我自己的文件目录路径" class="headerlink" title="1. 我自己的文件目录路径"></a>1. 我自己的文件目录路径</h2><p>这个 Vault 的根目录 <code>C:\Users\youdr\iCloudDrive\Doc\notes\</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></pre></td><td class="code"><pre><span class="line">notes/</span><br><span class="line">├── .claude/       # Claude Code 的 vault 级配置</span><br><span class="line">├── .claudian/     # Claudian 插件的运行时数据</span><br><span class="line">├── .obsidian/     # Obsidian 本体的所有配置</span><br><span class="line">└── .omc/          # oh-my-claudecode (OMC) 的状态存储</span><br></pre></td></tr></table></figure><div class="canvas-embed" data-canvas-slug="attachments/Canvas/20260509.ObsidianFunctionLearning-vault-directory-map"><svg xmlns="http://www.w3.org/2000/svg" class="canvas-svg" data-canvas-revision="f89e0e616853" width="249" height="280" viewBox="-40 -980 1680 1890" preserveAspectRatio="xMidYMid meet" role="img" aria-label="20260509.ObsidianFunctionLearning-vault-directory-map"><title>20260509.ObsidianFunctionLearning-vault-directory-map</title><defs><marker id="canvas-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" /></marker></defs><g class="canvas-groups"></g><g class="canvas-edges"><g class="canvas-edge-group" data-id="b1b2c3d4e5f60001" data-from-node="a1b2c3d4e5f60001" data-to-node="a1b2c3d4e5f60002" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 340 0 C 610.7192067232927 0, 209.2807932767073 -800, 480 -800" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60002" data-from-node="a1b2c3d4e5f60001" data-to-node="a1b2c3d4e5f60006" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 340 0 C 432.6162932629987 0, 387.3837067370013 -240, 480 -240" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60003" data-from-node="a1b2c3d4e5f60001" data-to-node="a1b2c3d4e5f60009" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 340 0 C 432.6162932629987 0, 387.3837067370013 240, 480 240" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60004" data-from-node="a1b2c3d4e5f60001" data-to-node="a1b2c3d4e5f60015" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 340 0 C 604.154836748786 0, 215.845163251214 780, 480 780" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60005" data-from-node="a1b2c3d4e5f60002" data-to-node="a1b2c3d4e5f60003" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 760 -800 C 818.3333333333334 -800, 841.6666666666666 -905, 900 -905" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60006" data-from-node="a1b2c3d4e5f60002" data-to-node="a1b2c3d4e5f60004" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 760 -800 C 806.9337594677625 -800, 853.0662405322375 -815, 900 -815" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60007" data-from-node="a1b2c3d4e5f60002" data-to-node="a1b2c3d4e5f60005" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 760 -800 C 812.941267247562 -800, 847.058732752438 -725, 900 -725" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60008" data-from-node="a1b2c3d4e5f60006" data-to-node="a1b2c3d4e5f60007" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 760 -240 C 817.3488351136175 -240, 842.6511648863825 -340, 900 -340" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60009" data-from-node="a1b2c3d4e5f60006" data-to-node="a1b2c3d4e5f60008" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 760 -240 C 810.1386965216377 -240, 849.8613034783623 -185, 900 -185" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60010" data-from-node="a1b2c3d4e5f60009" data-to-node="a1b2c3d4e5f60010" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 760 240 C 814.3366563401008 240, 845.6633436598992 156.5, 900 156.5" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60011" data-from-node="a1b2c3d4e5f60009" data-to-node="a1b2c3d4e5f60011" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 760 240 C 852.6162932629987 240, 807.3837067370013 480, 900 480" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60012" data-from-node="a1b2c3d4e5f60011" data-to-node="a1b2c3d4e5f60012" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 1140 480 C 1205.659052011974 480, 1254.340947988026 400, 1320 400" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60013" data-from-node="a1b2c3d4e5f60011" data-to-node="a1b2c3d4e5f60013" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 1140 480 C 1200.2079728939614 480, 1259.7920271060386 495, 1320 495" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60014" data-from-node="a1b2c3d4e5f60011" data-to-node="a1b2c3d4e5f60014" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 1140 480 C 1209.462219947249 480, 1250.537780052751 585, 1320 585" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60015" data-from-node="a1b2c3d4e5f60015" data-to-node="a1b2c3d4e5f60016" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 760 780 C 809.0181372328425 780, 850.9818627671575 735, 900 735" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60016" data-from-node="a1b2c3d4e5f60015" data-to-node="a1b2c3d4e5f60017" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 760 780 C 810.1386965216377 780, 849.8613034783623 835, 900 835" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60017" data-from-node="a1b2c3d4e5f60005" data-to-node="a1b2c3d4e5f60018" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 1140 -725 C 1224.8528137423857 -725, 1235.1471862576143 -905, 1320 -905" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60018" data-from-node="a1b2c3d4e5f60005" data-to-node="a1b2c3d4e5f60019" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 1140 -725 C 1207.0820393249937 -725, 1252.9179606750063 -815, 1320 -815" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60019" data-from-node="a1b2c3d4e5f60005" data-to-node="a1b2c3d4e5f60020" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 1140 -725 C 1200 -725, 1260 -725, 1320 -725" fill="none" marker-end="url(#canvas-arrow)" /></g><g class="canvas-edge-group" data-id="b1b2c3d4e5f60020" data-from-node="a1b2c3d4e5f60005" data-to-node="a1b2c3d4e5f60021" data-from-side="right" data-to-side="left"><path class="canvas-edge" d="M 1140 -725 C 1207.0820393249937 -725, 1252.9179606750063 -635, 1320 -635" fill="none" marker-end="url(#canvas-arrow)" /></g></g><g class="canvas-nodes"><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60001" data-x="0" data-y="-40" data-width="340" data-height="80" data-color="4"><rect class="canvas-node__bg" x="0" y="-40" width="340" height="80" rx="8" /><foreignObject x="0" y="-40" width="340" height="80"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>notes&#x2F; 根目录</strong><br><code>C:\Users\youdr\iCloudDrive\Doc\notes</code></p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60002" data-x="480" data-y="-840" data-width="280" data-height="80" data-color="5"><rect class="canvas-node__bg" x="480" y="-840" width="280" height="80" rx="8" /><foreignObject x="480" y="-840" width="280" height="80"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>.claude/</code></strong><br>Claude Code vault 级配置</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60003" data-x="900" data-y="-940" data-width="240" data-height="70"><rect class="canvas-node__bg" x="900" y="-940" width="240" height="70" rx="8" /><foreignObject x="900" y="-940" width="240" height="70"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>agents/</code></strong><br>自定义 Agent 角色（空）</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60004" data-x="900" data-y="-850" data-width="240" data-height="70"><rect class="canvas-node__bg" x="900" y="-850" width="240" height="70" rx="8" /><foreignObject x="900" y="-850" width="240" height="70"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>commands/</code></strong><br>自定义斜杠命令（空）</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60005" data-x="900" data-y="-760" data-width="240" data-height="70"><rect class="canvas-node__bg" x="900" y="-760" width="240" height="70" rx="8" /><foreignObject x="900" y="-760" width="240" height="70"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>skills/</code></strong><br>vault 专属技能包（4个）</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60006" data-x="480" data-y="-280" data-width="280" data-height="80" data-color="6"><rect class="canvas-node__bg" x="480" y="-280" width="280" height="80" rx="8" /><foreignObject x="480" y="-280" width="280" height="80"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>.claudian/</code></strong><br>Claudian 插件运行时数据</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60008" data-x="900" data-y="-220" data-width="240" data-height="70"><rect class="canvas-node__bg" x="900" y="-220" width="240" height="70" rx="8" /><foreignObject x="900" y="-220" width="240" height="70"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>sessions/</code></strong><br>对话历史元数据（4条）</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60009" data-x="480" data-y="200" data-width="280" data-height="80" data-color="2"><rect class="canvas-node__bg" x="480" y="200" width="280" height="80" rx="8" /><foreignObject x="480" y="200" width="280" height="80"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>.obsidian/</code></strong><br>Obsidian 核心配置</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60010" data-x="900" data-y="60" data-width="280" data-height="193"><rect class="canvas-node__bg" x="900" y="60" width="280" height="193" rx="8" /><foreignObject x="900" y="60" width="280" height="193"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>配置文件</strong><br>• <code>app.json</code> 应用层设置<br>• <code>appearance.json</code> 外观主题<br>• <code>core-plugins.json</code> 核心插件<br>• <code>community-plugins.json</code> 社区插件<br>• <code>hotkeys.json</code> 快捷键配置<br>• <code>daily-notes.json</code> 日记路径<br>• <code>graph.json</code> 知识图谱参数<br>• <code>workspace.json</code> 窗口布局 ⚠️不跟踪</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60011" data-x="900" data-y="440" data-width="240" data-height="80"><rect class="canvas-node__bg" x="900" y="440" width="240" height="80" rx="8" /><foreignObject x="900" y="440" width="240" height="80"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>plugins/</code></strong><br>已安装插件本体（3个）</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60012" data-x="1320" data-y="360" data-width="280" data-height="80"><rect class="canvas-node__bg" x="1320" y="360" width="280" height="80" rx="8" /><foreignObject x="1320" y="360" width="280" height="80"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>obsidian-image-auto-upload</strong><br>图片自动上传至 PicList 图床</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60013" data-x="1320" data-y="460" data-width="240" data-height="70"><rect class="canvas-node__bg" x="1320" y="460" width="240" height="70" rx="8" /><foreignObject x="1320" y="460" width="240" height="70"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>pdf-plus&#x2F;</strong><br>PDF 增强阅读与标注</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60014" data-x="1320" data-y="550" data-width="240" data-height="70"><rect class="canvas-node__bg" x="1320" y="550" width="240" height="70" rx="8" /><foreignObject x="1320" y="550" width="240" height="70"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong>realclaudian&#x2F;</strong><br>Claudian 插件本体（本 AI）</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60015" data-x="480" data-y="740" data-width="280" data-height="80" data-color="1"><rect class="canvas-node__bg" x="480" y="740" width="280" height="80" rx="8" /><foreignObject x="480" y="740" width="280" height="80"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>.omc/</code></strong><br>oh-my-claudecode 状态存储</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60016" data-x="900" data-y="700" data-width="260" data-height="70"><rect class="canvas-node__bg" x="900" y="700" width="260" height="70" rx="8" /><foreignObject x="900" y="700" width="260" height="70"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>sessions/</code></strong><br>OMC session 上下文快照</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60017" data-x="900" data-y="800" data-width="260" data-height="70"><rect class="canvas-node__bg" x="900" y="800" width="260" height="70" rx="8" /><foreignObject x="900" y="800" width="260" height="70"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>state/sessions/</code></strong><br>agent 间共享状态</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60018" data-x="1320" data-y="-940" data-width="260" data-height="70"><rect class="canvas-node__bg" x="1320" y="-940" width="260" height="70" rx="8" /><foreignObject x="1320" y="-940" width="260" height="70"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>defuddle</code></strong><br>网页抓取为干净 Markdown</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60019" data-x="1320" data-y="-850" data-width="260" data-height="70"><rect class="canvas-node__bg" x="1320" y="-850" width="260" height="70" rx="8" /><foreignObject x="1320" y="-850" width="260" height="70"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>json-canvas</code></strong><br>读写 .canvas 白板文件</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60020" data-x="1320" data-y="-760" data-width="260" data-height="70"><rect class="canvas-node__bg" x="1320" y="-760" width="260" height="70" rx="8" /><foreignObject x="1320" y="-760" width="260" height="70"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>obsidian-cli</code></strong><br>obsidian:&#x2F;&#x2F; URI 协议调用</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60021" data-x="1320" data-y="-670" data-width="260" data-height="70"><rect class="canvas-node__bg" x="1320" y="-670" width="260" height="70" rx="8" /><foreignObject x="1320" y="-670" width="260" height="70"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>obsidian-markdown</code></strong><br>Obsidian 专属 MD 语法</p></div></foreignObject></g><g class="canvas-node canvas-node--text" data-id="a1b2c3d4e5f60007" data-x="900" data-y="-400" data-width="280" data-height="120"><rect class="canvas-node__bg" x="900" y="-400" width="280" height="120" rx="8" /><foreignObject x="900" y="-400" width="280" height="120"><div xmlns="http://www.w3.org/1999/xhtml" class="canvas-node__body"><p><strong><code>claudian-settings.json</code></strong><br>模型: sonnet · 权限: yolo<br>思考预算: low · 努力: high<br>UI 位置: 侧边栏右侧</p></div></foreignObject></g></g></svg><span class="canvas-embed__expand" aria-hidden="true" title="点击放大">⛶</span></div><hr><h2 id="1-学习路径总览（建议按周推进）"><a href="#1-学习路径总览（建议按周推进）" class="headerlink" title="1. 学习路径总览（建议按周推进）"></a>1. 学习路径总览（建议按周推进）</h2><table><thead><tr><th>阶段</th><th>时长</th><th>核心目标</th><th>关键产出</th></tr></thead><tbody><tr><td><strong>W1 基础</strong></td><td>3–5 天</td><td>掌握 Vault、Markdown、双链、标签</td><td>第一篇带链接的笔记</td></tr><tr><td><strong>W2 组织</strong></td><td>1 周</td><td>文件夹策略、模板、每日笔记</td><td>个人 PKM 结构成型</td></tr><tr><td><strong>W3 进阶</strong></td><td>1 周</td><td>Dataview、Templater、Graph View</td><td>自动化索引页</td></tr><tr><td><strong>W4 工作流</strong></td><td>1 周</td><td>与 Hexo &#x2F; Git &#x2F; VSCode 联动</td><td>笔记 → 博客一键流程</td></tr><tr><td><strong>持续</strong></td><td>—</td><td>方法论（Zettelkasten &#x2F; PARA &#x2F; Johnny Decimal）</td><td>可复利的二阶笔记</td></tr></tbody></table><hr><h2 id="2-W1：基础——把”骨架”立起来"><a href="#2-W1：基础——把”骨架”立起来" class="headerlink" title="2. W1：基础——把”骨架”立起来"></a>2. W1：基础——把”骨架”立起来</h2><h3 id="2-1-核心概念"><a href="#2-1-核心概念" class="headerlink" title="2.1 核心概念"></a>2.1 核心概念</h3><ul><li><strong>Vault（库）</strong>：一个文件夹 &#x3D; 一个 Vault。所有 <code>.md</code> 与 <code>.obsidian/</code> 配置都在里面。</li><li><strong>Note（笔记）</strong>：一个 <code>.md</code> 文件 &#x3D; 一条笔记。命名建议：<code>日期前缀 + 主题</code>，如 <code>20260509.ObsidianFunctionLearning.md</code>。</li><li><strong>Frontmatter（YAML 元数据）</strong>：文件最顶部的 <code>---</code> 块，存放 <code>title / tags / date / status</code>，被 Dataview &#x2F; 主题 &#x2F; Hexo 共同消费。</li><li><strong>Link（双链）</strong>：<code>[[文件名]]</code> 或 <code>[[文件名|显示文本]]</code>；<code>[[A#二级标题]]</code> 跳转到具体小节；<code>[[A^block-id]]</code> 引用块。</li><li><strong>Backlink（反向链接）</strong>：右侧面板自动列出”谁链接了我”，是 Obsidian 的灵魂功能。</li><li><strong>Tag（标签）</strong>：<code>#topic/subtopic</code> 支持层级；和文件夹是互补关系，不是替代。</li></ul><h3 id="2-2-必须背下来的快捷键"><a href="#2-2-必须背下来的快捷键" class="headerlink" title="2.2 必须背下来的快捷键"></a>2.2 必须背下来的快捷键</h3><table><thead><tr><th>操作</th><th>快捷键</th></tr></thead><tbody><tr><td>全局命令面板</td><td><code>Ctrl + P</code></td></tr><tr><td>快速切换文件</td><td><code>Ctrl + O</code></td></tr><tr><td>新建笔记</td><td><code>Ctrl + N</code></td></tr><tr><td>双链补全</td><td>输入 <code>[[</code> 触发</td></tr><tr><td>切换源码&#x2F;预览</td><td><code>Ctrl + E</code></td></tr><tr><td>打开当天 Daily Note</td><td><code>Ctrl + Shift + D</code>（启用 Daily Notes 插件后）</td></tr><tr><td>源码模式切换（自己设置）</td><td>ctrl + &#x2F;</td></tr></tbody></table><h3 id="2-3-W1-练习"><a href="#2-3-W1-练习" class="headerlink" title="2.3 W1 练习"></a>2.3 W1 练习</h3><ol><li>在 <code>notes/</code> 打开 Vault，写 3 条笔记（任意主题）。</li><li>让其中两条用 <code>[[]]</code> 互相链接。</li><li>给每条加 <code>tags: [...]</code>，在右侧面板看 Backlink。</li></ol><hr><h2 id="3-W2：组织——确立结构与模板"><a href="#3-W2：组织——确立结构与模板" class="headerlink" title="3. W2：组织——确立结构与模板"></a>3. W2：组织——确立结构与模板</h2><h3 id="3-1-文件夹策略（与本仓库现状对齐）"><a href="#3-1-文件夹策略（与本仓库现状对齐）" class="headerlink" title="3.1 文件夹策略（与本仓库现状对齐）"></a>3.1 文件夹策略（与本仓库现状对齐）</h3><p>当前仓库已有的目录可直接套用：</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></pre></td><td class="code"><pre><span class="line">notes/</span><br><span class="line">├── _posts/        # Hexo 发布的正式文章（双语）</span><br><span class="line">│   ├── zh-CN/</span><br><span class="line">│   └── en/</span><br><span class="line">├── Learning/      # 学习笔记 / 个人草稿（本文件所在）</span><br><span class="line">├── AIdocs/        # 项目级架构、决策、路线图</span><br><span class="line">└── about/         # 关于页</span><br></pre></td></tr></table></figure><p>建议在 <code>Learning/</code> 下再分：</p><ul><li><code>daily/</code>：每日笔记（自动创建）</li><li><code>topic/</code>：主题长文（成熟后迁出到 <code>_posts/</code>）</li><li><code>inbox/</code>：临时草稿，未分类</li></ul><h3 id="3-2-三种主流方法论（任选其一即可，别全上）"><a href="#3-2-三种主流方法论（任选其一即可，别全上）" class="headerlink" title="3.2 三种主流方法论（任选其一即可，别全上）"></a>3.2 三种主流方法论（任选其一即可，别全上）</h3><table><thead><tr><th>方法</th><th>一句话</th><th>适合谁</th></tr></thead><tbody><tr><td><strong>Zettelkasten</strong></td><td>一卡一念，靠双链组网，不靠分类</td><td>长期写作者、研究者</td></tr><tr><td><strong>PARA</strong></td><td>Project &#x2F; Area &#x2F; Resource &#x2F; Archive</td><td>项目驱动型工作者</td></tr><tr><td><strong>Johnny Decimal</strong></td><td><code>10-19 / 11.01</code> 编号制</td><td>偏好结构与索引的人</td></tr></tbody></table><blockquote><p><strong>建议</strong>：你已经有 <code>_posts / Learning / AIdocs</code> 这种”项目+资源”分布，<strong>先跑 PARA</strong>，等笔记数量过 500 篇再考虑 Zettelkasten。</p></blockquote><h3 id="3-3-必装核心插件（自带）"><a href="#3-3-必装核心插件（自带）" class="headerlink" title="3.3 必装核心插件（自带）"></a>3.3 必装核心插件（自带）</h3><p>进入 <code>设置 → 核心插件</code>，把这些打开：</p><ul><li>✅ <strong>Daily Notes</strong>：每日一篇时间轴笔记</li><li>✅ <strong>Templates</strong>：插入模板内容</li><li>✅ <strong>Outline</strong>：右侧大纲</li><li>✅ <strong>Backlinks &#x2F; Outgoing Links</strong>：反向 &#x2F; 正向链接面板</li><li>✅ <strong>Graph View</strong>：知识图谱</li><li>✅ <strong>Tag Pane</strong>：标签面板</li><li>✅ <strong>File Recovery</strong>：自动备份，<strong>强烈推荐</strong></li><li>⚠️ <strong>Workspaces</strong>：多布局切换（进阶可开）</li></ul><hr><h2 id="4-W3：进阶——让笔记自己动起来"><a href="#4-W3：进阶——让笔记自己动起来" class="headerlink" title="4. W3：进阶——让笔记自己动起来"></a>4. W3：进阶——让笔记自己动起来</h2><h3 id="4-1-必装社区插件（短列表，不要贪多）"><a href="#4-1-必装社区插件（短列表，不要贪多）" class="headerlink" title="4.1 必装社区插件（短列表，不要贪多）"></a>4.1 必装社区插件（短列表，不要贪多）</h3><table><thead><tr><th>插件</th><th>作用</th></tr></thead><tbody><tr><td><strong>Dataview</strong></td><td>用类 SQL 查询笔记元数据，自动生成索引页</td></tr><tr><td><strong>Templater</strong></td><td>比内置 Templates 强 100 倍，支持 JS 脚本</td></tr><tr><td><strong>Excalidraw</strong></td><td>手绘 &#x2F; 流程图，附带双链</td></tr><tr><td><strong>Advanced Tables</strong></td><td>表格编辑器（写 Markdown 表格的人都需要）</td></tr><tr><td><strong>Obsidian Git</strong></td><td>Vault 自动 commit &#x2F; push（你这个仓库正好用得上）</td></tr><tr><td><strong>Iconize</strong> &#x2F; <strong>Iconic</strong></td><td>给文件夹&#x2F;文件加图标，提升可视性</td></tr><tr><td><strong>Style Settings</strong></td><td>调主题细节（字体、间距、颜色）</td></tr><tr><td><strong>Linter</strong></td><td>Markdown 风格统一，YAML 排序</td></tr></tbody></table><h3 id="4-2-Dataview-入门示例"><a href="#4-2-Dataview-入门示例" class="headerlink" title="4.2 Dataview 入门示例"></a>4.2 Dataview 入门示例</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></pre></td><td class="code"><pre><span class="line"><span class="code">```dataview</span></span><br><span class="line"><span class="code">TABLE date, status, file.tags AS tags</span></span><br><span class="line"><span class="code">FROM &quot;Learning&quot;</span></span><br><span class="line"><span class="code">WHERE status = &quot;in-progress&quot;</span></span><br><span class="line"><span class="code">SORT date DESC</span></span><br><span class="line"><span class="code">```</span></span><br></pre></td></tr></table></figure><p>→ 自动列出 <code>Learning/</code> 下所有 <code>status: in-progress</code> 的笔记。</p><h3 id="4-3-Templater-模板示例"><a href="#4-3-Templater-模板示例" class="headerlink" title="4.3 Templater 模板示例"></a>4.3 Templater 模板示例</h3><p>在 <code>notes/Templates/learning.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></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">title: &lt;% tp.file.title %&gt;</span><br><span class="line">date: &lt;% tp.date.now(&quot;YYYY-MM-DD&quot;) %&gt;</span><br><span class="line">tags: []</span><br><span class="line"><span class="section">status: in-progress</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line"><span class="section"># &lt;% tp.file.title %&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 背景</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 内容</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 参考</span></span><br></pre></td></tr></table></figure><p>→ 新建笔记时一键套用，<code>title / date</code> 自动填。</p><hr><h2 id="5-W4：工作流——把-Obsidian-嵌进现有管线"><a href="#5-W4：工作流——把-Obsidian-嵌进现有管线" class="headerlink" title="5. W4：工作流——把 Obsidian 嵌进现有管线"></a>5. W4：工作流——把 Obsidian 嵌进现有管线</h2><p>本仓库是 <strong>Hexo 博客 + Git 版本管理 + Obsidian 笔记</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></pre></td><td class="code"><pre><span class="line">草稿（Learning/inbox/） →</span><br><span class="line">成熟（Learning/topic/） →</span><br><span class="line">发布（_posts/zh-CN/ 与 _posts/en/） →</span><br><span class="line">博客上线（Hexo build）</span><br></pre></td></tr></table></figure><h3 id="5-1-与-Hexo-兼容的-frontmatter"><a href="#5-1-与-Hexo-兼容的-frontmatter" class="headerlink" title="5.1 与 Hexo 兼容的 frontmatter"></a>5.1 与 Hexo 兼容的 frontmatter</h3><p>博客文章需要的字段（参考 <code>notes/_posts/</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><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">title:</span> <span class="string">文章标题</span></span><br><span class="line"><span class="attr">date:</span> <span class="number">2026-05-09 12:00:00</span></span><br><span class="line"><span class="attr">categories:</span> [<span class="string">分类</span>]</span><br><span class="line"><span class="attr">tags:</span> [<span class="string">标签1</span>, <span class="string">标签2</span>]</span><br><span class="line"><span class="attr">lang:</span> <span class="string">zh-CN</span></span><br><span class="line"><span class="meta">---</span></span><br></pre></td></tr></table></figure><h3 id="5-2-与-Git-联动"><a href="#5-2-与-Git-联动" class="headerlink" title="5.2 与 Git 联动"></a>5.2 与 Git 联动</h3><ul><li>用 <code>Obsidian Git</code> 插件做”自动 commit”。</li><li>但本仓库已经有自己的提交规范（见 <code>git log</code> 风格），建议：<ul><li><strong>写作期间</strong>：手动 commit。</li><li><strong>每日睡前</strong>：用 <code>Obsidian Git</code> 一键 push。</li></ul></li></ul><h3 id="5-3-与博客主题（hexo-theme-magnetic）的注意事项"><a href="#5-3-与博客主题（hexo-theme-magnetic）的注意事项" class="headerlink" title="5.3 与博客主题（hexo-theme-magnetic）的注意事项"></a>5.3 与博客主题（hexo-theme-magnetic）的注意事项</h3><ul><li>你当前主题里的 <code>tag-graph.js</code> 与 Obsidian Graph View 是<strong>两套图谱</strong>，互不影响。</li><li>笔记里 <code>[[wikilink]]</code> 在博客渲染时<strong>不会</strong>自动转成超链接（除非装 Hexo 插件 <code>hexo-filter-github-emojis</code> 类的扩展）。如果要发到博客，改成标准 Markdown 链接。</li></ul><hr><h2 id="6-进阶专题（按需展开）"><a href="#6-进阶专题（按需展开）" class="headerlink" title="6. 进阶专题（按需展开）"></a>6. 进阶专题（按需展开）</h2><h3 id="6-1-Canvas（白板）"><a href="#6-1-Canvas（白板）" class="headerlink" title="6.1 Canvas（白板）"></a>6.1 Canvas（白板）</h3><p>内置功能，<code>新建白板</code> → 把多张笔记拖进来当卡片，画连线。适合做<strong>知识地图、项目看板</strong>。</p><h3 id="6-2-Sync-方案对比"><a href="#6-2-Sync-方案对比" class="headerlink" title="6.2 Sync 方案对比"></a>6.2 Sync 方案对比</h3><table><thead><tr><th>方式</th><th>成本</th><th>优点</th><th>坑</th></tr></thead><tbody><tr><td><strong>Obsidian Sync 官方</strong></td><td>$4&#x2F;月</td><td>端到端加密、最稳</td><td>收费</td></tr><tr><td><strong>iCloud &#x2F; OneDrive</strong></td><td>免费</td><td>简单</td><td><code>.obsidian/</code> 容易冲突</td></tr><tr><td><strong>Git（推荐你这种）</strong></td><td>免费</td><td>完整版本史</td><td>大文件需 LFS</td></tr><tr><td><strong>Syncthing</strong></td><td>免费</td><td>局域网快</td><td>配置略折腾</td></tr></tbody></table><h3 id="6-3-移动端"><a href="#6-3-移动端" class="headerlink" title="6.3 移动端"></a>6.3 移动端</h3><ul><li>iOS &#x2F; Android 客户端免费。</li><li>移动端 + iCloud &#x2F; Git 跨设备 → 手机随手记，电脑深度整理。</li></ul><hr><h2 id="7-路径布置建议（针对本仓库）"><a href="#7-路径布置建议（针对本仓库）" class="headerlink" title="7. 路径布置建议（针对本仓库）"></a>7. 路径布置建议（针对本仓库）</h2><blockquote><p><strong>关键问题</strong>：根目录 <code>D:\Project\UGit\EugenePage\.obsidian</code> 已存在，说明 Vault 当前打开的是<strong>整个仓库</strong>而不是 <code>notes/</code>。</p></blockquote><p>两种方案，<strong>二选一</strong>：</p><h3 id="方案-A：把-notes-单独作为-Vault（推荐）"><a href="#方案-A：把-notes-单独作为-Vault（推荐）" class="headerlink" title="方案 A：把 notes/ 单独作为 Vault（推荐）"></a>方案 A：把 <code>notes/</code> 单独作为 Vault（推荐）</h3><ul><li>在 Obsidian 起始页 → “打开文件夹作为库” → 选 <code>D:\Project\UGit\EugenePage\notes</code>。</li><li>优点：Vault 范围干净，只看到笔记，不被 <code>themes/</code>、<code>scripts/</code> 干扰。</li><li>操作：把根目录的 <code>.obsidian/</code> 移动到 <code>notes/.obsidian/</code>（或删掉重建），并在 <code>.gitignore</code> 里<strong>保留</strong> <code>notes/.obsidian/workspace.json</code>（个人布局，不必跟踪）但<strong>保留</strong>核心插件配置。</li></ul><h3 id="方案-B：保持仓库根作为-Vault"><a href="#方案-B：保持仓库根作为-Vault" class="headerlink" title="方案 B：保持仓库根作为 Vault"></a>方案 B：保持仓库根作为 Vault</h3><ul><li>优点：可以同时编辑主题代码与笔记。</li><li>缺点：Graph View 会扫描所有 <code>.md</code>，大量噪音。</li><li>必须做：在 Obsidian <code>设置 → 文件与链接 → 排除的文件</code> 里把 <code>themes/</code>、<code>node_modules/</code>、<code>public/</code> 全部排除。</li></ul><h3 id="gitignore-建议（任一方案都加）"><a href="#gitignore-建议（任一方案都加）" class="headerlink" title=".gitignore 建议（任一方案都加）"></a><code>.gitignore</code> 建议（任一方案都加）</h3><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><span class="line"># Obsidian 个人配置（团队不共享）</span><br><span class="line">.obsidian/workspace.json</span><br><span class="line">.obsidian/workspace-mobile.json</span><br><span class="line">.obsidian/cache</span><br><span class="line">.obsidian/plugins/*/data.json   # 视情况，含敏感的不要 push</span><br></pre></td></tr></table></figure><p>但 <code>.obsidian/core-plugins.json</code>、<code>community-plugins.json</code>、<code>appearance.json</code>、<code>hotkeys.json</code> <strong>建议跟踪</strong>，方便多机同步。</p><hr><h2 id="8-插件-流程"><a href="#8-插件-流程" class="headerlink" title="8. 插件&#x2F;流程"></a>8. 插件&#x2F;流程</h2><h3 id="Image-Auto-Upload"><a href="#Image-Auto-Upload" class="headerlink" title="Image Auto Upload"></a>Image Auto Upload</h3><p>复制或拖入图片时自动上传至图床，与 PicGo 生态兼容。底层依赖 <strong>PicList</strong>（PicGo 的社区增强版）的命令行接口，需提前配置好图床后方可使用。功能定位与 Typora 的图片上传一致，是笔记软件的基础素质之一。</p><h3 id="Obsidian-CLI-Claudian"><a href="#Obsidian-CLI-Claudian" class="headerlink" title="Obsidian CLI + Claudian"></a>Obsidian CLI + Claudian</h3><p>让 AI（Claude Code）直接读写 Vault 的桥梁，分两步启用：</p><ol><li><strong>开启 CLI</strong>：<code>设置 → 关于 → Obsidian CLI</code> → 点击注册 → 重启 Obsidian。</li><li><strong>安装插件</strong>：从社区插件市场搜索并安装 <strong>Claudian</strong>。<blockquote><p>前提：本机已完成 Claude Code 的配置，Claudian 会自动识别并接入。<br><img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo@main/Images/20260515-040708.png" alt="image.png"></p></blockquote></li></ol><p>整体的体验下来，会很像在 VSCode 里面使用 GitHub Copilot。在输入框的 Yolo 功能,，相当于是自动化修改。<br>而且 Claude 里面其实也有 Plan 模式的，点击 Shift + Tab 就可以直接在对话框里切换 Plan 模式<br>然后和它共同商量每一步应该怎么做，最后再让它去执行.<br>同时使用斜杠，依旧可以调用一些 Claude 里面的命令.</p><p>这一部分参考教程：<br><a href="https://www.bilibili.com/video/BV1xFwxzKE5D">https://www.bilibili.com/video/BV1xFwxzKE5D</a></p><h3 id="配套-Claude-Code-Skills（kepano-obsidian-skills）"><a href="#配套-Claude-Code-Skills（kepano-obsidian-skills）" class="headerlink" title="配套 Claude Code Skills（kepano&#x2F;obsidian-skills）"></a>配套 Claude Code Skills（kepano&#x2F;obsidian-skills）</h3><p>Obsidian CEO Steph Ango 在 <a href="https://github.com/kepano/obsidian-skills">kepano&#x2F;obsidian-skills</a> 发布了一组官方 Agent Skill，让 Claude Code 真正”懂” Obsidian 的文件格式与协议。安装方式：把每个 skill 文件夹放到 vault 的 <code>.claude/skills/&lt;name&gt;/</code> 下，<strong>仅对该 vault 启动的 Claude Code 生效</strong>，不会污染全局或其它项目。</p><table><thead><tr><th>Skill</th><th>用途</th><th>我个人是否安装</th></tr></thead><tbody><tr><td><strong>obsidian-markdown</strong></td><td>读写 Obsidian Flavored Markdown：<code>[[wikilink]]</code>、<code>![[embed]]</code>、callouts（<code>&gt; [!note]</code>）、properties frontmatter 等 Obsidian 专属语法。不装的话 Claude 写 <code>.md</code> 时会按通用 Markdown 处理，可能破坏专属语法。</td><td>未安装（计划安装）</td></tr><tr><td><strong>obsidian-bases</strong></td><td>读写 <code>.base</code> 文件（Obsidian 1.9+ 引入的数据库视图，支持 views &#x2F; filters &#x2F; formulas &#x2F; summaries）</td><td>未安装（暂不需要，当前 vault 还没有 <code>.base</code> 文件，等真正用到 Bases 再补）</td></tr><tr><td><strong>json-canvas</strong></td><td>读写 <code>.canvas</code> 文件（白板的 JSON 格式，包含节点、边、组、连线），让 Claude 能直接生成或修改 Canvas</td><td>未安装（计划安装）</td></tr><tr><td><strong>obsidian-cli</strong></td><td>教 Claude 调用 Obsidian <strong>内置</strong>的 <code>obsidian://</code> URI 协议（如 <code>obsidian://open?vault=...&amp;file=...</code>）以及可选的 HTTP API。<strong>不需要额外安装任何命令行二进制</strong>——所有调用走 Obsidian 自带能力。</td><td>未安装（计划安装）</td></tr><tr><td><strong>defuddle</strong></td><td>用 Defuddle 库从网页抽取干净 Markdown，自动去掉导航栏、广告、推荐等噪音，节省 token，适合”网页剪藏 → 笔记”场景</td><td>未安装（计划安装）</td></tr></tbody></table><blockquote><p><strong>关于 <code>obsidian-cli</code> 的常见误解</strong>：这个 skill 不等于”装一个独立 CLI 工具”。<code>obsidian://</code> URI 协议从 Obsidian 1.0 起就是<strong>默认内置功能</strong>，skill 的作用只是让 Claude 学会调用它来实现”打开某篇笔记、触发某个命令、跳转到指定 block”等操作。HTTP API 部分若想启用，需要额外安装社区插件 <strong>Local REST API</strong>（可选）。</p></blockquote><blockquote><p><strong>与 Claudian 的关系</strong>：<code>Claudian</code> 是 Obsidian 端的插件，提供”在 Obsidian UI 里跟 Claude Code 对话”的入口；上面这些 skill 是 Claude Code 端的能力包，让 Claude 在读写 vault 文件时更专业。两者互补、不冲突。</p></blockquote><p>下面我挨个介绍我比较推荐的这几个 skills。</p><h3 id="Advanced-Canvas-插件-json-canvas"><a href="#Advanced-Canvas-插件-json-canvas" class="headerlink" title="Advanced Canvas 插件 + json-canvas"></a>Advanced Canvas 插件 + json-canvas</h3><p>比如说我这一篇文章的顶部，有一个关于文件路径的介绍。上面有个思维导图，这个思维导图就是用 JSON Canvas 画出来的。<br>如果遇到一些比较难的文章或者是比较复杂的架构，可以让他帮你整理思维导图，方便理解。</p><p><strong>Advanced Canvas</strong> 提供 30+ 增强功能：自定义流程图节点样式、Graph View 集成、幻灯片演示模式；支持 Portal（Canvas 套娃）与单节点嵌入 Markdown。</p><h2 id="9-参考资源"><a href="#9-参考资源" class="headerlink" title="9. 参考资源"></a>9. 参考资源</h2><ul><li>官方文档：<a href="https://help.obsidian.md/">https://help.obsidian.md/</a></li><li>官方论坛：<a href="https://forum.obsidian.md/">https://forum.obsidian.md/</a></li><li>中文社区：少数派（sspai.com）的 Obsidian 系列</li><li>YouTube：Linking Your Thinking、Nicole van der Hoeven、Bryan Jenks</li><li>方法论：<ul><li>Niklas Luhmann《How to Take Smart Notes》（Zettelkasten 圣经）</li><li>Tiago Forte《Building a Second Brain》（PARA 提出者）</li></ul></li></ul>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/Obsidian/">Obsidian</category>
      
      <category domain="https://eugenepage.com/tags/PKM/">PKM</category>
      
      <category domain="https://eugenepage.com/tags/NoteTaking/">NoteTaking</category>
      
      <category domain="https://eugenepage.com/tags/LearningPath/">LearningPath</category>
      
      
      <comments>https://eugenepage.com/zh-CN/2026/05/08/20260509.ObsidianFunctionLearning/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Houdini MCP Project Comparison</title>
      <link>https://eugenepage.com/2026/05/02/20260502.HoudiniMCPComparison/</link>
      <guid>https://eugenepage.com/2026/05/02/20260502.HoudiniMCPComparison/</guid>
      <pubDate>Sat, 02 May 2026 04:00:00 GMT</pubDate>
      
        
        
      <description>&lt;h1 id=&quot;Houdini-MCP-Project-Comparison-capoomgit-houdini-mcp-vs-healkeiser-fxhoudinimcp&quot;&gt;&lt;a href=&quot;#Houdini-MCP-Project-Comparison-capoomgit-</description>
        
      
      
      
      <content:encoded><![CDATA[<h1 id="Houdini-MCP-Project-Comparison-capoomgit-houdini-mcp-vs-healkeiser-fxhoudinimcp"><a href="#Houdini-MCP-Project-Comparison-capoomgit-houdini-mcp-vs-healkeiser-fxhoudinimcp" class="headerlink" title="Houdini MCP Project Comparison: capoomgit&#x2F;houdini-mcp vs healkeiser&#x2F;fxhoudinimcp"></a>Houdini MCP Project Comparison: capoomgit&#x2F;houdini-mcp vs healkeiser&#x2F;fxhoudinimcp</h1><h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>As the MCP (Model Context Protocol) standard gains traction, more and more DCC applications are adding AI assistant integrations. In the Houdini ecosystem, two major open-source MCP projects currently exist:</p><ol><li><strong><a href="https://github.com/capoomgit/houdini-mcp">capoomgit&#x2F;houdini-mcp</a></strong> — an early-stage project with a clean, minimal structure</li><li><strong><a href="https://github.com/healkeiser/fxhoudinimcp">healkeiser&#x2F;fxhoudinimcp</a></strong> — a newer, more feature-complete implementation</li></ol><p>This post compares the two across architecture design, feature coverage, installation experience, and extensibility to help you pick the right one for your workflow.</p><hr><h2 id="Overview-Comparison"><a href="#Overview-Comparison" class="headerlink" title="Overview Comparison"></a>Overview Comparison</h2><table><thead><tr><th>Dimension</th><th>houdini-mcp (capoomgit)</th><th>fxhoudinimcp (healkeiser)</th></tr></thead><tbody><tr><td>Focus</td><td>Lightweight MCP bridge</td><td>Full-featured Houdini MCP server</td></tr><tr><td>Tool count</td><td>Unspecified; covers basic operations</td><td><strong>168 tools</strong> + 8 resources + 6 workflow prompts</td></tr><tr><td>Architecture</td><td>Custom TCP socket (port 9876)</td><td>Houdini’s built-in <code>hwebserver</code> (port 8100)</td></tr><tr><td>Installation</td><td>Manual file copy to Houdini directory</td><td>PyPI package, <code>pip install fxhoudinimcp</code></td></tr><tr><td>Package manager dependency</td><td>Requires <code>uv</code></td><td>Standard <code>pip</code> works fine</td></tr><tr><td>Thread safety</td><td>Not explicitly addressed</td><td><code>hdefereval.executeInMainThreadWithResult()</code></td></tr><tr><td>License</td><td>Not specified</td><td>MIT</td></tr><tr><td>Maintenance status</td><td>Community-maintained</td><td>Actively developed</td></tr></tbody></table><hr><h2 id="Architecture-Comparison"><a href="#Architecture-Comparison" class="headerlink" title="Architecture Comparison"></a>Architecture Comparison</h2><h3 id="houdini-mcp-capoomgit"><a href="#houdini-mcp-capoomgit" class="headerlink" title="houdini-mcp (capoomgit)"></a>houdini-mcp (capoomgit)</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Claude Desktop  ──(stdio)──&gt;  MCP Bridge Script  ──(TCP :9876)──&gt;  Houdini Plugin</span><br></pre></td></tr></table></figure><ul><li><strong>Communication</strong>: The MCP Bridge Script talks to Claude via stdin&#x2F;stdout and to Houdini via a custom TCP socket.</li><li><strong>Server side</strong>: A hand-rolled <code>HoudiniMCPServer</code> listening on <code>localhost:9876</code>.</li><li><strong>Inspired by</strong>: Adapted from <a href="https://github.com/ahujasid/blender-mcp">blender-mcp</a>.</li></ul><h3 id="fxhoudinimcp-healkeiser"><a href="#fxhoudinimcp-healkeiser" class="headerlink" title="fxhoudinimcp (healkeiser)"></a>fxhoudinimcp (healkeiser)</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Claude Desktop / Cursor / Claude Code  ──(stdio/streamable-http)──&gt;  FXHoudini MCP Server  ──(HTTP :8100)──&gt;  Houdini hwebserver</span><br></pre></td></tr></table></figure><ul><li><strong>Communication</strong>: The MCP Server talks to AI clients via stdio or streamable-http, and talks to Houdini via HTTP&#x2F;JSON.</li><li><strong>Server side</strong>: Uses Houdini’s built-in <code>hwebserver</code> directly — no custom server process needed.</li><li><strong>Thread safety</strong>: Uses <code>hdefereval.executeInMainThreadWithResult()</code> to ensure all <code>hou.*</code> API calls run on the main thread.</li></ul><h3 id="Architecture-Analysis"><a href="#Architecture-Analysis" class="headerlink" title="Architecture Analysis"></a>Architecture Analysis</h3><table><thead><tr><th>Aspect</th><th>houdini-mcp</th><th>fxhoudinimcp</th></tr></thead><tbody><tr><td>Server implementation</td><td>Custom socket</td><td>Houdini native <code>hwebserver</code></td></tr><tr><td>Transport protocol</td><td>TCP</td><td>HTTP &#x2F; JSON</td></tr><tr><td>MCP transport</td><td>stdio</td><td>stdio + streamable-http</td></tr><tr><td>Thread safety</td><td>Unknown</td><td>Explicitly guaranteed</td></tr><tr><td>Dependency complexity</td><td>Requires a separate Bridge Script process</td><td>MCP Server communicates directly with hwebserver</td></tr></tbody></table><p><strong>Verdict</strong>: fxhoudinimcp’s architecture is more robust — it reuses Houdini’s native components, reducing the surface area for custom-code bugs.</p><hr><h2 id="Feature-Coverage-Comparison"><a href="#Feature-Coverage-Comparison" class="headerlink" title="Feature Coverage Comparison"></a>Feature Coverage Comparison</h2><h3 id="houdini-mcp-Feature-Set"><a href="#houdini-mcp-Feature-Set" class="headerlink" title="houdini-mcp Feature Set"></a>houdini-mcp Feature Set</h3><p>Provides basic Houdini control:</p><ul><li>Create and modify nodes</li><li>Execute Python &#x2F; HScript code</li><li>Basic scene operations</li><li><strong>OPUS integration</strong>: Connects to the OPUS procedural furniture and environment asset library via RapidAPI (exclusive feature)</li></ul><h3 id="fxhoudinimcp-Feature-Set-19-categories-168-tools"><a href="#fxhoudinimcp-Feature-Set-19-categories-168-tools" class="headerlink" title="fxhoudinimcp Feature Set (19 categories, 168 tools)"></a>fxhoudinimcp Feature Set (19 categories, 168 tools)</h3><table><thead><tr><th>Category</th><th>Tools</th><th>Description</th></tr></thead><tbody><tr><td>Scene Management</td><td>7</td><td>Open, save, import&#x2F;export, scene info</td></tr><tr><td>Node Operations</td><td>16</td><td>Create, delete, copy, connect, layout, flag</td></tr><tr><td>Parameters</td><td>10</td><td>Get&#x2F;set values, expressions, keyframes, spare parameters</td></tr><tr><td>Geometry (SOPs)</td><td>12</td><td>Points, primitives, attributes, groups, sampling, nearest-point lookup</td></tr><tr><td>LOPs&#x2F;USD</td><td>18</td><td>Stage inspection, Prim, layers, composition, variants, lights</td></tr><tr><td>DOPs</td><td>8</td><td>Simulation info, DOP objects, step&#x2F;reset, memory usage</td></tr><tr><td>PDG&#x2F;TOPs</td><td>10</td><td>Cook, Work Items, scheduler, dependency graph</td></tr><tr><td>COPs (Copernicus)</td><td>7</td><td>Image nodes, layers, VDB data</td></tr><tr><td>HDAs</td><td>10</td><td>Create, install, and manage digital assets</td></tr><tr><td>Animation</td><td>9</td><td>Keyframes, playbar control, frame range</td></tr><tr><td>Rendering</td><td>9</td><td>Viewport screenshots, render nodes, settings, render launch</td></tr><tr><td>VEX</td><td>5</td><td>Create&#x2F;edit Wrangle nodes, validate VEX code</td></tr><tr><td>Code Execution</td><td>4</td><td>Python, HScript, expressions, environment variables</td></tr><tr><td>Viewport&#x2F;UI</td><td>11</td><td>Pane management, screenshots, status messages, error detection</td></tr><tr><td>Scene Context</td><td>8</td><td>Network overview, Cook chain, selection, scene summary, error analysis</td></tr><tr><td>Workflows</td><td>8</td><td>One-click Pyro&#x2F;RBD&#x2F;FLIP&#x2F;Vellum setup, SOP chains, render configuration</td></tr><tr><td>Materials</td><td>4</td><td>List, inspect, create materials and shader networks</td></tr><tr><td>CHOPs</td><td>4</td><td>Channel data, CHOP nodes, export channels to parameters</td></tr><tr><td>Cache</td><td>4</td><td>List, inspect, clear, write file caches</td></tr><tr><td>Takes</td><td>4</td><td>List, create, switch Takes and parameter overrides</td></tr></tbody></table><p><strong>Highlights</strong>:</p><ul><li><strong>One-click workflows</strong>: Instant Pyro, RBD, FLIP, and Vellum simulation setup</li><li><strong>Full USD&#x2F;LOPs support</strong>: 18 tools covering the USD pipeline</li><li><strong>Copernicus (COPs) support</strong>: Image processing node operations</li><li><strong>Scene context analysis</strong>: Error detection and Cook chain tracing</li></ul><hr><h2 id="Installation-and-Configuration-Comparison"><a href="#Installation-and-Configuration-Comparison" class="headerlink" title="Installation and Configuration Comparison"></a>Installation and Configuration Comparison</h2><h3 id="houdini-mcp-Installation-Steps"><a href="#houdini-mcp-Installation-Steps" class="headerlink" title="houdini-mcp Installation Steps"></a>houdini-mcp Installation Steps</h3><ol><li>Install <code>uv</code> (Python package manager)</li><li>Manually create the Houdini scripts directory and copy files</li><li>Run <code>uv add &quot;mcp[cli]&quot;</code> in the directory</li><li>Manually create a Shelf Tool</li><li>(Optional) Create a Houdini Package JSON for auto-loading</li><li>Configure <code>claude_desktop_config.json</code></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><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mcpServers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;houdini&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;uv&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;run&quot;</span><span class="punctuation">,</span> <span class="string">&quot;python&quot;</span><span class="punctuation">,</span> <span class="string">&quot;C:/path/to/houdini_mcp_server.py&quot;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="fxhoudinimcp-Installation-Steps"><a href="#fxhoudinimcp-Installation-Steps" class="headerlink" title="fxhoudinimcp Installation Steps"></a>fxhoudinimcp Installation Steps</h3><ol><li><code>pip install fxhoudinimcp</code> (or <code>uv pip install fxhoudinimcp</code>)</li><li>Copy the Package JSON to the Houdini packages directory</li><li>Configure the MCP client</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mcpServers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;fxhoudini&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;python&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;-m&quot;</span><span class="punctuation">,</span> <span class="string">&quot;fxhoudinimcp&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;env&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;HOUDINI_HOST&quot;</span><span class="punctuation">:</span> <span class="string">&quot;localhost&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;HOUDINI_PORT&quot;</span><span class="punctuation">:</span> <span class="string">&quot;8100&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>Claude Code support</strong> (one-liner):</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">claude mcp add --scope user fxhoudini -- python -m fxhoudinimcp</span><br></pre></td></tr></table></figure><h3 id="Installation-Experience-Comparison"><a href="#Installation-Experience-Comparison" class="headerlink" title="Installation Experience Comparison"></a>Installation Experience Comparison</h3><table><thead><tr><th>Aspect</th><th>houdini-mcp</th><th>fxhoudinimcp</th></tr></thead><tbody><tr><td>Installation steps</td><td>5-6 steps, multiple manual operations</td><td>2-3 steps, standardized process</td></tr><tr><td>Package manager</td><td>Requires <code>uv</code></td><td>Standard <code>pip</code> or <code>uv</code> both work</td></tr><tr><td>PyPI package</td><td>No</td><td>Yes</td></tr><tr><td>Auto-start</td><td>Requires manual Package configuration</td><td><code>uiready.py</code> handles auto-start</td></tr><tr><td>Documentation quality</td><td>Basic README</td><td>Detailed categorized docs + environment variable reference</td></tr></tbody></table><hr><h2 id="Client-Support-Comparison"><a href="#Client-Support-Comparison" class="headerlink" title="Client Support Comparison"></a>Client Support Comparison</h2><table><thead><tr><th>AI Client</th><th>houdini-mcp</th><th>fxhoudinimcp</th></tr></thead><tbody><tr><td>Claude Desktop</td><td>Supported</td><td>Supported</td></tr><tr><td>Cursor</td><td>Supported</td><td>Supported</td></tr><tr><td>VS Code</td><td>Not mentioned</td><td>Supported</td></tr><tr><td>Claude Code CLI</td><td>Not mentioned</td><td>Supported (one-liner)</td></tr></tbody></table><hr><h2 id="Exclusive-Features"><a href="#Exclusive-Features" class="headerlink" title="Exclusive Features"></a>Exclusive Features</h2><h3 id="Exclusive-to-houdini-mcp"><a href="#Exclusive-to-houdini-mcp" class="headerlink" title="Exclusive to houdini-mcp"></a>Exclusive to houdini-mcp</h3><ul><li><strong>OPUS integration</strong>: Access to the OPUS procedural asset library (furniture and environment assets) via RapidAPI. Requires a RapidAPI account and an active API subscription.</li></ul><h3 id="Exclusive-to-fxhoudinimcp"><a href="#Exclusive-to-fxhoudinimcp" class="headerlink" title="Exclusive to fxhoudinimcp"></a>Exclusive to fxhoudinimcp</h3><ul><li><strong>One-click simulation workflows</strong>: Pyro &#x2F; RBD &#x2F; FLIP &#x2F; Vellum setup in a single call</li><li><strong>Deep USD&#x2F;LOPs support</strong>: 18 dedicated tools</li><li><strong>Copernicus image processing</strong>: COPs node operations</li><li><strong>Scene error analysis</strong>: Automatic Cook error detection and reporting</li><li><strong>Environment variable configuration</strong>: <code>HOUDINI_HOST</code>, <code>HOUDINI_PORT</code>, <code>FXHOUDINIMCP_AUTOSTART</code>, and more</li><li><strong>Dual transport mode</strong>: stdio + streamable-http</li></ul><hr><h2 id="Recommendations-by-Use-Case"><a href="#Recommendations-by-Use-Case" class="headerlink" title="Recommendations by Use Case"></a>Recommendations by Use Case</h2><h3 id="Choose-houdini-mcp-capoomgit-if-you"><a href="#Choose-houdini-mcp-capoomgit-if-you" class="headerlink" title="Choose houdini-mcp (capoomgit) if you:"></a>Choose houdini-mcp (capoomgit) if you:</h3><ul><li>Only need basic AI control of Houdini</li><li>Are already using a <code>uv</code>-based workflow</li><li>Specifically need OPUS procedural asset library integration</li><li>Have a simple project scope and want to get started quickly</li></ul><h3 id="Choose-fxhoudinimcp-healkeiser-if-you"><a href="#Choose-fxhoudinimcp-healkeiser-if-you" class="headerlink" title="Choose fxhoudinimcp (healkeiser) if you:"></a>Choose fxhoudinimcp (healkeiser) if you:</h3><ul><li>Need comprehensive Houdini coverage (SOPs, LOPs, DOPs, TOPs, COPs, etc.)</li><li>Work with USD&#x2F;LOPs pipelines</li><li>Want one-click simulation workflows (Pyro &#x2F; FLIP &#x2F; Vellum &#x2F; RBD)</li><li>Prefer a standardized installation via a PyPI package</li><li>Use Claude Code CLI as your primary AI tool</li><li>Need guaranteed thread safety</li><li>Value active maintenance and long-term project evolution</li></ul><hr><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><table><thead><tr><th>Evaluation Dimension</th><th>houdini-mcp</th><th>fxhoudinimcp</th><th>Winner</th></tr></thead><tbody><tr><td>Feature richness</td><td>Basic</td><td>168 tools</td><td>fxhoudinimcp</td></tr><tr><td>Architecture robustness</td><td>Custom socket</td><td>Native hwebserver</td><td>fxhoudinimcp</td></tr><tr><td>Installation convenience</td><td>Multi-step manual</td><td>One-liner pip</td><td>fxhoudinimcp</td></tr><tr><td>Client compatibility</td><td>Desktop + Cursor</td><td>Desktop + Cursor + VSCode + Claude Code</td><td>fxhoudinimcp</td></tr><tr><td>Asset ecosystem</td><td>OPUS integration</td><td>None</td><td>houdini-mcp</td></tr><tr><td>Documentation quality</td><td>Basic</td><td>Comprehensive</td><td>fxhoudinimcp</td></tr><tr><td>Maintenance activity</td><td>Community-maintained</td><td>Actively developed</td><td>fxhoudinimcp</td></tr></tbody></table><p><strong>Overall recommendation</strong>: For most users, <strong>fxhoudinimcp</strong> is the better choice — broader feature coverage, a more robust architecture, and a smoother installation process. If you specifically need the OPUS procedural asset library integration, <strong>houdini-mcp</strong> is worth a look as a complementary tool.</p><hr><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><ul><li><a href="https://github.com/capoomgit/houdini-mcp">capoomgit&#x2F;houdini-mcp</a></li><li><a href="https://github.com/healkeiser/fxhoudinimcp">healkeiser&#x2F;fxhoudinimcp</a></li><li><a href="https://github.com/ahujasid/blender-mcp">blender-mcp</a> — the project that inspired houdini-mcp</li></ul>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/Houdini/">Houdini</category>
      
      <category domain="https://eugenepage.com/tags/MCP/">MCP</category>
      
      
      <comments>https://eugenepage.com/2026/05/02/20260502.HoudiniMCPComparison/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Houdini MCP 项目对比评测</title>
      <link>https://eugenepage.com/zh-CN/2026/05/02/20260502.HoudiniMCPComparison/</link>
      <guid>https://eugenepage.com/zh-CN/2026/05/02/20260502.HoudiniMCPComparison/</guid>
      <pubDate>Sat, 02 May 2026 04:00:00 GMT</pubDate>
      
        
        
      <description>&lt;h1 id=&quot;Houdini-MCP-项目对比评测：capoomgit-houdini-mcp-vs-healkeiser-fxhoudinimcp&quot;&gt;&lt;a href=&quot;#Houdini-MCP-项目对比评测：capoomgit-houdini-mcp-vs-healkeise</description>
        
      
      
      
      <content:encoded><![CDATA[<h1 id="Houdini-MCP-项目对比评测：capoomgit-houdini-mcp-vs-healkeiser-fxhoudinimcp"><a href="#Houdini-MCP-项目对比评测：capoomgit-houdini-mcp-vs-healkeiser-fxhoudinimcp" class="headerlink" title="Houdini MCP 项目对比评测：capoomgit&#x2F;houdini-mcp vs healkeiser&#x2F;fxhoudinimcp"></a>Houdini MCP 项目对比评测：capoomgit&#x2F;houdini-mcp vs healkeiser&#x2F;fxhoudinimcp</h1><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>随着 MCP（Model Context Protocol）协议的普及，越来越多的 DCC 软件开始接入 AI 助手。在 Houdini 生态中，目前有两个主要的 MCP 开源项目：</p><ol><li><strong><a href="https://github.com/capoomgit/houdini-mcp">capoomgit&#x2F;houdini-mcp</a></strong> — 早期项目，结构简洁</li><li><strong><a href="https://github.com/healkeiser/fxhoudinimcp">healkeiser&#x2F;fxhoudinimcp</a></strong> — 后起之秀，功能全面</li></ol><p>本文从架构设计、功能覆盖、安装体验、扩展性等维度进行对比，帮助选择适合自己工作流的项目。</p><hr><h2 id="总览对比"><a href="#总览对比" class="headerlink" title="总览对比"></a>总览对比</h2><table><thead><tr><th>维度</th><th>houdini-mcp (capoomgit)</th><th>fxhoudinimcp (healkeiser)</th></tr></thead><tbody><tr><td>定位</td><td>轻量级 MCP 桥接</td><td>全面型 Houdini MCP 服务器</td></tr><tr><td>工具数量</td><td>未明确分类，基础功能为主</td><td><strong>168 个工具</strong> + 8 资源 + 6 工作流提示</td></tr><tr><td>架构</td><td>自定义 TCP Socket（端口 9876）</td><td>Houdini 内置 <code>hwebserver</code>（端口 8100）</td></tr><tr><td>安装方式</td><td>手动复制文件到 Houdini 目录</td><td>PyPI 发布，<code>pip install fxhoudinimcp</code></td></tr><tr><td>包管理依赖</td><td>强依赖 <code>uv</code></td><td>标准 <code>pip</code> 即可</td></tr><tr><td>线程安全</td><td>未明确说明</td><td><code>hdefereval.executeInMainThreadWithResult()</code></td></tr><tr><td>许可证</td><td>未明确</td><td>MIT</td></tr><tr><td>维护状态</td><td>社区维护</td><td>活跃开发中</td></tr></tbody></table><hr><h2 id="架构设计对比"><a href="#架构设计对比" class="headerlink" title="架构设计对比"></a>架构设计对比</h2><h3 id="houdini-mcp（capoomgit）"><a href="#houdini-mcp（capoomgit）" class="headerlink" title="houdini-mcp（capoomgit）"></a>houdini-mcp（capoomgit）</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Claude Desktop  ──(stdio)──&gt;  MCP Bridge Script  ──(TCP :9876)──&gt;  Houdini Plugin</span><br></pre></td></tr></table></figure><ul><li><strong>通信方式</strong>：MCP Bridge Script 通过 stdin&#x2F;stdout 与 Claude 通信，通过自定义 TCP Socket 与 Houdini 通信。</li><li><strong>服务端</strong>：自己实现的 <code>HoudiniMCPServer</code>，监听在 <code>localhost:9876</code>。</li><li><strong>灵感来源</strong>：基于 <a href="https://github.com/ahujasid/blender-mcp">blender-mcp</a> 改写。</li></ul><h3 id="fxhoudinimcp（healkeiser）"><a href="#fxhoudinimcp（healkeiser）" class="headerlink" title="fxhoudinimcp（healkeiser）"></a>fxhoudinimcp（healkeiser）</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Claude Desktop / Cursor / Claude Code  ──(stdio/streamable-http)──&gt;  FXHoudini MCP Server  ──(HTTP :8100)──&gt;  Houdini hwebserver</span><br></pre></td></tr></table></figure><ul><li><strong>通信方式</strong>：MCP Server 通过 stdio 或 streamable-http 与 AI 客户端通信，通过 HTTP&#x2F;JSON 与 Houdini 通信。</li><li><strong>服务端</strong>：直接使用 Houdini 内置的 <code>hwebserver</code>，无需额外启动自定义服务器。</li><li><strong>线程安全</strong>：使用 <code>hdefereval.executeInMainThreadWithResult()</code> 确保 <code>hou.*</code> API 调用在主线程执行。</li></ul><h3 id="架构差异分析"><a href="#架构差异分析" class="headerlink" title="架构差异分析"></a>架构差异分析</h3><table><thead><tr><th>对比点</th><th>houdini-mcp</th><th>fxhoudinimcp</th></tr></thead><tbody><tr><td>服务端实现</td><td>自定义 Socket</td><td>Houdini 原生 <code>hwebserver</code></td></tr><tr><td>传输协议</td><td>TCP</td><td>HTTP &#x2F; JSON</td></tr><tr><td>MCP 传输</td><td>stdio</td><td>stdio + streamable-http</td></tr><tr><td>线程安全</td><td>未知</td><td>有明确保障</td></tr><tr><td>依赖复杂度</td><td>需要额外运行 Bridge Script</td><td>MCP Server 直接与 hwebserver 通信</td></tr></tbody></table><p><strong>结论</strong>：fxhoudinimcp 的架构更稳健 — 复用 Houdini 原生组件，减少自定义代码带来的潜在问题。</p><hr><h2 id="功能覆盖对比"><a href="#功能覆盖对比" class="headerlink" title="功能覆盖对比"></a>功能覆盖对比</h2><h3 id="houdini-mcp-功能范围"><a href="#houdini-mcp-功能范围" class="headerlink" title="houdini-mcp 功能范围"></a>houdini-mcp 功能范围</h3><p>提供基础的 Houdini 控制：</p><ul><li>创建和修改节点</li><li>执行 Python &#x2F; HScript 代码</li><li>场景基础操作</li><li><strong>OPUS 集成</strong>：通过 RapidAPI 接入 OPUS 的程序化家具和环境资产库（独有功能）</li></ul><h3 id="fxhoudinimcp-功能范围（19-个分类，168-个工具）"><a href="#fxhoudinimcp-功能范围（19-个分类，168-个工具）" class="headerlink" title="fxhoudinimcp 功能范围（19 个分类，168 个工具）"></a>fxhoudinimcp 功能范围（19 个分类，168 个工具）</h3><table><thead><tr><th>分类</th><th>工具数</th><th>说明</th></tr></thead><tbody><tr><td>Scene Management</td><td>7</td><td>打开、保存、导入&#x2F;导出、场景信息</td></tr><tr><td>Node Operations</td><td>16</td><td>创建、删除、复制、连接、布局、标记</td></tr><tr><td>Parameters</td><td>10</td><td>获取&#x2F;设置值、表达式、关键帧、自定义参数</td></tr><tr><td>Geometry (SOPs)</td><td>12</td><td>点、面、属性、组、采样、最近点查找</td></tr><tr><td>LOPs&#x2F;USD</td><td>18</td><td>Stage 检查、Prim、层、合成、变体、灯光</td></tr><tr><td>DOPs</td><td>8</td><td>模拟信息、DOP 对象、步进&#x2F;重置、内存使用</td></tr><tr><td>PDG&#x2F;TOPs</td><td>10</td><td>Cook、Work Item、调度器、依赖图</td></tr><tr><td>COPs (Copernicus)</td><td>7</td><td>图像节点、层、VDB 数据</td></tr><tr><td>HDAs</td><td>10</td><td>创建、安装、管理数字资产</td></tr><tr><td>Animation</td><td>9</td><td>关键帧、播放条控制、帧范围</td></tr><tr><td>Rendering</td><td>9</td><td>视口截图、渲染节点、设置、渲染启动</td></tr><tr><td>VEX</td><td>5</td><td>创建&#x2F;编辑 Wrangle、验证 VEX 代码</td></tr><tr><td>Code Execution</td><td>4</td><td>Python、HScript、表达式、环境变量</td></tr><tr><td>Viewport&#x2F;UI</td><td>11</td><td>面板管理、截图、状态消息、错误检测</td></tr><tr><td>Scene Context</td><td>8</td><td>网络概览、Cook 链、选择、场景摘要、错误分析</td></tr><tr><td>Workflows</td><td>8</td><td>一键 Pyro&#x2F;RBD&#x2F;FLIP&#x2F;Vellum 设置、SOP 链、渲染配置</td></tr><tr><td>Materials</td><td>4</td><td>列出、检查、创建材质和着色器网络</td></tr><tr><td>CHOPs</td><td>4</td><td>通道数据、CHOP 节点、导出通道到参数</td></tr><tr><td>Cache</td><td>4</td><td>列出、检查、清除、写入文件缓存</td></tr><tr><td>Takes</td><td>4</td><td>列出、创建、切换 Take 及参数覆盖</td></tr></tbody></table><p><strong>亮点</strong>：</p><ul><li><strong>一键工作流</strong>：Pyro、RBD、FLIP、Vellum 模拟一键搭建</li><li><strong>USD&#x2F;LOPs 全面支持</strong>：18 个工具覆盖 USD 工作流</li><li><strong>Copernicus (COPs) 支持</strong>：图像处理节点操作</li><li><strong>场景上下文分析</strong>：错误检测、Cook 链追踪</li></ul><hr><h2 id="安装与配置对比"><a href="#安装与配置对比" class="headerlink" title="安装与配置对比"></a>安装与配置对比</h2><h3 id="houdini-mcp-安装步骤"><a href="#houdini-mcp-安装步骤" class="headerlink" title="houdini-mcp 安装步骤"></a>houdini-mcp 安装步骤</h3><ol><li>安装 <code>uv</code>（Python 包管理工具）</li><li>手动创建 Houdini 脚本目录并复制文件</li><li>在目录中运行 <code>uv add &quot;mcp[cli]&quot;</code></li><li>手动创建 Shelf Tool</li><li>（可选）创建 Houdini Package JSON 实现自动加载</li><li>配置 <code>claude_desktop_config.json</code></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><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mcpServers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;houdini&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;uv&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;run&quot;</span><span class="punctuation">,</span> <span class="string">&quot;python&quot;</span><span class="punctuation">,</span> <span class="string">&quot;C:/path/to/houdini_mcp_server.py&quot;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="fxhoudinimcp-安装步骤"><a href="#fxhoudinimcp-安装步骤" class="headerlink" title="fxhoudinimcp 安装步骤"></a>fxhoudinimcp 安装步骤</h3><ol><li><code>pip install fxhoudinimcp</code>（或 <code>uv pip install fxhoudinimcp</code>）</li><li>复制 Package JSON 到 Houdini packages 目录</li><li>配置 MCP 客户端</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><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mcpServers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;fxhoudini&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;python&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;-m&quot;</span><span class="punctuation">,</span> <span class="string">&quot;fxhoudinimcp&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;env&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;HOUDINI_HOST&quot;</span><span class="punctuation">:</span> <span class="string">&quot;localhost&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;HOUDINI_PORT&quot;</span><span class="punctuation">:</span> <span class="string">&quot;8100&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>Claude Code 支持</strong>（一行命令）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">claude mcp add --scope user fxhoudini -- python -m fxhoudinimcp</span><br></pre></td></tr></table></figure><h3 id="安装体验对比"><a href="#安装体验对比" class="headerlink" title="安装体验对比"></a>安装体验对比</h3><table><thead><tr><th>对比点</th><th>houdini-mcp</th><th>fxhoudinimcp</th></tr></thead><tbody><tr><td>安装步骤</td><td>5-6 步，多处手动操作</td><td>2-3 步，标准化流程</td></tr><tr><td>包管理</td><td>强依赖 <code>uv</code></td><td>标准 <code>pip</code> &#x2F; <code>uv</code> 均可</td></tr><tr><td>PyPI 发布</td><td>无</td><td>有</td></tr><tr><td>自动启动</td><td>需手动配置 Package</td><td><code>uiready.py</code> 自动启动</td></tr><tr><td>文档质量</td><td>基础 README</td><td>详细的分类文档 + 环境变量说明</td></tr></tbody></table><hr><h2 id="客户端支持对比"><a href="#客户端支持对比" class="headerlink" title="客户端支持对比"></a>客户端支持对比</h2><table><thead><tr><th>AI 客户端</th><th>houdini-mcp</th><th>fxhoudinimcp</th></tr></thead><tbody><tr><td>Claude Desktop</td><td>支持</td><td>支持</td></tr><tr><td>Cursor</td><td>支持</td><td>支持</td></tr><tr><td>VS Code</td><td>未提及</td><td>支持</td></tr><tr><td>Claude Code CLI</td><td>未提及</td><td>支持（一行命令）</td></tr></tbody></table><hr><h2 id="独有功能"><a href="#独有功能" class="headerlink" title="独有功能"></a>独有功能</h2><h3 id="houdini-mcp-独有"><a href="#houdini-mcp-独有" class="headerlink" title="houdini-mcp 独有"></a>houdini-mcp 独有</h3><ul><li><strong>OPUS 集成</strong>：通过 RapidAPI 接入 OPUS 程序化资产库，可获取家具和环境资产。需要注册 RapidAPI 账号并订阅 API。</li></ul><h3 id="fxhoudinimcp-独有"><a href="#fxhoudinimcp-独有" class="headerlink" title="fxhoudinimcp 独有"></a>fxhoudinimcp 独有</h3><ul><li><strong>一键模拟工作流</strong>：Pyro &#x2F; RBD &#x2F; FLIP &#x2F; Vellum 一键搭建</li><li><strong>USD&#x2F;LOPs 深度支持</strong>：18 个工具</li><li><strong>Copernicus 图像处理</strong>：COPs 节点操作</li><li><strong>场景错误分析</strong>：自动检测和报告 Cook 错误</li><li><strong>环境变量配置</strong>：<code>HOUDINI_HOST</code>、<code>HOUDINI_PORT</code>、<code>FXHOUDINIMCP_AUTOSTART</code> 等</li><li><strong>双传输模式</strong>：stdio + streamable-http</li></ul><hr><h2 id="适用场景建议"><a href="#适用场景建议" class="headerlink" title="适用场景建议"></a>适用场景建议</h2><h3 id="选择-houdini-mcp（capoomgit）的情况"><a href="#选择-houdini-mcp（capoomgit）的情况" class="headerlink" title="选择 houdini-mcp（capoomgit）的情况"></a>选择 houdini-mcp（capoomgit）的情况</h3><ul><li>只需要基础的 AI 控制 Houdini 功能</li><li>已经在使用 <code>uv</code> 工作流</li><li>需要 OPUS 程序化资产库的集成</li><li>项目结构简单，希望快速上手</li></ul><h3 id="选择-fxhoudinimcp（healkeiser）的情况"><a href="#选择-fxhoudinimcp（healkeiser）的情况" class="headerlink" title="选择 fxhoudinimcp（healkeiser）的情况"></a>选择 fxhoudinimcp（healkeiser）的情况</h3><ul><li>需要全面的 Houdini 功能覆盖（SOPs、LOPs、DOPs、TOPs、COPs 等）</li><li>需要 USD&#x2F;LOPs 工作流支持</li><li>需要一键模拟工作流（Pyro &#x2F; FLIP &#x2F; Vellum &#x2F; RBD）</li><li>希望使用标准化安装（PyPI 包）</li><li>使用 Claude Code CLI 作为主要 AI 工具</li><li>需要线程安全保障</li><li>重视项目的活跃维护和长期演进</li></ul><hr><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><table><thead><tr><th>评价维度</th><th>houdini-mcp</th><th>fxhoudinimcp</th><th>胜出</th></tr></thead><tbody><tr><td>功能丰富度</td><td>基础</td><td>168 工具</td><td>fxhoudinimcp</td></tr><tr><td>架构稳健性</td><td>自定义 Socket</td><td>原生 hwebserver</td><td>fxhoudinimcp</td></tr><tr><td>安装便利性</td><td>手动多步</td><td>pip 一键</td><td>fxhoudinimcp</td></tr><tr><td>客户端兼容</td><td>Desktop + Cursor</td><td>Desktop + Cursor + VSCode + Claude Code</td><td>fxhoudinimcp</td></tr><tr><td>资产生态</td><td>OPUS 集成</td><td>无</td><td>houdini-mcp</td></tr><tr><td>文档质量</td><td>基础</td><td>完善</td><td>fxhoudinimcp</td></tr><tr><td>维护活跃度</td><td>社区维护</td><td>活跃开发</td><td>fxhoudinimcp</td></tr></tbody></table><p><strong>综合推荐</strong>：对于大多数用户，<strong>fxhoudinimcp</strong> 是更好的选择 — 更全面的功能覆盖、更稳健的架构、更便捷的安装流程。如果你特别需要 OPUS 程序化资产库的集成，可以额外关注 <strong>houdini-mcp</strong>。</p><hr><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://github.com/capoomgit/houdini-mcp">capoomgit&#x2F;houdini-mcp</a></li><li><a href="https://github.com/healkeiser/fxhoudinimcp">healkeiser&#x2F;fxhoudinimcp</a></li><li><a href="https://github.com/ahujasid/blender-mcp">blender-mcp</a> — houdini-mcp 的灵感来源</li></ul>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/Houdini/">Houdini</category>
      
      <category domain="https://eugenepage.com/tags/MCP/">MCP</category>
      
      
      <comments>https://eugenepage.com/zh-CN/2026/05/02/20260502.HoudiniMCPComparison/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>AI Agent Framework Research Notes</title>
      <link>https://eugenepage.com/2026/04/30/20260430.AIAgentFrameworkResearchNotes/</link>
      <guid>https://eugenepage.com/2026/04/30/20260430.AIAgentFrameworkResearchNotes/</guid>
      <pubDate>Thu, 30 Apr 2026 04:00:00 GMT</pubDate>
      
        
        
      <description>&lt;h1 id=&quot;AI-Agent-Framework-Research-Notes&quot;&gt;&lt;a href=&quot;#AI-Agent-Framework-Research-Notes&quot; class=&quot;headerlink&quot; title=&quot;AI Agent Framework Researc</description>
        
      
      
      
      <content:encoded><![CDATA[<h1 id="AI-Agent-Framework-Research-Notes"><a href="#AI-Agent-Framework-Research-Notes" class="headerlink" title="AI Agent Framework Research Notes"></a>AI Agent Framework Research Notes</h1><blockquote><p>Last updated: 2026-04-30</p><p>As AI Agent technology evolves at a rapid pace, new agent development frameworks keep appearing. This document surveys and compares six of the most widely adopted Agent frameworks available today, helping developers choose the right tool for their use case.</p></blockquote><hr><h2 id="Table-of-Contents"><a href="#Table-of-Contents" class="headerlink" title="Table of Contents"></a>Table of Contents</h2><ul><li><a href="#i-framework-overview-comparison">I. Framework Overview Comparison</a></li><li><a href="#ii-langgraph">II. LangGraph</a></li><li><a href="#iii-crewai">III. CrewAI</a></li><li><a href="#iv-llamaindex">IV. LlamaIndex</a></li><li><a href="#v-dify">V. Dify</a></li><li><a href="#vi-openai-agents-sdk">VI. OpenAI Agents SDK</a></li><li><a href="#vii-google-adk">VII. Google ADK</a></li><li><a href="#viii-framework-selection-guide">VIII. Framework Selection Guide</a></li></ul><hr><h2 id="I-Framework-Overview-Comparison"><a href="#I-Framework-Overview-Comparison" class="headerlink" title="I. Framework Overview Comparison"></a>I. Framework Overview Comparison</h2><table><thead><tr><th>Dimension</th><th>LangGraph</th><th>CrewAI</th><th>LlamaIndex</th><th>Dify</th><th>OpenAI Agents SDK</th><th>Google ADK</th></tr></thead><tbody><tr><td><strong>Developer</strong></td><td>LangChain Inc.</td><td>CrewAI Inc.</td><td>LlamaIndex Inc.</td><td>LangGenius</td><td>OpenAI</td><td>Google</td></tr><tr><td><strong>Latest Version</strong></td><td>v1.1.10</td><td>v1.14.3</td><td>v0.14.6</td><td>v1.6.0+</td><td>v0.14.6</td><td>v1.31.1</td></tr><tr><td><strong>License</strong></td><td>MIT</td><td>MIT</td><td>MIT</td><td>Dify License (Apache 2.0+)</td><td>MIT</td><td>Apache 2.0</td></tr><tr><td><strong>Language</strong></td><td>Python &#x2F; JS</td><td>Python</td><td>Python &#x2F; TS</td><td>Visual (multi-language SDK)</td><td>Python &#x2F; JS</td><td>Python &#x2F; Java &#x2F; Go &#x2F; TS</td></tr><tr><td><strong>Core Philosophy</strong></td><td>Graph orchestration</td><td>Role-playing teams</td><td>RAG + Agent</td><td>Low-code platform</td><td>Minimal multi-agent</td><td>Code-first</td></tr><tr><td><strong>Model Support</strong></td><td>Model-agnostic</td><td>Model-agnostic</td><td>Model-agnostic</td><td>Model-agnostic</td><td>100+ LLMs</td><td>Model-agnostic</td></tr><tr><td><strong>Learning Curve</strong></td><td>Steep</td><td>Moderate</td><td>Moderate</td><td>Low</td><td>Low</td><td>Moderate</td></tr><tr><td><strong>Best For</strong></td><td>Complex stateful workflows</td><td>Multi-role collaboration</td><td>RAG + Agent</td><td>Rapid prototyping &#x2F; non-technical users</td><td>OpenAI ecosystem apps</td><td>Google ecosystem apps</td></tr></tbody></table><hr><h2 id="II-LangGraph"><a href="#II-LangGraph" class="headerlink" title="II. LangGraph"></a>II. LangGraph</h2><h3 id="2-1-Introduction"><a href="#2-1-Introduction" class="headerlink" title="2.1 Introduction"></a>2.1 Introduction</h3><p><strong>LangGraph</strong> is a <strong>low-level orchestration framework</strong> developed by the LangChain team, specifically designed for building long-running, stateful AI Agents. It draws design inspiration from Google’s Pregel and Apache Beam.</p><p>Core positioning: rather than abstracting away prompts or architecture, LangGraph provides low-level infrastructure that gives developers fine-grained control over agent workflows. It is already used in production by companies such as Klarna, Replit, and Elastic.</p><table><thead><tr><th>Project Info</th><th>Details</th></tr></thead><tbody><tr><td>Latest Version</td><td>v1.1.10 (2026-04-27)</td></tr><tr><td>License</td><td>MIT</td></tr><tr><td>Install</td><td><code>pip install -U langgraph</code></td></tr><tr><td>GitHub</td><td><a href="https://github.com/langchain-ai/langgraph">langchain-ai&#x2F;langgraph</a></td></tr><tr><td>Docs</td><td><a href="https://docs.langchain.com/oss/python/langgraph">docs.langchain.com&#x2F;oss&#x2F;python&#x2F;langgraph</a></td></tr></tbody></table><h3 id="2-2-Core-Architecture"><a href="#2-2-Core-Architecture" class="headerlink" title="2.2 Core Architecture"></a>2.2 Core Architecture</h3><p>LangGraph models agent workflows as a <strong>Graph</strong>, built from three core components:</p><ul><li><strong>State</strong>: A shared data structure, typically defined using <code>TypedDict</code> or a <code>Pydantic Model</code></li><li><strong>Nodes</strong>: Functions that encode agent logic — they receive the current state and return an updated state</li><li><strong>Edges</strong>: Functions that determine the next node, supporting conditional branching or fixed transitions</li></ul><h3 id="2-3-Key-Features"><a href="#2-3-Key-Features" class="headerlink" title="2.3 Key Features"></a>2.3 Key Features</h3><ul><li><strong>Persistence</strong>: Saves the graph state as a checkpoint after each execution step; supports in-memory, Redis, and other backends</li><li><strong>Human-in-the-Loop</strong>: Uses <code>interrupt()</code> to pause execution and wait for human input before resuming</li><li><strong>Streaming</strong>: Supports multiple streaming modes including values, messages, and updates</li><li><strong>Subgraphs</strong>: Supports nested graphs where subgraphs have their own independent checkpoints and interrupt capabilities</li><li><strong>Time Travel</strong>: Can rewind to any historical checkpoint, with support for forking and replaying</li><li><strong>Visualization</strong>: After compilation, can generate Mermaid diagrams to visualize the workflow structure</li></ul><h3 id="2-4-Code-Example"><a href="#2-4-Code-Example" class="headerlink" title="2.4 Code Example"></a>2.4 Code Example</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Literal</span></span><br><span class="line"><span class="keyword">from</span> langgraph.graph <span class="keyword">import</span> StateGraph, MessagesState, START, END</span><br><span class="line"><span class="keyword">from</span> langchain.messages <span class="keyword">import</span> SystemMessage, HumanMessage, ToolMessage</span><br><span class="line"></span><br><span class="line"><span class="comment"># Define tools</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">multiply</span>(<span class="params">a: <span class="built_in">int</span>, b: <span class="built_in">int</span></span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Multiply two numbers.&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> a * b</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">add</span>(<span class="params">a: <span class="built_in">int</span>, b: <span class="built_in">int</span></span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Add two numbers.&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> a + b</span><br><span class="line"></span><br><span class="line">tools = [multiply, add]</span><br><span class="line">tools_by_name = &#123;tool.name: tool <span class="keyword">for</span> tool <span class="keyword">in</span> tools&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Define nodes</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">llm_call</span>(<span class="params">state: MessagesState</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;LLM decides whether to call a tool&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="string">&quot;messages&quot;</span>: [</span><br><span class="line">            llm_with_tools.invoke(</span><br><span class="line">                [SystemMessage(content=<span class="string">&quot;You are a helpful arithmetic assistant.&quot;</span>)]</span><br><span class="line">                + state[<span class="string">&quot;messages&quot;</span>]</span><br><span class="line">            )</span><br><span class="line">        ]</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">tool_node</span>(<span class="params">state: <span class="built_in">dict</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Execute tool calls&quot;&quot;&quot;</span></span><br><span class="line">    result = []</span><br><span class="line">    <span class="keyword">for</span> tool_call <span class="keyword">in</span> state[<span class="string">&quot;messages&quot;</span>][-<span class="number">1</span>].tool_calls:</span><br><span class="line">        tool = tools_by_name[tool_call[<span class="string">&quot;name&quot;</span>]]</span><br><span class="line">        observation = tool.invoke(tool_call[<span class="string">&quot;args&quot;</span>])</span><br><span class="line">        result.append(ToolMessage(content=<span class="built_in">str</span>(observation), tool_call_id=tool_call[<span class="string">&quot;id&quot;</span>]))</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;messages&quot;</span>: result&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Define conditional edge routing</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">should_continue</span>(<span class="params">state: MessagesState</span>) -&gt; <span class="type">Literal</span>[<span class="string">&quot;tool_node&quot;</span>, END]:</span><br><span class="line">    last_message = state[<span class="string">&quot;messages&quot;</span>][-<span class="number">1</span>]</span><br><span class="line">    <span class="keyword">if</span> last_message.tool_calls:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;tool_node&quot;</span></span><br><span class="line">    <span class="keyword">return</span> END</span><br><span class="line"></span><br><span class="line"><span class="comment"># Build and compile the graph</span></span><br><span class="line">builder = StateGraph(MessagesState)</span><br><span class="line">builder.add_node(<span class="string">&quot;llm_call&quot;</span>, llm_call)</span><br><span class="line">builder.add_node(<span class="string">&quot;tool_node&quot;</span>, tool_node)</span><br><span class="line">builder.add_edge(START, <span class="string">&quot;llm_call&quot;</span>)</span><br><span class="line">builder.add_conditional_edges(<span class="string">&quot;llm_call&quot;</span>, should_continue, [<span class="string">&quot;tool_node&quot;</span>, END])</span><br><span class="line">builder.add_edge(<span class="string">&quot;tool_node&quot;</span>, <span class="string">&quot;llm_call&quot;</span>)</span><br><span class="line"></span><br><span class="line">agent = builder.<span class="built_in">compile</span>()</span><br><span class="line"></span><br><span class="line"><span class="comment"># Run</span></span><br><span class="line">result = agent.invoke(&#123;<span class="string">&quot;messages&quot;</span>: [HumanMessage(content=<span class="string">&quot;Add 3 and 4, then multiply by 5.&quot;</span>)]&#125;)</span><br></pre></td></tr></table></figure><h3 id="2-5-Strengths-and-Limitations"><a href="#2-5-Strengths-and-Limitations" class="headerlink" title="2.5 Strengths and Limitations"></a>2.5 Strengths and Limitations</h3><p><strong>Strengths:</strong> Fine-grained control, stateful execution, native human-in-the-loop, fault-tolerant recovery, time-travel debugging, framework-agnostic</p><p><strong>Limitations:</strong> Steep learning curve, lots of boilerplate code, best experience requires the LangSmith ecosystem, fast-moving release cycle</p><hr><h2 id="III-CrewAI"><a href="#III-CrewAI" class="headerlink" title="III. CrewAI"></a>III. CrewAI</h2><h3 id="3-1-Introduction"><a href="#3-1-Introduction" class="headerlink" title="3.1 Introduction"></a>3.1 Introduction</h3><p><strong>CrewAI</strong> is a Python framework for orchestrating multiple autonomous AI Agents, built entirely from scratch with <strong>no dependency on LangChain or any other framework</strong>. Its core idea is to simulate real team collaboration through role-playing.</p><table><thead><tr><th>Project Info</th><th>Details</th></tr></thead><tbody><tr><td>Latest Version</td><td>v1.14.3 (2025-04-24)</td></tr><tr><td>License</td><td>MIT</td></tr><tr><td>Install</td><td><code>pip install &#39;crewai[tools]&#39;</code></td></tr><tr><td>GitHub</td><td><a href="https://github.com/crewAIInc/crewAI">crewAIInc&#x2F;crewAI</a></td></tr><tr><td>Docs</td><td><a href="https://docs.crewai.com/">docs.crewai.com</a></td></tr></tbody></table><h3 id="3-2-Core-Concepts"><a href="#3-2-Core-Concepts" class="headerlink" title="3.2 Core Concepts"></a>3.2 Core Concepts</h3><ul><li><strong>Agent</strong>: Identity and behavior defined through <code>role</code>, <code>goal</code>, and <code>backstory</code></li><li><strong>Task</strong>: A concrete unit of work; can specify the assigned agent, context dependencies, and output format</li><li><strong>Crew</strong>: A collection of collaborating agents, defining the execution process and memory configuration</li><li><strong>Tools</strong>: A rich set of built-in tools (search, file read&#x2F;write, code execution, etc.) with MCP integration support</li><li><strong>Process</strong>: Either Sequential or Hierarchical (automatically creates a Manager Agent)</li></ul><h3 id="3-3-Key-Features"><a href="#3-3-Key-Features" class="headerlink" title="3.3 Key Features"></a>3.3 Key Features</h3><ul><li><strong>Role-playing design</strong>: Intuitive role definitions that closely mirror real team collaboration</li><li><strong>Collaborative workflows</strong>: Agents can delegate tasks to one another and pass context between them</li><li><strong>Four memory systems</strong>: Short-term memory, long-term memory, entity memory, and contextual memory</li><li><strong>Flows</strong>: Enterprise-grade event-driven workflow orchestration, supporting <code>@start</code>, <code>@listen</code>, and <code>@router</code> decorators</li><li><strong>Checkpoint &amp; Fork</strong>: Supports saving, restoring, and branching execution state</li><li><strong>YAML-driven configuration</strong>: Agents and tasks can be defined via YAML files</li></ul><h3 id="3-4-Code-Example"><a href="#3-4-Code-Example" class="headerlink" title="3.4 Code Example"></a>3.4 Code Example</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> crewai <span class="keyword">import</span> Agent, Task, Crew, Process</span><br><span class="line"></span><br><span class="line"><span class="comment"># Define Agents</span></span><br><span class="line">researcher = Agent(</span><br><span class="line">    role=<span class="string">&#x27;Senior AI Researcher&#x27;</span>,</span><br><span class="line">    goal=<span class="string">&#x27;Discover the latest development trends in the AI Agent space&#x27;</span>,</span><br><span class="line">    backstory=<span class="string">&#x27;You are an experienced researcher with a knack for spotting cutting-edge technology developments.&#x27;</span>,</span><br><span class="line">    verbose=<span class="literal">True</span>,</span><br><span class="line">    memory=<span class="literal">True</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">writer = Agent(</span><br><span class="line">    role=<span class="string">&#x27;Technical Report Writing Specialist&#x27;</span>,</span><br><span class="line">    goal=<span class="string">&#x27;Transform research findings into clear, well-structured reports&#x27;</span>,</span><br><span class="line">    backstory=<span class="string">&#x27;You are a technical writing expert who excels at turning complex information into readable reports.&#x27;</span>,</span><br><span class="line">    verbose=<span class="literal">True</span>,</span><br><span class="line">    memory=<span class="literal">True</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Define Tasks</span></span><br><span class="line">research_task = Task(</span><br><span class="line">    description=<span class="string">&#x27;Conduct comprehensive research on &#123;topic&#125; and gather the latest development trends.&#x27;</span>,</span><br><span class="line">    expected_output=<span class="string">&#x27;A detailed list of research findings with 10 key points&#x27;</span>,</span><br><span class="line">    agent=researcher,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">writing_task = Task(</span><br><span class="line">    description=<span class="string">&#x27;Write a complete technical report based on the research findings.&#x27;</span>,</span><br><span class="line">    expected_output=<span class="string">&#x27;A complete report in Markdown format&#x27;</span>,</span><br><span class="line">    agent=writer,</span><br><span class="line">    context=[research_task],</span><br><span class="line">    output_file=<span class="string">&#x27;report.md&#x27;</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Assemble the Crew and run</span></span><br><span class="line">crew = Crew(</span><br><span class="line">    agents=[researcher, writer],</span><br><span class="line">    tasks=[research_task, writing_task],</span><br><span class="line">    process=Process.sequential,</span><br><span class="line">    verbose=<span class="literal">True</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">result = crew.kickoff(inputs=&#123;<span class="string">&#x27;topic&#x27;</span>: <span class="string">&#x27;multi-agent collaboration systems&#x27;</span>&#125;)</span><br></pre></td></tr></table></figure><h3 id="3-5-Strengths-and-Limitations"><a href="#3-5-Strengths-and-Limitations" class="headerlink" title="3.5 Strengths and Limitations"></a>3.5 Strengths and Limitations</h3><p><strong>Strengths:</strong> Fully standalone with no external dependencies, intuitive role-playing design, four memory systems, YAML-driven configuration, active community (100,000+ certified developers)</p><p><strong>Limitations:</strong> Python only, high API overhead for multi-agent collaboration, complex to debug, enterprise features require a paid plan</p><hr><h2 id="IV-LlamaIndex"><a href="#IV-LlamaIndex" class="headerlink" title="IV. LlamaIndex"></a>IV. LlamaIndex</h2><h3 id="4-1-Introduction"><a href="#4-1-Introduction" class="headerlink" title="4.1 Introduction"></a>4.1 Introduction</h3><p><strong>LlamaIndex</strong> (formerly GPT Index) is an open-source framework that started out focused on RAG (Retrieval-Augmented Generation) and has since expanded into a <strong>document intelligence and OCR platform</strong>. Founded by Jerry Liu in 2022.</p><table><thead><tr><th>Project Info</th><th>Details</th></tr></thead><tbody><tr><td>Latest Version</td><td>v0.14.6</td></tr><tr><td>License</td><td>MIT</td></tr><tr><td>Install</td><td><code>pip install llama-index</code></td></tr><tr><td>GitHub</td><td><a href="https://github.com/run-llama/llama_index">run-llama&#x2F;llama_index</a></td></tr><tr><td>Docs</td><td><a href="https://developers.llamaindex.ai/python">developers.llamaindex.ai</a></td></tr></tbody></table><h3 id="4-2-Core-Concepts"><a href="#4-2-Core-Concepts" class="headerlink" title="4.2 Core Concepts"></a>4.2 Core Concepts</h3><ul><li><strong>Workflow</strong>: An event-driven orchestration mechanism where steps are defined using the <code>@step</code> decorator</li><li><strong>Context</strong>: A global runtime context that coordinates data passing between steps and supports persistence</li><li><strong>Event-driven architecture</strong>: <code>StartEvent</code> → custom events → <code>StopEvent</code>, forming a directed graph</li><li><strong>AgentWorkflow</strong>: A high-level abstraction that automatically selects the appropriate agent type based on LLM capabilities</li></ul><h3 id="4-3-Agent-Types"><a href="#4-3-Agent-Types" class="headerlink" title="4.3 Agent Types"></a>4.3 Agent Types</h3><table><thead><tr><th>Type</th><th>Use Case</th><th>Characteristics</th></tr></thead><tbody><tr><td><strong>FunctionAgent</strong></td><td>When the LLM supports function calling</td><td>Uses native function calling directly — most efficient</td></tr><tr><td><strong>ReActAgent</strong></td><td>When the LLM does not support function calling</td><td>Executes via the ReAct (Reasoning + Acting) loop</td></tr><tr><td><strong>CodeActAgent</strong></td><td>Scenarios that require code execution</td><td>Generates and executes code via <code>&lt;execute&gt;</code> tags</td></tr></tbody></table><h3 id="4-4-Key-Features"><a href="#4-4-Key-Features" class="headerlink" title="4.4 Key Features"></a>4.4 Key Features</h3><ul><li><strong>RAG + Agent integration</strong>: RAG is a first-class capability, not an add-on; supports 130+ data formats</li><li><strong>Multi-agent collaboration</strong>: Native support for multi-agent handoff mechanisms</li><li><strong>Context persistence</strong>: Workflow state can be serialized and restored, suitable for production environments</li><li><strong>LlamaParse</strong>: Enterprise-grade document parsing and OCR</li><li><strong>300+ integration packages</strong>: Covers mainstream LLMs, vector databases, and data sources</li></ul><h3 id="4-5-Code-Example"><a href="#4-5-Code-Example" class="headerlink" title="4.5 Code Example"></a>4.5 Code Example</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> llama_index.core <span class="keyword">import</span> VectorStoreIndex, SimpleDirectoryReader</span><br><span class="line"><span class="keyword">from</span> llama_index.core.agent.workflow <span class="keyword">import</span> FunctionAgent</span><br><span class="line"><span class="keyword">from</span> llama_index.llms.openai <span class="keyword">import</span> OpenAI</span><br><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"></span><br><span class="line"><span class="comment"># Build a RAG index</span></span><br><span class="line">documents = SimpleDirectoryReader(<span class="string">&quot;data&quot;</span>).load_data()</span><br><span class="line">index = VectorStoreIndex.from_documents(documents)</span><br><span class="line">query_engine = index.as_query_engine()</span><br><span class="line"></span><br><span class="line"><span class="comment"># Define tools</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">multiply</span>(<span class="params">a: <span class="built_in">float</span>, b: <span class="built_in">float</span></span>) -&gt; <span class="built_in">float</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Multiply two numbers.&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> a * b</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">search_documents</span>(<span class="params">query: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Search documents for answers.&quot;&quot;&quot;</span></span><br><span class="line">    response = <span class="keyword">await</span> query_engine.aquery(query)</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">str</span>(response)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Create the agent</span></span><br><span class="line">agent = FunctionAgent(</span><br><span class="line">    tools=[multiply, search_documents],</span><br><span class="line">    llm=OpenAI(model=<span class="string">&quot;gpt-4o-mini&quot;</span>),</span><br><span class="line">    system_prompt=<span class="string">&quot;You are a helpful assistant that can calculate and search documents.&quot;</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Run</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    response = <span class="keyword">await</span> agent.run(<span class="string">&quot;What did the author do in college? Also, what&#x27;s 7 * 8?&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(response)</span><br><span class="line"></span><br><span class="line">asyncio.run(main())</span><br></pre></td></tr></table></figure><h3 id="4-6-Strengths-and-Limitations"><a href="#4-6-Strengths-and-Limitations" class="headerlink" title="4.6 Strengths and Limitations"></a>4.6 Strengths and Limitations</h3><p><strong>Strengths:</strong> Deep RAG + Agent integration, flexible event-driven architecture, 300+ ecosystem integrations, multi-agent support, LlamaParse enterprise-grade parsing</p><p><strong>Limitations:</strong> Steep learning curve, relatively heavy framework, TypeScript version has incomplete feature coverage, fast release cycle with frequent breaking changes, enterprise features require a paid plan</p><hr><h2 id="V-Dify"><a href="#V-Dify" class="headerlink" title="V. Dify"></a>V. Dify</h2><h3 id="5-1-Introduction"><a href="#5-1-Introduction" class="headerlink" title="5.1 Introduction"></a>5.1 Introduction</h3><p><strong>Dify</strong> (Do It For You) is an open-source LLM application development platform positioned as an <strong>agentic workflow builder</strong>. It combines Backend-as-a-Service with LLMOps, enabling both non-technical users and developers to rapidly build AI applications.</p><table><thead><tr><th>Project Info</th><th>Details</th></tr></thead><tbody><tr><td>Latest Version</td><td>v1.6.0+</td></tr><tr><td>License</td><td>Dify Open Source License (Apache 2.0+)</td></tr><tr><td>Deploy</td><td><code>docker compose up -d</code></td></tr><tr><td>GitHub</td><td><a href="https://github.com/langgenius/dify">langgenius&#x2F;dify</a></td></tr><tr><td>Docs</td><td><a href="https://docs.dify.ai/en/use-dify/getting-started/introduction">docs.dify.ai</a></td></tr></tbody></table><h3 id="5-2-Core-Features"><a href="#5-2-Core-Features" class="headerlink" title="5.2 Core Features"></a>5.2 Core Features</h3><ul><li><strong>Visual workflow builder</strong>: Drag-and-drop canvas supporting parallel processing, conditional branching, and loop nodes</li><li><strong>Agent strategies</strong>: Supports Function Calling, ReAct, and custom strategy plugins</li><li><strong>RAG pipeline</strong>: A complete data source → processing → knowledge base → retrieval flow</li><li><strong>Model management</strong>: Seamless integration with hundreds of LLMs, with model switching and performance comparison</li><li><strong>Prompt IDE</strong>: An intuitive prompt authoring interface</li><li><strong>LLMOps</strong>: Monitor and analyze application logs and performance</li></ul><h3 id="5-3-Agent-Strategies"><a href="#5-3-Agent-Strategies" class="headerlink" title="5.3 Agent Strategies"></a>5.3 Agent Strategies</h3><table><thead><tr><th>Strategy</th><th>Use Case</th></tr></thead><tbody><tr><td><strong>Function Calling</strong></td><td>Models with native tool calling support (e.g., GPT-4, Claude)</td></tr><tr><td><strong>ReAct</strong></td><td>Models without native function calling, or when explicit reasoning traces are needed</td></tr><tr><td><strong>Custom Strategy Plugin</strong></td><td>Complex behaviors requiring multi-turn tool calls, etc.</td></tr></tbody></table><h3 id="5-4-How-to-Create-an-Agent"><a href="#5-4-How-to-Create-an-Agent" class="headerlink" title="5.4 How to Create an Agent"></a>5.4 How to Create an Agent</h3><p>Dify uses a visual &#x2F; no-code approach:</p><ol><li>Create an “Agent” type application in Dify Studio</li><li>Select an LLM model</li><li>Set the Agent strategy (automatically detects Function Calling support)</li><li>Choose from 50+ built-in tools or add custom tools</li><li>Write a system prompt</li><li>Preview and debug, then publish with one click</li></ol><h3 id="5-5-Integration-Capabilities"><a href="#5-5-Integration-Capabilities" class="headerlink" title="5.5 Integration Capabilities"></a>5.5 Integration Capabilities</h3><ul><li><strong>API</strong>: Full RESTful API with SSE streaming support</li><li><strong>SDK</strong>: Node.js, PHP, and Java clients</li><li><strong>Plugin system</strong>: Six plugin categories — models, tools, agent strategies, extensions, data sources, and triggers</li><li><strong>MCP integration</strong>: Native support for the Model Context Protocol</li><li><strong>Deployment</strong>: Docker Compose, Kubernetes, Terraform, AWS CDK</li></ul><h3 id="5-6-Strengths-and-Limitations"><a href="#5-6-Strengths-and-Limitations" class="headerlink" title="5.6 Strengths and Limitations"></a>5.6 Strengths and Limitations</h3><p><strong>Strengths:</strong> Low-code &#x2F; no-code, ready out of the box (50+ built-in tools), rapid path from prototype to production, multi-model support, active community (800+ contributors)</p><p><strong>Limitations:</strong> Limited customization flexibility (less than code-first frameworks), execution subject to step&#x2F;time limits, license is not pure Apache 2.0, risk of platform lock-in, advanced reasoning modes are less mature than dedicated frameworks</p><hr><h2 id="VI-OpenAI-Agents-SDK"><a href="#VI-OpenAI-Agents-SDK" class="headerlink" title="VI. OpenAI Agents SDK"></a>VI. OpenAI Agents SDK</h2><h3 id="6-1-Introduction"><a href="#6-1-Introduction" class="headerlink" title="6.1 Introduction"></a>6.1 Introduction</h3><p><strong>OpenAI Agents SDK</strong> is a lightweight multi-agent framework officially released by OpenAI, evolved from the internal Swarm experimental project. Its core philosophy is <strong>minimalist design</strong> — building complex workflows from just a few concepts: Agent, Handoff, Guardrail, and Tool.</p><table><thead><tr><th>Project Info</th><th>Details</th></tr></thead><tbody><tr><td>Latest Version</td><td>v0.14.6 (2026-04-25)</td></tr><tr><td>License</td><td>MIT</td></tr><tr><td>Install</td><td><code>pip install openai-agents</code></td></tr><tr><td>GitHub</td><td><a href="https://github.com/openai/openai-agents-python">openai&#x2F;openai-agents-python</a></td></tr><tr><td>Docs</td><td><a href="https://openai.github.io/openai-agents-python">openai.github.io&#x2F;openai-agents-python</a></td></tr></tbody></table><h3 id="6-2-Core-Concepts"><a href="#6-2-Core-Concepts" class="headerlink" title="6.2 Core Concepts"></a>6.2 Core Concepts</h3><ul><li><strong>Agent</strong>: An LLM configured with instructions, tools, guardrails, and handoff capabilities</li><li><strong>Runner</strong>: The agent executor, providing <code>run()</code> (async), <code>run_sync()</code> (synchronous), and <code>run_streamed()</code> (streaming)</li><li><strong>Handoff</strong>: Task delegation between agents; the receiving agent inherits the full conversation history</li><li><strong>Guardrails</strong>: Safety rails in three categories — input guardrails, output guardrails, and tool guardrails</li><li><strong>Tools</strong>: Supports function tools, MCP tools, OpenAI hosted tools, and Agent as Tool</li></ul><h3 id="6-3-Key-Features"><a href="#6-3-Key-Features" class="headerlink" title="6.3 Key Features"></a>6.3 Key Features</h3><ul><li><strong>Minimalist design</strong>: Few core primitives, gentle learning curve</li><li><strong>Provider-agnostic</strong>: Supports 100+ LLMs via any-llm &#x2F; LiteLLM</li><li><strong>Three-layer guardrails</strong>: Safety validation at the input → output → tool level</li><li><strong>Built-in Tracing</strong>: Visualize and debug agent execution flows</li><li><strong>Realtime Agents</strong>: Build voice agents (gpt-realtime-1.5)</li><li><strong>Sandbox Agents</strong>: Added in v0.14.0 — executes code in a containerized environment</li><li><strong>Structured output</strong>: Define output types via Pydantic Model using <code>output_type</code></li></ul><h3 id="6-4-Code-Example"><a href="#6-4-Code-Example" class="headerlink" title="6.4 Code Example"></a>6.4 Code Example</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">from</span> agents <span class="keyword">import</span> Agent, Runner, function_tool</span><br><span class="line"></span><br><span class="line"><span class="comment"># Define a tool</span></span><br><span class="line"><span class="meta">@function_tool</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_weather</span>(<span class="params">city: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Get the weather for a specified city.&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">f&quot;The weather in <span class="subst">&#123;city&#125;</span> is sunny.&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Define specialist Agents</span></span><br><span class="line">billing_agent = Agent(</span><br><span class="line">    name=<span class="string">&quot;Billing Agent&quot;</span>,</span><br><span class="line">    instructions=<span class="string">&quot;You are a billing specialist.&quot;</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">refund_agent = Agent(</span><br><span class="line">    name=<span class="string">&quot;Refund Agent&quot;</span>,</span><br><span class="line">    instructions=<span class="string">&quot;You are a refund specialist.&quot;</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Define a triage Agent</span></span><br><span class="line">triage_agent = Agent(</span><br><span class="line">    name=<span class="string">&quot;Triage Agent&quot;</span>,</span><br><span class="line">    instructions=<span class="string">&quot;Route the user&#x27;s question to the correct specialist Agent: billing issues -&gt; Billing Agent; refund issues -&gt; Refund Agent.&quot;</span>,</span><br><span class="line">    handoffs=[billing_agent, refund_agent],</span><br><span class="line">    tools=[get_weather],</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Run</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    result = <span class="keyword">await</span> Runner.run(</span><br><span class="line">        triage_agent,</span><br><span class="line">        <span class="string">&quot;I was charged twice for my subscription. Please help me resolve this.&quot;</span>,</span><br><span class="line">    )</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Final answer: <span class="subst">&#123;result.final_output&#125;</span>&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Handled by Agent: <span class="subst">&#123;result.last_agent.name&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">asyncio.run(main())</span><br></pre></td></tr></table></figure><h3 id="6-5-Strengths-and-Limitations"><a href="#6-5-Strengths-and-Limitations" class="headerlink" title="6.5 Strengths and Limitations"></a>6.5 Strengths and Limitations</h3><p><strong>Strengths:</strong> Officially maintained, minimalist design, provider-agnostic, three-layer guardrails, built-in tracing, voice agent support</p><p><strong>Limitations:</strong> Still at 0.x — API may change, deep dependency on the OpenAI ecosystem, no parallel agent execution support, no built-in persistent memory system</p><hr><h2 id="VII-Google-ADK"><a href="#VII-Google-ADK" class="headerlink" title="VII. Google ADK"></a>VII. Google ADK</h2><h3 id="7-1-Introduction"><a href="#7-1-Introduction" class="headerlink" title="7.1 Introduction"></a>7.1 Introduction</h3><p><strong>Google ADK (Agent Development Kit)</strong> is an open-source, code-first agent development framework released by Google. Its design philosophy is to make AI agent development feel like traditional software development. It is optimized for Gemini and Google Cloud, while remaining model-agnostic and deployment-agnostic.</p><table><thead><tr><th>Project Info</th><th>Details</th></tr></thead><tbody><tr><td>Latest Version</td><td>v1.31.1 (2026-04-30)</td></tr><tr><td>License</td><td>Apache 2.0</td></tr><tr><td>Install</td><td><code>pip install google-adk</code></td></tr><tr><td>GitHub</td><td><a href="https://github.com/google/adk-python">google&#x2F;adk-python</a></td></tr><tr><td>Docs</td><td><a href="https://google.github.io/adk-docs/">google.github.io&#x2F;adk-docs</a></td></tr></tbody></table><h3 id="7-2-Core-Concepts"><a href="#7-2-Core-Concepts" class="headerlink" title="7.2 Core Concepts"></a>7.2 Core Concepts</h3><ul><li><strong>LlmAgent</strong> (alias <code>Agent</code>): The core building block — combines an LLM model, instructions, and tools</li><li><strong>SequentialAgent</strong>: Executes sub-agents one after another in order (pipeline style)</li><li><strong>ParallelAgent</strong>: Runs multiple sub-agents concurrently</li><li><strong>LoopAgent</strong>: Repeatedly executes sub-agents with support for exit conditions</li><li><strong>sub_agents</strong>: Nesting sub-agents to build hierarchical multi-agent architectures</li></ul><h3 id="7-3-Key-Features"><a href="#7-3-Key-Features" class="headerlink" title="7.3 Key Features"></a>7.3 Key Features</h3><ul><li><strong>Multi-agent orchestration</strong>: Sequential, parallel, loop-based, and LLM-driven dynamic routing</li><li><strong>Built-in tools</strong>: Google Search, Vertex AI Search, code executor, and more</li><li><strong>Google ecosystem integration</strong>: Native Gemini, Vertex AI Agent Engine, Cloud Run</li><li><strong>Flexible deployment</strong>: Local, Agent Engine (fully managed), Cloud Run, GKE, Docker</li><li><strong>Built-in evaluation</strong>: CLI tool <code>adk eval</code> for systematic agent performance assessment</li><li><strong>A2A protocol</strong>: Supports Agent-to-Agent remote communication</li><li><strong>Lifecycle callbacks</strong>: <code>before/after_agent</code>, <code>before/after_model</code>, and <code>before/after_tool</code> hooks</li></ul><h3 id="7-4-Code-Example"><a href="#7-4-Code-Example" class="headerlink" title="7.4 Code Example"></a>7.4 Code Example</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">from</span> google.adk.agents <span class="keyword">import</span> Agent, SequentialAgent</span><br><span class="line"><span class="keyword">from</span> google.adk.runners <span class="keyword">import</span> Runner</span><br><span class="line"><span class="keyword">from</span> google.adk.sessions <span class="keyword">import</span> InMemorySessionService</span><br><span class="line"><span class="keyword">from</span> google.genai <span class="keyword">import</span> types</span><br><span class="line"><span class="keyword">from</span> google.adk.tools <span class="keyword">import</span> google_search</span><br><span class="line"></span><br><span class="line"><span class="comment"># Define a weather Agent</span></span><br><span class="line">weather_agent = Agent(</span><br><span class="line">    name=<span class="string">&quot;weather_assistant&quot;</span>,</span><br><span class="line">    model=<span class="string">&quot;gemini-2.5-flash&quot;</span>,</span><br><span class="line">    instruction=<span class="string">&quot;You are a weather query assistant. Use Google Search to find the latest weather information.&quot;</span>,</span><br><span class="line">    tools=[google_search],</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Define a translation Agent</span></span><br><span class="line">translate_agent = Agent(</span><br><span class="line">    name=<span class="string">&quot;translate_assistant&quot;</span>,</span><br><span class="line">    model=<span class="string">&quot;gemini-2.5-flash&quot;</span>,</span><br><span class="line">    instruction=<span class="string">&quot;You are a translation assistant. Translate content into Chinese.&quot;</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Compose into a sequential workflow</span></span><br><span class="line">pipeline = SequentialAgent(</span><br><span class="line">    name=<span class="string">&quot;WeatherPipeline&quot;</span>,</span><br><span class="line">    sub_agents=[weather_agent, translate_agent],</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Run</span></span><br><span class="line">session_service = InMemorySessionService()</span><br><span class="line">runner = Runner(agent=pipeline, app_name=<span class="string">&quot;weather_app&quot;</span>, session_service=session_service)</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">run_agent</span>(<span class="params">query: <span class="built_in">str</span></span>):</span><br><span class="line">    session = session_service.create_session(</span><br><span class="line">        app_name=<span class="string">&quot;weather_app&quot;</span>, user_id=<span class="string">&quot;user_1&quot;</span>, session_id=<span class="string">&quot;session_1&quot;</span></span><br><span class="line">    )</span><br><span class="line">    content = types.Content(role=<span class="string">&#x27;user&#x27;</span>, parts=[types.Part(text=query)])</span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">for</span> event <span class="keyword">in</span> runner.run_async(</span><br><span class="line">        user_id=<span class="string">&quot;user_1&quot;</span>, session_id=<span class="string">&quot;session_1&quot;</span>, new_message=content</span><br><span class="line">    ):</span><br><span class="line">        <span class="keyword">if</span> event.is_final_response() <span class="keyword">and</span> event.content <span class="keyword">and</span> event.content.parts:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;Agent reply: <span class="subst">&#123;event.content.parts[<span class="number">0</span>].text.strip()&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">asyncio.run(run_agent(<span class="string">&quot;What&#x27;s the weather in Tokyo today?&quot;</span>))</span><br></pre></td></tr></table></figure><h3 id="7-5-Strengths-and-Limitations"><a href="#7-5-Strengths-and-Limitations" class="headerlink" title="7.5 Strengths and Limitations"></a>7.5 Strengths and Limitations</h3><p><strong>Strengths:</strong> Code-first, powerful orchestration (sequential &#x2F; parallel &#x2F; loop), deep Google ecosystem integration, built-in evaluation, multi-language support (Python &#x2F; Java &#x2F; Go &#x2F; TS), Apache 2.0 open source</p><p><strong>Limitations:</strong> Best experience requires Gemini and Google Cloud, relatively new framework with an early-stage community ecosystem, frequent releases mean the API may change, access to Google services is restricted from mainland China</p><hr><h2 id="VIII-Framework-Selection-Guide"><a href="#VIII-Framework-Selection-Guide" class="headerlink" title="VIII. Framework Selection Guide"></a>VIII. Framework Selection Guide</h2><h3 id="Choose-by-Use-Case"><a href="#Choose-by-Use-Case" class="headerlink" title="Choose by Use Case"></a>Choose by Use Case</h3><table><thead><tr><th>Use Case</th><th>Recommended Framework</th><th>Reason</th></tr></thead><tbody><tr><td><strong>Complex stateful workflows</strong></td><td>LangGraph</td><td>Low-level graph orchestration, persistence, time travel</td></tr><tr><td><strong>Multi-role team collaboration</strong></td><td>CrewAI</td><td>Role-playing design, delegation mechanism, memory systems</td></tr><tr><td><strong>RAG + Agent</strong></td><td>LlamaIndex</td><td>Deep RAG integration, 130+ data formats, document parsing</td></tr><tr><td><strong>Rapid prototyping &#x2F; non-technical teams</strong></td><td>Dify</td><td>Visual drag-and-drop, low-code, ready out of the box</td></tr><tr><td><strong>Primarily OpenAI models</strong></td><td>OpenAI Agents SDK</td><td>Officially maintained, minimal API, tracing and debugging</td></tr><tr><td><strong>Google Cloud deployment</strong></td><td>Google ADK</td><td>Gemini-optimized, Vertex AI integration, built-in evaluation</td></tr><tr><td><strong>Need fine-grained control</strong></td><td>LangGraph &#x2F; Google ADK</td><td>Low-level APIs, lifecycle callback hooks</td></tr><tr><td><strong>Need production-grade guardrails</strong></td><td>OpenAI Agents SDK</td><td>Three-layer Guardrails</td></tr></tbody></table><h3 id="Choose-by-Team-Profile"><a href="#Choose-by-Team-Profile" class="headerlink" title="Choose by Team Profile"></a>Choose by Team Profile</h3><table><thead><tr><th>Team Profile</th><th>Recommendation</th></tr></thead><tbody><tr><td>Full-stack development teams</td><td>LangGraph, Google ADK</td></tr><tr><td>Python data science teams</td><td>CrewAI, LlamaIndex</td></tr><tr><td>Product managers &#x2F; operations teams</td><td>Dify</td></tr><tr><td>Heavy OpenAI ecosystem users</td><td>OpenAI Agents SDK</td></tr><tr><td>Google Cloud users</td><td>Google ADK</td></tr><tr><td>Need to validate ideas quickly</td><td>Dify, OpenAI Agents SDK</td></tr></tbody></table><blockquote><p><strong>Note</strong>: The framework information above is based on research conducted in April 2026. All frameworks iterate quickly — check the official documentation for the latest information before getting started.</p></blockquote>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/AI/">AI</category>
      
      <category domain="https://eugenepage.com/tags/OpenAI/">OpenAI</category>
      
      <category domain="https://eugenepage.com/tags/Framework/">Framework</category>
      
      <category domain="https://eugenepage.com/tags/Agent/">Agent</category>
      
      <category domain="https://eugenepage.com/tags/LangGraph/">LangGraph</category>
      
      <category domain="https://eugenepage.com/tags/CrewAI/">CrewAI</category>
      
      <category domain="https://eugenepage.com/tags/LlamaIndex/">LlamaIndex</category>
      
      <category domain="https://eugenepage.com/tags/Dify/">Dify</category>
      
      <category domain="https://eugenepage.com/tags/GoogleADK/">GoogleADK</category>
      
      
      <comments>https://eugenepage.com/2026/04/30/20260430.AIAgentFrameworkResearchNotes/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>AI Agent 框架调研笔记</title>
      <link>https://eugenepage.com/zh-CN/2026/04/30/20260430.AIAgentFrameworkResearchNotes/</link>
      <guid>https://eugenepage.com/zh-CN/2026/04/30/20260430.AIAgentFrameworkResearchNotes/</guid>
      <pubDate>Thu, 30 Apr 2026 04:00:00 GMT</pubDate>
      
        
        
      <description>&lt;h1 id=&quot;AI-Agent-框架调研笔记&quot;&gt;&lt;a href=&quot;#AI-Agent-框架调研笔记&quot; class=&quot;headerlink&quot; title=&quot;AI Agent 框架调研笔记&quot;&gt;&lt;/a&gt;AI Agent 框架调研笔记&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;更新时间：</description>
        
      
      
      
      <content:encoded><![CDATA[<h1 id="AI-Agent-框架调研笔记"><a href="#AI-Agent-框架调研笔记" class="headerlink" title="AI Agent 框架调研笔记"></a>AI Agent 框架调研笔记</h1><blockquote><p>更新时间：2026-04-30</p><p>随着 AI Agent 技术的快速发展，各类 Agent 开发框架层出不穷。本文档对当前主流的 6 个 Agent 框架进行调研和对比分析，帮助开发者选择合适的工具。</p></blockquote><hr><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ul><li><a href="#%E4%B8%80%E6%A1%86%E6%9E%B6%E6%A6%82%E8%A7%88%E5%AF%B9%E6%AF%94">一、框架概览对比</a></li><li><a href="#%E4%BA%8Clanggraph">二、LangGraph</a></li><li><a href="#%E4%B8%89crewai">三、CrewAI</a></li><li><a href="#%E5%9B%9Bllamaindex">四、LlamaIndex</a></li><li><a href="#%E4%BA%94dify">五、Dify</a></li><li><a href="#%E5%85%ADopenai-agents-sdk">六、OpenAI Agents SDK</a></li><li><a href="#%E4%B8%83google-adk">七、Google ADK</a></li><li><a href="#%E5%85%AB%E6%A1%86%E6%9E%B6%E9%80%89%E5%9E%8B%E6%8C%87%E5%8D%97">八、框架选型指南</a></li></ul><hr><h2 id="一、框架概览对比"><a href="#一、框架概览对比" class="headerlink" title="一、框架概览对比"></a>一、框架概览对比</h2><table><thead><tr><th>维度</th><th>LangGraph</th><th>CrewAI</th><th>LlamaIndex</th><th>Dify</th><th>OpenAI Agents SDK</th><th>Google ADK</th></tr></thead><tbody><tr><td><strong>开发方</strong></td><td>LangChain Inc.</td><td>CrewAI Inc.</td><td>LlamaIndex Inc.</td><td>LangGenius</td><td>OpenAI</td><td>Google</td></tr><tr><td><strong>最新版本</strong></td><td>v1.1.10</td><td>v1.14.3</td><td>v0.14.6</td><td>v1.6.0+</td><td>v0.14.6</td><td>v1.31.1</td></tr><tr><td><strong>许可证</strong></td><td>MIT</td><td>MIT</td><td>MIT</td><td>Dify License (Apache 2.0+)</td><td>MIT</td><td>Apache 2.0</td></tr><tr><td><strong>语言</strong></td><td>Python &#x2F; JS</td><td>Python</td><td>Python &#x2F; TS</td><td>可视化（多语言 SDK）</td><td>Python &#x2F; JS</td><td>Python &#x2F; Java &#x2F; Go &#x2F; TS</td></tr><tr><td><strong>核心理念</strong></td><td>图编排</td><td>角色扮演团队</td><td>RAG + Agent</td><td>低代码平台</td><td>极简多 Agent</td><td>代码优先</td></tr><tr><td><strong>模型支持</strong></td><td>模型无关</td><td>模型无关</td><td>模型无关</td><td>模型无关</td><td>100+ LLM</td><td>模型无关</td></tr><tr><td><strong>学习曲线</strong></td><td>较陡</td><td>中等</td><td>中等</td><td>低</td><td>低</td><td>中等</td></tr><tr><td><strong>适合场景</strong></td><td>复杂有状态工作流</td><td>多角色协作</td><td>RAG + Agent</td><td>快速原型&#x2F;非技术用户</td><td>OpenAI 生态应用</td><td>Google 生态应用</td></tr></tbody></table><hr><h2 id="二、LangGraph"><a href="#二、LangGraph" class="headerlink" title="二、LangGraph"></a>二、LangGraph</h2><h3 id="2-1-简介"><a href="#2-1-简介" class="headerlink" title="2.1 简介"></a>2.1 简介</h3><p><strong>LangGraph</strong> 是由 LangChain 团队开发的<strong>底层编排框架</strong>，专门用于构建长时间运行的、有状态的 AI Agent。设计灵感来自 Google 的 Pregel 和 Apache Beam。</p><p>核心定位：不抽象化提示词或架构，提供底层基础设施，让开发者精细控制 Agent 工作流。已被 Klarna、Replit、Elastic 等公司用于生产环境。</p><table><thead><tr><th>项目信息</th><th>详情</th></tr></thead><tbody><tr><td>最新版本</td><td>v1.1.10（2026-04-27）</td></tr><tr><td>许可证</td><td>MIT</td></tr><tr><td>安装</td><td><code>pip install -U langgraph</code></td></tr><tr><td>GitHub</td><td><a href="https://github.com/langchain-ai/langgraph">langchain-ai&#x2F;langgraph</a></td></tr><tr><td>文档</td><td><a href="https://docs.langchain.com/oss/python/langgraph">docs.langchain.com&#x2F;oss&#x2F;python&#x2F;langgraph</a></td></tr></tbody></table><h3 id="2-2-核心架构"><a href="#2-2-核心架构" class="headerlink" title="2.2 核心架构"></a>2.2 核心架构</h3><p>LangGraph 将 Agent 工作流建模为<strong>图（Graph）</strong>，由三个核心组件构成：</p><ul><li><strong>State（状态）</strong>：共享数据结构，通常用 <code>TypedDict</code> 或 <code>Pydantic Model</code> 定义</li><li><strong>Nodes（节点）</strong>：编码 Agent 逻辑的函数，接收当前状态、返回更新后的状态</li><li><strong>Edges（边）</strong>：决定下一个节点的函数，支持条件分支或固定转换</li></ul><h3 id="2-3-关键特性"><a href="#2-3-关键特性" class="headerlink" title="2.3 关键特性"></a>2.3 关键特性</h3><ul><li><strong>持久化（Persistence）</strong>：每个执行步骤将图状态保存为 checkpoint，支持内存、Redis 等后端</li><li><strong>人机协作（Human-in-the-Loop）</strong>：通过 <code>interrupt()</code> 暂停执行，等待人工输入后恢复</li><li><strong>流式输出（Streaming）</strong>：支持 values、messages、updates 等多种流式模式</li><li><strong>子图（Subgraphs）</strong>：支持图嵌套，子图拥有独立的 checkpoint 和中断能力</li><li><strong>时间旅行</strong>：可回溯到任意历史 checkpoint，支持 fork 和重放</li><li><strong>可视化</strong>：编译后可生成 Mermaid 图形展示工作流结构</li></ul><h3 id="2-4-代码示例"><a href="#2-4-代码示例" class="headerlink" title="2.4 代码示例"></a>2.4 代码示例</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Literal</span></span><br><span class="line"><span class="keyword">from</span> langgraph.graph <span class="keyword">import</span> StateGraph, MessagesState, START, END</span><br><span class="line"><span class="keyword">from</span> langchain.messages <span class="keyword">import</span> SystemMessage, HumanMessage, ToolMessage</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义工具</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">multiply</span>(<span class="params">a: <span class="built_in">int</span>, b: <span class="built_in">int</span></span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Multiply two numbers.&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> a * b</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">add</span>(<span class="params">a: <span class="built_in">int</span>, b: <span class="built_in">int</span></span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Add two numbers.&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> a + b</span><br><span class="line"></span><br><span class="line">tools = [multiply, add]</span><br><span class="line">tools_by_name = &#123;tool.name: tool <span class="keyword">for</span> tool <span class="keyword">in</span> tools&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义节点</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">llm_call</span>(<span class="params">state: MessagesState</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;LLM 决定是否调用工具&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="string">&quot;messages&quot;</span>: [</span><br><span class="line">            llm_with_tools.invoke(</span><br><span class="line">                [SystemMessage(content=<span class="string">&quot;You are a helpful arithmetic assistant.&quot;</span>)]</span><br><span class="line">                + state[<span class="string">&quot;messages&quot;</span>]</span><br><span class="line">            )</span><br><span class="line">        ]</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">tool_node</span>(<span class="params">state: <span class="built_in">dict</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;执行工具调用&quot;&quot;&quot;</span></span><br><span class="line">    result = []</span><br><span class="line">    <span class="keyword">for</span> tool_call <span class="keyword">in</span> state[<span class="string">&quot;messages&quot;</span>][-<span class="number">1</span>].tool_calls:</span><br><span class="line">        tool = tools_by_name[tool_call[<span class="string">&quot;name&quot;</span>]]</span><br><span class="line">        observation = tool.invoke(tool_call[<span class="string">&quot;args&quot;</span>])</span><br><span class="line">        result.append(ToolMessage(content=<span class="built_in">str</span>(observation), tool_call_id=tool_call[<span class="string">&quot;id&quot;</span>]))</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;messages&quot;</span>: result&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义条件边路由</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">should_continue</span>(<span class="params">state: MessagesState</span>) -&gt; <span class="type">Literal</span>[<span class="string">&quot;tool_node&quot;</span>, END]:</span><br><span class="line">    last_message = state[<span class="string">&quot;messages&quot;</span>][-<span class="number">1</span>]</span><br><span class="line">    <span class="keyword">if</span> last_message.tool_calls:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;tool_node&quot;</span></span><br><span class="line">    <span class="keyword">return</span> END</span><br><span class="line"></span><br><span class="line"><span class="comment"># 构建并编译图</span></span><br><span class="line">builder = StateGraph(MessagesState)</span><br><span class="line">builder.add_node(<span class="string">&quot;llm_call&quot;</span>, llm_call)</span><br><span class="line">builder.add_node(<span class="string">&quot;tool_node&quot;</span>, tool_node)</span><br><span class="line">builder.add_edge(START, <span class="string">&quot;llm_call&quot;</span>)</span><br><span class="line">builder.add_conditional_edges(<span class="string">&quot;llm_call&quot;</span>, should_continue, [<span class="string">&quot;tool_node&quot;</span>, END])</span><br><span class="line">builder.add_edge(<span class="string">&quot;tool_node&quot;</span>, <span class="string">&quot;llm_call&quot;</span>)</span><br><span class="line"></span><br><span class="line">agent = builder.<span class="built_in">compile</span>()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行</span></span><br><span class="line">result = agent.invoke(&#123;<span class="string">&quot;messages&quot;</span>: [HumanMessage(content=<span class="string">&quot;Add 3 and 4, then multiply by 5.&quot;</span>)]&#125;)</span><br></pre></td></tr></table></figure><h3 id="2-5-优势与局限"><a href="#2-5-优势与局限" class="headerlink" title="2.5 优势与局限"></a>2.5 优势与局限</h3><p><strong>优势：</strong> 精细化控制、有状态执行、原生人机协作、容错恢复、时间旅行调试、框架无关</p><p><strong>局限：</strong> 学习曲线较陡、样板代码多、最佳体验需配合 LangSmith 生态、版本迭代快</p><hr><h2 id="三、CrewAI"><a href="#三、CrewAI" class="headerlink" title="三、CrewAI"></a>三、CrewAI</h2><h3 id="3-1-简介"><a href="#3-1-简介" class="headerlink" title="3.1 简介"></a>3.1 简介</h3><p><strong>CrewAI</strong> 是一个用于编排多个自主 AI Agent 的 Python 框架，完全从零构建，<strong>不依赖 LangChain 或其他框架</strong>。核心理念是通过角色扮演模拟真实团队协作。</p><table><thead><tr><th>项目信息</th><th>详情</th></tr></thead><tbody><tr><td>最新版本</td><td>v1.14.3（2025-04-24）</td></tr><tr><td>许可证</td><td>MIT</td></tr><tr><td>安装</td><td><code>pip install &#39;crewai[tools]&#39;</code></td></tr><tr><td>GitHub</td><td><a href="https://github.com/crewAIInc/crewAI">crewAIInc&#x2F;crewAI</a></td></tr><tr><td>文档</td><td><a href="https://docs.crewai.com/">docs.crewai.com</a></td></tr></tbody></table><h3 id="3-2-核心概念"><a href="#3-2-核心概念" class="headerlink" title="3.2 核心概念"></a>3.2 核心概念</h3><ul><li><strong>Agent（智能体）</strong>：通过 <code>role</code>（角色）、<code>goal</code>（目标）、<code>backstory</code>（背景故事）定义身份和行为</li><li><strong>Task（任务）</strong>：具体工作单元，可指定执行者、上下文依赖和输出格式</li><li><strong>Crew（团队）</strong>：一组协作 Agent 的集合，定义执行流程和记忆配置</li><li><strong>Tools（工具）</strong>：丰富的内置工具集（搜索、文件读写、代码执行等），支持 MCP 集成</li><li><strong>Process（流程）</strong>：Sequential（顺序）或 Hierarchical（层级，自动创建 Manager Agent）</li></ul><h3 id="3-3-关键特性"><a href="#3-3-关键特性" class="headerlink" title="3.3 关键特性"></a>3.3 关键特性</h3><ul><li><strong>角色扮演设计</strong>：直观的角色定义方式，贴近真实团队协作</li><li><strong>协作工作流</strong>：Agent 间可委派任务、传递上下文</li><li><strong>四种记忆系统</strong>：短期记忆、长期记忆、实体记忆、上下文记忆</li><li><strong>Flows（流程编排）</strong>：企业级事件驱动工作流，支持 <code>@start</code>、<code>@listen</code>、<code>@router</code> 装饰器</li><li><strong>Checkpoint &amp; Fork</strong>：支持执行状态的保存、恢复和分支</li><li><strong>YAML 配置驱动</strong>：Agent 和 Task 可通过 YAML 文件定义</li></ul><h3 id="3-4-代码示例"><a href="#3-4-代码示例" class="headerlink" title="3.4 代码示例"></a>3.4 代码示例</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> crewai <span class="keyword">import</span> Agent, Task, Crew, Process</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义 Agent</span></span><br><span class="line">researcher = Agent(</span><br><span class="line">    role=<span class="string">&#x27;高级 AI 研究员&#x27;</span>,</span><br><span class="line">    goal=<span class="string">&#x27;发现 AI Agent 领域的最新发展趋势&#x27;</span>,</span><br><span class="line">    backstory=<span class="string">&#x27;你是一位经验丰富的研究员，擅长发现前沿技术的最新动态。&#x27;</span>,</span><br><span class="line">    verbose=<span class="literal">True</span>,</span><br><span class="line">    memory=<span class="literal">True</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">writer = Agent(</span><br><span class="line">    role=<span class="string">&#x27;技术报告撰写专家&#x27;</span>,</span><br><span class="line">    goal=<span class="string">&#x27;将研究发现转化为清晰、结构化的报告&#x27;</span>,</span><br><span class="line">    backstory=<span class="string">&#x27;你是一位技术写作专家，擅长将复杂信息转化为易读的报告。&#x27;</span>,</span><br><span class="line">    verbose=<span class="literal">True</span>,</span><br><span class="line">    memory=<span class="literal">True</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义 Task</span></span><br><span class="line">research_task = Task(</span><br><span class="line">    description=<span class="string">&#x27;对 &#123;topic&#125; 进行全面调研，收集最新的发展趋势。&#x27;</span>,</span><br><span class="line">    expected_output=<span class="string">&#x27;包含 10 个要点的详细研究发现列表&#x27;</span>,</span><br><span class="line">    agent=researcher,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">writing_task = Task(</span><br><span class="line">    description=<span class="string">&#x27;根据研究发现撰写一份完整的技术报告。&#x27;</span>,</span><br><span class="line">    expected_output=<span class="string">&#x27;完整的 Markdown 格式报告&#x27;</span>,</span><br><span class="line">    agent=writer,</span><br><span class="line">    context=[research_task],</span><br><span class="line">    output_file=<span class="string">&#x27;report.md&#x27;</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 组建 Crew 并执行</span></span><br><span class="line">crew = Crew(</span><br><span class="line">    agents=[researcher, writer],</span><br><span class="line">    tasks=[research_task, writing_task],</span><br><span class="line">    process=Process.sequential,</span><br><span class="line">    verbose=<span class="literal">True</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">result = crew.kickoff(inputs=&#123;<span class="string">&#x27;topic&#x27;</span>: <span class="string">&#x27;多智能体协作系统&#x27;</span>&#125;)</span><br></pre></td></tr></table></figure><h3 id="3-5-优势与局限"><a href="#3-5-优势与局限" class="headerlink" title="3.5 优势与局限"></a>3.5 优势与局限</h3><p><strong>优势：</strong> 完全独立无依赖、角色扮演直观、四种记忆系统、YAML 配置驱动、活跃社区（10 万+ 认证开发者）</p><p><strong>局限：</strong> 仅支持 Python、多 Agent 协作 API 开销大、调试复杂、企业功能需付费</p><hr><h2 id="四、LlamaIndex"><a href="#四、LlamaIndex" class="headerlink" title="四、LlamaIndex"></a>四、LlamaIndex</h2><h3 id="4-1-简介"><a href="#4-1-简介" class="headerlink" title="4.1 简介"></a>4.1 简介</h3><p><strong>LlamaIndex</strong>（原名 GPT Index）是一个开源框架，最初专注于 RAG（检索增强生成），现已扩展为<strong>文档智能体和 OCR 平台</strong>。由 Jerry Liu 于 2022 年创立。</p><table><thead><tr><th>项目信息</th><th>详情</th></tr></thead><tbody><tr><td>最新版本</td><td>v0.14.6</td></tr><tr><td>许可证</td><td>MIT</td></tr><tr><td>安装</td><td><code>pip install llama-index</code></td></tr><tr><td>GitHub</td><td><a href="https://github.com/run-llama/llama_index">run-llama&#x2F;llama_index</a></td></tr><tr><td>文档</td><td><a href="https://developers.llamaindex.ai/python">developers.llamaindex.ai</a></td></tr></tbody></table><h3 id="4-2-核心概念"><a href="#4-2-核心概念" class="headerlink" title="4.2 核心概念"></a>4.2 核心概念</h3><ul><li><strong>Workflow（工作流）</strong>：事件驱动的编排机制，通过 <code>@step</code> 装饰器定义步骤</li><li><strong>Context（上下文）</strong>：全局运行时上下文，协调步骤间数据传递，支持持久化</li><li><strong>事件驱动架构</strong>：<code>StartEvent</code> → 自定义事件 → <code>StopEvent</code>，形成有向图</li><li><strong>AgentWorkflow</strong>：高层封装，自动根据 LLM 能力选择合适的 Agent 类型</li></ul><h3 id="4-3-Agent-类型"><a href="#4-3-Agent-类型" class="headerlink" title="4.3 Agent 类型"></a>4.3 Agent 类型</h3><table><thead><tr><th>类型</th><th>适用场景</th><th>特点</th></tr></thead><tbody><tr><td><strong>FunctionAgent</strong></td><td>LLM 支持函数调用时</td><td>直接使用原生 function calling，效率最高</td></tr><tr><td><strong>ReActAgent</strong></td><td>LLM 不支持函数调用时</td><td>通过 ReAct（推理+行动）循环执行</td></tr><tr><td><strong>CodeActAgent</strong></td><td>需要执行代码的场景</td><td>通过 <code>&lt;execute&gt;</code> 标签生成并执行代码</td></tr></tbody></table><h3 id="4-4-关键特性"><a href="#4-4-关键特性" class="headerlink" title="4.4 关键特性"></a>4.4 关键特性</h3><ul><li><strong>RAG + Agent 一体化</strong>：RAG 是核心能力而非补充，130+ 数据格式接入</li><li><strong>多智能体协作</strong>：原生支持多 Agent 交接（handoff）机制</li><li><strong>Context 持久化</strong>：工作流状态可序列化恢复，适合生产环境</li><li><strong>LlamaParse</strong>：企业级文档解析和 OCR</li><li><strong>300+ 集成包</strong>：覆盖主流 LLM、向量数据库、数据源</li></ul><h3 id="4-5-代码示例"><a href="#4-5-代码示例" class="headerlink" title="4.5 代码示例"></a>4.5 代码示例</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> llama_index.core <span class="keyword">import</span> VectorStoreIndex, SimpleDirectoryReader</span><br><span class="line"><span class="keyword">from</span> llama_index.core.agent.workflow <span class="keyword">import</span> FunctionAgent</span><br><span class="line"><span class="keyword">from</span> llama_index.llms.openai <span class="keyword">import</span> OpenAI</span><br><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"></span><br><span class="line"><span class="comment"># 构建 RAG 索引</span></span><br><span class="line">documents = SimpleDirectoryReader(<span class="string">&quot;data&quot;</span>).load_data()</span><br><span class="line">index = VectorStoreIndex.from_documents(documents)</span><br><span class="line">query_engine = index.as_query_engine()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义工具</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">multiply</span>(<span class="params">a: <span class="built_in">float</span>, b: <span class="built_in">float</span></span>) -&gt; <span class="built_in">float</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Multiply two numbers.&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> a * b</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">search_documents</span>(<span class="params">query: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Search documents for answers.&quot;&quot;&quot;</span></span><br><span class="line">    response = <span class="keyword">await</span> query_engine.aquery(query)</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">str</span>(response)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建智能体</span></span><br><span class="line">agent = FunctionAgent(</span><br><span class="line">    tools=[multiply, search_documents],</span><br><span class="line">    llm=OpenAI(model=<span class="string">&quot;gpt-4o-mini&quot;</span>),</span><br><span class="line">    system_prompt=<span class="string">&quot;You are a helpful assistant that can calculate and search documents.&quot;</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    response = <span class="keyword">await</span> agent.run(<span class="string">&quot;What did the author do in college? Also, what&#x27;s 7 * 8?&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(response)</span><br><span class="line"></span><br><span class="line">asyncio.run(main())</span><br></pre></td></tr></table></figure><h3 id="4-6-优势与局限"><a href="#4-6-优势与局限" class="headerlink" title="4.6 优势与局限"></a>4.6 优势与局限</h3><p><strong>优势：</strong> RAG + Agent 深度集成、事件驱动架构灵活、300+ 生态集成、多智能体支持、LlamaParse 企业级解析</p><p><strong>局限：</strong> 学习曲线较陡、框架较重、TS 版本功能覆盖不全、版本迭代快有 breaking changes、企业功能需付费</p><hr><h2 id="五、Dify"><a href="#五、Dify" class="headerlink" title="五、Dify"></a>五、Dify</h2><h3 id="5-1-简介"><a href="#5-1-简介" class="headerlink" title="5.1 简介"></a>5.1 简介</h3><p><strong>Dify</strong>（Do It For You）是一个开源的 LLM 应用开发平台，定位为<strong>智能体工作流构建器</strong>。将 Backend-as-a-Service 与 LLMOps 结合，让非技术用户和开发者都能快速构建 AI 应用。</p><table><thead><tr><th>项目信息</th><th>详情</th></tr></thead><tbody><tr><td>最新版本</td><td>v1.6.0+</td></tr><tr><td>许可证</td><td>Dify Open Source License (Apache 2.0+)</td></tr><tr><td>部署</td><td><code>docker compose up -d</code></td></tr><tr><td>GitHub</td><td><a href="https://github.com/langgenius/dify">langgenius&#x2F;dify</a></td></tr><tr><td>文档</td><td><a href="https://docs.dify.ai/en/use-dify/getting-started/introduction">docs.dify.ai</a></td></tr></tbody></table><h3 id="5-2-核心功能"><a href="#5-2-核心功能" class="headerlink" title="5.2 核心功能"></a>5.2 核心功能</h3><ul><li><strong>可视化工作流构建器</strong>：拖拽式画布，支持并行处理、条件分支、循环节点</li><li><strong>Agent 策略</strong>：支持 Function Calling、ReAct 和自定义策略插件</li><li><strong>RAG 管道</strong>：完整的数据源 → 处理 → 知识库 → 检索流程</li><li><strong>模型管理</strong>：无缝集成数百种 LLM，支持模型切换和性能比较</li><li><strong>Prompt IDE</strong>：直观的提示词编写界面</li><li><strong>LLMOps</strong>：监控和分析应用日志和性能</li></ul><h3 id="5-3-Agent-策略"><a href="#5-3-Agent-策略" class="headerlink" title="5.3 Agent 策略"></a>5.3 Agent 策略</h3><table><thead><tr><th>策略</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>Function Calling</strong></td><td>模型原生支持工具调用（如 GPT-4、Claude）</td></tr><tr><td><strong>ReAct</strong></td><td>模型不支持原生函数调用，或需要显式推理追踪</td></tr><tr><td><strong>自定义策略插件</strong></td><td>需要多轮工具调用等复杂行为</td></tr></tbody></table><h3 id="5-4-创建-Agent-的方式"><a href="#5-4-创建-Agent-的方式" class="headerlink" title="5.4 创建 Agent 的方式"></a>5.4 创建 Agent 的方式</h3><p>Dify 采用可视化&#x2F;无代码方式：</p><ol><li>在 Dify Studio 中创建 “Agent” 类型应用</li><li>选择 LLM 模型</li><li>设置 Agent 策略（自动检测 Function Calling 支持）</li><li>从 50+ 内置工具中选择或添加自定义工具</li><li>编写系统提示词</li><li>调试预览后一键发布</li></ol><h3 id="5-5-集成能力"><a href="#5-5-集成能力" class="headerlink" title="5.5 集成能力"></a>5.5 集成能力</h3><ul><li><strong>API</strong>：完整的 RESTful API，支持 SSE 流式响应</li><li><strong>SDK</strong>：Node.js、PHP、Java 客户端</li><li><strong>插件系统</strong>：模型、工具、Agent 策略、扩展、数据源、触发器六类插件</li><li><strong>MCP 集成</strong>：原生支持 Model Context Protocol</li><li><strong>部署</strong>：Docker Compose、Kubernetes、Terraform、AWS CDK</li></ul><h3 id="5-6-优势与局限"><a href="#5-6-优势与局限" class="headerlink" title="5.6 优势与局限"></a>5.6 优势与局限</h3><p><strong>优势：</strong> 低代码&#x2F;无代码、开箱即用（50+ 内置工具）、快速原型到生产、多模型支持、活跃社区（800+ 贡献者）</p><p><strong>局限：</strong> 自定义灵活性受限（不如代码框架）、执行有步骤&#x2F;时间限制、许可证非纯 Apache 2.0、平台锁定风险、高级推理模式不如专用框架成熟</p><hr><h2 id="六、OpenAI-Agents-SDK"><a href="#六、OpenAI-Agents-SDK" class="headerlink" title="六、OpenAI Agents SDK"></a>六、OpenAI Agents SDK</h2><h3 id="6-1-简介"><a href="#6-1-简介" class="headerlink" title="6.1 简介"></a>6.1 简介</h3><p><strong>OpenAI Agents SDK</strong> 是 OpenAI 官方推出的轻量级多智能体框架，从内部 Swarm 实验项目演化而来。核心理念是<strong>极简设计</strong>——只用 Agent &#x2F; Handoff &#x2F; Guardrail &#x2F; Tool 几个概念构建复杂工作流。</p><table><thead><tr><th>项目信息</th><th>详情</th></tr></thead><tbody><tr><td>最新版本</td><td>v0.14.6（2026-04-25）</td></tr><tr><td>许可证</td><td>MIT</td></tr><tr><td>安装</td><td><code>pip install openai-agents</code></td></tr><tr><td>GitHub</td><td><a href="https://github.com/openai/openai-agents-python">openai&#x2F;openai-agents-python</a></td></tr><tr><td>文档</td><td><a href="https://openai.github.io/openai-agents-python">openai.github.io&#x2F;openai-agents-python</a></td></tr></tbody></table><h3 id="6-2-核心概念"><a href="#6-2-核心概念" class="headerlink" title="6.2 核心概念"></a>6.2 核心概念</h3><ul><li><strong>Agent</strong>：配置了指令、工具、护栏和交接能力的 LLM</li><li><strong>Runner</strong>：Agent 执行器，提供 <code>run()</code>（异步）、<code>run_sync()</code>（同步）、<code>run_streamed()</code>（流式）</li><li><strong>Handoff</strong>：Agent 间的任务委托，被委托者继承完整对话历史</li><li><strong>Guardrails</strong>：安全护栏，分输入护栏、输出护栏、工具护栏三类</li><li><strong>Tools</strong>：支持函数工具、MCP 工具、OpenAI 托管工具、Agent as Tool</li></ul><h3 id="6-3-关键特性"><a href="#6-3-关键特性" class="headerlink" title="6.3 关键特性"></a>6.3 关键特性</h3><ul><li><strong>极简设计</strong>：核心原语少，学习曲线平缓</li><li><strong>Provider 无关</strong>：通过 any-llm &#x2F; LiteLLM 支持 100+ LLM</li><li><strong>三层护栏</strong>：输入 → 输出 → 工具级别的安全校验</li><li><strong>内置追踪（Tracing）</strong>：可视化调试 Agent 运行流程</li><li><strong>Realtime Agents</strong>：支持构建语音 Agent（gpt-realtime-1.5）</li><li><strong>Sandbox Agents</strong>：v0.14.0 新增，在容器环境中执行代码</li><li><strong>结构化输出</strong>：通过 Pydantic Model 定义 output_type</li></ul><h3 id="6-4-代码示例"><a href="#6-4-代码示例" class="headerlink" title="6.4 代码示例"></a>6.4 代码示例</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">from</span> agents <span class="keyword">import</span> Agent, Runner, function_tool</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义工具</span></span><br><span class="line"><span class="meta">@function_tool</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_weather</span>(<span class="params">city: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;获取指定城市的天气信息。&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">f&quot;The weather in <span class="subst">&#123;city&#125;</span> is sunny.&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义专业 Agent</span></span><br><span class="line">billing_agent = Agent(</span><br><span class="line">    name=<span class="string">&quot;Billing Agent&quot;</span>,</span><br><span class="line">    instructions=<span class="string">&quot;你是账单问题专家。&quot;</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">refund_agent = Agent(</span><br><span class="line">    name=<span class="string">&quot;Refund Agent&quot;</span>,</span><br><span class="line">    instructions=<span class="string">&quot;你是退款问题专家。&quot;</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义分流 Agent</span></span><br><span class="line">triage_agent = Agent(</span><br><span class="line">    name=<span class="string">&quot;Triage Agent&quot;</span>,</span><br><span class="line">    instructions=<span class="string">&quot;根据用户问题路由到正确的专业 Agent：账单 -&gt; Billing Agent；退款 -&gt; Refund Agent。&quot;</span>,</span><br><span class="line">    handoffs=[billing_agent, refund_agent],</span><br><span class="line">    tools=[get_weather],</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    result = <span class="keyword">await</span> Runner.run(</span><br><span class="line">        triage_agent,</span><br><span class="line">        <span class="string">&quot;我的订阅被扣了两次费用，请帮我处理。&quot;</span>,</span><br><span class="line">    )</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;最终回答: <span class="subst">&#123;result.final_output&#125;</span>&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;处理 Agent: <span class="subst">&#123;result.last_agent.name&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">asyncio.run(main())</span><br></pre></td></tr></table></figure><h3 id="6-5-优势与局限"><a href="#6-5-优势与局限" class="headerlink" title="6.5 优势与局限"></a>6.5 优势与局限</h3><p><strong>优势：</strong> 官方维护、极简设计、Provider 无关、三层护栏、内置追踪、语音 Agent 支持</p><p><strong>局限：</strong> 仍处 0.x 阶段 API 可能变动、深度依赖 OpenAI 生态、不支持并行 Agent 执行、无内置持久化记忆系统</p><hr><h2 id="七、Google-ADK"><a href="#七、Google-ADK" class="headerlink" title="七、Google ADK"></a>七、Google ADK</h2><h3 id="7-1-简介"><a href="#7-1-简介" class="headerlink" title="7.1 简介"></a>7.1 简介</h3><p><strong>Google ADK（Agent Development Kit）</strong> 是 Google 推出的开源、代码优先的 Agent 开发框架。设计理念是让 AI Agent 开发更像传统软件开发，针对 Gemini 和 Google Cloud 优化，但保持模型无关和部署无关。</p><table><thead><tr><th>项目信息</th><th>详情</th></tr></thead><tbody><tr><td>最新版本</td><td>v1.31.1（2026-04-30）</td></tr><tr><td>许可证</td><td>Apache 2.0</td></tr><tr><td>安装</td><td><code>pip install google-adk</code></td></tr><tr><td>GitHub</td><td><a href="https://github.com/google/adk-python">google&#x2F;adk-python</a></td></tr><tr><td>文档</td><td><a href="https://google.github.io/adk-docs/">google.github.io&#x2F;adk-docs</a></td></tr></tbody></table><h3 id="7-2-核心概念"><a href="#7-2-核心概念" class="headerlink" title="7.2 核心概念"></a>7.2 核心概念</h3><ul><li><strong>LlmAgent</strong>（别名 <code>Agent</code>）：核心构建块，组合 LLM 模型 + 指令 + 工具</li><li><strong>SequentialAgent</strong>：按顺序依次执行子 Agent（管道式）</li><li><strong>ParallelAgent</strong>：并发执行多个子 Agent</li><li><strong>LoopAgent</strong>：重复执行子 Agent，支持退出条件</li><li><strong>sub_agents</strong>：通过嵌套构建层级式多 Agent 架构</li></ul><h3 id="7-3-关键特性"><a href="#7-3-关键特性" class="headerlink" title="7.3 关键特性"></a>7.3 关键特性</h3><ul><li><strong>多 Agent 编排</strong>：顺序、并行、循环和 LLM 驱动的动态路由</li><li><strong>内置工具</strong>：Google Search、Vertex AI Search、代码执行器等</li><li><strong>Google 生态集成</strong>：原生 Gemini、Vertex AI Agent Engine、Cloud Run</li><li><strong>灵活部署</strong>：本地、Agent Engine（全托管）、Cloud Run、GKE、Docker</li><li><strong>内置评估</strong>：CLI 工具 <code>adk eval</code> 系统化评估 Agent 性能</li><li><strong>A2A 协议</strong>：支持 Agent-to-Agent 远程通信</li><li><strong>生命周期回调</strong>：<code>before/after_agent</code>、<code>before/after_model</code>、<code>before/after_tool</code> 钩子</li></ul><h3 id="7-4-代码示例"><a href="#7-4-代码示例" class="headerlink" title="7.4 代码示例"></a>7.4 代码示例</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"><span class="keyword">from</span> google.adk.agents <span class="keyword">import</span> Agent, SequentialAgent</span><br><span class="line"><span class="keyword">from</span> google.adk.runners <span class="keyword">import</span> Runner</span><br><span class="line"><span class="keyword">from</span> google.adk.sessions <span class="keyword">import</span> InMemorySessionService</span><br><span class="line"><span class="keyword">from</span> google.genai <span class="keyword">import</span> types</span><br><span class="line"><span class="keyword">from</span> google.adk.tools <span class="keyword">import</span> google_search</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义天气 Agent</span></span><br><span class="line">weather_agent = Agent(</span><br><span class="line">    name=<span class="string">&quot;weather_assistant&quot;</span>,</span><br><span class="line">    model=<span class="string">&quot;gemini-2.5-flash&quot;</span>,</span><br><span class="line">    instruction=<span class="string">&quot;你是一个天气查询助手。使用 Google 搜索查找最新天气信息。&quot;</span>,</span><br><span class="line">    tools=[google_search],</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义翻译 Agent</span></span><br><span class="line">translate_agent = Agent(</span><br><span class="line">    name=<span class="string">&quot;translate_assistant&quot;</span>,</span><br><span class="line">    model=<span class="string">&quot;gemini-2.5-flash&quot;</span>,</span><br><span class="line">    instruction=<span class="string">&quot;你是一个翻译助手，将内容翻译成中文。&quot;</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 组合成顺序工作流</span></span><br><span class="line">pipeline = SequentialAgent(</span><br><span class="line">    name=<span class="string">&quot;WeatherPipeline&quot;</span>,</span><br><span class="line">    sub_agents=[weather_agent, translate_agent],</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行</span></span><br><span class="line">session_service = InMemorySessionService()</span><br><span class="line">runner = Runner(agent=pipeline, app_name=<span class="string">&quot;weather_app&quot;</span>, session_service=session_service)</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">run_agent</span>(<span class="params">query: <span class="built_in">str</span></span>):</span><br><span class="line">    session = session_service.create_session(</span><br><span class="line">        app_name=<span class="string">&quot;weather_app&quot;</span>, user_id=<span class="string">&quot;user_1&quot;</span>, session_id=<span class="string">&quot;session_1&quot;</span></span><br><span class="line">    )</span><br><span class="line">    content = types.Content(role=<span class="string">&#x27;user&#x27;</span>, parts=[types.Part(text=query)])</span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">for</span> event <span class="keyword">in</span> runner.run_async(</span><br><span class="line">        user_id=<span class="string">&quot;user_1&quot;</span>, session_id=<span class="string">&quot;session_1&quot;</span>, new_message=content</span><br><span class="line">    ):</span><br><span class="line">        <span class="keyword">if</span> event.is_final_response() <span class="keyword">and</span> event.content <span class="keyword">and</span> event.content.parts:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;Agent 回复: <span class="subst">&#123;event.content.parts[<span class="number">0</span>].text.strip()&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">asyncio.run(run_agent(<span class="string">&quot;What&#x27;s the weather in Tokyo today?&quot;</span>))</span><br></pre></td></tr></table></figure><h3 id="7-5-优势与局限"><a href="#7-5-优势与局限" class="headerlink" title="7.5 优势与局限"></a>7.5 优势与局限</h3><p><strong>优势：</strong> 代码优先、强大编排能力（顺序&#x2F;并行&#x2F;循环）、Google 生态深度集成、内置评估、多语言支持（Python&#x2F;Java&#x2F;Go&#x2F;TS）、Apache 2.0 开源</p><p><strong>局限：</strong> 最佳体验需 Gemini 和 Google Cloud、框架较新社区生态初期、高频发布 API 可能变动、中国大陆访问 Google 服务受限</p><hr><h2 id="八、框架选型指南"><a href="#八、框架选型指南" class="headerlink" title="八、框架选型指南"></a>八、框架选型指南</h2><h3 id="按使用场景选择"><a href="#按使用场景选择" class="headerlink" title="按使用场景选择"></a>按使用场景选择</h3><table><thead><tr><th>场景</th><th>推荐框架</th><th>理由</th></tr></thead><tbody><tr><td><strong>复杂有状态工作流</strong></td><td>LangGraph</td><td>底层图编排、持久化、时间旅行</td></tr><tr><td><strong>多角色团队协作</strong></td><td>CrewAI</td><td>角色扮演设计、委派机制、记忆系统</td></tr><tr><td><strong>RAG + Agent</strong></td><td>LlamaIndex</td><td>RAG 深度集成、130+ 数据格式、文档解析</td></tr><tr><td><strong>快速原型 &#x2F; 非技术团队</strong></td><td>Dify</td><td>可视化拖拽、低代码、开箱即用</td></tr><tr><td><strong>OpenAI 模型为主</strong></td><td>OpenAI Agents SDK</td><td>官方维护、极简 API、追踪调试</td></tr><tr><td><strong>Google Cloud 部署</strong></td><td>Google ADK</td><td>Gemini 优化、Vertex AI 集成、内置评估</td></tr><tr><td><strong>需要精细控制</strong></td><td>LangGraph &#x2F; Google ADK</td><td>底层 API、回调钩子</td></tr><tr><td><strong>需要生产级护栏</strong></td><td>OpenAI Agents SDK</td><td>三层 Guardrails</td></tr></tbody></table><h3 id="按团队特点选择"><a href="#按团队特点选择" class="headerlink" title="按团队特点选择"></a>按团队特点选择</h3><table><thead><tr><th>团队特点</th><th>推荐</th></tr></thead><tbody><tr><td>全栈开发团队</td><td>LangGraph、Google ADK</td></tr><tr><td>Python 数据科学团队</td><td>CrewAI、LlamaIndex</td></tr><tr><td>产品经理 &#x2F; 运营团队</td><td>Dify</td></tr><tr><td>OpenAI 生态重度用户</td><td>OpenAI Agents SDK</td></tr><tr><td>Google Cloud 用户</td><td>Google ADK</td></tr><tr><td>需要快速验证想法</td><td>Dify、OpenAI Agents SDK</td></tr></tbody></table><blockquote><p><strong>注意</strong>：以上框架信息基于 2026 年 4 月的调研，各框架迭代较快，建议使用前查看官方文档获取最新信息。</p></blockquote>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/AI/">AI</category>
      
      <category domain="https://eugenepage.com/tags/OpenAI/">OpenAI</category>
      
      <category domain="https://eugenepage.com/tags/Framework/">Framework</category>
      
      <category domain="https://eugenepage.com/tags/Agent/">Agent</category>
      
      <category domain="https://eugenepage.com/tags/LangGraph/">LangGraph</category>
      
      <category domain="https://eugenepage.com/tags/CrewAI/">CrewAI</category>
      
      <category domain="https://eugenepage.com/tags/LlamaIndex/">LlamaIndex</category>
      
      <category domain="https://eugenepage.com/tags/Dify/">Dify</category>
      
      <category domain="https://eugenepage.com/tags/GoogleADK/">GoogleADK</category>
      
      
      <comments>https://eugenepage.com/zh-CN/2026/04/30/20260430.AIAgentFrameworkResearchNotes/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Tile Explorer Web — 24h AI GameDev Hackathon Project (Software)</title>
      <link>https://eugenepage.com/2026/04/28/20260428.TileExplorerWeb/</link>
      <guid>https://eugenepage.com/2026/04/28/20260428.TileExplorerWeb/</guid>
      <pubDate>Tue, 28 Apr 2026 04:00:00 GMT</pubDate>
      
        
        
      <description>&lt;p&gt;Tile Explorer is a browser-based tile-matching puzzle game I built in 24 hours. The entire project runs on a purely native web stack — Pi</description>
        
      
      
      
      <content:encoded><![CDATA[<p>Tile Explorer is a browser-based tile-matching puzzle game I built in 24 hours. The entire project runs on a purely native web stack — PixiJS (loaded via CDN) for rendering, Web Audio API for procedurally synthesized sound effects, zero build tools, zero npm dependencies. Double-click <code>index.html</code> and it just runs. The game is deployed on GitHub Pages, with a live leaderboard powered by Supabase’s free tier. Total hosting cost: ¥0&#x2F;month.</p><div style="position: relative; width: 100%; padding-bottom: 75%; margin: 20px 0; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 16px rgba(0,0,0,0.15);">  <iframe src="https://youdrew.github.io/24h-AI-GameDevTest/" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none;" loading="lazy" allow="autoplay"></iframe></div><p style="text-align: center; font-size: 13px; color: #888; margin-top: 4px;">↑ Playable right here (requires an internet connection to load)</p><p><strong>Core gameplay</strong>: Patterned tiles are stacked across the board. Tap an accessible tile to send it into a 7-slot collection tray at the bottom. Match 3 identical patterns and they clear automatically. Clear every tile from the board to complete the level.</p><p>Key highlights of the project:</p><ol><li><strong>Mathematically guaranteed solvability</strong>: Level layouts are generated from a difficulty formula where total tile count &#x3D; <code>patternTypes × setsPerType × 3</code>, which structurally ensures every pattern appears in multiples of three. A backtracking solver runs inside a Web Worker to forward-validate each layout — only layouts with a confirmed solution path are accepted. The solver also records the optimal move count, which serves as the star-rating baseline.</li><li><strong>Procedural audio synthesis</strong>: Every interactive sound effect — taps, clears, combos, power-ups, warnings — is synthesized in real time via the Web Audio API. Zero audio files, zero network requests. Combo sounds are built on a C-major chord progression system, progressively brightening from triangle waves to sawtooth waves to give players a satisfying sense of escalating momentum. When BGM is playing, sound effects auto-duck by 6dB and smoothly recover over 200ms.</li><li><strong>Data-driven architecture</strong>: Difficulty curves, power-up properties, and theme configurations are all declarative, editable config tables. A designer can tune difficulty curves and power-up parameters by editing JS config files directly — no touching game logic code. Six visual themes each have their own library of 32 emoji patterns, a background image, and a BGM track; themes rotate automatically every 3 levels.</li><li><strong>PWA + offline support</strong>: Full Progressive Web App support is implemented — installable to a phone’s home screen and fully playable offline. The Service Worker uses a three-tier caching strategy: precached static assets, cache-first for CDN resources, and Stale-While-Revalidate for theme media. Dual-CDN failover provides automatic fallback.</li><li><strong>Zero-cost online leaderboard</strong>: Built on Supabase’s free tier (PostgreSQL + REST API). A UUID is auto-generated on first visit and stored in localStorage — no account required. The database enforces row-level security (RLS); the client holds only the anon key. All input goes through dual regex validation plus XSS sanitization. Scores earned offline are queued locally and submitted automatically once connectivity is restored.</li></ol><p>On the engineering side: tile occlusion uses spatial hashing (O(n) instead of O(n²)); clear particle effects use a pre-allocated object pool to avoid GC jitter; opacity calculations follow an exponential decay model based on the Weber–Fechner law; and the collection slots use a smart clustering insertion algorithm to help players quickly spot matching opportunities.</p><p>The entire project was completed within 24 hours. My own code spans 14 JS modules + 3 CSS files + 1 HTML file, covering 10,000 levels, 6 power-up types, and 6 themes. AI assistance generated the vast majority of the code, along with all audio synthesis parameters and BGM assets. My role focused on architecture design, requirements refinement, data structure design, and overall code quality.</p>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/SoftwareProjects/">SoftwareProjects</category>
      
      <category domain="https://eugenepage.com/tags/WebDevelopment/">WebDevelopment</category>
      
      <category domain="https://eugenepage.com/tags/GameDev/">GameDev</category>
      
      
      <comments>https://eugenepage.com/2026/04/28/20260428.TileExplorerWeb/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Tile Explorer Web — 24h AI GameDev 马拉松作品 (软件作品)</title>
      <link>https://eugenepage.com/zh-CN/2026/04/28/20260428.TileExplorerWeb/</link>
      <guid>https://eugenepage.com/zh-CN/2026/04/28/20260428.TileExplorerWeb/</guid>
      <pubDate>Tue, 28 Apr 2026 04:00:00 GMT</pubDate>
      
        
        
      <description>&lt;p&gt;Tile Explorer 是我在 24 小时内完成的一款浏览器三消瓦片解谜游戏。整个项目完全采用 Web 原生技术栈开发，渲染引擎使用 PixiJS（CDN 引入），音效通过 Web Audio API 程序化合成，零构建工具、零 npm 依赖——双击 &lt;code&gt;ind</description>
        
      
      
      
      <content:encoded><![CDATA[<p>Tile Explorer 是我在 24 小时内完成的一款浏览器三消瓦片解谜游戏。整个项目完全采用 Web 原生技术栈开发，渲染引擎使用 PixiJS（CDN 引入），音效通过 Web Audio API 程序化合成，零构建工具、零 npm 依赖——双击 <code>index.html</code> 即可运行。游戏已部署至 GitHub Pages，后端使用 Supabase 免费层实现在线排行榜，整体运维成本为 0 元&#x2F;月。</p><div style="position: relative; width: 100%; padding-bottom: 75%; margin: 20px 0; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 16px rgba(0,0,0,0.15);">  <iframe src="https://youdrew.github.io/24h-AI-GameDevTest/" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none;" loading="lazy" allow="autoplay"></iframe></div><p style="text-align: center; font-size: 13px; color: #888; margin-top: 4px;">↑ 上方可直接游玩（需要联网加载）</p><p><strong>核心玩法</strong>：版面上堆叠着带有图案的瓦片，点击可用瓦片将其送入底部 7 格收集槽，凑齐 3 个相同图案自动消除，清空版面上所有瓦片即通关。</p><p>项目的主要亮点：</p><ol><li><strong>数学可解性保证</strong>：关卡布局由难度公式推导生成，瓦片总数 &#x3D; <code>patternTypes × setsPerType × 3</code>，从根本上保证每种图案数量均为 3 的倍数。同时，Web Worker 中运行回溯求解器对每个布局做正向验证，只有确认存在通关路径才会采用，并记录最优步数作为星级评分基准。</li><li><strong>程序化音效合成</strong>：所有交互音效（点击、消除、连击、道具、警告等）均通过 Web Audio API 实时合成，零音频文件、零网络请求。连击音效基于 C 大调和弦递进系统设计，从三角波到锯齿波逐渐变亮，给玩家”蓄力”的感知。BGM 播放时音效自动 Ducking（降 6dB），200ms 后平滑恢复。</li><li><strong>数据驱动架构</strong>：难度曲线、道具属性、主题配置均为可编辑的声明式配置表。策划可直接修改 JS 配置文件调整难度曲线和道具参数，无需触碰游戏逻辑代码。6 套视觉主题各有独立的 32 emoji 图案库、背景图和 BGM，每 3 关自动轮换。</li><li><strong>PWA + 离线支持</strong>：实现了完整的 Progressive Web App 支持——可安装到手机主屏幕、支持完全离线游玩。Service Worker 采用三级缓存策略（静态资源预缓存、CDN 资源缓存优先、主题媒体 Stale-While-Revalidate），双 CDN 容灾自动回退。</li><li><strong>零成本在线排行榜</strong>：使用 Supabase 免费层（PostgreSQL + REST API），首次访问自动生成 UUID 存入 localStorage，无需注册。数据库启用行级安全（RLS），客户端仅持有 anon key，输入经双重正则校验 + XSS 清洗。离线成绩存入本地队列，联网后自动提交。</li></ol><p>工程方面，瓦片覆盖关系使用空间哈希（O(n) 替代 O(n²)），消除特效使用预分配粒子对象池避免 GC 抖动，透明度计算遵循韦伯-费希纳定律的指数衰减模型，槽位采用智能聚类插入算法帮助玩家快速识别匹配机会。</p><p>整个项目在 24 小时内完成，自有代码 14 个 JS 模块 + 3 个 CSS + 1 个 HTML，覆盖 10,000 关、6 种道具、6 套主题。过程中 AI 辅助生成了绝大部分代码与全部音效参数、BGM 资产，我主要负责架构设计、需求梳理、数据结构设计及代码质量把控。</p>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/SoftwareProjects/">SoftwareProjects</category>
      
      <category domain="https://eugenepage.com/tags/WebDevelopment/">WebDevelopment</category>
      
      <category domain="https://eugenepage.com/tags/GameDev/">GameDev</category>
      
      
      <comments>https://eugenepage.com/zh-CN/2026/04/28/20260428.TileExplorerWeb/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Veil Lingo — Online English Education Platform (Software Project)</title>
      <link>https://eugenepage.com/2026/04/18/20260428.VeilLingo/</link>
      <guid>https://eugenepage.com/2026/04/18/20260428.VeilLingo/</guid>
      <pubDate>Sat, 18 Apr 2026 04:00:00 GMT</pubDate>
      
        
        
      <description>&lt;p&gt;Veil Lingo is a live one-on-one English speaking education platform targeting Chinese learners, connecting them with professional teacher</description>
        
      
      
      
      <content:encoded><![CDATA[<p>Veil Lingo is a live one-on-one English speaking education platform targeting Chinese learners, connecting them with professional teachers from English-speaking countries. The platform name draws from John Rawls’ philosophical concept of the “veil of ignorance” — the idea being to create a fair, transparent teaching marketplace where the quality of instruction itself becomes the core basis for pricing. The project is deployed and live at <a href="https://talk-lingo.com/">talk-lingo.com</a>.</p><div style="position: relative; width: 100%; padding-bottom: 65%; margin: 20px 0; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 16px rgba(0,0,0,0.15);">  <iframe src="https://talk-lingo.com" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none;" loading="lazy"></iframe></div><p style="text-align: center; font-size: 13px; color: #888; margin-top: 4px;">↑ Live site preview above (or visit <a href="https://talk-lingo.com" target="_blank">talk-lingo.com</a> directly)</p><p>The project covers three user-facing portals: a student portal (browse teachers, book lessons, credit wallet, review system), a teacher portal (personal profile, calendar scheduling, earnings dashboard, rating feedback), and an admin backend (data dashboard, teacher approval, review moderation, violation management, system parameter configuration) — totaling 28+ pages and 34+ components.</p><p>Key technical highlights:</p><ol><li><strong>Dynamic Pricing and Salary Algorithm</strong>: The platform’s core differentiating design. Lesson prices float dynamically based on a teacher’s booking rate — high-demand teachers see prices automatically rise, while prices pull back when demand is low, creating a positive incentive loop. Teacher salaries are similarly auto-adjusted based on demand and ratings, ensuring top teachers earn higher returns. All parameters are configurable in the admin backend, so strategy adjustments require no code changes.</li><li><strong>Pairwise Comparison Review System</strong>: Students can evaluate two teachers they’ve taken lessons with in a head-to-head comparison. This produces more reliable quality signals than traditional independent scoring, helping the platform more accurately identify differences in teaching ability.</li><li><strong>Multi-Dimensional Radar Chart Scoring</strong>: Teacher evaluations span multiple teaching dimensions, visualized as radar charts. This gives students an intuitive view of a teacher’s style and strengths, and provides teachers with clear direction for improvement.</li><li><strong>Mainland China Network Optimization</strong>: Geo-aware routing via Cloudflare Workers automatically selects the optimal access path for mainland users, reducing latency and improving availability.</li><li><strong>Full Internationalization Support</strong>: Complete bilingual coverage in Chinese and English, with 874 translation keys managing all user-facing copy through a translation system.</li></ol><p>On the tech stack side, the frontend uses Next.js (App Router + Server Components) + TypeScript + Tailwind CSS + shadcn&#x2F;ui. The backend runs on Supabase (PostgreSQL + Auth + Storage + Realtime), with Row-Level Security enforcing data access control. The app is deployed on Vercel, with Cloudflare handling CDN and DNS. The entire project was built from scratch to production launch, covering full-stack development end to end: database design (21 tables + 26 migration scripts), authentication and authorization, payment wallet, scheduled jobs, SEO optimization, and more.</p>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/SoftwareProjects/">SoftwareProjects</category>
      
      <category domain="https://eugenepage.com/tags/WebDevelopment/">WebDevelopment</category>
      
      <category domain="https://eugenepage.com/tags/FullStack/">FullStack</category>
      
      
      <comments>https://eugenepage.com/2026/04/18/20260428.VeilLingo/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Veil Lingo — 在线英语教育平台 (软件作品)</title>
      <link>https://eugenepage.com/zh-CN/2026/04/18/20260428.VeilLingo/</link>
      <guid>https://eugenepage.com/zh-CN/2026/04/18/20260428.VeilLingo/</guid>
      <pubDate>Sat, 18 Apr 2026 04:00:00 GMT</pubDate>
      
        
        
      <description>&lt;p&gt;Veil Lingo（无知之幕）是一个已上线的在线一对一口语教育平台，面向中国英语学习者，连接来自英语国家的专业教师。平台名取自约翰·罗尔斯的「无知之幕」哲学概念——意在创造一个公平、透明的教学市场，让教学质量本身成为定价的核心依据。项目已部署上线，域名为 &lt;a href=</description>
        
      
      
      
      <content:encoded><![CDATA[<p>Veil Lingo（无知之幕）是一个已上线的在线一对一口语教育平台，面向中国英语学习者，连接来自英语国家的专业教师。平台名取自约翰·罗尔斯的「无知之幕」哲学概念——意在创造一个公平、透明的教学市场，让教学质量本身成为定价的核心依据。项目已部署上线，域名为 <a href="https://talk-lingo.com/">talk-lingo.com</a>。</p><div style="position: relative; width: 100%; padding-bottom: 65%; margin: 20px 0; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 16px rgba(0,0,0,0.15);">  <iframe src="https://talk-lingo.com" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none;" loading="lazy"></iframe></div><p style="text-align: center; font-size: 13px; color: #888; margin-top: 4px;">↑ 上方为线上实站点预览（也可直接访问 <a href="https://talk-lingo.com" target="_blank">talk-lingo.com</a>）</p><p>项目包含三个用户端：学生端（浏览教师、预约课程、信用钱包、评价系统）、教师端（个人档案、日历排班、收入看板、评分反馈）和管理后台（数据看板、教师审批、评价审核、违规管理、系统参数配置），合计 28+ 个页面、34+ 个组件。</p><p>技术上的主要亮点：</p><ol><li><strong>动态定价与薪资算法</strong>：平台核心差异化设计。课程价格根据教师预约率动态浮动——高需求教师价格自动上调，低需求时回调，形成正向激励循环。教师薪资同样根据需求与评价自动调节，确保优秀教师获得更高回报。所有参数可在管理后台配置，无需改代码即可调整策略。</li><li><strong>配对比较评价系统</strong>：学生可以对上过课的两位教师进行头对头对比评价，比传统独立评分能产生更可靠的质量信号，帮助平台更准确地识别教学水平差异。</li><li><strong>多维度雷达图评分</strong>：教师评价覆盖多个教学维度，通过雷达图可视化呈现，帮助学生直观了解教师的教学风格和强项，也为教师提供清晰的改进方向。</li><li><strong>中国大陆网络优化</strong>：通过 Cloudflare Workers 实现地理感知路由，针对大陆用户自动选择最优访问路径，降低延迟并提升可用性。</li><li><strong>完整的国际化支持</strong>：中英双语全覆盖，874 个翻译键，所有面向用户的文案均通过翻译系统管理。</li></ol><p>技术栈方面，前端采用 Next.js（App Router + Server Components）+ TypeScript + Tailwind CSS + shadcn&#x2F;ui，后端使用 Supabase（PostgreSQL + Auth + Storage + Realtime），通过 Row-Level Security 确保数据安全。部署在 Vercel 上，Cloudflare 提供 CDN 和 DNS 服务。整个项目从零到上线，涉及完整的全栈开发：数据库设计（21 张表 + 26 个迁移脚本）、认证授权、支付钱包、定时任务、SEO 优化等。</p>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/SoftwareProjects/">SoftwareProjects</category>
      
      <category domain="https://eugenepage.com/tags/WebDevelopment/">WebDevelopment</category>
      
      <category domain="https://eugenepage.com/tags/FullStack/">FullStack</category>
      
      
      <comments>https://eugenepage.com/zh-CN/2026/04/18/20260428.VeilLingo/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Hermes Agent Research Notes</title>
      <link>https://eugenepage.com/2026/04/16/20260416.Hermes%20Agent/</link>
      <guid>https://eugenepage.com/2026/04/16/20260416.Hermes%20Agent/</guid>
      <pubDate>Thu, 16 Apr 2026 02:00:00 GMT</pubDate>
      
        
        
      <description>&lt;h1 id=&quot;Hermes-Agent-Research-Notes&quot;&gt;&lt;a href=&quot;#Hermes-Agent-Research-Notes&quot; class=&quot;headerlink&quot; title=&quot;Hermes Agent Research Notes&quot;&gt;&lt;/a&gt;Herme</description>
        
      
      
      
      <content:encoded><![CDATA[<h1 id="Hermes-Agent-Research-Notes"><a href="#Hermes-Agent-Research-Notes" class="headerlink" title="Hermes Agent Research Notes"></a>Hermes Agent Research Notes</h1><h2 id="1-Project-Overview"><a href="#1-Project-Overview" class="headerlink" title="1. Project Overview"></a>1. Project Overview</h2><p><strong>Hermes Agent</strong> is an open-source, self-learning AI agent framework developed by <a href="https://github.com/NousResearch">Nous Research</a>.</p><table><thead><tr><th>Project Info</th><th>Details</th></tr></thead><tbody><tr><td>Initial Release</td><td>2026-02-25 (v0.1.0)</td></tr><tr><td>Current Version</td><td>v0.8.0 (2026-04-08)</td></tr><tr><td>GitHub Stars</td><td>22k+</td></tr><tr><td>License</td><td>MIT</td></tr><tr><td>Language</td><td>Python</td></tr></tbody></table><p>Core philosophy: <strong>an agent should grow alongside its user</strong> — through a built-in learning loop, it creates skills from experience and continuously improves. The more you use it, the better it gets.</p><h2 id="2-Core-Features"><a href="#2-Core-Features" class="headerlink" title="2. Core Features"></a>2. Core Features</h2><h3 id="2-1-Self-Learning-Feedback-Loop"><a href="#2-1-Self-Learning-Feedback-Loop" class="headerlink" title="2.1 Self-Learning Feedback Loop"></a>2.1 Self-Learning Feedback Loop</h3><ul><li>Automatically creates reusable <strong>Skill documents</strong> after completing complex tasks</li><li>Skills self-iterate and improve through usage</li><li>Built-in FTS5 full-text search + LLM summarization for cross-session memory recall</li><li>Honcho-based user modeling to understand who you are</li></ul><h3 id="2-2-Multi-Platform-Integration"><a href="#2-2-Multi-Platform-Integration" class="headerlink" title="2.2 Multi-Platform Integration"></a>2.2 Multi-Platform Integration</h3><p>A single Gateway process covers: Telegram, Discord, Slack, WhatsApp, Signal, Email. Supports voice memo transcription with continuous cross-platform conversations.</p><h3 id="2-3-Terminal-Interface"><a href="#2-3-Terminal-Interface" class="headerlink" title="2.3 Terminal Interface"></a>2.3 Terminal Interface</h3><p>Full TUI: multi-line editing, slash command completion, conversation history, interrupt redirection, and streaming tool output.</p><h3 id="2-4-Model-Agnostic"><a href="#2-4-Model-Agnostic" class="headerlink" title="2.4 Model-Agnostic"></a>2.4 Model-Agnostic</h3><p>Supports Nous Portal, OpenRouter (200+ models), OpenAI, Anthropic, Hugging Face, Xiaomi MiMo, and more. Switch with <code>hermes model</code> — zero code changes required.</p><h3 id="2-5-Scheduled-Tasks"><a href="#2-5-Scheduled-Tasks" class="headerlink" title="2.5 Scheduled Tasks"></a>2.5 Scheduled Tasks</h3><p>Built-in Cron scheduler. Define scheduled tasks in natural language (daily digests, backups, audits) and results are automatically delivered to any platform.</p><h3 id="2-6-Parallel-Sub-Agents"><a href="#2-6-Parallel-Sub-Agents" class="headerlink" title="2.6 Parallel Sub-Agents"></a>2.6 Parallel Sub-Agents</h3><p>Spawn isolated sub-agents for parallel workflows. Supports Python scripts that call tools via RPC, compressing multi-step pipelines into single-turn operations with zero context overhead.</p><h3 id="2-7-Flexible-Deployment"><a href="#2-7-Flexible-Deployment" class="headerlink" title="2.7 Flexible Deployment"></a>2.7 Flexible Deployment</h3><p>6 terminal backends: Local, Docker, SSH, Daytona, Singularity, Modal. Serverless on-demand wake-up keeps idle costs near zero.</p><h2 id="3-Quick-Start"><a href="#3-Quick-Start" class="headerlink" title="3. Quick Start"></a>3. Quick Start</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Install (supports Linux / macOS / WSL2 / Termux)</span></span><br><span class="line">curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash</span><br><span class="line"></span><br><span class="line"><span class="comment"># Start</span></span><br><span class="line"><span class="built_in">source</span> ~/.bashrc</span><br><span class="line">hermes              <span class="comment"># Start a conversation</span></span><br><span class="line">hermes model        <span class="comment"># Select a model</span></span><br><span class="line">hermes tools        <span class="comment"># Configure tools</span></span><br><span class="line">hermes gateway      <span class="comment"># Start the message gateway</span></span><br><span class="line">hermes setup        <span class="comment"># Full setup wizard</span></span><br></pre></td></tr></table></figure><h2 id="4-Comparison-with-OpenClaw"><a href="#4-Comparison-with-OpenClaw" class="headerlink" title="4. Comparison with OpenClaw"></a>4. Comparison with OpenClaw</h2><p><a href="https://github.com/openclaw/openclaw">OpenClaw</a> (formerly Clawdbot&#x2F;MoltBot) was released in January 2026 by Austrian engineer Peter Steinberger, and is the hottest open-source agent project of 2026 (200k+ Stars). Hermes has a clear lineage connection — it even ships a built-in OpenClaw migration tool (<code>hermes claw migrate</code>).</p><table><thead><tr><th>Dimension</th><th>Hermes Agent</th><th>OpenClaw</th></tr></thead><tbody><tr><td>Release Date</td><td>2026-02</td><td>2026-01</td></tr><tr><td>Developer</td><td>Nous Research (team)</td><td>Peter Steinberger (solo start)</td></tr><tr><td>GitHub Stars</td><td>22k+</td><td>200k+</td></tr><tr><td>Core Philosophy</td><td><strong>Self-learning loop</strong> — builds skills from experience, continuously iterates</td><td><strong>Autonomous execution</strong> — completes real tasks on behalf of the user</td></tr><tr><td>Skill System</td><td>Auto-created + self-improving, compatible with agentskills.io standard</td><td>Primarily manual configuration, no automatic learning loop</td></tr><tr><td>Model Support</td><td>Model-agnostic (OpenRouter &#x2F; Xiaomi MiMo &#x2F; HuggingFace, etc.)</td><td>Primarily tied to the Claude family</td></tr><tr><td>Messaging Platforms</td><td>Telegram &#x2F; Discord &#x2F; Slack &#x2F; WhatsApp &#x2F; Signal &#x2F; Email</td><td>Telegram &#x2F; Discord &#x2F; Slack &#x2F; Feishu</td></tr><tr><td>Deployment</td><td>VPS &#x2F; Docker &#x2F; SSH &#x2F; Serverless (6 backends)</td><td>Local-first, Docker &#x2F; self-hosted</td></tr><tr><td>Memory System</td><td>Honcho user modeling + FTS5 cross-session search</td><td>MEMORY.md static memory file</td></tr><tr><td>Community Size</td><td>Rapidly growing</td><td>Large ecosystem, rich plugins and templates</td></tr></tbody></table><p><strong>Summary</strong>: OpenClaw has a more mature ecosystem and a larger community — a better fit for users who need autonomous execution out of the box. Hermes is lighter and emphasizes a “the more you use it, the better it knows you” self-learning mechanism, making it ideal for users who want an agent that’s a long-term companion and continuously adapts to their habits. Migration paths exist between the two, so you can switch as needed.</p><h2 id="5-Comparison-with-Other-Tools"><a href="#5-Comparison-with-Other-Tools" class="headerlink" title="5. Comparison with Other Tools"></a>5. Comparison with Other Tools</h2><table><thead><tr><th>Feature</th><th>Hermes Agent</th><th>Claude Code</th><th>OpenAI Codex</th></tr></thead><tbody><tr><td>Self-Learning Skill System</td><td>Yes</td><td>Yes (OMC extension)</td><td>No</td></tr><tr><td>Multi-Platform Messaging</td><td>Telegram &#x2F; Discord &#x2F; Slack &#x2F; WhatsApp &#x2F; Signal</td><td>CLI + IDE</td><td>CLI + API</td></tr><tr><td>Model Choice</td><td>Any model</td><td>Claude family</td><td>GPT family</td></tr><tr><td>Scheduled Tasks</td><td>Built-in Cron</td><td>Requires external scheduler</td><td>No</td></tr><tr><td>Deployment</td><td>VPS &#x2F; Docker &#x2F; Serverless</td><td>Local &#x2F; IDE</td><td>Cloud</td></tr><tr><td>Open Source</td><td>MIT</td><td>Partial</td><td>No</td></tr></tbody></table><h2 id="6-Assessment"><a href="#6-Assessment" class="headerlink" title="6. Assessment"></a>6. Assessment</h2><p><strong>Strengths</strong>: Unique self-learning mechanism, model-agnostic, broad platform coverage, flexible deployment, active community.</p><p><strong>Limitations</strong>: The project is relatively new (only 2 months old), and API stability remains to be seen. Compared to mature tools like Claude Code, the ecosystem and plugin count still have room to grow.</p><p><strong>Best Use Case</strong>: When you want a long-running personal agent that continuously learns your preferences — especially for cross-platform scenarios (Telegram, WeChat, etc.).</p><hr><blockquote><p>Sources: <a href="https://github.com/nousresearch/hermes-agent">Hermes GitHub</a> | <a href="https://hermes-agent.nousresearch.com/">Hermes Official Docs</a> | <a href="https://github.com/openclaw/openclaw">OpenClaw GitHub</a> | <a href="https://www.mittrchina.com/news/detail/16243">MIT Technology Review China</a></p></blockquote>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/AI/">AI</category>
      
      <category domain="https://eugenepage.com/tags/Agent/">Agent</category>
      
      <category domain="https://eugenepage.com/tags/OpenSource/">OpenSource</category>
      
      <category domain="https://eugenepage.com/tags/NousResearch/">NousResearch</category>
      
      
      <comments>https://eugenepage.com/2026/04/16/20260416.Hermes%20Agent/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Hermes Agent 调研笔记</title>
      <link>https://eugenepage.com/zh-CN/2026/04/16/20260416.Hermes%20Agent/</link>
      <guid>https://eugenepage.com/zh-CN/2026/04/16/20260416.Hermes%20Agent/</guid>
      <pubDate>Thu, 16 Apr 2026 02:00:00 GMT</pubDate>
      
        
        
      <description>&lt;h1 id=&quot;Hermes-Agent-调研笔记&quot;&gt;&lt;a href=&quot;#Hermes-Agent-调研笔记&quot; class=&quot;headerlink&quot; title=&quot;Hermes Agent 调研笔记&quot;&gt;&lt;/a&gt;Hermes Agent 调研笔记&lt;/h1&gt;&lt;h2 id=&quot;一、项目概</description>
        
      
      
      
      <content:encoded><![CDATA[<h1 id="Hermes-Agent-调研笔记"><a href="#Hermes-Agent-调研笔记" class="headerlink" title="Hermes Agent 调研笔记"></a>Hermes Agent 调研笔记</h1><h2 id="一、项目概览"><a href="#一、项目概览" class="headerlink" title="一、项目概览"></a>一、项目概览</h2><p><strong>Hermes Agent</strong> 是 <a href="https://github.com/NousResearch">Nous Research</a> 开发的开源、自学习 AI Agent 框架。</p><table><thead><tr><th>项目信息</th><th>详情</th></tr></thead><tbody><tr><td>首次发布</td><td>2026-02-25 (v0.1.0)</td></tr><tr><td>当前版本</td><td>v0.8.0 (2026-04-08)</td></tr><tr><td>GitHub Stars</td><td>22k+</td></tr><tr><td>协议</td><td>MIT</td></tr><tr><td>语言</td><td>Python</td></tr></tbody></table><p>核心理念：<strong>Agent 应该随用户一起成长</strong>——通过内置学习循环，从经验中创建技能、持续改进，越用越强。</p><h2 id="二、核心特性"><a href="#二、核心特性" class="headerlink" title="二、核心特性"></a>二、核心特性</h2><h3 id="2-1-自学习闭环"><a href="#2-1-自学习闭环" class="headerlink" title="2.1 自学习闭环"></a>2.1 自学习闭环</h3><ul><li>完成复杂任务后自动创建可复用的 <strong>Skill 文档</strong></li><li>Skill 在使用过程中自我迭代优化</li><li>内置 FTS5 全文搜索 + LLM 摘要，支持跨会话记忆召回</li><li>基于 Honcho 的用户画像建模，理解你是谁</li></ul><h3 id="2-2-多平台接入"><a href="#2-2-多平台接入" class="headerlink" title="2.2 多平台接入"></a>2.2 多平台接入</h3><p>单一 Gateway 进程即可覆盖：Telegram、Discord、Slack、WhatsApp、Signal、Email。支持语音备忘录转录，跨平台对话连续。</p><h3 id="2-3-终端交互"><a href="#2-3-终端交互" class="headerlink" title="2.3 终端交互"></a>2.3 终端交互</h3><p>完整的 TUI 界面：多行编辑、斜杠命令补全、对话历史、中断重定向、流式工具输出。</p><h3 id="2-4-模型无关"><a href="#2-4-模型无关" class="headerlink" title="2.4 模型无关"></a>2.4 模型无关</h3><p>支持 Nous Portal、OpenRouter (200+ 模型)、OpenAI、Anthropic、Hugging Face、小米 MiMo 等，<code>hermes model</code> 一键切换，零代码改动。</p><h3 id="2-5-定时任务"><a href="#2-5-定时任务" class="headerlink" title="2.5 定时任务"></a>2.5 定时任务</h3><p>内置 Cron 调度器，用自然语言定义定时任务（日报、备份、审计），结果自动投递到任意平台。</p><h3 id="2-6-并行子代理"><a href="#2-6-并行子代理" class="headerlink" title="2.6 并行子代理"></a>2.6 并行子代理</h3><p>可生成隔离子代理并行工作流，支持通过 RPC 调用工具的 Python 脚本，将多步骤流水线压缩为零上下文开销的单轮操作。</p><h3 id="2-7-灵活部署"><a href="#2-7-灵活部署" class="headerlink" title="2.7 灵活部署"></a>2.7 灵活部署</h3><p>6 种终端后端：Local、Docker、SSH、Daytona、Singularity、Modal。支持 Serverless 按需唤醒，空闲时几乎零成本。</p><h2 id="三、快速上手"><a href="#三、快速上手" class="headerlink" title="三、快速上手"></a>三、快速上手</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装（支持 Linux / macOS / WSL2 / Termux）</span></span><br><span class="line">curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动</span></span><br><span class="line"><span class="built_in">source</span> ~/.bashrc</span><br><span class="line">hermes              <span class="comment"># 开始对话</span></span><br><span class="line">hermes model        <span class="comment"># 选择模型</span></span><br><span class="line">hermes tools        <span class="comment"># 配置工具</span></span><br><span class="line">hermes gateway      <span class="comment"># 启动消息网关</span></span><br><span class="line">hermes setup        <span class="comment"># 完整设置向导</span></span><br></pre></td></tr></table></figure><h2 id="四、与-OpenClaw-对比"><a href="#四、与-OpenClaw-对比" class="headerlink" title="四、与 OpenClaw 对比"></a>四、与 OpenClaw 对比</h2><p><a href="https://github.com/openclaw/openclaw">OpenClaw</a>（前身 Clawdbot&#x2F;MoltBot）由奥地利工程师 Peter Steinberger 于 2026 年 1 月发布，是 2026 年最火的开源 Agent 项目（200k+ Stars）。Hermes 与之有明确的渊源——内置了 OpenClaw 迁移工具（<code>hermes claw migrate</code>）。</p><table><thead><tr><th>维度</th><th>Hermes Agent</th><th>OpenClaw</th></tr></thead><tbody><tr><td>发布时间</td><td>2026-02</td><td>2026-01</td></tr><tr><td>开发者</td><td>Nous Research（团队）</td><td>Peter Steinberger（个人起步）</td></tr><tr><td>GitHub Stars</td><td>22k+</td><td>200k+</td></tr><tr><td>核心理念</td><td><strong>自学习闭环</strong>——从经验中创建技能、持续迭代</td><td><strong>自主执行</strong>——代替用户完成真实操作</td></tr><tr><td>Skill 系统</td><td>自动创建 + 自我改进，兼容 agentskills.io 标准</td><td>手动配置为主，无自动学习闭环</td></tr><tr><td>模型支持</td><td>模型无关（OpenRouter&#x2F;小米 MiMo&#x2F;HuggingFace 等）</td><td>主要绑定 Claude 系列</td></tr><tr><td>消息平台</td><td>Telegram&#x2F;Discord&#x2F;Slack&#x2F;WhatsApp&#x2F;Signal&#x2F;Email</td><td>Telegram&#x2F;Discord&#x2F;Slack&#x2F;飞书</td></tr><tr><td>部署方式</td><td>VPS&#x2F;Docker&#x2F;SSH&#x2F;Serverless（6 种后端）</td><td>本地优先，Docker&#x2F;自托管</td></tr><tr><td>记忆系统</td><td>Honcho 用户画像 + FTS5 跨会话搜索</td><td>MEMORY.md 静态记忆文件</td></tr><tr><td>社区规模</td><td>快速增长中</td><td>庞大生态，插件&#x2F;模板丰富</td></tr></tbody></table><p><strong>总结</strong>：OpenClaw 生态更成熟、社区更大，适合需要”开箱即用”自主执行的用户；Hermes 更轻量、更强调”越用越懂你”的自学习机制，适合希望 Agent 长期陪伴并持续适应自己习惯的用户。两者有迁移路径，可以按需切换。</p><h2 id="五、与其他工具对比"><a href="#五、与其他工具对比" class="headerlink" title="五、与其他工具对比"></a>五、与其他工具对比</h2><table><thead><tr><th>特性</th><th>Hermes Agent</th><th>Claude Code</th><th>OpenAI Codex</th></tr></thead><tbody><tr><td>自学习 Skill 系统</td><td>有</td><td>有 (OMC 扩展)</td><td>无</td></tr><tr><td>多平台消息</td><td>Telegram&#x2F;Discord&#x2F;Slack&#x2F;WhatsApp&#x2F;Signal</td><td>CLI + IDE</td><td>CLI + API</td></tr><tr><td>模型选择</td><td>任意模型</td><td>Claude 系列</td><td>GPT 系列</td></tr><tr><td>定时任务</td><td>内置 Cron</td><td>需外部调度</td><td>无</td></tr><tr><td>部署方式</td><td>VPS &#x2F; Docker &#x2F; Serverless</td><td>本地 &#x2F; IDE</td><td>云端</td></tr><tr><td>开源</td><td>MIT</td><td>部分</td><td>否</td></tr></tbody></table><h2 id="六、评价"><a href="#六、评价" class="headerlink" title="六、评价"></a>六、评价</h2><p><strong>优势</strong>：自学习机制独特、模型无关、多平台覆盖、部署灵活、社区活跃。</p><p><strong>局限</strong>：项目较新（仅 2 个月），API 稳定性待观察；与 Claude Code 等成熟工具相比，生态和插件数量尚有差距。</p><p><strong>适用场景</strong>：需要一个长期运行、持续学习你偏好的个人 Agent，尤其是跨平台（Telegram&#x2F;微信）使用场景。</p><hr><blockquote><p>参考来源：<a href="https://github.com/nousresearch/hermes-agent">Hermes GitHub</a> | <a href="https://hermes-agent.nousresearch.com/">Hermes 官方文档</a> | <a href="https://github.com/openclaw/openclaw">OpenClaw GitHub</a> | <a href="https://www.mittrchina.com/news/detail/16243">MIT Technology Review China</a></p></blockquote>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/AI/">AI</category>
      
      <category domain="https://eugenepage.com/tags/Agent/">Agent</category>
      
      <category domain="https://eugenepage.com/tags/OpenSource/">OpenSource</category>
      
      <category domain="https://eugenepage.com/tags/NousResearch/">NousResearch</category>
      
      
      <comments>https://eugenepage.com/zh-CN/2026/04/16/20260416.Hermes%20Agent/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>SQL Basics Notes</title>
      <link>https://eugenepage.com/2026/04/12/20260412.SQLBasicsNotes/</link>
      <guid>https://eugenepage.com/2026/04/12/20260412.SQLBasicsNotes/</guid>
      <pubDate>Sun, 12 Apr 2026 02:00:00 GMT</pubDate>
      
        
        
      <description>&lt;h1 id=&quot;I-Introduction-to-SQL&quot;&gt;&lt;a href=&quot;#I-Introduction-to-SQL&quot; class=&quot;headerlink&quot; title=&quot;I. Introduction to SQL&quot;&gt;&lt;/a&gt;I. Introduction to SQL</description>
        
      
      
      
      <content:encoded><![CDATA[<h1 id="I-Introduction-to-SQL"><a href="#I-Introduction-to-SQL" class="headerlink" title="I. Introduction to SQL"></a>I. Introduction to SQL</h1><h2 id="1-1-What-is-SQL"><a href="#1-1-What-is-SQL" class="headerlink" title="1.1 What is SQL"></a>1.1 What is SQL</h2><p><strong>SQL (Structured Query Language)</strong>: the standard programming language for managing relational databases.</p><ul><li><strong>RDBMS</strong>: Relational Database Management System</li><li>Common databases (by type): MySQL, PostgreSQL, SQLite, Oracle, SQL Server<ol><li>SQLite: lightweight, embedded — great for mobile apps</li><li>MySQL: open-source, widely used — great for web apps</li><li>PostgreSQL: open-source, feature-rich — great for complex apps</li><li>Oracle: enterprise-grade, fully featured — great for large-scale apps</li><li>SQL Server: developed by Microsoft — great for Windows environments</li></ol></li></ul><h2 id="1-2-Basic-SQL-Categories"><a href="#1-2-Basic-SQL-Categories" class="headerlink" title="1.2 Basic SQL Categories"></a>1.2 Basic SQL Categories</h2><p>Four schools of thought — these are the disciplines you use to communicate with a database. Master them and you’re a data wrangler; give up and you’re just a data janitor. 🐶</p><table><thead><tr><th>Category</th><th>Purpose</th><th>Keywords</th></tr></thead><tbody><tr><td>DDL</td><td>Define database structure</td><td>CREATE, ALTER, DROP</td></tr><tr><td>DML</td><td>Manipulate data</td><td>INSERT, UPDATE, DELETE</td></tr><tr><td>DQL</td><td>Query data</td><td>SELECT</td></tr><tr><td>DCL</td><td>Control permissions</td><td>GRANT, REVOKE</td></tr></tbody></table><hr><h1 id="II-Basic-Syntax"><a href="#II-Basic-Syntax" class="headerlink" title="II. Basic Syntax"></a>II. Basic Syntax</h1><h2 id="2-1-Basic-Rules"><a href="#2-1-Basic-Rules" class="headerlink" title="2.1 Basic Rules"></a>2.1 Basic Rules</h2><ul><li>SQL statements end with a semicolon <code>;</code> (some databases allow omitting it)</li><li>Keywords are case-insensitive, but the convention is to write keywords in uppercase and table&#x2F;column names in lowercase</li><li>Strings and dates are wrapped in single quotes <code>&#39; &#39;</code></li><li>Comments: <code>-- single-line comment</code>, <code>/* multi-line comment */</code></li></ul><h2 id="2-2-Writing-Style"><a href="#2-2-Writing-Style" class="headerlink" title="2.2 Writing Style"></a>2.2 Writing Style</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Recommended writing style</span></span><br><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">    id,</span><br><span class="line">    name,</span><br><span class="line">    email</span><br><span class="line"><span class="keyword">FROM</span></span><br><span class="line">    users<span class="comment">/* Whether to use double quotes depends on the database type */</span></span><br><span class="line"><span class="keyword">WHERE</span></span><br><span class="line">    status <span class="operator">=</span> <span class="string">&#x27;active&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span></span><br><span class="line">    create_time <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><h2 id="2-3-Common-Operators"><a href="#2-3-Common-Operators" class="headerlink" title="2.3 Common Operators"></a>2.3 Common Operators</h2><h3 id="Arithmetic-Operators"><a href="#Arithmetic-Operators" class="headerlink" title="Arithmetic Operators"></a>Arithmetic Operators</h3><table><thead><tr><th>Operator</th><th>Description</th></tr></thead><tbody><tr><td><code>+</code></td><td>Addition</td></tr><tr><td><code>-</code></td><td>Subtraction</td></tr><tr><td><code>*</code></td><td>Multiplication</td></tr><tr><td><code>/</code></td><td>Division</td></tr><tr><td><code>%</code> or <code>MOD()</code></td><td>Modulo</td></tr></tbody></table><h3 id="Comparison-Operators"><a href="#Comparison-Operators" class="headerlink" title="Comparison Operators"></a>Comparison Operators</h3><table><thead><tr><th>Operator</th><th>Description</th></tr></thead><tbody><tr><td><code>=</code></td><td>Equal to</td></tr><tr><td><code>&lt;&gt;</code> or <code>!=</code></td><td>Not equal to</td></tr><tr><td><code>&gt;</code></td><td>Greater than</td></tr><tr><td><code>&lt;</code></td><td>Less than</td></tr><tr><td><code>&gt;=</code></td><td>Greater than or equal to</td></tr><tr><td><code>&lt;=</code></td><td>Less than or equal to</td></tr></tbody></table><h3 id="Logical-Operators"><a href="#Logical-Operators" class="headerlink" title="Logical Operators"></a>Logical Operators</h3><table><thead><tr><th>Operator</th><th>Description</th></tr></thead><tbody><tr><td><code>AND</code></td><td>Logical AND (higher precedence than OR — use parentheses like in C++)</td></tr><tr><td><code>OR</code></td><td>Logical OR</td></tr><tr><td><code>NOT</code></td><td>Logical NOT</td></tr></tbody></table><h2 id="2-4-Common-Commands-MySQL"><a href="#2-4-Common-Commands-MySQL" class="headerlink" title="2.4 Common Commands (MySQL)"></a>2.4 Common Commands (MySQL)</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Show all databases</span></span><br><span class="line"><span class="keyword">SHOW</span> DATABASES;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Show all tables in the current database</span></span><br><span class="line"><span class="keyword">SHOW</span> TABLES;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- View table structure</span></span><br><span class="line"><span class="keyword">DESC</span> table_name;</span><br><span class="line"><span class="comment">-- or</span></span><br><span class="line"><span class="keyword">DESCRIBE</span> table_name;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- View the CREATE TABLE statement</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">CREATE TABLE</span> table_name;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Show full column info for a table</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">FULL</span> COLUMNS <span class="keyword">FROM</span> table_name;</span><br></pre></td></tr></table></figure><h2 id="2-5-⚠️-Things-to-Watch-Out-For"><a href="#2-5-⚠️-Things-to-Watch-Out-For" class="headerlink" title="2.5 ⚠️ Things to Watch Out For"></a>2.5 ⚠️ Things to Watch Out For</h2><ol><li>Query syntax keywords have a specific ordering relationship.</li><li><img src="https://cdn.jsdelivr.net/gh/youdrew/MyPicGo/Images/%E6%88%AA%E5%B1%8F2026-04-12%2018.51.16.png" alt="Screenshot 2026-04-12 18.51.16"></li></ol><hr><h1 id="III-DDL-—-Data-Definition"><a href="#III-DDL-—-Data-Definition" class="headerlink" title="III. DDL — Data Definition"></a>III. DDL — Data Definition</h1><h2 id="2-1-Creating-a-Database"><a href="#2-1-Creating-a-Database" class="headerlink" title="2.1 Creating a Database"></a>2.1 Creating a Database</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> DATABASE database_name;</span><br><span class="line">USE database_name;</span><br></pre></td></tr></table></figure><h2 id="2-2-Creating-a-Table"><a href="#2-2-Creating-a-Table" class="headerlink" title="2.2 Creating a Table"></a>2.2 Creating a Table</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> table_name (</span><br><span class="line">    column1 data_type [<span class="keyword">constraint</span>],</span><br><span class="line">    column2 data_type [<span class="keyword">constraint</span>],</span><br><span class="line">    ...</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><strong>Common data types</strong>:</p><ul><li>Integer: <code>INT</code>, <code>BIGINT</code></li><li>Decimal: <code>DECIMAL(m,n)</code>, <code>FLOAT</code>, <code>DOUBLE</code></li><li>String: <code>VARCHAR(n)</code>, <code>CHAR(n)</code>, <code>TEXT</code></li><li>Date&#x2F;Time: <code>DATE</code>, <code>DATETIME</code>, <code>TIMESTAMP</code></li></ul><h2 id="2-3-Constraints"><a href="#2-3-Constraints" class="headerlink" title="2.3 Constraints"></a>2.3 Constraints</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> users (</span><br><span class="line">    id <span class="type">INT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">    name <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">    email <span class="type">VARCHAR</span>(<span class="number">100</span>) <span class="keyword">UNIQUE</span>,</span><br><span class="line">    age <span class="type">INT</span> <span class="keyword">DEFAULT</span> <span class="number">18</span>,</span><br><span class="line">    <span class="keyword">FOREIGN KEY</span> (dept_id) <span class="keyword">REFERENCES</span> departments(id)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><strong>Common constraints</strong>:</p><ul><li><code>PRIMARY KEY</code>: primary key, uniquely identifies a row</li><li><code>NOT NULL</code>: value cannot be null</li><li><code>UNIQUE</code>: value must be unique</li><li><code>DEFAULT</code>: default value</li><li><code>FOREIGN KEY</code>: foreign key constraint</li><li><code>AUTO_INCREMENT</code>: auto-increment (MySQL)</li></ul><h2 id="2-4-Altering-Table-Structure"><a href="#2-4-Altering-Table-Structure" class="headerlink" title="2.4 Altering Table Structure"></a>2.4 Altering Table Structure</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Add a column</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> table_name <span class="keyword">ADD</span> column_name data_type;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Modify a column</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> table_name MODIFY column_name new_data_type;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Drop a column</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> table_name <span class="keyword">DROP</span> <span class="keyword">COLUMN</span> column_name;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Rename a table</span></span><br><span class="line"><span class="keyword">ALTER TABLE</span> table_name RENAME <span class="keyword">TO</span> new_table_name;</span><br></pre></td></tr></table></figure><h2 id="2-5-Dropping-a-Table"><a href="#2-5-Dropping-a-Table" class="headerlink" title="2.5 Dropping a Table"></a>2.5 Dropping a Table</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DROP</span> <span class="keyword">TABLE</span> table_name;           <span class="comment">-- Drop the table entirely</span></span><br><span class="line"><span class="keyword">TRUNCATE</span> <span class="keyword">TABLE</span> table_name;       <span class="comment">-- Clear all data (keep the structure)</span></span><br></pre></td></tr></table></figure><hr><h1 id="III-DML-—-Data-Manipulation"><a href="#III-DML-—-Data-Manipulation" class="headerlink" title="III. DML — Data Manipulation"></a>III. DML — Data Manipulation</h1><h2 id="3-1-Inserting-Data"><a href="#3-1-Inserting-Data" class="headerlink" title="3.1 Inserting Data"></a>3.1 Inserting Data</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Insert a single row</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> table_name (col1, col2) <span class="keyword">VALUES</span> (val1, val2);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Insert multiple rows</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> table_name (col1, col2) <span class="keyword">VALUES</span></span><br><span class="line">(val1, val2),</span><br><span class="line">(val3, val4),</span><br><span class="line">(val5, val6);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Import from another table</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> table_name <span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> other_table <span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><h2 id="3-2-Updating-Data"><a href="#3-2-Updating-Data" class="headerlink" title="3.2 Updating Data"></a>3.2 Updating Data</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">UPDATE</span> table_name</span><br><span class="line"><span class="keyword">SET</span> col1 <span class="operator">=</span> new_val1, col2 <span class="operator">=</span> new_val2</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><h2 id="3-3-Deleting-Data"><a href="#3-3-Deleting-Data" class="headerlink" title="3.3 Deleting Data"></a>3.3 Deleting Data</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> table_name <span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><hr><h1 id="IV-DQL-—-Data-Query-Core"><a href="#IV-DQL-—-Data-Query-Core" class="headerlink" title="IV. DQL — Data Query (Core)"></a>IV. DQL — Data Query (Core)</h1><h2 id="4-1-Basic-Queries"><a href="#4-1-Basic-Queries" class="headerlink" title="4.1 Basic Queries"></a>4.1 Basic Queries</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Query all columns</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Query specific columns</span></span><br><span class="line"><span class="keyword">SELECT</span> col1, col2 <span class="keyword">FROM</span> table_name;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Deduplicate</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> col <span class="keyword">FROM</span> table_name;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Alias</span></span><br><span class="line"><span class="keyword">SELECT</span> col <span class="keyword">AS</span> alias <span class="keyword">FROM</span> table_name;</span><br></pre></td></tr></table></figure><h2 id="4-2-Conditional-Queries-—-WHERE"><a href="#4-2-Conditional-Queries-—-WHERE" class="headerlink" title="4.2 Conditional Queries — WHERE"></a>4.2 Conditional Queries — WHERE</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name <span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Comparison operators</span></span><br><span class="line"><span class="keyword">WHERE</span> age <span class="operator">&gt;</span> <span class="number">18</span></span><br><span class="line"><span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;Alice&#x27;</span></span><br><span class="line"><span class="keyword">WHERE</span> age <span class="operator">&gt;=</span> <span class="number">18</span> <span class="keyword">AND</span> age <span class="operator">&lt;=</span> <span class="number">30</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- Range</span></span><br><span class="line"><span class="keyword">WHERE</span> age <span class="keyword">BETWEEN</span> <span class="number">18</span> <span class="keyword">AND</span> <span class="number">30</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- Enumeration</span></span><br><span class="line"><span class="keyword">WHERE</span> status <span class="keyword">IN</span> (<span class="string">&#x27;active&#x27;</span>, <span class="string">&#x27;pending&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Pattern matching</span></span><br><span class="line"><span class="keyword">WHERE</span> name <span class="keyword">LIKE</span> <span class="string">&#x27;A%&#x27;</span>       <span class="comment">-- starts with A</span></span><br><span class="line"><span class="keyword">WHERE</span> name <span class="keyword">LIKE</span> <span class="string">&#x27;%son%&#x27;</span>    <span class="comment">-- contains &quot;son&quot;</span></span><br><span class="line"><span class="keyword">WHERE</span> name <span class="keyword">LIKE</span> <span class="string">&#x27;A_&#x27;</span>       <span class="comment">-- starts with A, exactly 2 characters</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- Null checks</span></span><br><span class="line"><span class="keyword">WHERE</span> email <span class="keyword">IS</span> <span class="keyword">NULL</span></span><br><span class="line"><span class="keyword">WHERE</span> email <span class="keyword">IS</span> <span class="keyword">NOT NULL</span></span><br></pre></td></tr></table></figure><h2 id="4-3-Sorting-—-ORDER-BY"><a href="#4-3-Sorting-—-ORDER-BY" class="headerlink" title="4.3 Sorting — ORDER BY"></a>4.3 Sorting — ORDER BY</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name <span class="keyword">ORDER</span> <span class="keyword">BY</span> col1 <span class="keyword">ASC</span>, col2 <span class="keyword">DESC</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ASC: ascending (default)</span></span><br><span class="line"><span class="comment">-- DESC: descending</span></span><br></pre></td></tr></table></figure><h2 id="4-4-Limiting-Results-—-LIMIT"><a href="#4-4-Limiting-Results-—-LIMIT" class="headerlink" title="4.4 Limiting Results — LIMIT"></a>4.4 Limiting Results — LIMIT</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- MySQL</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name LIMIT <span class="number">10</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name LIMIT <span class="number">5</span>, <span class="number">10</span>;  <span class="comment">-- Start from row 5, fetch 10 rows</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- SQL Server</span></span><br><span class="line"><span class="keyword">SELECT</span> TOP <span class="number">10</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Oracle</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name <span class="keyword">WHERE</span> ROWNUM <span class="operator">&lt;=</span> <span class="number">10</span>;</span><br></pre></td></tr></table></figure><h2 id="4-5-Aggregate-Functions"><a href="#4-5-Aggregate-Functions" class="headerlink" title="4.5 Aggregate Functions"></a>4.5 Aggregate Functions</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">    <span class="built_in">COUNT</span>(<span class="operator">*</span>)          <span class="keyword">AS</span> total_rows,</span><br><span class="line">    <span class="built_in">COUNT</span>(col)        <span class="keyword">AS</span> non_null_count,</span><br><span class="line">    <span class="built_in">SUM</span>(col)          <span class="keyword">AS</span> total,</span><br><span class="line">    <span class="built_in">AVG</span>(col)          <span class="keyword">AS</span> average,</span><br><span class="line">    <span class="built_in">MAX</span>(col)          <span class="keyword">AS</span> maximum,</span><br><span class="line">    <span class="built_in">MIN</span>(col)          <span class="keyword">AS</span> minimum</span><br><span class="line"><span class="keyword">FROM</span> table_name;</span><br></pre></td></tr></table></figure><h2 id="4-6-Grouping-—-GROUP-BY"><a href="#4-6-Grouping-—-GROUP-BY" class="headerlink" title="4.6 Grouping — GROUP BY"></a>4.6 Grouping — GROUP BY</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> col, aggregate_function</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> col</span><br><span class="line"><span class="keyword">HAVING</span> aggregate_condition;</span><br></pre></td></tr></table></figure><p><strong>Note</strong>: <code>WHERE</code> filters before grouping; <code>HAVING</code> filters after grouping.</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Example: average salary per department</span></span><br><span class="line"><span class="keyword">SELECT</span> dept_id, <span class="built_in">AVG</span>(salary) <span class="keyword">AS</span> avg_salary</span><br><span class="line"><span class="keyword">FROM</span> employees</span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> dept_id</span><br><span class="line"><span class="keyword">HAVING</span> <span class="built_in">AVG</span>(salary) <span class="operator">&gt;</span> <span class="number">5000</span>;</span><br></pre></td></tr></table></figure><h2 id="4-7-Multi-Table-Queries"><a href="#4-7-Multi-Table-Queries" class="headerlink" title="4.7 Multi-Table Queries"></a>4.7 Multi-Table Queries</h2><h3 id="Joins-JOIN"><a href="#Joins-JOIN" class="headerlink" title="Joins (JOIN)"></a>Joins (JOIN)</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- INNER JOIN: only keep matching rows</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">FROM</span> table1</span><br><span class="line"><span class="keyword">INNER</span> <span class="keyword">JOIN</span> table2 <span class="keyword">ON</span> table1.col <span class="operator">=</span> table2.col;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- LEFT JOIN: keep all rows from the left table; NULLs where there&#x27;s no match on the right</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">FROM</span> table1</span><br><span class="line"><span class="keyword">LEFT</span> <span class="keyword">JOIN</span> table2 <span class="keyword">ON</span> table1.col <span class="operator">=</span> table2.col;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- RIGHT JOIN: keep all rows from the right table</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">FROM</span> table1</span><br><span class="line"><span class="keyword">RIGHT</span> <span class="keyword">JOIN</span> table2 <span class="keyword">ON</span> table1.col <span class="operator">=</span> table2.col;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- FULL JOIN (MySQL doesn&#x27;t support this natively — simulate with UNION)</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table1 <span class="keyword">LEFT</span> <span class="keyword">JOIN</span> table2 <span class="keyword">ON</span> ...</span><br><span class="line"><span class="keyword">UNION</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table1 <span class="keyword">RIGHT</span> <span class="keyword">JOIN</span> table2 <span class="keyword">ON</span> ...;</span><br></pre></td></tr></table></figure><h3 id="Subqueries"><a href="#Subqueries" class="headerlink" title="Subqueries"></a>Subqueries</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Subquery in WHERE</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name <span class="keyword">WHERE</span> col <span class="operator">=</span> (<span class="keyword">SELECT</span> col <span class="keyword">FROM</span> ...);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- IN subquery</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name <span class="keyword">WHERE</span> col <span class="keyword">IN</span> (<span class="keyword">SELECT</span> col <span class="keyword">FROM</span> ...);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- EXISTS subquery</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name <span class="keyword">WHERE</span> <span class="keyword">EXISTS</span> (<span class="keyword">SELECT</span> <span class="number">1</span> <span class="keyword">FROM</span> ... <span class="keyword">WHERE</span> <span class="keyword">condition</span>);</span><br></pre></td></tr></table></figure><h2 id="4-8-UNION-—-Combined-Queries"><a href="#4-8-UNION-—-Combined-Queries" class="headerlink" title="4.8 UNION — Combined Queries"></a>4.8 UNION — Combined Queries</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> col <span class="keyword">FROM</span> table1</span><br><span class="line"><span class="keyword">UNION</span>                 <span class="comment">-- merge and deduplicate</span></span><br><span class="line"><span class="keyword">SELECT</span> col <span class="keyword">FROM</span> table2;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> col <span class="keyword">FROM</span> table1</span><br><span class="line"><span class="keyword">UNION</span> <span class="keyword">ALL</span>            <span class="comment">-- merge and keep duplicates</span></span><br><span class="line"><span class="keyword">SELECT</span> col <span class="keyword">FROM</span> table2;</span><br></pre></td></tr></table></figure><hr><h1 id="V-Common-Functions"><a href="#V-Common-Functions" class="headerlink" title="V. Common Functions"></a>V. Common Functions</h1><h2 id="5-1-String-Functions"><a href="#5-1-String-Functions" class="headerlink" title="5.1 String Functions"></a>5.1 String Functions</h2><table><thead><tr><th>Function</th><th>Description</th></tr></thead><tbody><tr><td><code>CONCAT(s1, s2)</code></td><td>Concatenate strings</td></tr><tr><td><code>LENGTH(s)</code></td><td>Get string length</td></tr><tr><td><code>UPPER(s)</code> &#x2F; <code>LOWER(s)</code></td><td>Convert case</td></tr><tr><td><code>TRIM(s)</code></td><td>Strip leading&#x2F;trailing spaces</td></tr><tr><td><code>SUBSTRING(s, start, len)</code></td><td>Extract a substring</td></tr><tr><td><code>REPLACE(s, old, new)</code></td><td>Replace substring</td></tr><tr><td><code>IFNULL(s, default)</code></td><td>Replace NULL with a default value</td></tr></tbody></table><h2 id="5-2-Numeric-Functions"><a href="#5-2-Numeric-Functions" class="headerlink" title="5.2 Numeric Functions"></a>5.2 Numeric Functions</h2><table><thead><tr><th>Function</th><th>Description</th></tr></thead><tbody><tr><td><code>ROUND(n, d)</code></td><td>Round to d decimal places</td></tr><tr><td><code>CEIL(n)</code> &#x2F; <code>FLOOR(n)</code></td><td>Ceiling &#x2F; floor</td></tr><tr><td><code>ABS(n)</code></td><td>Absolute value</td></tr><tr><td><code>MOD(n, m)</code></td><td>Modulo</td></tr><tr><td><code>RAND()</code></td><td>Random number</td></tr></tbody></table><h2 id="5-3-Date-Functions"><a href="#5-3-Date-Functions" class="headerlink" title="5.3 Date Functions"></a>5.3 Date Functions</h2><table><thead><tr><th>Function</th><th>Description</th></tr></thead><tbody><tr><td><code>NOW()</code> &#x2F; <code>SYSDATE()</code></td><td>Current date and time</td></tr><tr><td><code>CURDATE()</code></td><td>Current date</td></tr><tr><td><code>YEAR(d)</code> &#x2F; <code>MONTH(d)</code> &#x2F; <code>DAY(d)</code></td><td>Extract year &#x2F; month &#x2F; day</td></tr><tr><td><code>DATE_FORMAT(d, format)</code></td><td>Format a date</td></tr><tr><td><code>DATE_ADD(d, INTERVAL n unit)</code></td><td>Add&#x2F;subtract from a date</td></tr><tr><td><code>DATEDIFF(d1, d2)</code></td><td>Difference between two dates</td></tr></tbody></table><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> DATE_FORMAT(create_time, <span class="string">&#x27;%Y-%m-%d %H:%i:%s&#x27;</span>) <span class="keyword">FROM</span> table_name;</span><br></pre></td></tr></table></figure><h2 id="5-4-Conditional-Logic"><a href="#5-4-Conditional-Logic" class="headerlink" title="5.4 Conditional Logic"></a>5.4 Conditional Logic</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- IF</span></span><br><span class="line"><span class="keyword">SELECT</span> IF(age <span class="operator">&gt;=</span> <span class="number">18</span>, <span class="string">&#x27;adult&#x27;</span>, <span class="string">&#x27;minor&#x27;</span>) <span class="keyword">FROM</span> table_name;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- CASE WHEN</span></span><br><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">    <span class="keyword">CASE</span></span><br><span class="line">        <span class="keyword">WHEN</span> score <span class="operator">&gt;=</span> <span class="number">90</span> <span class="keyword">THEN</span> <span class="string">&#x27;A&#x27;</span></span><br><span class="line">        <span class="keyword">WHEN</span> score <span class="operator">&gt;=</span> <span class="number">80</span> <span class="keyword">THEN</span> <span class="string">&#x27;B&#x27;</span></span><br><span class="line">        <span class="keyword">WHEN</span> score <span class="operator">&gt;=</span> <span class="number">60</span> <span class="keyword">THEN</span> <span class="string">&#x27;C&#x27;</span></span><br><span class="line">        <span class="keyword">ELSE</span> <span class="string">&#x27;D&#x27;</span></span><br><span class="line">    <span class="keyword">END</span> <span class="keyword">AS</span> grade</span><br><span class="line"><span class="keyword">FROM</span> table_name;</span><br></pre></td></tr></table></figure><hr><h1 id="VI-Indexes"><a href="#VI-Indexes" class="headerlink" title="VI. Indexes"></a>VI. Indexes</h1><h2 id="6-1-Index-Types"><a href="#6-1-Index-Types" class="headerlink" title="6.1 Index Types"></a>6.1 Index Types</h2><table><thead><tr><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>Regular index</td><td>Allows duplicate values</td></tr><tr><td>Unique index</td><td>Values must be unique</td></tr><tr><td>Primary key index</td><td>Auto-created with the primary key; unique and not null</td></tr><tr><td>Full-text index</td><td>Full-text search (MyISAM)</td></tr><tr><td>Composite index</td><td>Spans multiple columns</td></tr></tbody></table><h2 id="6-2-Creating-Indexes"><a href="#6-2-Creating-Indexes" class="headerlink" title="6.2 Creating Indexes"></a>6.2 Creating Indexes</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Create an index</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX index_name <span class="keyword">ON</span> table_name(col);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Create a unique index</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">UNIQUE</span> INDEX index_name <span class="keyword">ON</span> table_name(col);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Create a composite index</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX index_name <span class="keyword">ON</span> table_name(col1, col2);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- View indexes</span></span><br><span class="line"><span class="keyword">SHOW</span> INDEX <span class="keyword">FROM</span> table_name;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Drop an index</span></span><br><span class="line"><span class="keyword">DROP</span> INDEX index_name <span class="keyword">ON</span> table_name;</span><br></pre></td></tr></table></figure><h2 id="6-3-Indexing-Principles"><a href="#6-3-Indexing-Principles" class="headerlink" title="6.3 Indexing Principles"></a>6.3 Indexing Principles</h2><ul><li><strong>Good candidates</strong>: large datasets, frequently queried columns, columns often used in WHERE</li><li><strong>Avoid</strong>: small datasets, frequently updated columns, low-cardinality columns</li><li><strong>Leftmost prefix rule</strong>: composite indexes are used starting from the leftmost column</li></ul><hr><h1 id="VII-Transactions"><a href="#VII-Transactions" class="headerlink" title="VII. Transactions"></a>VII. Transactions</h1><h2 id="7-1-Transaction-Properties-ACID"><a href="#7-1-Transaction-Properties-ACID" class="headerlink" title="7.1 Transaction Properties (ACID)"></a>7.1 Transaction Properties (ACID)</h2><ul><li><strong>Atomicity</strong>: either everything succeeds or everything fails</li><li><strong>Consistency</strong>: data is in a valid state before and after the transaction</li><li><strong>Isolation</strong>: concurrent transactions don’t interfere with each other</li><li><strong>Durability</strong>: once committed, data is permanently saved</li></ul><h2 id="7-2-Transaction-Control"><a href="#7-2-Transaction-Control" class="headerlink" title="7.2 Transaction Control"></a>7.2 Transaction Control</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Start a transaction</span></span><br><span class="line"><span class="keyword">START</span> TRANSACTION;</span><br><span class="line"><span class="comment">-- or</span></span><br><span class="line"><span class="keyword">BEGIN</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Commit</span></span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Rollback</span></span><br><span class="line"><span class="keyword">ROLLBACK</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Set a savepoint</span></span><br><span class="line"><span class="keyword">SAVEPOINT</span> savepoint_name;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Rollback to a savepoint</span></span><br><span class="line"><span class="keyword">ROLLBACK</span> <span class="keyword">TO</span> savepoint_name;</span><br></pre></td></tr></table></figure><h2 id="7-3-Isolation-Levels"><a href="#7-3-Isolation-Levels" class="headerlink" title="7.3 Isolation Levels"></a>7.3 Isolation Levels</h2><table><thead><tr><th>Isolation Level</th><th>Dirty Read</th><th>Non-Repeatable Read</th><th>Phantom Read</th></tr></thead><tbody><tr><td>READ UNCOMMITTED</td><td>Possible</td><td>Possible</td><td>Possible</td></tr><tr><td>READ COMMITTED</td><td>Not possible</td><td>Possible</td><td>Possible</td></tr><tr><td>REPEATABLE READ (default)</td><td>Not possible</td><td>Not possible</td><td>Possible</td></tr><tr><td>SERIALIZABLE</td><td>Not possible</td><td>Not possible</td><td>Not possible</td></tr></tbody></table><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SET</span> SESSION TRANSACTION ISOLATION LEVEL level;</span><br></pre></td></tr></table></figure><hr><h1 id="VIII-Views"><a href="#VIII-Views" class="headerlink" title="VIII. Views"></a>VIII. Views</h1><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Create a view</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">VIEW</span> view_name <span class="keyword">AS</span></span><br><span class="line"><span class="keyword">SELECT</span> col1, col2</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Use a view</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> view_name;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- Drop a view</span></span><br><span class="line"><span class="keyword">DROP</span> <span class="keyword">VIEW</span> view_name;</span><br></pre></td></tr></table></figure><hr><h1 id="IX-References"><a href="#IX-References" class="headerlink" title="IX. References"></a>IX. References</h1><ol><li><p><a href="https://www.bilibili.com/video/BV1bQxMehETa/">Bilibili Quick-Start Course</a></p></li><li><p><a href="https://sqlzoo.net/wiki/SQL_Tutorial">SQL Practice Website</a></p></li></ol>]]></content:encoded>
      
      
      
      <category domain="https://eugenepage.com/tags/SQL/">SQL</category>
      
      <category domain="https://eugenepage.com/tags/Database/">Database</category>
      
      
      <comments>https://eugenepage.com/2026/04/12/20260412.SQLBasicsNotes/#disqus_thread</comments>
      
    </item>
    
  </channel>
</rss>
