<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>XlongLab Blog</title>
        <link>https://www.xlonglab.com/articles</link>
        <description>XlongLab Blog</description>
        <lastBuildDate>Tue, 28 Jul 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-Hans</language>
        <copyright>Copyright © 2026 XlongLab</copyright>
        <item>
            <title><![CDATA[3000 万 SKU 商超检索案例拆解：语义召回、过滤与数据更新]]></title>
            <link>https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study</link>
            <guid>https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study</guid>
            <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[基于公开资料拆解滴滴 Grocery 如何用向量检索补充关键词搜索，并区分公开事实、工程推断和可迁移经验。]]></description>
            <content:encoded><![CDATA[<p>企业案例最容易出现两种问题：要么变成品牌宣传，要么把内部系统的局部做法包装成通用架构。更有价值的分析方式，是明确区分三层信息：公开资料已经披露的事实、根据事实做出的工程推断、以及可以用公开数据独立验证的设计取舍。</p>
<p>本文基于 Zilliz 在 2025 年发布的滴滴 Grocery 案例，拆解一个商超语义搜索系统。文中的规模、收益和内部实现均来自该公开文章，XlongLab 未接触滴滴内部系统，也未独立验证其业务指标。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="公开资料披露了什么">公开资料披露了什么<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E5%85%AC%E5%BC%80%E8%B5%84%E6%96%99%E6%8A%AB%E9%9C%B2%E4%BA%86%E4%BB%80%E4%B9%88" class="hash-link" aria-label="公开资料披露了什么的直接链接" title="公开资料披露了什么的直接链接">​</a></h2>
<p>根据原文，滴滴 Grocery 在墨西哥、哥伦比亚、哥斯达黎加等市场提供商超业务。店内商品数量可能达到 3 万，整个商品库包含超过 3000 万条 SKU。</p>
<p>原文描述的主要问题包括：</p>
<ul>
<li>用户拼写错误；</li>
<li>英语和西班牙语混用；</li>
<li>同一种商品存在多种表达；</li>
<li>传统关键词搜索出现无结果或结果不理想。</li>
</ul>
<p>公开方案使用 Jina Embeddings v3 生成商品名称向量，使用 Milvus 存储和检索，并把向量召回作为 Elasticsearch 文本搜索的补充链路。</p>
<p>原文还披露了几组结果：向量链路触发比例约 15%；对原本无结果的查询，约 19% 召回高相关结果；向量结果曝光后加购比例约 4%。这些数字只能作为该文章在当时场景下的公开陈述，不能视为经过独立审计的通用基准。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第一项关键设计先消除重复再讨论索引">第一项关键设计：先消除重复，再讨论索引<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E7%AC%AC%E4%B8%80%E9%A1%B9%E5%85%B3%E9%94%AE%E8%AE%BE%E8%AE%A1%E5%85%88%E6%B6%88%E9%99%A4%E9%87%8D%E5%A4%8D%E5%86%8D%E8%AE%A8%E8%AE%BA%E7%B4%A2%E5%BC%95" class="hash-link" aria-label="第一项关键设计：先消除重复，再讨论索引的直接链接" title="第一项关键设计：先消除重复，再讨论索引的直接链接">​</a></h2>
<p>3000 万 SKU 不等于 3000 万种不同商品名称。原文采用商品名称聚合：相同名称只保留一条向量，同时维护商品对应的店铺列表。公开文章称，聚合后向量规模从 3000 万降低到约 20 万。</p>
<p>这一步的价值可能比更换索引更大：</p>
<ul>
<li>减少向量生成次数；</li>
<li>降低向量与索引存储；</li>
<li>减少相同名称占据多个召回位置；</li>
<li>数据量降低后可以使用更高召回率的索引配置。</li>
</ul>
<p>它同时引入新的问题：</p>
<ul>
<li>同名商品在不同店铺的描述和属性可能不同；</li>
<li>店铺列表可能很大；</li>
<li>名称聚合可能丢失规格、品牌或包装差异；</li>
<li>商品上下架需要更新关联列表。</li>
</ul>
<p>因此，聚合键不能简单等同于原始名称。更稳妥的做法是先规范化语言、单位、品牌和规格，再根据业务定义建立可回溯的“语义商品 ID”。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第二项关键设计向量搜索是补充召回">第二项关键设计：向量搜索是补充召回<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E7%AC%AC%E4%BA%8C%E9%A1%B9%E5%85%B3%E9%94%AE%E8%AE%BE%E8%AE%A1%E5%90%91%E9%87%8F%E6%90%9C%E7%B4%A2%E6%98%AF%E8%A1%A5%E5%85%85%E5%8F%AC%E5%9B%9E" class="hash-link" aria-label="第二项关键设计：向量搜索是补充召回的直接链接" title="第二项关键设计：向量搜索是补充召回的直接链接">​</a></h2>
<p>公开架构没有直接用向量搜索替换 Elasticsearch。系统保留原有文本搜索，向量链路主要处理无结果或结果不理想的查询。</p>
<p>这是一个值得保留的产品策略：</p>
<ul>
<li>精确型号、品牌和库存字段仍适合关键词与结构化过滤；</li>
<li>拼写错误、同义表达和跨语言问题适合语义召回；</li>
<li>新链路可以先在失败查询上小流量验证；</li>
<li>即使向量服务故障，主搜索仍可降级运行。</li>
</ul>
<p>向量搜索并不是关键词搜索的替代品，而是召回系统中的另一种信号。</p>
<p>一种通用流程可以写成：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Text</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">用户查询</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 关键词搜索</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 判断是否需要补充召回</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 查询 Embedding</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 向量召回候选商品名称</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 按店铺、库存与区域过滤</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 与关键词结果合并、去重和排序</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 返回结果</span><br></span></code></pre></div></div>
<p>“是否需要补充召回”应基于明确规则，例如无结果、结果数量不足、低相关性或查询被识别为拼写错误。不能让两个召回链路无条件堆叠，否则成本和排序复杂度都会增加。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第三项关键设计多语言不只是翻译">第三项关键设计：多语言不只是翻译<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E7%AC%AC%E4%B8%89%E9%A1%B9%E5%85%B3%E9%94%AE%E8%AE%BE%E8%AE%A1%E5%A4%9A%E8%AF%AD%E8%A8%80%E4%B8%8D%E5%8F%AA%E6%98%AF%E7%BF%BB%E8%AF%91" class="hash-link" aria-label="第三项关键设计：多语言不只是翻译的直接链接" title="第三项关键设计：多语言不只是翻译的直接链接">​</a></h2>
<p>原文比较了 Jina Embeddings v3 和 BGE-M3，并认为前者在西班牙语商品场景中效果更好。这个结论依赖其内部数据，不能直接泛化。</p>
<p>但评测方法值得借鉴：多语言搜索应该使用真实业务查询，而不是只看模型宣称支持多少种语言。</p>
<p>建议构造以下测试集：</p>
<ul>
<li>同一商品的英语和西班牙语表达；如果目标市场另有语言，再按实际市场扩展；</li>
<li>品牌名不翻译、品类词翻译的混合查询；</li>
<li>拼写错误和输入法异常字符；</li>
<li>地区化名称；</li>
<li>只改变颜色、规格或包装的难负例。</li>
</ul>
<p>指标不应只有“找到了同类商品”，还要验证属性是否正确。把红色鞋召回成蓝色鞋，语义上接近，业务上仍可能是错误。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第四项关键设计过滤发生在召回之后">第四项关键设计：过滤发生在召回之后<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E7%AC%AC%E5%9B%9B%E9%A1%B9%E5%85%B3%E9%94%AE%E8%AE%BE%E8%AE%A1%E8%BF%87%E6%BB%A4%E5%8F%91%E7%94%9F%E5%9C%A8%E5%8F%AC%E5%9B%9E%E4%B9%8B%E5%90%8E" class="hash-link" aria-label="第四项关键设计：过滤发生在召回之后的直接链接" title="第四项关键设计：过滤发生在召回之后的直接链接">​</a></h2>
<p>聚合后的向量记录需要保存商品对应的店铺信息。查询先找到相似商品名称，再过滤出当前店铺可售商品。</p>
<p>原文 schema 使用数组保存店铺 ID。这个设计在特定规模下可以工作，但需要评估：</p>
<ul>
<li>单个商品关联多少店铺；</li>
<li>数组字段更新频率；</li>
<li>过滤选择性；</li>
<li>热门商品是否形成超大列表；</li>
<li>查询时是否只返回有效库存；</li>
<li>店铺关闭或商品下架如何清理。</li>
</ul>
<p>如果关联关系非常大或更新频繁，把所有店铺 ID 放进向量记录可能不是最佳方案。可以考虑向量层只返回语义商品 ID，再由高速商品服务完成店铺与库存过滤。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第五项关键设计t1-更新是业务取舍">第五项关键设计：T+1 更新是业务取舍<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E7%AC%AC%E4%BA%94%E9%A1%B9%E5%85%B3%E9%94%AE%E8%AE%BE%E8%AE%A1t1-%E6%9B%B4%E6%96%B0%E6%98%AF%E4%B8%9A%E5%8A%A1%E5%8F%96%E8%88%8D" class="hash-link" aria-label="第五项关键设计：T+1 更新是业务取舍的直接链接" title="第五项关键设计：T+1 更新是业务取舍的直接链接">​</a></h2>
<p>原文称向量链路主要用于补充召回，对实时性要求较低，因此采用 T+1 批量更新，并通过 GPU 平台生成向量。</p>
<p>这不是“向量系统不需要实时”的普遍结论，而是业务优先级选择。对商超搜索，需要分别看：</p>
<ul>
<li>新商品多久可以被搜索到；</li>
<li>下架商品是否仍被召回；</li>
<li>价格和库存是否来自实时系统；</li>
<li>向量链路是否只保存较稳定的商品名称；</li>
<li>延迟更新期间如何降级。</li>
</ul>
<p>一种合理拆分是：较稳定的名称和语义向量批量更新，价格、库存和店铺可售状态在查询时实时过滤。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="如何用公开数据做缩小验证">如何用公开数据做缩小验证<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E5%A6%82%E4%BD%95%E7%94%A8%E5%85%AC%E5%BC%80%E6%95%B0%E6%8D%AE%E5%81%9A%E7%BC%A9%E5%B0%8F%E9%AA%8C%E8%AF%81" class="hash-link" aria-label="如何用公开数据做缩小验证的直接链接" title="如何用公开数据做缩小验证的直接链接">​</a></h2>
<p>我们无法复现滴滴内部规模，但可以验证关键取舍。准备一个公开商品数据集，包含：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Text</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">product_id, normalized_name, title, brand, category,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">language, shop_id, region, in_stock, updated_at</span><br></span></code></pre></div></div>
<p>然后完成五组实验：</p>
<ol>
<li>关键词、向量和混合召回对拼写错误的影响；</li>
<li>中文、英语和西班牙语查询的跨语言召回；</li>
<li>名称聚合前后的向量数量、召回重复率和存储；</li>
<li>类目、地区、店铺和库存过滤对延迟的影响；</li>
<li>商品新增、下架和库存变化时的错误窗口。</li>
</ol>
<p>必须明确：这个缩小实验只能验证设计取舍，不能证明原企业系统的性能或收益。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="产品指标比-qps-更重要">产品指标比 QPS 更重要<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E4%BA%A7%E5%93%81%E6%8C%87%E6%A0%87%E6%AF%94-qps-%E6%9B%B4%E9%87%8D%E8%A6%81" class="hash-link" aria-label="产品指标比 QPS 更重要的直接链接" title="产品指标比 QPS 更重要的直接链接">​</a></h2>
<p>搜索系统最终要改善用户行为。可以建立一组分层指标：</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="检索质量">检索质量<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E6%A3%80%E7%B4%A2%E8%B4%A8%E9%87%8F" class="hash-link" aria-label="检索质量的直接链接" title="检索质量的直接链接">​</a></h3>
<ul>
<li>无结果率；</li>
<li>Recall@K、nDCG；</li>
<li>拼写错误修复率；</li>
<li>多语言查询成功率；</li>
<li>属性错误率。</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="系统质量">系统质量<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E7%B3%BB%E7%BB%9F%E8%B4%A8%E9%87%8F" class="hash-link" aria-label="系统质量的直接链接" title="系统质量的直接链接">​</a></h3>
<ul>
<li>P95/P99 延迟；</li>
<li>向量链路错误率；</li>
<li>更新延迟；</li>
<li>降级成功率；</li>
<li>单次查询成本。</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="产品结果">产品结果<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E4%BA%A7%E5%93%81%E7%BB%93%E6%9E%9C" class="hash-link" aria-label="产品结果的直接链接" title="产品结果的直接链接">​</a></h3>
<ul>
<li>搜索结果点击率；</li>
<li>加购率；</li>
<li>查询改写率；</li>
<li>用户退出率；</li>
<li>错误商品投诉。</li>
</ul>
<p>如果向量召回提高了点击率，却增加了错误规格商品，不能只看总转化。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="可以迁移的经验">可以迁移的经验<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E5%8F%AF%E4%BB%A5%E8%BF%81%E7%A7%BB%E7%9A%84%E7%BB%8F%E9%AA%8C" class="hash-link" aria-label="可以迁移的经验的直接链接" title="可以迁移的经验的直接链接">​</a></h2>
<p>这份案例最值得迁移的不是某个具体索引参数，而是五个思路：</p>
<ol>
<li>先通过数据建模减少重复向量；</li>
<li>用向量搜索补充关键词搜索，而不是盲目替换；</li>
<li>用真实语言和错误查询评估 Embedding；</li>
<li>把稳定语义与实时库存分开处理；</li>
<li>通过产品指标验证搜索收益。</li>
</ol>
<p>不可直接迁移的是具体模型优劣、3000 万到 20 万的压缩比例、T+1 更新和业务转化数字。它们取决于商品结构、用户语言、内部平台和业务策略。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="事实推断与验证边界">事实、推断与验证边界<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E4%BA%8B%E5%AE%9E%E6%8E%A8%E6%96%AD%E4%B8%8E%E9%AA%8C%E8%AF%81%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="事实、推断与验证边界的直接链接" title="事实、推断与验证边界的直接链接">​</a></h2>
<table><thead><tr><th>内容</th><th>本文处理方式</th></tr></thead><tbody><tr><td>3000 万 SKU、约 20 万聚合名称</td><td>引用公开原文，未独立验证</td></tr><tr><td>15% 触发、19% 高相关召回、4% 加购</td><td>引用公开原文，不能外推</td></tr><tr><td>名称聚合的存储收益</td><td>基于公开架构的工程分析</td></tr><tr><td>数组过滤和 T+1 的风险</td><td>工程推断，需要在实际数据上验证</td></tr><tr><td>公开商品数据实验</td><td>建议的独立验证方案，不等同于复现生产系统</td></tr></tbody></table>
<p>企业案例写作只有在边界清楚时才有价值。事实要能追溯，推断要明确标记，实验不能冒充生产复现。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="参考资料">参考资料<a href="https://www.xlonglab.com/articles/didi-grocery-vector-search-case-study#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接">​</a></h2>
<ul>
<li><a href="https://mp.weixin.qq.com/s?__biz=MzUzMDI5OTA5NQ==&amp;mid=2247507938&amp;idx=1&amp;sn=3daa373ee3aaef9e726138d0e4ce6828&amp;chksm=fa517e5acd26f74c6301ba425e0b86594474844b917ab9c78048cbf26fb9ecfa6a43ed0c5600#rd" target="_blank" rel="noopener noreferrer">Zilliz：滴滴 Grocery 商超检索案例</a></li>
<li><a href="https://huggingface.co/jinaai/jina-embeddings-v3" target="_blank" rel="noopener noreferrer">Jina Embeddings v3 模型卡</a></li>
<li><a href="https://huggingface.co/BAAI/bge-m3" target="_blank" rel="noopener noreferrer">BGE-M3 模型卡</a></li>
<li><a href="https://www.xlonglab.com/docs/tutorials/hybrid_search_with_milvus.md">Milvus 混合搜索教程</a></li>
<li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html" target="_blank" rel="noopener noreferrer">Elasticsearch kNN Search</a></li>
</ul>]]></content:encoded>
            <category>产品观察</category>
        </item>
        <item>
            <title><![CDATA[Embedding 模型怎么选：中文、长文本与多模态评测方法]]></title>
            <link>https://www.xlonglab.com/articles/embedding-model-selection-2026</link>
            <guid>https://www.xlonglab.com/articles/embedding-model-selection-2026</guid>
            <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[不迷信一次性排行榜，从中文检索、跨语言、长文本、多模态、延迟和成本六个维度建立可重复运行的 Embedding 选型流程。]]></description>
            <content:encoded><![CDATA[<p>Embedding 模型正在快速分化：有的强调中文与多语言，有的面向长文本，有的支持图像、视频或 PDF，还有的允许通过 Matryoshka Representation Learning（MRL）裁剪向量维度。面对几十个模型，最危险的做法是直接抄一张排行榜，然后把第一名接进生产系统。</p>
<p>更可靠的结论只有一句话：<strong>先建立评测流程，再选择模型。</strong> 模型会更新，业务数据也会变化；一套能反复运行的评测流程，比一次性的“冠军模型”更有长期价值。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="为什么公开排行榜不够">为什么公开排行榜不够<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%85%AC%E5%BC%80%E6%8E%92%E8%A1%8C%E6%A6%9C%E4%B8%8D%E5%A4%9F" class="hash-link" aria-label="为什么公开排行榜不够的直接链接" title="为什么公开排行榜不够的直接链接">​</a></h2>
<p>MTEB、MMTEB 和 MMEB 等公开基准非常适合筛选候选模型，但它们无法替代业务评测，主要有四个原因。</p>
<p>第一，公开数据和你的数据分布不同。客服知识库里会出现产品型号、缩写、内部术语和不完整问句；电商搜索会出现错别字、品牌别名与属性组合。这些问题通常不会完整出现在通用榜单中。</p>
<p>第二，总分会掩盖场景差异。一个模型可能在英文语义相似度任务上领先，却不擅长中文专有名词；也可能在图文检索上表现很好，但编码成本过高。</p>
<p>第三，模型质量不是唯一指标。Embedding 服务还受到延迟、吞吐、显存、向量维度、API 费用、许可证和数据合规约束。</p>
<p>第四，榜单有时间性。模型版本、API 价格和推理框架都在变化。即使一篇文章做过完整实测，几个月后也需要重新运行。</p>
<p>因此，公开榜单的正确用法是把几十个模型缩小到 4～6 个候选，而不是直接决定生产选型。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="先把业务拆成具体任务">先把业务拆成具体任务<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#%E5%85%88%E6%8A%8A%E4%B8%9A%E5%8A%A1%E6%8B%86%E6%88%90%E5%85%B7%E4%BD%93%E4%BB%BB%E5%8A%A1" class="hash-link" aria-label="先把业务拆成具体任务的直接链接" title="先把业务拆成具体任务的直接链接">​</a></h2>
<p>在选模型之前，先回答“我们到底在检索什么”。下面这张表比模型排行榜更重要。</p>
<table><thead><tr><th>任务</th><th>需要重点验证的能力</th><th>常见失败</th></tr></thead><tbody><tr><td>中文知识库</td><td>中文语义、专有名词、长文档</td><td>同义表达召回不到，型号被误分词</td></tr><tr><td>中英混合资料</td><td>跨语言对齐</td><td>中文问题只能命中中文文档</td></tr><tr><td>代码与技术文档</td><td>标识符、代码语义、版本差异</td><td>自然语言相似，但 API 版本错误</td></tr><tr><td>电商或内容搜索</td><td>错别字、属性、时效与难负例</td><td>返回“看起来相似”但属性错误的结果</td></tr><tr><td>图片与视频搜索</td><td>图文对齐、细粒度视觉属性</td><td>只理解大类，分不清颜色、数量和款式</td></tr><tr><td>大规模向量库</td><td>维度、吞吐、存储与成本</td><td>模型质量提升很小，成本却成倍增加</td></tr></tbody></table>
<p>如果一个系统同时包含文本和图片，不要强行寻找一个“全能模型”。文本知识库和图文检索可以分别使用最合适的模型，再通过业务层统一结果。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="一套最小但有效的评测集">一套最小但有效的评测集<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#%E4%B8%80%E5%A5%97%E6%9C%80%E5%B0%8F%E4%BD%86%E6%9C%89%E6%95%88%E7%9A%84%E8%AF%84%E6%B5%8B%E9%9B%86" class="hash-link" aria-label="一套最小但有效的评测集的直接链接" title="一套最小但有效的评测集的直接链接">​</a></h2>
<p>完整评测不一定需要几万条数据。初期可以从真实日志和人工构造问题中整理 200～500 个查询，覆盖以下类型：</p>
<ul>
<li>高频正常查询；</li>
<li>低频但重要的长尾查询；</li>
<li>错别字、缩写和口语表达；</li>
<li>只有一个词不同的难负例；</li>
<li>无法回答或不应返回结果的查询；</li>
<li>中文查询检索英文文档；</li>
<li>需要精确型号、日期、价格或数字的查询。</li>
</ul>
<p>对有答案的查询，每条至少标注一个相关文档，并尽可能区分“强相关”和“可接受”；对无答案查询，将相关文档集合保留为空，并放入单独的拒答评测。不要把两类查询混进同一个 Recall 平均值。如果标注成本有限，优先保证难负例质量，而不是盲目扩大样本量。</p>
<p>推荐至少记录四个检索指标：</p>
<ul>
<li><code>Recall@K</code>：正确文档是否进入前 K 个结果；</li>
<li><code>MRR</code>：第一个正确结果出现得有多靠前；</li>
<li><code>nDCG@K</code>：多个不同相关等级的结果是否排序合理；</li>
<li>无答案查询误接受率：知识库没有证据时，系统是否仍返回了超过阈值的候选或生成答案。</li>
</ul>
<p>下面的 Python 代码可以作为 Recall@K 的最小实现：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">recall_at_k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">ranked_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> relevant_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> k</span><span class="token operator">=</span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    top_k </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">ranked_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    relevant </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">relevant_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> relevant</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Recall@K 只适用于至少有一个相关文档的查询"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">top_k </span><span class="token operator">&amp;</span><span class="token plain"> relevant</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">/</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">relevant</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">mean_recall_at_k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">results</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> labels</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> k</span><span class="token operator">=</span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    answerable_query_ids </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        query_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> query_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> relevant_ids </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> labels</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> relevant_ids</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> answerable_query_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"评测集中没有可计算 Recall@K 的查询"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    scores </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        recall_at_k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">results</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">query_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> labels</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">query_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> query_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> answerable_query_ids</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">sum</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">scores</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">/</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">scores</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">no_answer_false_accept_rate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">scored_results</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> labels</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> threshold</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""scored_results[qid] 是按分数降序排列的 {id, score} 列表。"""</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    no_answer_query_ids </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        query_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> query_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> relevant_ids </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> labels</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> relevant_ids</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> no_answer_query_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"评测集中没有无答案查询"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    false_accepts </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">sum</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">bool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">scored_results</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">query_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">and</span><span class="token plain"> scored_results</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">query_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"score"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&gt;=</span><span class="token plain"> threshold</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> query_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> no_answer_query_ids</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> false_accepts </span><span class="token operator">/</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">no_answer_query_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>阈值必须在独立验证集上确定，不能反复调整到刚好适配测试集。真正运行时，还要保存每个查询的结果明细。平均分只能告诉你模型是否变好，失败查询才能告诉你为什么。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="六个必须分开测试的维度">六个必须分开测试的维度<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#%E5%85%AD%E4%B8%AA%E5%BF%85%E9%A1%BB%E5%88%86%E5%BC%80%E6%B5%8B%E8%AF%95%E7%9A%84%E7%BB%B4%E5%BA%A6" class="hash-link" aria-label="六个必须分开测试的维度的直接链接" title="六个必须分开测试的维度的直接链接">​</a></h2>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="1-中文与跨语言检索">1. 中文与跨语言检索<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#1-%E4%B8%AD%E6%96%87%E4%B8%8E%E8%B7%A8%E8%AF%AD%E8%A8%80%E6%A3%80%E7%B4%A2" class="hash-link" aria-label="1. 中文与跨语言检索的直接链接" title="1. 中文与跨语言检索的直接链接">​</a></h3>
<p>中文评测应包含产品名、机构名、数字、英文缩写和中英混写。跨语言测试不能只使用直译句子，还应加入成语、行业术语和表达顺序不同的句子。</p>
<p>如果知识库主要是英文、用户主要用中文提问，就必须把“中文问题检索英文段落”作为独立测试项。不要因为模型标注“支持 100 多种语言”就默认跨语言效果满足业务要求。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="2-长文本与切块策略">2. 长文本与切块策略<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#2-%E9%95%BF%E6%96%87%E6%9C%AC%E4%B8%8E%E5%88%87%E5%9D%97%E7%AD%96%E7%95%A5" class="hash-link" aria-label="2. 长文本与切块策略的直接链接" title="2. 长文本与切块策略的直接链接">​</a></h3>
<p>模型支持较长上下文，并不意味着应该直接把整篇文档编码成一个向量。长文档中往往包含多个主题，一个向量会把局部信息平均掉。</p>
<p>建议同时比较：</p>
<ul>
<li>300～500 token 的短块；</li>
<li>800～1200 token 的中等块；</li>
<li>按标题、段落或表格切分的结构化块；</li>
<li>父子块检索；</li>
<li>直接编码长文档。</li>
</ul>
<p>Embedding 模型和切块策略应联合评估。只更换模型、不检查切块，常常无法解决召回问题。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="3-难负例">3. 难负例<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#3-%E9%9A%BE%E8%B4%9F%E4%BE%8B" class="hash-link" aria-label="3. 难负例的直接链接" title="3. 难负例的直接链接">​</a></h3>
<p>难负例是“很像但不对”的候选。例如：</p>
<ul>
<li>“支持 Python 3.10”与“仅支持 Python 3.11”；</li>
<li>“红色 256 GB”与“蓝色 256 GB”；</li>
<li>“2025 年价格”与“2026 年价格”；</li>
<li>同一 API 在不同版本中的用法。</li>
</ul>
<p>这类样本比随机负例更能区分模型。生产系统中的错误通常不是完全无关，而是细节错误。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="4-多模态检索">4. 多模态检索<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#4-%E5%A4%9A%E6%A8%A1%E6%80%81%E6%A3%80%E7%B4%A2" class="hash-link" aria-label="4. 多模态检索的直接链接" title="4. 多模态检索的直接链接">​</a></h3>
<p>图文检索需要分别测试文本找图和图片找文本。建议使用 Recall@1、Recall@5 和失败案例画廊，而不是只展示几个成功截图。</p>
<p>中文场景还应比较三种方案：中文直接查询、翻译后查询、多语言多模态模型。一个英文 CLIP 模型在英文提示词上表现不错，不代表中文搜索也可靠。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="5-向量维度与存储">5. 向量维度与存储<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#5-%E5%90%91%E9%87%8F%E7%BB%B4%E5%BA%A6%E4%B8%8E%E5%AD%98%E5%82%A8" class="hash-link" aria-label="5. 向量维度与存储的直接链接" title="5. 向量维度与存储的直接链接">​</a></h3>
<p>向量维度会直接影响内存、磁盘、网络传输和索引构建成本。假设保存一亿条 <code>float32</code> 向量：</p>
<ul>
<li>256 维原始向量约占 95 GiB；</li>
<li>1024 维约占 381 GiB；</li>
<li>3072 维约占 1.1 TiB。</li>
</ul>
<p>这还没有计算索引、元数据、副本和系统开销。支持 MRL 的模型可以测试 256、512、1024 等不同截断维度，观察质量下降是否可接受。不能把“不支持 MRL 的模型”直接截断后与原模型等价使用。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="6-延迟吞吐与总成本">6. 延迟、吞吐与总成本<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#6-%E5%BB%B6%E8%BF%9F%E5%90%9E%E5%90%90%E4%B8%8E%E6%80%BB%E6%88%90%E6%9C%AC" class="hash-link" aria-label="6. 延迟、吞吐与总成本的直接链接" title="6. 延迟、吞吐与总成本的直接链接">​</a></h3>
<p>至少记录：</p>
<ul>
<li>单条请求 P50/P95 延迟；</li>
<li>批量编码吞吐；</li>
<li>峰值显存或内存；</li>
<li>每百万条文档的编码时间；</li>
<li>API 调用费用；</li>
<li>向量存储与索引空间。</li>
</ul>
<p>同一个模型在单条请求和批量离线编码中的表现可能完全不同。在线查询关注尾延迟，离线建库更关注吞吐与失败重试。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="如何组织一次可复现评测">如何组织一次可复现评测<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#%E5%A6%82%E4%BD%95%E7%BB%84%E7%BB%87%E4%B8%80%E6%AC%A1%E5%8F%AF%E5%A4%8D%E7%8E%B0%E8%AF%84%E6%B5%8B" class="hash-link" aria-label="如何组织一次可复现评测的直接链接" title="如何组织一次可复现评测的直接链接">​</a></h2>
<p>建议把每次评测保存成一个不可变的运行目录：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Text</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">runs/2026-07-28-qwen3-embedding/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── environment.json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── model.json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── dataset-manifest.json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── retrieval-results.jsonl</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── metrics.json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">└── failures.md</span><br></span></code></pre></div></div>
<p>其中必须包含模型版本、推理框架、硬件、向量维度、归一化方式、查询提示词、索引参数和数据集版本。缺少这些信息，模型之间的分数无法可靠比较。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="一个更实用的选型顺序">一个更实用的选型顺序<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%AE%9E%E7%94%A8%E7%9A%84%E9%80%89%E5%9E%8B%E9%A1%BA%E5%BA%8F" class="hash-link" aria-label="一个更实用的选型顺序的直接链接" title="一个更实用的选型顺序的直接链接">​</a></h2>
<p>可以按以下顺序降低决策成本：</p>
<ol>
<li>根据语言、模态、许可证和部署方式排除不适用模型；</li>
<li>用 200～500 条真实查询做离线检索评测；</li>
<li>对前两名进行延迟、吞吐和成本测试；</li>
<li>与现有模型做线上小流量 A/B 测试；</li>
<li>保存评测流程，模型更新后重新运行。</li>
</ol>
<p>最终选中的模型不一定是离线分数最高的模型。质量接近时，更低的延迟、更稳定的服务、更清晰的许可证和更可控的成本通常更重要。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="为什么本文不直接给出2026-冠军模型">为什么本文不直接给出“2026 冠军模型”<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%9C%AC%E6%96%87%E4%B8%8D%E7%9B%B4%E6%8E%A5%E7%BB%99%E5%87%BA2026-%E5%86%A0%E5%86%9B%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="为什么本文不直接给出“2026 冠军模型”的直接链接" title="为什么本文不直接给出“2026 冠军模型”的直接链接">​</a></h2>
<p>Zilliz 在 2026 年的一次公开测试中比较了 Gemini、Jina、Qwen、BGE、OpenAI 等模型，并覆盖跨模态、跨语言、长文本与 MRL。它提供了有价值的任务设计，也明确承认样本量较小、未覆盖所有模态与垂直领域。</p>
<p>因此，本文不直接复用它的排名，也不把这套通用方法包装成一份 2026 年实时榜单。更值得保留的是测试思路：<strong>没有一个模型在所有任务上都占优；建立自己的评测能力，比记住某次排名更重要。</strong></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="参考资料">参考资料<a href="https://www.xlonglab.com/articles/embedding-model-selection-2026#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接">​</a></h2>
<ul>
<li><a href="https://huggingface.co/spaces/mteb/leaderboard" target="_blank" rel="noopener noreferrer">MTEB Leaderboard</a></li>
<li><a href="https://huggingface.co/spaces/TIGER-Lab/MMEB-Leaderboard" target="_blank" rel="noopener noreferrer">MMEB Leaderboard</a></li>
<li><a href="https://huggingface.co/papers/2502.13595" target="_blank" rel="noopener noreferrer">MMTEB 论文</a></li>
<li><a href="https://mp.weixin.qq.com/s?__biz=MzUzMDI5OTA5NQ==&amp;mid=2247512144&amp;idx=1&amp;sn=2783fc78e14c7a792a748f2053ca3284&amp;chksm=fa5111e8cd2698fe398e1f05f18bb3169c871b6c53664b5ba751753c54726f03a632186c3fa7#rd" target="_blank" rel="noopener noreferrer">Zilliz：2026 年 Embedding 模型测试</a></li>
</ul>]]></content:encoded>
            <category>技术解析</category>
        </item>
        <item>
            <title><![CDATA[CLIP + Milvus 图文检索基线：从数据建模到多模态问答接口]]></title>
            <link>https://www.xlonglab.com/articles/clip-milvus-multimodal-rag</link>
            <guid>https://www.xlonglab.com/articles/clip-milvus-multimodal-rag</guid>
            <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[用授权图片清单、OpenCLIP、Milvus Lite 和 OpenAI 兼容视觉接口搭建图文检索基线，并分开评估召回与回答质量。]]></description>
            <content:encoded><![CDATA[<p>多模态检索 Demo 很容易让人产生错觉：输入“红色汽车”，系统返回几张红色汽车图片，看起来就已经成功了。但真正的产品会遇到中文查询、细粒度属性、相似但错误的图片、元数据过滤、增量图片和版权边界。</p>
<p>本文从一个可连接自有授权图片的图文检索基线开始，再演示如何把检索结果交给 OpenAI 兼容的视觉语言模型。重点不是展示成功截图，也不公布未经执行的跑分，而是说明如何准备数据、评价系统、分析失败和逐步走向生产。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="先区分三个任务">先区分三个任务<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E5%85%88%E5%8C%BA%E5%88%86%E4%B8%89%E4%B8%AA%E4%BB%BB%E5%8A%A1" class="hash-link" aria-label="先区分三个任务的直接链接" title="先区分三个任务的直接链接">​</a></h2>
<p>“多模态搜索”通常混合了三类不同问题：</p>
<ol>
<li><strong>以文搜图</strong>：输入文本，返回相关图片；</li>
<li><strong>以图搜图</strong>：输入图片，返回视觉或语义相似图片；</li>
<li><strong>多模态 RAG</strong>：先检索图片及其元数据，再让视觉语言模型基于证据回答。</li>
</ol>
<p>三类任务应分别评估。一个模型可能擅长图像相似，却无法理解复杂文本属性；视觉语言模型能描述图片，也不代表检索阶段准确。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="clip-做了什么">CLIP 做了什么<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#clip-%E5%81%9A%E4%BA%86%E4%BB%80%E4%B9%88" class="hash-link" aria-label="CLIP 做了什么的直接链接" title="CLIP 做了什么的直接链接">​</a></h2>
<p>CLIP 使用文本编码器和图像编码器，把两种输入映射到同一向量空间。语义相关的图文向量距离更近，因此可以使用同一种相似度搜索。</p>
<p>它的优势是结构简单、生态成熟；局限也很明确：</p>
<ul>
<li>经典 CLIP 主要使用英文图文对训练；</li>
<li>对数量、方位、否定和细粒度属性可能不敏感；</li>
<li>文本长度有限；</li>
<li>视觉上相似不等于业务上相关；</li>
<li>不同模态仍可能存在分布差异。</li>
</ul>
<p>因此，中文产品应同时评估中文多模态模型、翻译查询和多语言模型，而不是直接照搬英文 Demo。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="数据设计比模型调用更重要">数据设计比模型调用更重要<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E6%95%B0%E6%8D%AE%E8%AE%BE%E8%AE%A1%E6%AF%94%E6%A8%A1%E5%9E%8B%E8%B0%83%E7%94%A8%E6%9B%B4%E9%87%8D%E8%A6%81" class="hash-link" aria-label="数据设计比模型调用更重要的直接链接" title="数据设计比模型调用更重要的直接链接">​</a></h2>
<p>每张图片至少保存：</p>
<ul>
<li>稳定的 <code>image_id</code>；</li>
<li>文件 URI 或对象存储地址；</li>
<li>标题或 caption；</li>
<li>类别、标签、语言等元数据；</li>
<li>来源与版权；</li>
<li>创建和更新时间；</li>
<li>图片向量。</li>
</ul>
<p>如果同一图片有多个语言描述，不要用一个字符串覆盖。可以把描述作为独立记录，或者保存为数组并明确查询策略。</p>
<p>下面是一条示例数据：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">JSON</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"image_id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"catalog-00142"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"uri"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"s3://demo/catalog-00142.jpg"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"caption"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"一双红色低帮跑鞋，白色鞋底"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"category"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"shoes"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"language"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"zh-CN"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"source"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"demo-catalog"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"license"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"internal-demo"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="使用-openclip-生成图文向量">使用 OpenCLIP 生成图文向量<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E4%BD%BF%E7%94%A8-openclip-%E7%94%9F%E6%88%90%E5%9B%BE%E6%96%87%E5%90%91%E9%87%8F" class="hash-link" aria-label="使用 OpenCLIP 生成图文向量的直接链接" title="使用 OpenCLIP 生成图文向量的直接链接">​</a></h2>
<p>下面使用 <code>open_clip_torch</code> 构建一个英文基线。模型名和预训练权重应在运行时锁定，中文应用必须另外评估。以下示例要求 Python 3.10 或更高版本。</p>
<p>OpenCLIP 代码、具体预训练权重和训练数据不是同一个授权对象。示例权重 <code>laion2b_s34b_b79k</code> 来自 LAION 数据训练；上线前应分别核对代码许可证、模型卡、权重条款、训练数据边界和图片自身版权，不能因为代码可用就推断图片内容可以商用。</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Shell</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">python3 </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-m</span><span class="token plain"> venv .venv</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">source</span><span class="token plain"> .venv/bin/activate</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">python </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-m</span><span class="token plain"> pip </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"open_clip_torch&gt;=2.30,&lt;3"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"torch&gt;=2.4,&lt;3"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"pillow&gt;=10,&lt;12"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"pymilvus[milvus_lite]&gt;=2.5,&lt;3"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"openai&gt;=1.60,&lt;3"</span><br></span></code></pre></div></div>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> pathlib </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Path</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> open_clip</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> torch</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> PIL </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Image</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">device </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"cuda"</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> torch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">is_available</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"cpu"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> _</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> preprocess </span><span class="token operator">=</span><span class="token plain"> open_clip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">create_model_and_transforms</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"ViT-B-32"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    pretrained</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"laion2b_s34b_b79k"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">tokenizer </span><span class="token operator">=</span><span class="token plain"> open_clip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get_tokenizer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"ViT-B-32"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">model </span><span class="token operator">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">to</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">device</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">eval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">preprocess_image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> torch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Tensor</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">with</span><span class="token plain"> Image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">open</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> preprocess</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">convert</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"RGB"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@torch</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">inference_mode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">encode_images</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    paths</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    batch_size</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">32</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    encoded </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> start </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">range</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">paths</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> batch_size</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        batch_paths </span><span class="token operator">=</span><span class="token plain"> paths</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">start </span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> start </span><span class="token operator">+</span><span class="token plain"> batch_size</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        batch </span><span class="token operator">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stack</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">preprocess_image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> path </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> batch_paths</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">to</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">device</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        vectors </span><span class="token operator">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode_image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        vectors </span><span class="token operator">=</span><span class="token plain"> vectors </span><span class="token operator">/</span><span class="token plain"> vectors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">norm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">dim</span><span class="token operator">=</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> keepdim</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        encoded</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">extend</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">vectors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">cpu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tolist</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> encoded</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@torch</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">inference_mode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">encode_texts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">texts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    tokens </span><span class="token operator">=</span><span class="token plain"> tokenizer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">texts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">to</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">device</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    vectors </span><span class="token operator">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode_text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    vectors </span><span class="token operator">=</span><span class="token plain"> vectors </span><span class="token operator">/</span><span class="token plain"> vectors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">norm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">dim</span><span class="token operator">=</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> keepdim</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> vectors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">cpu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tolist</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>归一化后可以使用余弦相似度。不要让文本向量使用一种归一化方式、图片向量使用另一种方式。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="用清单连接图片与元数据">用清单连接图片与元数据<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E7%94%A8%E6%B8%85%E5%8D%95%E8%BF%9E%E6%8E%A5%E5%9B%BE%E7%89%87%E4%B8%8E%E5%85%83%E6%95%B0%E6%8D%AE" class="hash-link" aria-label="用清单连接图片与元数据的直接链接" title="用清单连接图片与元数据的直接链接">​</a></h2>
<p>文章不附带来源不明的示例图片。请准备自己的授权图片，并创建 <code>images/manifest.jsonl</code>。每行描述一张图片：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">JSON</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"image_id"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"catalog-00142"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"path"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"images/catalog-00142.jpg"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"caption"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"一双红色低帮跑鞋，白色鞋底"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"category"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"shoes"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"source"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"internal-demo"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"license"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"owned"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>然后读取清单、检查空数据和缺失文件，再写入 Milvus Lite：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> pymilvus </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> MilvusClient</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">client </span><span class="token operator">=</span><span class="token plain"> MilvusClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">uri</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"./multimodal.db"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">collection </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"catalog_images"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">manifest_path </span><span class="token operator">=</span><span class="token plain"> Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"images/manifest.jsonl"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> manifest_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">exists</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> FileNotFoundError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"请先创建 images/manifest.jsonl"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">records </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">loads</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> line </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> manifest_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">read_text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">encoding</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"utf-8"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">splitlines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">strip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> records</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"manifest 为空，至少需要一张授权图片"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">sample_paths </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"path"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> record </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> records</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">missing_paths </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> path </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> sample_paths </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">is_file</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> missing_paths</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> FileNotFoundError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"manifest 中的图片不存在：</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">missing_paths</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">vectors </span><span class="token operator">=</span><span class="token plain"> encode_images</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">sample_paths</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">dimension </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">vectors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">has_collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">drop_collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">create_collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    collection_name</span><span class="token operator">=</span><span class="token plain">collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    dimension</span><span class="token operator">=</span><span class="token plain">dimension</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    metric_type</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"COSINE"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    consistency_level</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"Strong"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">insert</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    collection_name</span><span class="token operator">=</span><span class="token plain">collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    data</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"vector"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> vector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"image_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"image_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"uri"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"caption"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"caption"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"category"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"category"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"source"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"source"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"license"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"license"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> vector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">enumerate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token builtin" style="color:rgb(189, 147, 249)">zip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">records</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> sample_paths</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> vectors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>小数据可以使用精确搜索或默认索引。生产数据需要根据规模、召回率和延迟目标选择索引，并保存原始图片到对象存储，而不是放进向量数据库。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="以文搜图">以文搜图<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E4%BB%A5%E6%96%87%E6%90%9C%E5%9B%BE" class="hash-link" aria-label="以文搜图的直接链接" title="以文搜图的直接链接">​</a></h2>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">search_images</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">8</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> category</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    query_vector </span><span class="token operator">=</span><span class="token plain"> encode_texts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    filter_expression </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"category == </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">json</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">dumps</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation interpolation">category</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token string-interpolation interpolation"> ensure_ascii</span><span class="token string-interpolation interpolation operator">=</span><span class="token string-interpolation interpolation boolean">False</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> category</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    results </span><span class="token operator">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">search</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        collection_name</span><span class="token operator">=</span><span class="token plain">collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        data</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">query_vector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">filter</span><span class="token operator">=</span><span class="token plain">filter_expression</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        limit</span><span class="token operator">=</span><span class="token plain">limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        output_fields</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"image_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"uri"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"caption"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"category"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"source"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        search_params</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"metric_type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"COSINE"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"params"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"score"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> hit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"distance"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">**</span><span class="token plain">hit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"entity"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> hit </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> results</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></span></code></pre></div></div>
<p>经典 CLIP 的中文能力有限。可比较三种方案：</p>
<ul>
<li>中文直接输入；</li>
<li>把中文翻译成英文后输入；</li>
<li>替换为经过中文或多语言训练的图文模型。</li>
</ul>
<p>比较时必须使用同一查询集，而不是为每个模型挑选最有利的示例。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="以图搜图">以图搜图<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E4%BB%A5%E5%9B%BE%E6%90%9C%E5%9B%BE" class="hash-link" aria-label="以图搜图的直接链接" title="以图搜图的直接链接">​</a></h2>
<p>查询图片使用同一个图像编码器：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">query_vector </span><span class="token operator">=</span><span class="token plain"> encode_images</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"query.jpg"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">results </span><span class="token operator">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">search</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    collection_name</span><span class="token operator">=</span><span class="token plain">collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    data</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">query_vector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    limit</span><span class="token operator">=</span><span class="token number">8</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    output_fields</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"image_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"uri"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"category"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    search_params</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"metric_type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"COSINE"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"params"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>以图搜图通常更偏向视觉外观，以文搜图更偏向语义。产品需要决定用户想要“同款”“相似风格”还是“同一类物体”，并据此标注评测集。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="加入元数据过滤">加入元数据过滤<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E5%8A%A0%E5%85%A5%E5%85%83%E6%95%B0%E6%8D%AE%E8%BF%87%E6%BB%A4" class="hash-link" aria-label="加入元数据过滤的直接链接" title="加入元数据过滤的直接链接">​</a></h2>
<p>实际搜索往往还要满足类目、时间、权限或库存条件。例如：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">results </span><span class="token operator">=</span><span class="token plain"> search_images</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"red running shoes"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> limit</span><span class="token operator">=</span><span class="token number">8</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> category</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"shoes"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>过滤字段应使用稳定枚举或标识符。把自然语言标签直接当作唯一过滤条件，会产生拼写、语言和同义词问题。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="从检索升级到多模态-rag">从检索升级到多模态 RAG<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E4%BB%8E%E6%A3%80%E7%B4%A2%E5%8D%87%E7%BA%A7%E5%88%B0%E5%A4%9A%E6%A8%A1%E6%80%81-rag" class="hash-link" aria-label="从检索升级到多模态 RAG的直接链接" title="从检索升级到多模态 RAG的直接链接">​</a></h2>
<p>多模态 RAG 可以采用以下流程：</p>
<ol>
<li>文本或图片查询生成向量；</li>
<li>从 Milvus 召回图片 ID、caption 和来源；</li>
<li>使用元数据、caption 或跨编码器重排；</li>
<li>读取最终图片；</li>
<li>把图片和问题交给视觉语言模型；</li>
<li>回答中返回使用过的图片 ID。</li>
</ol>
<p>视觉语言模型只能看到最终上下文，因此检索失败时，它可能基于错误图片生成非常流畅的错误回答。检索指标和回答指标必须分开。</p>
<p>一个安全的提示词应要求：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Text</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">只根据提供的图片和元数据回答。</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">无法从图片确认的细节必须明确说明。</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">每个结论标注对应的 image_id。</span><br></span></code></pre></div></div>
<p>下面的函数把最终图片作为 Data URL 发送给兼容 OpenAI Chat Completions 的视觉模型。请只向模型服务发送允许外传的图片，并结合具体供应商的请求大小、图片数量和数据保留规则调整：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> base64</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> mimetypes</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> os</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> openai </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> OpenAI</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">vision_client </span><span class="token operator">=</span><span class="token plain"> OpenAI</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    api_key</span><span class="token operator">=</span><span class="token plain">os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">environ</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"OPENAI_API_KEY"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    base_url</span><span class="token operator">=</span><span class="token plain">os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">environ</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"OPENAI_BASE_URL"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">image_data_url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    mime_type </span><span class="token operator">=</span><span class="token plain"> mimetypes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">guess_type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"image/jpeg"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    encoded </span><span class="token operator">=</span><span class="token plain"> base64</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">b64encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">read_bytes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">decode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"ascii"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"data:</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">mime_type</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">;base64,</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">encoded</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">answer_with_images</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> hits</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> hits</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"answer"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"没有检索到可用图片。"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"image_ids"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    content </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string" style="color:rgb(255, 121, 198)">"只根据随后提供的图片和元数据回答。无法确认的细节要明确说明，"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string" style="color:rgb(255, 121, 198)">"每个结论标注 [图片: image_id]。\n\n问题："</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> query</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> hit </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> hits</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">4</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        content</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string" style="color:rgb(255, 121, 198)">"type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f'图片 ID：</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">hit</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string-interpolation interpolation string" style="color:rgb(255, 121, 198)">"image_id"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">；描述：</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">hit</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string-interpolation interpolation string" style="color:rgb(255, 121, 198)">"caption"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        content</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string" style="color:rgb(255, 121, 198)">"type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"image_url"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string" style="color:rgb(255, 121, 198)">"image_url"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"url"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> image_data_url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">hit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"uri"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    response </span><span class="token operator">=</span><span class="token plain"> vision_client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">completions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">create</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        model</span><span class="token operator">=</span><span class="token plain">os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">environ</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"VISION_MODEL"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        temperature</span><span class="token operator">=</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        messages</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"role"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"user"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"content"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> content</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"answer"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">choices</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">content </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"image_ids"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">hit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"image_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> hit </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> hits</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">4</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>这只是接口层参考实现，不是视觉模型质量证明。调用前还应限制图片大小、验证 MIME 类型、处理超时，并检查回答引用的图片 ID 是否来自实际上下文。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="怎样单独评价回答质量">怎样单独评价回答质量<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E6%80%8E%E6%A0%B7%E5%8D%95%E7%8B%AC%E8%AF%84%E4%BB%B7%E5%9B%9E%E7%AD%94%E8%B4%A8%E9%87%8F" class="hash-link" aria-label="怎样单独评价回答质量的直接链接" title="怎样单独评价回答质量的直接链接">​</a></h2>
<p>检索 Recall 合格，不代表视觉模型回答正确。为多模态问答准备一组独立测试案例，每条至少包含：问题、允许使用的图片 ID、必须出现的事实、明确不能推断的事实，以及证据不足时的预期拒答。</p>
<p>例如：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">JSON</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"question"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"哪双鞋是红色低帮并带白色鞋底？"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"allowed_image_ids"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"catalog-00142"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"required_claims"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"红色"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"低帮"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"白色鞋底"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"forbidden_claims"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"防水"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"真皮"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"should_abstain"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>至少分开记录四项：</p>
<ul>
<li><strong>回答正确性</strong>：关键事实是否与图片和人工标注一致；</li>
<li><strong>证据支撑率</strong>：回答中的可验证结论有多少能在提供的图片中找到依据；</li>
<li><strong>图片引用准确率</strong>：<code>[图片: image_id]</code> 是否确实来自本次上下文并支持对应结论；</li>
<li><strong>拒答准确率</strong>：图片不足以回答时，模型是否停止推断。</li>
</ul>
<p>引用合法性可以先做确定性检查：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> re</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">validate_image_citations</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">answer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> allowed_image_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    cited </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">findall</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"\[图片:\s*([^\]]+)\]"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> answer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"cited_image_ids"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">sorted</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">cited</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"unknown_image_ids"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">sorted</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">cited </span><span class="token operator">-</span><span class="token plain"> allowed_image_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"has_valid_citation"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">bool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">cited </span><span class="token operator">&amp;</span><span class="token plain"> allowed_image_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>但“引用存在”不等于“引用支持结论”。回答正确性、证据支撑和禁止推断项应由固定规则、人工标注或与生成模型隔离的评审流程打分，并定期抽样复核。最终报告应同时列出检索指标和回答指标，不能把两者合成一个看似完整的总分。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="怎样评价多模态检索">怎样评价多模态检索<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E6%80%8E%E6%A0%B7%E8%AF%84%E4%BB%B7%E5%A4%9A%E6%A8%A1%E6%80%81%E6%A3%80%E7%B4%A2" class="hash-link" aria-label="怎样评价多模态检索的直接链接" title="怎样评价多模态检索的直接链接">​</a></h2>
<p>至少准备三类评测：</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="文本找图">文本找图<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E6%96%87%E6%9C%AC%E6%89%BE%E5%9B%BE" class="hash-link" aria-label="文本找图的直接链接" title="文本找图的直接链接">​</a></h3>
<p>为每张图片准备一条正确描述和多条难负例。例如正确描述是“红色低帮跑鞋，白色鞋底”，难负例可以只改变颜色、鞋帮或鞋底。</p>
<p>记录 Recall@1、Recall@5 和 nDCG@10。下面的最小实现只计算至少有一个相关图片的查询；无答案查询应像文本检索一样单独评价误接受率。</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">recall_at_k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">retrieved_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> relevant_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> relevant_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Recall@K 不适用于没有相关图片的查询"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">retrieved_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&amp;</span><span class="token plain"> relevant_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">/</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">relevant_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">evaluate_text_to_image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">test_cases</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    scores </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    failures </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> test_cases</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        hits </span><span class="token operator">=</span><span class="token plain"> search_images</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"query"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> limit</span><span class="token operator">=</span><span class="token plain">k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        retrieved_ids </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">hit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"image_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> hit </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> hits</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        score </span><span class="token operator">=</span><span class="token plain"> recall_at_k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">retrieved_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"relevant_ids"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        scores</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">score</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> score </span><span class="token operator">&lt;</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            failures</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token operator">**</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"retrieved_ids"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> retrieved_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"recall_at_</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">k</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">sum</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">scores</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">/</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">scores</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"failures"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> failures</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="图片找图">图片找图<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E5%9B%BE%E7%89%87%E6%89%BE%E5%9B%BE" class="hash-link" aria-label="图片找图的直接链接" title="图片找图的直接链接">​</a></h3>
<p>根据产品目标标注“同款”“同风格”或“同类别”。三个标签不能混成一个相关性定义。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="错误案例">错误案例<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E9%94%99%E8%AF%AF%E6%A1%88%E4%BE%8B" class="hash-link" aria-label="错误案例的直接链接" title="错误案例的直接链接">​</a></h3>
<p>至少把错误分成：</p>
<ul>
<li>数量错误；</li>
<li>颜色或材质错误；</li>
<li>文字与 Logo 识别错误；</li>
<li>细粒度类别错误；</li>
<li>中文表达错误；</li>
<li>数据标注或图片本身歧义。</li>
</ul>
<p>平均分告诉你系统好不好，错误类型决定下一步应该换模型、加 OCR、改元数据还是增加重排。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="从-demo-到产品还缺什么">从 Demo 到产品还缺什么<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E4%BB%8E-demo-%E5%88%B0%E4%BA%A7%E5%93%81%E8%BF%98%E7%BC%BA%E4%BB%80%E4%B9%88" class="hash-link" aria-label="从 Demo 到产品还缺什么的直接链接" title="从 Demo 到产品还缺什么的直接链接">​</a></h2>
<ul>
<li>图片来源和版权管理；</li>
<li>对象存储与 CDN；</li>
<li>重复图片和近重复检测；</li>
<li>增量更新与删除；</li>
<li>内容审核；</li>
<li>模型版本和向量重建；</li>
<li>多语言查询；</li>
<li>用户反馈与在线评估；</li>
<li>查询缓存和成本控制。</li>
</ul>
<p>多模态系统不是“换一个 Embedding 模型”就结束。它需要把图片资产、向量、元数据、生成证据和评估流程连接起来。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="参考资料">参考资料<a href="https://www.xlonglab.com/articles/clip-milvus-multimodal-rag#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接">​</a></h2>
<ul>
<li><a href="https://arxiv.org/abs/2103.00020" target="_blank" rel="noopener noreferrer">CLIP 论文</a></li>
<li><a href="https://github.com/mlfoundations/open_clip" target="_blank" rel="noopener noreferrer">OpenCLIP</a></li>
<li><a href="https://huggingface.co/laion/CLIP-ViT-B-32-laion2B-s34B-b79K" target="_blank" rel="noopener noreferrer">LAION CLIP ViT-B/32 权重模型卡</a></li>
<li><a href="https://www.xlonglab.com/docs/tutorials/image_similarity_search.md">Milvus 图片相似度搜索</a></li>
<li><a href="https://www.xlonglab.com/docs/tutorials/multimodal_rag_with_milvus.md">Milvus 多模态 RAG 教程</a></li>
<li><a href="https://mp.weixin.qq.com/s?__biz=MzUzMDI5OTA5NQ==&amp;mid=2247505503&amp;idx=1&amp;sn=10d2a98c1cf1900da6b030af4ad57b35&amp;chksm=fa5177e7cd26fef12c2d2d1b38de24a3713fb8e1641826d992f86b85b39a90295b6afc085431#rd" target="_blank" rel="noopener noreferrer">Zilliz：JinaCLIP 与多模态检索</a></li>
<li><a href="https://mp.weixin.qq.com/s?__biz=MzUzMDI5OTA5NQ==&amp;mid=2247509404&amp;idx=1&amp;sn=6830031274e6b875a1c0a4752bee2067&amp;chksm=fa510424cd268d3293a3359bead757364dd02020439eb62525ca8edc947c8f707ccf11398ea1#rd" target="_blank" rel="noopener noreferrer">Zilliz：CLIP + Milvus 以文搜图</a></li>
</ul>]]></content:encoded>
            <category>工程实践</category>
        </item>
        <item>
            <title><![CDATA[Qwen3 Embedding + Milvus 知识库基线：从可追踪切块到带引用回答]]></title>
            <link>https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base</link>
            <guid>https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base</guid>
            <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[使用稳定文档标识、Qwen3 Embedding、Milvus Lite、Qwen3 Reranker 和 OpenAI 兼容接口搭建可检查的知识库参考实现。]]></description>
            <content:encoded><![CDATA[<p>几十行代码就能把文档、向量数据库和大模型连成一个 RAG Demo，但企业知识库真正困难的部分不是“跑起来”，而是回答三个问题：为什么召回了这些内容？回答是否真的有依据？系统变更之后，质量到底变好还是变坏？</p>
<p>本文构建一个以 Qwen3 Embedding 和 Milvus Lite 为基础的知识库参考基线。重点不是追求组件数量，而是把数据处理、检索、重排、生成和评估拆开，让每一层都可以独立检查。本文提供的是可连接自有文档与模型服务的实现骨架，不宣称已经给出适用于所有企业数据的质量结果。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="目标与边界">目标与边界<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E7%9B%AE%E6%A0%87%E4%B8%8E%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="目标与边界的直接链接" title="目标与边界的直接链接">​</a></h2>
<p>我们希望得到的不是一个聊天页面，而是一条可重复运行的流水线：</p>
<ol>
<li>从 Markdown 或纯文本读取文档；</li>
<li>保留文档来源、标题和更新时间；</li>
<li>使用结构化切块生成检索单元；</li>
<li>用 Qwen3 Embedding 编码并写入 Milvus；</li>
<li>召回较大的候选集，再进行重排；</li>
<li>把最终上下文交给生成模型；</li>
<li>使用固定问题集评估每次变更。</li>
</ol>
<p>示例使用 Milvus Lite，适合本地开发和质量验证。大规模数据、并发服务、权限隔离和高可用需要切换到 Milvus 服务端部署，不能直接用 Lite 的结果推断生产性能。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="环境准备">环境准备<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E7%8E%AF%E5%A2%83%E5%87%86%E5%A4%87" class="hash-link" aria-label="环境准备的直接链接" title="环境准备的直接链接">​</a></h2>
<p>Qwen3 Embedding 有不同参数规模。本文以 0.6B 版本说明流程，它更容易在开发机上运行；批量建库仍建议使用 GPU。以下示例要求 Python 3.10 或更高版本。</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Shell</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">python3 </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-m</span><span class="token plain"> venv .venv</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">source</span><span class="token plain"> .venv/bin/activate</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">pip </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"pymilvus[milvus_lite]&gt;=2.5,&lt;3"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"sentence-transformers&gt;=2.7,&lt;6"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"transformers&gt;=4.51,&lt;5"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"torch&gt;=2.4,&lt;3"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"openai&gt;=1.60,&lt;3"</span><br></span></code></pre></div></div>
<p>锁定实际安装版本并保存：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Shell</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">pip freeze </span><span class="token operator">&gt;</span><span class="token plain"> requirements-lock.txt</span><br></span></code></pre></div></div>
<p>模型接口和推荐提示词可能更新，运行前应查看 <a href="https://huggingface.co/Qwen/Qwen3-Embedding-0.6B" target="_blank" rel="noopener noreferrer">Qwen3 Embedding 模型卡</a> 与当前版本说明。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第一步先准备可追踪的文档">第一步：先准备可追踪的文档<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E7%AC%AC%E4%B8%80%E6%AD%A5%E5%85%88%E5%87%86%E5%A4%87%E5%8F%AF%E8%BF%BD%E8%B8%AA%E7%9A%84%E6%96%87%E6%A1%A3" class="hash-link" aria-label="第一步：先准备可追踪的文档的直接链接" title="第一步：先准备可追踪的文档的直接链接">​</a></h2>
<p>不要只保存一段 <code>text</code>。知识库至少应保留：</p>
<ul>
<li><code>document_id</code>：稳定的文档标识；</li>
<li><code>chunk_id</code>：块标识；</li>
<li><code>title</code>：标题或章节；</li>
<li><code>source</code>：原始文件或 URL；</li>
<li><code>updated_at</code>：更新时间；</li>
<li><code>text</code>：检索内容；</li>
<li>权限或租户字段。</li>
</ul>
<p>下面是一个面向 Markdown 的最小切块函数。它优先按二级标题切分，再对过长章节做窗口切分。</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> hashlib</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> re</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> dataclasses </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> dataclass</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@dataclass</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">Chunk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    document_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    chunk_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    source</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    updated_at</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    access_scope</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">split_markdown</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    source</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    content</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    updated_at</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    access_scope</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    document_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    max_chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">1800</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># 生产系统应从内容管理系统传入永久 document_id；这里只用来源地址派生。</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    document_id </span><span class="token operator">=</span><span class="token plain"> document_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> hashlib</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sha256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">source</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hexdigest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">16</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    sections </span><span class="token operator">=</span><span class="token plain"> re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">split</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"(?=^##\s+)"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> content</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> flags</span><span class="token operator">=</span><span class="token plain">re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">MULTILINE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    chunks </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    chunk_occurrences </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> section </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> sections</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        section </span><span class="token operator">=</span><span class="token plain"> section</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">strip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> section</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">continue</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        first_line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain">_ </span><span class="token operator">=</span><span class="token plain"> section</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">splitlines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        title </span><span class="token operator">=</span><span class="token plain"> first_line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">removeprefix</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"## "</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">strip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> window_index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> start </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">enumerate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token builtin" style="color:rgb(189, 147, 249)">range</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">section</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> max_chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            text </span><span class="token operator">=</span><span class="token plain"> section</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">start </span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> start </span><span class="token operator">+</span><span class="token plain"> max_chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">strip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            content_hash </span><span class="token operator">=</span><span class="token plain"> hashlib</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sha256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">title</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">\n</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">window_index</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">\n</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">text</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hexdigest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">16</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            occurrence </span><span class="token operator">=</span><span class="token plain"> chunk_occurrences</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">content_hash</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            chunk_occurrences</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">content_hash</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> occurrence </span><span class="token operator">+</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            chunk_hash </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                content_hash</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> occurrence </span><span class="token operator">==</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"> hashlib</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sha256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">content_hash</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">:</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">occurrence</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hexdigest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">16</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            chunks</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                Chunk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    document_id</span><span class="token operator">=</span><span class="token plain">document_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    chunk_id</span><span class="token operator">=</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">document_id</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">:</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">chunk_hash</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    title</span><span class="token operator">=</span><span class="token plain">title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    text</span><span class="token operator">=</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    source</span><span class="token operator">=</span><span class="token plain">source</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    updated_at</span><span class="token operator">=</span><span class="token plain">updated_at</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    access_scope</span><span class="token operator">=</span><span class="token plain">access_scope</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> chunks</span><br></span></code></pre></div></div>
<p>这只是基线。正式项目还要处理表格、代码块、列表、PDF 页码和标题继承。切块的优劣必须通过检索评估决定，而不是凭经验选一个固定长度。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第二步区分查询和文档编码">第二步：区分查询和文档编码<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E7%AC%AC%E4%BA%8C%E6%AD%A5%E5%8C%BA%E5%88%86%E6%9F%A5%E8%AF%A2%E5%92%8C%E6%96%87%E6%A1%A3%E7%BC%96%E7%A0%81" class="hash-link" aria-label="第二步：区分查询和文档编码的直接链接" title="第二步：区分查询和文档编码的直接链接">​</a></h2>
<p>部分检索模型对查询和文档使用不同提示词。Qwen3 Embedding 的 <code>SentenceTransformer</code> 接口可以为查询使用检索提示。</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> sentence_transformers </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> SentenceTransformer</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">model </span><span class="token operator">=</span><span class="token plain"> SentenceTransformer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Qwen/Qwen3-Embedding-0.6B"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">encode_documents</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">texts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    vectors </span><span class="token operator">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        texts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        normalize_embeddings</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        batch_size</span><span class="token operator">=</span><span class="token number">16</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        show_progress_bar</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> vectors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tolist</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">encode_query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    vector </span><span class="token operator">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        prompt_name</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"query"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        normalize_embeddings</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> vector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tolist</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>归一化之后可以使用余弦相似度。不要混用“未归一化向量 + 内积”和“归一化向量 + 余弦”的测试结果。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第三步写入-milvus-lite">第三步：写入 Milvus Lite<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E7%AC%AC%E4%B8%89%E6%AD%A5%E5%86%99%E5%85%A5-milvus-lite" class="hash-link" aria-label="第三步：写入 Milvus Lite的直接链接" title="第三步：写入 Milvus Lite的直接链接">​</a></h2>
<p>Milvus Lite 把数据保存在本地文件中，适合复现教程。</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> pymilvus </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> MilvusClient</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">client </span><span class="token operator">=</span><span class="token plain"> MilvusClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">uri</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"./knowledge_base.db"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">collection </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"knowledge_chunks"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">dimension </span><span class="token operator">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get_sentence_embedding_dimension</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">has_collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">drop_collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">create_collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    collection_name</span><span class="token operator">=</span><span class="token plain">collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    dimension</span><span class="token operator">=</span><span class="token plain">dimension</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    metric_type</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"COSINE"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    consistency_level</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"Strong"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">with</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">open</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"handbook.md"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> encoding</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"utf-8"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> source_file</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    chunks </span><span class="token operator">=</span><span class="token plain"> split_markdown</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        source</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"handbook.md"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        content</span><span class="token operator">=</span><span class="token plain">source_file</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">read</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        updated_at</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"2026-07-28T00:00:00Z"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        access_scope</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"employees"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        document_id</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"employee-handbook"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">vectors </span><span class="token operator">=</span><span class="token plain"> encode_documents</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">chunk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">text </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> chunk </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> chunks</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">insert</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    collection_name</span><span class="token operator">=</span><span class="token plain">collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    data</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"vector"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> vector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"document_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> chunk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">document_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"chunk_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> chunk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">chunk_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"title"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> chunk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"source"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> chunk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">source</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"updated_at"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> chunk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">updated_at</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"access_scope"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> chunk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">access_scope</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> chunk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">chunk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> vector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">enumerate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token builtin" style="color:rgb(189, 147, 249)">zip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">chunks</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> vectors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>生产系统应使用显式 schema，并根据权限、租户、更新时间等过滤需求建立标量索引。动态字段方便 Demo，但不应替代正式的数据建模。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第四步召回候选而不是直接生成">第四步：召回候选，而不是直接生成<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E7%AC%AC%E5%9B%9B%E6%AD%A5%E5%8F%AC%E5%9B%9E%E5%80%99%E9%80%89%E8%80%8C%E4%B8%8D%E6%98%AF%E7%9B%B4%E6%8E%A5%E7%94%9F%E6%88%90" class="hash-link" aria-label="第四步：召回候选，而不是直接生成的直接链接" title="第四步：召回候选，而不是直接生成的直接链接">​</a></h2>
<p>Embedding 检索的职责是高召回。先取 10～30 个候选，再由更昂贵的重排模型选择最终上下文。示例把调用方已经获准访问的 <code>access_scope</code> 作为必填参数，并用 JSON 编码生成安全的字符串字面量，在检索阶段完成过滤；生产系统还应把用户身份映射为完整的租户、角色和文档 ACL 条件。</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">retrieve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> access_scope</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> access_scope</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"access_scope 不能为空"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    access_scope_literal </span><span class="token operator">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">dumps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">access_scope</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> ensure_ascii</span><span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    result </span><span class="token operator">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">search</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        collection_name</span><span class="token operator">=</span><span class="token plain">collection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        data</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">encode_query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token builtin" style="color:rgb(189, 147, 249)">filter</span><span class="token operator">=</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"access_scope == </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">access_scope_literal</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        limit</span><span class="token operator">=</span><span class="token plain">limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        output_fields</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"document_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"chunk_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"title"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"source"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"updated_at"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"access_scope"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        search_params</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"metric_type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"COSINE"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"params"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"score"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> hit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"distance"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">**</span><span class="token plain">hit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"entity"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> hit </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> result</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></span></code></pre></div></div>
<p>如果文档中有大量产品型号、错误码和 API 名称，建议加入关键词或稀疏检索形成混合召回。纯向量检索并不擅长所有精确匹配问题。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第五步用重排提高前几名质量">第五步：用重排提高前几名质量<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E7%AC%AC%E4%BA%94%E6%AD%A5%E7%94%A8%E9%87%8D%E6%8E%92%E6%8F%90%E9%AB%98%E5%89%8D%E5%87%A0%E5%90%8D%E8%B4%A8%E9%87%8F" class="hash-link" aria-label="第五步：用重排提高前几名质量的直接链接" title="第五步：用重排提高前几名质量的直接链接">​</a></h2>
<p>Qwen3 Reranker 可以对“查询—文档”对重新评分，但它比 Embedding 更慢，不应对全部文档运行。下面按官方模型卡的 <code>yes/no</code> 概率方法给出一个最小实现；CPU 可以运行，实际服务应加入批处理、显存预算与超时控制。</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> torch</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> transformers </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> AutoModelForCausalLM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> AutoTokenizer</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">reranker_name </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Qwen/Qwen3-Reranker-0.6B"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">reranker_tokenizer </span><span class="token operator">=</span><span class="token plain"> AutoTokenizer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">from_pretrained</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    reranker_name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    padding_side</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"left"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">reranker </span><span class="token operator">=</span><span class="token plain"> AutoModelForCausalLM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">from_pretrained</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">reranker_name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">eval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">reranker_device </span><span class="token operator">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">device</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"cuda"</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> torch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">is_available</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"cpu"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">reranker </span><span class="token operator">=</span><span class="token plain"> reranker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">to</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">reranker_device</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">prefix </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"&lt;|im_start|&gt;system\nJudge whether the Document meets the requirements "</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"based on the Query and the Instruct provided. Note that the answer can "</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">'only be "yes" or "no".&lt;|im_end|&gt;\n&lt;|im_start|&gt;user\n'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">suffix </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"&lt;|im_end|&gt;\n&lt;|im_start|&gt;assistant\n&lt;think&gt;\n\n&lt;/think&gt;\n\n"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">prefix_tokens </span><span class="token operator">=</span><span class="token plain"> reranker_tokenizer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">prefix</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> add_special_tokens</span><span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">suffix_tokens </span><span class="token operator">=</span><span class="token plain"> reranker_tokenizer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">suffix</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> add_special_tokens</span><span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">yes_token_id </span><span class="token operator">=</span><span class="token plain"> reranker_tokenizer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">convert_tokens_to_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"yes"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">no_token_id </span><span class="token operator">=</span><span class="token plain"> reranker_tokenizer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">convert_tokens_to_ids</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"no"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">max_length </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">8192</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@torch</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">inference_mode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">score_pairs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">pairs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">tuple</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    task </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"根据用户问题，判断文档是否包含能够支持回答的相关信息"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    texts </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"&lt;Instruct&gt;: </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">task</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">\n&lt;Query&gt;: </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">query</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">\n&lt;Document&gt;: </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">document</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> document </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> pairs</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    encoded </span><span class="token operator">=</span><span class="token plain"> reranker_tokenizer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        texts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        padding</span><span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        truncation</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"longest_first"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        max_length</span><span class="token operator">=</span><span class="token plain">max_length </span><span class="token operator">-</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">prefix_tokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">suffix_tokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        return_attention_mask</span><span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> token_ids </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">enumerate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">encoded</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"input_ids"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        encoded</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"input_ids"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> prefix_tokens </span><span class="token operator">+</span><span class="token plain"> token_ids </span><span class="token operator">+</span><span class="token plain"> suffix_tokens</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    inputs </span><span class="token operator">=</span><span class="token plain"> reranker_tokenizer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">pad</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">encoded</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> padding</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> return_tensors</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"pt"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    inputs </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">to</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">reranker_device</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> value </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> inputs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    next_token_logits </span><span class="token operator">=</span><span class="token plain"> reranker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">**</span><span class="token plain">inputs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">logits</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    binary_logits </span><span class="token operator">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stack</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">next_token_logits</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> no_token_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> next_token_logits</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> yes_token_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        dim</span><span class="token operator">=</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> binary_logits</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">softmax</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">dim</span><span class="token operator">=</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">cpu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tolist</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">rerank</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> candidates</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> candidates</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    scores </span><span class="token operator">=</span><span class="token plain"> score_pairs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> item </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> candidates</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ranked </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token operator">**</span><span class="token plain">candidate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"rerank_score"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">score</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> candidate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> score </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">zip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">candidates</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> scores</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">sorted</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">ranked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> key</span><span class="token operator">=</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">lambda</span><span class="token plain"> item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"rerank_score"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> reverse</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>正式服务应把模型加载、批处理和显存配置封装在独立模块中，避免检索逻辑与推理框架耦合。模型卡更新时也要重新验证前后缀与判定 token。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第六步生成回答时保留证据">第六步：生成回答时保留证据<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E7%AC%AC%E5%85%AD%E6%AD%A5%E7%94%9F%E6%88%90%E5%9B%9E%E7%AD%94%E6%97%B6%E4%BF%9D%E7%95%99%E8%AF%81%E6%8D%AE" class="hash-link" aria-label="第六步：生成回答时保留证据的直接链接" title="第六步：生成回答时保留证据的直接链接">​</a></h2>
<p>最终提示词至少包含三个约束：只能根据上下文回答、证据不足时明确拒答、回答中返回来源标识。下面使用 OpenAI Python SDK 连接任意兼容的聊天模型服务：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Shell</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">OPENAI_API_KEY</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"替换为实际密钥"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">OPENAI_BASE_URL</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"https://你的兼容服务/v1"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">CHAT_MODEL</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"替换为支持的模型名"</span><br></span></code></pre></div></div>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Python</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> os</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> re</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> openai </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> OpenAI</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">llm </span><span class="token operator">=</span><span class="token plain"> OpenAI</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    api_key</span><span class="token operator">=</span><span class="token plain">os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">environ</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"OPENAI_API_KEY"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    base_url</span><span class="token operator">=</span><span class="token plain">os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">environ</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"OPENAI_BASE_URL"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">answer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> access_scope</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> top_k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    candidates </span><span class="token operator">=</span><span class="token plain"> rerank</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        retrieve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> access_scope</span><span class="token operator">=</span><span class="token plain">access_scope</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> limit</span><span class="token operator">=</span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">top_k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> candidates</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"answer"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"现有权限范围内没有检索到可用资料。"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string" style="color:rgb(255, 121, 198)">"sources"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    context </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"\n\n"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">join</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f'[来源: </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">item</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string-interpolation interpolation string" style="color:rgb(255, 121, 198)">"chunk_id"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">]\n</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">item</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string-interpolation interpolation string" style="color:rgb(255, 121, 198)">"text"</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> item </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> candidates</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    response </span><span class="token operator">=</span><span class="token plain"> llm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">completions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">create</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        model</span><span class="token operator">=</span><span class="token plain">os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">environ</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"CHAT_MODEL"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        temperature</span><span class="token operator">=</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        messages</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string" style="color:rgb(255, 121, 198)">"role"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"system"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string" style="color:rgb(255, 121, 198)">"content"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token string" style="color:rgb(255, 121, 198)">"你是企业知识库助手。只能根据提供的上下文回答。"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token string" style="color:rgb(255, 121, 198)">"证据不足时回答‘现有资料无法确认’。"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token string" style="color:rgb(255, 121, 198)">"每个关键结论后必须标注 [来源: chunk_id]。"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string" style="color:rgb(255, 121, 198)">"role"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"user"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string" style="color:rgb(255, 121, 198)">"content"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"问题：</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">query</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">\n\n上下文：\n</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">context</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    answer_text </span><span class="token operator">=</span><span class="token plain"> response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">choices</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">content </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    allowed_sources </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"chunk_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> item </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> candidates</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    cited_sources </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">findall</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"\[来源:\s*([^\]]+)\]"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> answer_text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    unknown_sources </span><span class="token operator">=</span><span class="token plain"> cited_sources </span><span class="token operator">-</span><span class="token plain"> allowed_sources</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> unknown_sources</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"回答引用了未提供的来源：</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation builtin" style="color:rgb(189, 147, 249)">sorted</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation interpolation">unknown_sources</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"answer"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> answer_text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"sources"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"chunk_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"chunk_id"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"source"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"source"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> item </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> candidates</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>这段校验只能发现“引用了不存在的块”，不能证明引用内容真的支持结论；后者仍要通过固定评估集和人工抽检判断。检索结果必须携带 <code>chunk_id</code> 和 <code>source</code>，否则无法进行任何引用追踪。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第七步建立固定评估集">第七步：建立固定评估集<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E7%AC%AC%E4%B8%83%E6%AD%A5%E5%BB%BA%E7%AB%8B%E5%9B%BA%E5%AE%9A%E8%AF%84%E4%BC%B0%E9%9B%86" class="hash-link" aria-label="第七步：建立固定评估集的直接链接" title="第七步：建立固定评估集的直接链接">​</a></h2>
<p>至少准备四类问题：</p>
<ol>
<li>能在单个块中直接回答的问题；</li>
<li>需要组合多个块的问题；</li>
<li>包含缩写、同义词或跨语言表达的问题；</li>
<li>知识库中没有答案的问题。</li>
</ol>
<p>检索阶段建议评估 <code>Recall@5</code>、<code>Recall@20</code> 和 MRR。生成阶段至少检查：</p>
<ul>
<li>回答正确性；</li>
<li>上下文相关性；</li>
<li>引用是否支持结论；</li>
<li>无答案问题是否拒答；</li>
<li>P50/P95 延迟与 Token 消耗。</li>
</ul>
<p>不要只用同一个大模型同时生成答案和评判答案。模型评审可以提高效率，但应使用固定校验集并抽样人工复核。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="四组最值得做的对照实验">四组最值得做的对照实验<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E5%9B%9B%E7%BB%84%E6%9C%80%E5%80%BC%E5%BE%97%E5%81%9A%E7%9A%84%E5%AF%B9%E7%85%A7%E5%AE%9E%E9%AA%8C" class="hash-link" aria-label="四组最值得做的对照实验的直接链接" title="四组最值得做的对照实验的直接链接">​</a></h2>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="切块大小">切块大小<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E5%88%87%E5%9D%97%E5%A4%A7%E5%B0%8F" class="hash-link" aria-label="切块大小的直接链接" title="切块大小的直接链接">​</a></h3>
<p>比较 300、600、1000 token 以及结构化切块。记录召回、重排后排名和上下文 Token 数。块越大不一定越好。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="向量混合与重排">向量、混合与重排<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E5%90%91%E9%87%8F%E6%B7%B7%E5%90%88%E4%B8%8E%E9%87%8D%E6%8E%92" class="hash-link" aria-label="向量、混合与重排的直接链接" title="向量、混合与重排的直接链接">​</a></h3>
<p>至少比较：</p>
<ul>
<li>纯向量 Top 5；</li>
<li>纯向量 Top 20 + 重排 Top 5；</li>
<li>混合召回 Top 20 + 重排 Top 5。</li>
</ul>
<p>这样才能判断质量提升来自模型、召回方式还是重排。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="权限与元数据过滤">权限与元数据过滤<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E6%9D%83%E9%99%90%E4%B8%8E%E5%85%83%E6%95%B0%E6%8D%AE%E8%BF%87%E6%BB%A4" class="hash-link" aria-label="权限与元数据过滤的直接链接" title="权限与元数据过滤的直接链接">​</a></h3>
<p>构造两个用户只能访问不同文档的场景，确保过滤在检索阶段完成。生成后再删除内容无法防止敏感信息进入上下文。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="增量更新与删除">增量更新与删除<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E5%A2%9E%E9%87%8F%E6%9B%B4%E6%96%B0%E4%B8%8E%E5%88%A0%E9%99%A4" class="hash-link" aria-label="增量更新与删除的直接链接" title="增量更新与删除的直接链接">​</a></h3>
<p>更新原文后重新写入向量，检查旧块是否被删除、引用是否指向新版本。知识库最常见的生产问题之一，是搜索结果仍然命中过期内容。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="从本地基线走向生产">从本地基线走向生产<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E4%BB%8E%E6%9C%AC%E5%9C%B0%E5%9F%BA%E7%BA%BF%E8%B5%B0%E5%90%91%E7%94%9F%E4%BA%A7" class="hash-link" aria-label="从本地基线走向生产的直接链接" title="从本地基线走向生产的直接链接">​</a></h2>
<p>上线前还需要补齐：</p>
<ul>
<li>文档解析失败与重试；</li>
<li>增量更新和幂等写入；</li>
<li>权限与租户隔离；</li>
<li>检索、重排和生成的分层监控；</li>
<li>固定回归集与 CI 检查；</li>
<li>模型与提示词版本管理；</li>
<li>超时、限流和降级。</li>
</ul>
<p>RAG 的质量不是一个模型决定的，而是数据、切块、检索、重排、生成与评估共同决定的。先建立一个能测量的基线，才能知道下一次优化是否真的有效。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="参考资料">参考资料<a href="https://www.xlonglab.com/articles/qwen3-milvus-evaluable-knowledge-base#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接">​</a></h2>
<ul>
<li><a href="https://huggingface.co/Qwen/Qwen3-Embedding-0.6B" target="_blank" rel="noopener noreferrer">Qwen3 Embedding 0.6B 模型卡</a></li>
<li><a href="https://huggingface.co/Qwen/Qwen3-Reranker-0.6B" target="_blank" rel="noopener noreferrer">Qwen3 Reranker 0.6B 模型卡</a></li>
<li><a href="https://www.xlonglab.com/docs/getstarted/milvus_lite.md">Milvus Lite 文档</a></li>
<li><a href="https://www.xlonglab.com/docs/tutorials/build-rag-with-milvus.md">使用 Milvus 构建 RAG</a></li>
<li><a href="https://mp.weixin.qq.com/s?__biz=MzUzMDI5OTA5NQ==&amp;mid=2247509225&amp;idx=1&amp;sn=c2585bed584c72caaee7ac791277f99f&amp;chksm=fa510551cd268c4705424558905c03d29d5499b86ae16cb0771acdff097889da8b28b5269923#rd" target="_blank" rel="noopener noreferrer">Zilliz：Qwen3 Embedding + Milvus 教程</a></li>
</ul>]]></content:encoded>
            <category>工程实践</category>
        </item>
        <item>
            <title><![CDATA[Milvus、Qdrant、Chroma 怎么选：一套可复现的比较方法]]></title>
            <link>https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method</link>
            <guid>https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method</guid>
            <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[不复用过时跑分，从产品边界、目标召回率、过滤、写入、资源和运维六个方面公平比较 Milvus、Qdrant 与 Chroma。]]></description>
            <content:encoded><![CDATA[<p>“哪个向量数据库最快”是一个容易提问、却很难诚实回答的问题。Milvus、Qdrant 和 Chroma 的产品边界并不完全相同：它们面向的数据规模、部署方式、扩展模型和运维要求都有差异。只比较一组 QPS，往往会把不同定位的产品拉进一场不公平的比赛。</p>
<p>本文不复用某次旧版本跑分，也不宣布一个永久冠军，而是给出一套可以在自己的硬件和数据上重复运行的比较方法。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="先确定是不是在比较同一类问题">先确定是不是在比较同一类问题<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#%E5%85%88%E7%A1%AE%E5%AE%9A%E6%98%AF%E4%B8%8D%E6%98%AF%E5%9C%A8%E6%AF%94%E8%BE%83%E5%90%8C%E4%B8%80%E7%B1%BB%E9%97%AE%E9%A2%98" class="hash-link" aria-label="先确定是不是在比较同一类问题的直接链接" title="先确定是不是在比较同一类问题的直接链接">​</a></h2>
<p>选型前先写清楚系统约束：</p>
<ul>
<li>数据量是 10 万、1000 万还是 10 亿；</li>
<li>单机即可，还是需要水平扩展；</li>
<li>主要是离线写入，还是持续增量更新；</li>
<li>是否大量使用标量过滤；</li>
<li>能否接受服务端运维；</li>
<li>是否需要备份、监控、故障恢复和多租户；</li>
<li>目标召回率和 P95/P99 延迟是多少。</li>
</ul>
<p>三者现在都覆盖不止一种部署方式，但产品边界仍不相同：Milvus 提供 Lite、Standalone 和 Distributed；Qdrant 提供 Local Mode、自托管服务和 Qdrant Cloud；Chroma 提供 Local、Single-Node、Distributed 和 Cloud。名称相似不代表扩展模型、运维能力或适用规模相同，最终应以各项目当前文档和自己的需求矩阵为准。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="为什么旧跑分不能直接用于今天">为什么旧跑分不能直接用于今天<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%97%A7%E8%B7%91%E5%88%86%E4%B8%8D%E8%83%BD%E7%9B%B4%E6%8E%A5%E7%94%A8%E4%BA%8E%E4%BB%8A%E5%A4%A9" class="hash-link" aria-label="为什么旧跑分不能直接用于今天的直接链接" title="为什么旧跑分不能直接用于今天的直接链接">​</a></h2>
<p>Zilliz 公众号在 2024 年发布过一篇 Milvus、Qdrant、Chroma 的对比，使用当时的 Milvus 2.4.5、Qdrant 1.11.5 和 Chroma 0.5.8 开发版本。文章记录了测试机器、部署方式和 VectorDBBench 流程，这些方法有参考价值；但其性能结论受版本、参数和厂商来源影响，不应直接外推到当前版本。</p>
<p>复用旧文章时，最应该保留的是透明度：公开硬件、镜像版本、参数、超时、失败记录与资源监控。最不应该保留的是“某数据库永远最快”的结论。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="公平比较的第一原则先对齐召回率">公平比较的第一原则：先对齐召回率<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#%E5%85%AC%E5%B9%B3%E6%AF%94%E8%BE%83%E7%9A%84%E7%AC%AC%E4%B8%80%E5%8E%9F%E5%88%99%E5%85%88%E5%AF%B9%E9%BD%90%E5%8F%AC%E5%9B%9E%E7%8E%87" class="hash-link" aria-label="公平比较的第一原则：先对齐召回率的直接链接" title="公平比较的第一原则：先对齐召回率的直接链接">​</a></h2>
<p>近似向量索引通过牺牲部分召回率换取速度。一个系统使用激进参数取得更高 QPS，另一个系统保持更高召回率，两者的延迟无法直接比较。</p>
<p>正确流程是：</p>
<ol>
<li>使用同一数据集与查询集；</li>
<li>计算精确近邻作为 Ground Truth；</li>
<li>调整每个数据库的索引与搜索参数；</li>
<li>让它们达到相近的 <code>Recall@10</code>；</li>
<li>再比较延迟、吞吐和资源。</li>
</ol>
<p>最终报告应使用“召回率—延迟曲线”，而不是只展示一个最好数字。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="建议的工作负载矩阵">建议的工作负载矩阵<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#%E5%BB%BA%E8%AE%AE%E7%9A%84%E5%B7%A5%E4%BD%9C%E8%B4%9F%E8%BD%BD%E7%9F%A9%E9%98%B5" class="hash-link" aria-label="建议的工作负载矩阵的直接链接" title="建议的工作负载矩阵的直接链接">​</a></h2>
<p>一轮完整测试至少包含以下组合：</p>
<table><thead><tr><th>维度</th><th>建议范围</th><th>目的</th></tr></thead><tbody><tr><td>数据规模</td><td>100K、1M，条件允许再增加 10M</td><td>观察增长趋势，而非单点结果</td></tr><tr><td>向量维度</td><td>384、768、1536</td><td>模拟轻量、本地与 API 模型</td></tr><tr><td>查询 TopK</td><td>10、100</td><td>观察返回集合变大后的变化</td></tr><tr><td>并发</td><td>1、8、32、64</td><td>找到饱和点和尾延迟</td></tr><tr><td>过滤</td><td>无过滤、10%、1%、0.1% 选择性</td><td>验证标量过滤能力</td></tr><tr><td>写入</td><td>全量导入、持续增量写入</td><td>观察混合负载</td></tr><tr><td>缓存</td><td>冷启动、预热后</td><td>区分磁盘与缓存效果</td></tr></tbody></table>
<p>数据集可以从 ANN Benchmarks 或 VectorDBBench 支持的数据中选择。不要用完全随机向量替代真实分布做最终结论；随机向量适合验证脚本，不适合评估索引行为。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="测试环境必须记录什么">测试环境必须记录什么<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83%E5%BF%85%E9%A1%BB%E8%AE%B0%E5%BD%95%E4%BB%80%E4%B9%88" class="hash-link" aria-label="测试环境必须记录什么的直接链接" title="测试环境必须记录什么的直接链接">​</a></h2>
<p>建议为每次运行保存一个清单。下面只是待填写的配置模板，不代表本文已经在这组硬件上执行过测试：</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">YAML</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">run_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> replace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">with</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">actual</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">hardware</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">cpu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"填写实际 CPU 型号与核心数"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">memory_gib</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"填写实际容量"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">disk</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"填写实际介质、文件系统与 IOPS"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">dataset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"填写数据集与版本"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">dimension</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"填写实际维度"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">entities</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"填写实际数量"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">workload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">top_k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">concurrency</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">8</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">32</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">filter_selectivity</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">none</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0.1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0.01</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">targets</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">recall_at_10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">0.95</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">databases</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">milvus</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"填写实际版本"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">deployment</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> standalone</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">qdrant</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"填写实际版本"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">chroma</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"填写实际版本"</span><br></span></code></pre></div></div>
<p>还应记录内核、容器运行时、文件系统、客户端版本、网络拓扑、索引参数、复制因子和数据是否已加载到内存。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="六类指标缺一不可">六类指标缺一不可<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#%E5%85%AD%E7%B1%BB%E6%8C%87%E6%A0%87%E7%BC%BA%E4%B8%80%E4%B8%8D%E5%8F%AF" class="hash-link" aria-label="六类指标缺一不可的直接链接" title="六类指标缺一不可的直接链接">​</a></h2>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="1-检索质量">1. 检索质量<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#1-%E6%A3%80%E7%B4%A2%E8%B4%A8%E9%87%8F" class="hash-link" aria-label="1. 检索质量的直接链接" title="1. 检索质量的直接链接">​</a></h3>
<p>至少记录 Recall@10 和 Recall@100。如果包含过滤查询，还要验证 Ground Truth 是否应用了相同过滤条件。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="2-延迟与吞吐">2. 延迟与吞吐<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#2-%E5%BB%B6%E8%BF%9F%E4%B8%8E%E5%90%9E%E5%90%90" class="hash-link" aria-label="2. 延迟与吞吐的直接链接" title="2. 延迟与吞吐的直接链接">​</a></h3>
<p>记录 P50、P95、P99 和 QPS。平均延迟会隐藏尾部抖动，不适合作为唯一指标。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="3-写入与索引">3. 写入与索引<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#3-%E5%86%99%E5%85%A5%E4%B8%8E%E7%B4%A2%E5%BC%95" class="hash-link" aria-label="3. 写入与索引的直接链接" title="3. 写入与索引的直接链接">​</a></h3>
<p>分别记录数据导入、索引构建、可查询时间和增量写入吞吐。部分系统写入返回成功后仍在后台构建或刷新，必须明确“完成”的定义。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="4-资源使用">4. 资源使用<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#4-%E8%B5%84%E6%BA%90%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="4. 资源使用的直接链接" title="4. 资源使用的直接链接">​</a></h3>
<p>记录 CPU、内存、磁盘空间、磁盘 I/O 和网络。资源图需要与测试阶段对齐，不能只截一张峰值图。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="5-稳定性">5. 稳定性<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#5-%E7%A8%B3%E5%AE%9A%E6%80%A7" class="hash-link" aria-label="5. 稳定性的直接链接" title="5. 稳定性的直接链接">​</a></h3>
<p>连续运行混合读写，记录错误率、超时、延迟漂移和内存增长。短时间峰值跑分无法说明长期稳定性。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="6-运维成本">6. 运维成本<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#6-%E8%BF%90%E7%BB%B4%E6%88%90%E6%9C%AC" class="hash-link" aria-label="6. 运维成本的直接链接" title="6. 运维成本的直接链接">​</a></h3>
<p>检查部署、升级、监控、备份、恢复、扩容和故障处理。对于小团队，少一次夜间人工恢复，可能比提升 10% QPS 更有价值。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="使用-vectordbbench-时要注意什么">使用 VectorDBBench 时要注意什么<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#%E4%BD%BF%E7%94%A8-vectordbbench-%E6%97%B6%E8%A6%81%E6%B3%A8%E6%84%8F%E4%BB%80%E4%B9%88" class="hash-link" aria-label="使用 VectorDBBench 时要注意什么的直接链接" title="使用 VectorDBBench 时要注意什么的直接链接">​</a></h2>
<p><a href="https://github.com/zilliztech/VectorDBBench" target="_blank" rel="noopener noreferrer">VectorDBBench</a> 可以统一驱动多种数据库，但“同一个测试工具”不自动代表“公平”。运行前仍要检查：</p>
<ul>
<li>每个客户端使用的 API 与批大小；</li>
<li>默认索引是否一致；</li>
<li>是否达到相近召回率；</li>
<li>超时是否被修改；</li>
<li>一个测试结束后数据是否完全清理；</li>
<li>是否一次只运行一个数据库，避免资源干扰；</li>
<li>测试工具和数据库是否部署在同一台机器。</li>
</ul>
<p>如果测试工具与数据库同机，客户端会占用 CPU 和内存；如果分机，则必须报告网络延迟。两种方式都可以，但不能混用。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="不要只测试纯向量搜索">不要只测试“纯向量搜索”<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#%E4%B8%8D%E8%A6%81%E5%8F%AA%E6%B5%8B%E8%AF%95%E7%BA%AF%E5%90%91%E9%87%8F%E6%90%9C%E7%B4%A2" class="hash-link" aria-label="不要只测试“纯向量搜索”的直接链接" title="不要只测试“纯向量搜索”的直接链接">​</a></h2>
<p>生产查询通常还包括标量过滤、更新和返回字段。例如电商检索可能要求：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Text</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">向量相似 + category = "shoes" + in_stock = true + region = "EU"</span><br></span></code></pre></div></div>
<p>过滤选择性会改变查询计划。应至少测试高选择性和低选择性两组条件，并检查不同数据分布下的尾延迟。</p>
<p>另一个常见遗漏是读写并发。知识库可能每天批量更新，商品搜索可能持续写入。纯读测试无法说明索引更新期间的服务质量。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="如何阅读最终结果">如何阅读最终结果<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#%E5%A6%82%E4%BD%95%E9%98%85%E8%AF%BB%E6%9C%80%E7%BB%88%E7%BB%93%E6%9E%9C" class="hash-link" aria-label="如何阅读最终结果的直接链接" title="如何阅读最终结果的直接链接">​</a></h2>
<p>最终结果不应是一张总排名，而应是一组按场景解释的决策矩阵：</p>
<ul>
<li><strong>本地原型</strong>：安装速度、API 易用性和低资源占用优先；</li>
<li><strong>单机在线服务</strong>：尾延迟、过滤、持久化和监控优先；</li>
<li><strong>大规模生产集群</strong>：水平扩展、容量管理、高可用和运维工具优先；</li>
<li><strong>持续更新场景</strong>：写入、删除、索引维护与读写隔离优先。</li>
</ul>
<p>某个系统在 100K 数据上表现最好，不代表它在 100M 数据、复杂过滤或多副本部署中仍然最好。报告必须明确测试没有覆盖的范围。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="一份可信报告的验收清单">一份可信报告的验收清单<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#%E4%B8%80%E4%BB%BD%E5%8F%AF%E4%BF%A1%E6%8A%A5%E5%91%8A%E7%9A%84%E9%AA%8C%E6%94%B6%E6%B8%85%E5%8D%95" class="hash-link" aria-label="一份可信报告的验收清单的直接链接" title="一份可信报告的验收清单的直接链接">​</a></h2>
<ul>
<li>三个数据库使用相同硬件和独立运行环境；</li>
<li>版本、镜像、客户端与所有参数已公开；</li>
<li>先对齐召回率，再比较性能；</li>
<li>同时报告 P50、P95、P99 和错误率；</li>
<li>包含过滤、增量写入和恢复检查；</li>
<li>原始结果和脚本可下载；</li>
<li>失败测试与超时也被保留；</li>
<li>结论按场景表达，不宣布永久冠军；</li>
<li>如果作者与某个项目存在关联，明确披露。</li>
</ul>
<p>向量数据库选型不是一次跑分，而是一项容量和风险决策。最有价值的“对比文章”，不是替读者宣布答案，而是让读者能在自己的约束下复现答案。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="参考资料">参考资料<a href="https://www.xlonglab.com/articles/milvus-qdrant-chroma-comparison-method#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接">​</a></h2>
<ul>
<li><a href="https://github.com/zilliztech/VectorDBBench" target="_blank" rel="noopener noreferrer">VectorDBBench</a></li>
<li><a href="https://ann-benchmarks.com/" target="_blank" rel="noopener noreferrer">ANN Benchmarks</a></li>
<li><a href="https://www.xlonglab.com/docs/getstarted/install-overview.md">Milvus 部署选项</a></li>
<li><a href="https://qdrant.tech/documentation/" target="_blank" rel="noopener noreferrer">Qdrant 官方文档</a></li>
<li><a href="https://docs.trychroma.com/" target="_blank" rel="noopener noreferrer">Chroma 官方文档</a></li>
<li><a href="https://docs.trychroma.com/reference/architecture/overview.md" target="_blank" rel="noopener noreferrer">Chroma Architecture</a></li>
<li><a href="https://mp.weixin.qq.com/s?__biz=MzUzMDI5OTA5NQ==&amp;mid=2247504540&amp;idx=1&amp;sn=9d4e7f173b52dae80e088b85195dcff9&amp;chksm=fa517324cd26fa32f7c27d906760457c97cbefd9cd7838ab5d5aa91ee99c7dc4fe371c1bed81#rd" target="_blank" rel="noopener noreferrer">Zilliz：Milvus、Chroma、Qdrant 对比（2024）</a></li>
</ul>]]></content:encoded>
            <category>技术解析</category>
        </item>
        <item>
            <title><![CDATA[Milvus 性能排障手册：从慢查询到 Segment 与存储瓶颈]]></title>
            <link>https://www.xlonglab.com/articles/vector-database-performance-troubleshooting</link>
            <guid>https://www.xlonglab.com/articles/vector-database-performance-troubleshooting</guid>
            <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[用召回率基线、尾延迟和分层诊断树定位 Milvus 的客户端、查询、索引、Segment、存储与硬件瓶颈，并区分可迁移的一般方法与产品特有指标。]]></description>
            <content:encoded><![CDATA[<p>向量数据库变慢时，最常见的反应是修改索引参数或增加机器。但“慢”只是症状：问题可能来自客户端连接、查询并发、过滤条件、索引、Segment、缓存、磁盘、网络，甚至是召回率目标本身。</p>
<p>有效排障需要一条固定顺序：<strong>先保护检索质量，再定位延迟发生在哪一层，最后只改变一个变量验证。</strong></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第一步冻结质量基线">第一步：冻结质量基线<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E7%AC%AC%E4%B8%80%E6%AD%A5%E5%86%BB%E7%BB%93%E8%B4%A8%E9%87%8F%E5%9F%BA%E7%BA%BF" class="hash-link" aria-label="第一步：冻结质量基线的直接链接" title="第一步：冻结质量基线的直接链接">​</a></h2>
<p>性能优化不能以不可见的召回率下降为代价。开始前应固定：</p>
<ul>
<li>数据集与查询集；</li>
<li>Ground Truth；</li>
<li>TopK；</li>
<li>过滤条件；</li>
<li>Recall@K 或 nDCG；</li>
<li>目标 P95/P99 延迟与 QPS。</li>
</ul>
<p>如果调低搜索参数后延迟下降，但 Recall@10 从 0.96 降到 0.78，这不是优化，只是改变了产品质量。</p>
<p>建立基线时同时保存结果明细。平均召回率相同，可能仍有关键业务查询退化。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第二步把慢描述清楚">第二步：把“慢”描述清楚<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E7%AC%AC%E4%BA%8C%E6%AD%A5%E6%8A%8A%E6%85%A2%E6%8F%8F%E8%BF%B0%E6%B8%85%E6%A5%9A" class="hash-link" aria-label="第二步：把“慢”描述清楚的直接链接" title="第二步：把“慢”描述清楚的直接链接">​</a></h2>
<p>不同症状指向不同层：</p>
<table><thead><tr><th>症状</th><th>优先检查</th></tr></thead><tbody><tr><td>所有查询持续变慢</td><td>资源饱和、数据增长、索引或加载状态</td></tr><tr><td>只有 P99 抖动</td><td>队列、GC、磁盘尖峰、网络与资源争用</td></tr><tr><td>过滤查询慢</td><td>过滤选择性、标量索引、数据分布</td></tr><tr><td>数据增长后变慢</td><td>Segment 数量、索引大小、缓存与存储</td></tr><tr><td>写入时查询变慢</td><td>Compaction、索引构建、读写资源隔离</td></tr><tr><td>重启后短期变慢</td><td>冷缓存、数据加载与索引准备</td></tr><tr><td>只有应用端慢</td><td>连接池、序列化、重试和网络</td></tr></tbody></table>
<p>排障记录必须包含时间范围、请求类型、数据量、并发和最近变更。没有这些信息，一张 CPU 截图很难证明问题。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="第三步分解端到端延迟">第三步：分解端到端延迟<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E7%AC%AC%E4%B8%89%E6%AD%A5%E5%88%86%E8%A7%A3%E7%AB%AF%E5%88%B0%E7%AB%AF%E5%BB%B6%E8%BF%9F" class="hash-link" aria-label="第三步：分解端到端延迟的直接链接" title="第三步：分解端到端延迟的直接链接">​</a></h2>
<p>从用户请求到结果返回，可以粗略拆成：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Text</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">应用排队</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 客户端编码与序列化</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 网络</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 数据库调度与排队</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 标量过滤</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 向量索引搜索</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 结果合并与返回字段读取</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  → 网络与客户端反序列化</span><br></span></code></pre></div></div>
<p>首先在客户端记录端到端延迟和请求 ID，再与服务端指标对齐。只看服务端搜索耗时，会漏掉连接等待、重试和大字段返回。</p>
<p>建议同时记录 P50、P95、P99。平均延迟适合描述整体趋势，但尾延迟决定用户体验和超时率。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="客户端与查询层">客户端与查询层<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%B8%8E%E6%9F%A5%E8%AF%A2%E5%B1%82" class="hash-link" aria-label="客户端与查询层的直接链接" title="客户端与查询层的直接链接">​</a></h2>
<p>在扩容前先排除这些问题：</p>
<ul>
<li>每次查询都创建新连接；</li>
<li>客户端并发超过服务端容量，导致排队；</li>
<li><code>TopK</code> 或返回字段过大；</li>
<li>一次发送过多查询但批处理配置不合理；</li>
<li>超时过短触发重试风暴；</li>
<li>查询向量未归一化，却使用了不匹配的度量；</li>
<li>过滤表达式返回了远超预期的数据。</li>
</ul>
<p>诊断方法是逐步降低并发、TopK 和返回字段，观察数据库内部搜索时间与端到端时间是否同步变化。如果数据库内部稳定、应用端仍然变慢，问题通常不在索引。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="索引层一定要看-recalllatency-曲线">索引层：一定要看 Recall—Latency 曲线<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E7%B4%A2%E5%BC%95%E5%B1%82%E4%B8%80%E5%AE%9A%E8%A6%81%E7%9C%8B-recalllatency-%E6%9B%B2%E7%BA%BF" class="hash-link" aria-label="索引层：一定要看 Recall—Latency 曲线的直接链接" title="索引层：一定要看 Recall—Latency 曲线的直接链接">​</a></h2>
<p>HNSW、IVF 和磁盘索引都有质量与速度参数。不要寻找一组“最佳参数”，而要在自己的数据上画曲线。</p>
<p>例如 HNSW 搜索可以逐步提高搜索范围，记录：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Text</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">参数 A → Recall@10 0.90 → P95 18 ms</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">参数 B → Recall@10 0.95 → P95 29 ms</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">参数 C → Recall@10 0.98 → P95 61 ms</span><br></span></code></pre></div></div>
<p>产品团队需要决定从 0.95 提升到 0.98 是否值得付出两倍延迟。性能团队不能独自替业务做这个决定。</p>
<p>索引诊断还应检查：</p>
<ul>
<li>索引是否全部构建完成；</li>
<li>查询是否命中预期索引；</li>
<li>向量维度和度量是否一致；</li>
<li>数据是否频繁更新，导致索引维护压力；</li>
<li>不同分区或分片是否数据倾斜。</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="过滤查询慢的不一定是向量搜索">过滤查询：慢的不一定是向量搜索<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E8%BF%87%E6%BB%A4%E6%9F%A5%E8%AF%A2%E6%85%A2%E7%9A%84%E4%B8%8D%E4%B8%80%E5%AE%9A%E6%98%AF%E5%90%91%E9%87%8F%E6%90%9C%E7%B4%A2" class="hash-link" aria-label="过滤查询：慢的不一定是向量搜索的直接链接" title="过滤查询：慢的不一定是向量搜索的直接链接">​</a></h2>
<p>带过滤的向量查询同时涉及标量条件和向量相似度。过滤选择性不同，最优执行策略也不同。</p>
<p>至少测试三组：</p>
<ul>
<li>命中 50% 数据的低选择性过滤；</li>
<li>命中 1% 数据的中等选择性过滤；</li>
<li>命中 0.01% 数据的高选择性过滤。</li>
</ul>
<p>同时检查过滤字段的数据分布。一个店铺占据一半数据、其余店铺非常分散时，平均选择性会掩盖热点。</p>
<p>如果过滤字段经常使用，应核对标量索引和 schema 设计。把大型数组或高基数字符串直接塞进过滤条件，可能增加解析和扫描成本。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="milvus-的数据与-segment数据增长后的隐形成本">Milvus 的数据与 Segment：数据增长后的隐形成本<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#milvus-%E7%9A%84%E6%95%B0%E6%8D%AE%E4%B8%8E-segment%E6%95%B0%E6%8D%AE%E5%A2%9E%E9%95%BF%E5%90%8E%E7%9A%84%E9%9A%90%E5%BD%A2%E6%88%90%E6%9C%AC" class="hash-link" aria-label="Milvus 的数据与 Segment：数据增长后的隐形成本的直接链接" title="Milvus 的数据与 Segment：数据增长后的隐形成本的直接链接">​</a></h2>
<p>在 Milvus 中，小批量请求通常先进入 Growing Segment，本身不会自动形成大量小 Sealed Segment。真正需要警惕的是应用频繁调用 <code>flush()</code>、创建过多 Partition 或 Shard、反复中断短时写入，或者封存条件使未达到理想大小的 Segment 提前 Sealed；大量小 Segment 可能增加元数据、加载、搜索扇出和 Compaction 压力。</p>
<p>排查时关注：</p>
<ul>
<li>Segment 数量和大小分布；</li>
<li>Growing 与 Sealed Segment 比例；</li>
<li>Compaction 是否积压；</li>
<li>索引构建是否跟上写入；</li>
<li>删除数据是否大量累积；</li>
<li>热点分区是否集中在少量节点。</li>
</ul>
<p>一个实用实验是对比 Compaction 前后、正常缓冲写入与频繁 <code>flush()</code>、合理分区与过度分区、均匀分区与热点分区的 P95/P99 和资源占用。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="缓存与存储区分冷启动和持续瓶颈">缓存与存储：区分冷启动和持续瓶颈<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E7%BC%93%E5%AD%98%E4%B8%8E%E5%AD%98%E5%82%A8%E5%8C%BA%E5%88%86%E5%86%B7%E5%90%AF%E5%8A%A8%E5%92%8C%E6%8C%81%E7%BB%AD%E7%93%B6%E9%A2%88" class="hash-link" aria-label="缓存与存储：区分冷启动和持续瓶颈的直接链接" title="缓存与存储：区分冷启动和持续瓶颈的直接链接">​</a></h2>
<p>重启后变慢、预热后恢复，通常与加载或缓存有关；运行一段时间后越来越慢，则可能是内存压力、I/O 或后台任务。</p>
<p>建议分别测试：</p>
<ol>
<li>进程刚启动、数据未预热；</li>
<li>固定查询集运行数轮后；</li>
<li>限制磁盘 IOPS；</li>
<li>内存接近容量上限；</li>
<li>并发写入和 Compaction 期间。</li>
</ol>
<p>记录磁盘读写吞吐、I/O 等待、缓存命中、内存和尾延迟。如果磁盘已达到上限，继续增加查询节点但共享同一存储可能没有帮助。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="cpunuma-与资源争用">CPU、NUMA 与资源争用<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#cpunuma-%E4%B8%8E%E8%B5%84%E6%BA%90%E4%BA%89%E7%94%A8" class="hash-link" aria-label="CPU、NUMA 与资源争用的直接链接" title="CPU、NUMA 与资源争用的直接链接">​</a></h2>
<p>高 CPU 不一定是坏事，可能说明系统有效利用资源；低 CPU 也不一定健康，可能在等待 I/O 或网络。</p>
<p>在多路 CPU 机器上，NUMA 的远程内存访问会增加延迟。可以对比绑定与不绑定 CPU/内存节点时的尾延迟，但不要把某一台机器的收益泛化到所有环境。</p>
<p>还要检查同机的 Embedding、Reranker、监控或其他数据库是否与向量数据库争用 CPU、内存和磁盘。生产压测应尽可能隔离负载。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="三个常见诊断场景">三个常见诊断场景<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E4%B8%89%E4%B8%AA%E5%B8%B8%E8%A7%81%E8%AF%8A%E6%96%AD%E5%9C%BA%E6%99%AF" class="hash-link" aria-label="三个常见诊断场景的直接链接" title="三个常见诊断场景的直接链接">​</a></h2>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="场景一qps-提高p99-突然恶化">场景一：QPS 提高，P99 突然恶化<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E5%9C%BA%E6%99%AF%E4%B8%80qps-%E6%8F%90%E9%AB%98p99-%E7%AA%81%E7%84%B6%E6%81%B6%E5%8C%96" class="hash-link" aria-label="场景一：QPS 提高，P99 突然恶化的直接链接" title="场景一：QPS 提高，P99 突然恶化的直接链接">​</a></h3>
<p>先固定并发逐步增加，找到延迟拐点。检查服务端排队、客户端重试和 CPU 饱和。容量应运行在拐点之前，而不是以一次峰值 QPS 作为安全上限。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="场景二普通查询正常店铺过滤特别慢">场景二：普通查询正常，店铺过滤特别慢<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E5%9C%BA%E6%99%AF%E4%BA%8C%E6%99%AE%E9%80%9A%E6%9F%A5%E8%AF%A2%E6%AD%A3%E5%B8%B8%E5%BA%97%E9%93%BA%E8%BF%87%E6%BB%A4%E7%89%B9%E5%88%AB%E6%85%A2" class="hash-link" aria-label="场景二：普通查询正常，店铺过滤特别慢的直接链接" title="场景二：普通查询正常，店铺过滤特别慢的直接链接">​</a></h3>
<p>比较不同店铺的数据量和过滤选择性，检查是否存在热点店铺、数组过滤或缺少标量索引。不要只修改向量索引参数。</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="场景三写入几天后查询逐渐变慢">场景三：写入几天后查询逐渐变慢<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E5%9C%BA%E6%99%AF%E4%B8%89%E5%86%99%E5%85%A5%E5%87%A0%E5%A4%A9%E5%90%8E%E6%9F%A5%E8%AF%A2%E9%80%90%E6%B8%90%E5%8F%98%E6%85%A2" class="hash-link" aria-label="场景三：写入几天后查询逐渐变慢的直接链接" title="场景三：写入几天后查询逐渐变慢的直接链接">​</a></h3>
<p>检查 Segment 数量、索引构建积压、Compaction、删除数据和数据加载。用一次全量导入环境与持续小批写入环境对比，确认问题是否来自数据生命周期。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="推荐的验证方式">推荐的验证方式<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E6%8E%A8%E8%8D%90%E7%9A%84%E9%AA%8C%E8%AF%81%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="推荐的验证方式的直接链接" title="推荐的验证方式的直接链接">​</a></h2>
<p>每次只修改一个变量，并保留前后结果：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockToolbar_xPF_"><span class="languageLabel_QDpc">Text</span></div><div class="codeBlockContent_bxn0"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">变更：HNSW 搜索参数从 X 调整为 Y</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">数据：1M 条，768 维</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">查询：1000 条固定查询</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">并发：16</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">变更前：Recall@10 / P95 / P99 / QPS</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">变更后：Recall@10 / P95 / P99 / QPS</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">副作用：CPU、内存、磁盘、错误率</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">回滚条件：Recall 下降超过 1% 或 P99 上升超过 20%</span><br></span></code></pre></div></div>
<p>没有回滚条件的“调优”很容易变成不可控的参数漂移。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="上线前的性能检查清单">上线前的性能检查清单<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E4%B8%8A%E7%BA%BF%E5%89%8D%E7%9A%84%E6%80%A7%E8%83%BD%E6%A3%80%E6%9F%A5%E6%B8%85%E5%8D%95" class="hash-link" aria-label="上线前的性能检查清单的直接链接" title="上线前的性能检查清单的直接链接">​</a></h2>
<ul>
<li>固定数据集、查询集和 Ground Truth；</li>
<li>明确 Recall、P95、P99、QPS 和错误率目标；</li>
<li>客户端与服务端使用同一个请求 ID；</li>
<li>压测覆盖普通查询、过滤和读写并发；</li>
<li>测试冷启动和预热后状态；</li>
<li>检查 Segment、索引、Compaction 和删除数据；</li>
<li>记录 CPU、内存、I/O、网络和队列；</li>
<li>每次变更只有一个主要变量；</li>
<li>保存版本、配置和原始结果；</li>
<li>预先定义回滚条件。</li>
</ul>
<p>性能排障的目标不是把所有指标都推到最大，而是在满足检索质量的前提下，找到稳定、可解释、可回滚的容量边界。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="参考资料">参考资料<a href="https://www.xlonglab.com/articles/vector-database-performance-troubleshooting#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接">​</a></h2>
<ul>
<li><a href="https://www.xlonglab.com/docs/adminGuide/monitor/monitor_overview.md">Milvus 监控文档</a></li>
<li><a href="https://www.xlonglab.com/docs/aiTools/index_selection.md">Milvus 索引选择</a></li>
<li><a href="https://github.com/zilliztech/VectorDBBench" target="_blank" rel="noopener noreferrer">VectorDBBench</a></li>
<li><a href="https://mp.weixin.qq.com/s?__biz=MzUzMDI5OTA5NQ==&amp;mid=2247502147&amp;idx=1&amp;sn=cb7c53a76f75f0e92c5dac204cdd3424&amp;chksm=fa5168fbcd26e1ed4deaf52287e33a18c9454e7333e4e4c439946598521aa6ebc0ce6672b32b#rd" target="_blank" rel="noopener noreferrer">Zilliz：如何定位 Milvus 性能瓶颈</a></li>
<li><a href="https://mp.weixin.qq.com/s?__biz=MzUzMDI5OTA5NQ==&amp;mid=2247502315&amp;idx=1&amp;sn=0143ba7c00f43f11928f51ac19f344f6&amp;chksm=fa516853cd26e1456d3bf6574953d915ec3fc77d00be6597b51bb7ab3dc9f6af2214a21191b8#rd" target="_blank" rel="noopener noreferrer">Zilliz：评估与优化 Milvus 存储性能</a></li>
</ul>]]></content:encoded>
            <category>工程实践</category>
        </item>
    </channel>
</rss>