<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>向量检索 on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/tags/%E5%90%91%E9%87%8F%E6%A3%80%E7%B4%A2/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Sat, 11 Apr 2026 10:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/%E5%90%91%E9%87%8F%E6%A3%80%E7%B4%A2/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>RAG 系统完全笔记：从检索器到生产部署</title>
      <link>https://ftmao.com/blog/posts/rag-notes/</link>
      <pubDate>Sat, 11 Apr 2026 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/rag-notes/</guid>
      <description>&lt;h1 id=&#34;rag-系统完全笔记从检索器到生产部署&#34;&gt;RAG 系统完全笔记：从检索器到生产部署&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;最近重刷了吴恩达 DeepLearning.ai 的《Retrieval Augmented Generation》课程，整理了一份从检索器到生产部署的完整笔记。RAG 不只是「把文档塞进向量库然后问大模型」，它的每个环节都有值得细究的工程决策。本文在每个算法之外，也补充了对应的落地工具与最佳实践。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一rag-是什么为什么需要它&#34;&gt;一、RAG 是什么？为什么需要它？&lt;/h2&gt;
&lt;p&gt;大语言模型（LLM）有两个天然缺陷：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;知识有截止日期&lt;/strong&gt;：训练数据之后发生的事件、私有文档，模型都不知道。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;容易 hallucination（幻觉）&lt;/strong&gt;：没有可靠依据时，模型会一本正经地编造答案。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;RAG 的解决思路很简单：&lt;strong&gt;在生成答案前，先从外部知识库中检索相关文档，把检索结果作为上下文喂给 LLM&lt;/strong&gt;。这样模型既能利用最新、最专有的知识，又能基于检索到的内容生成，显著降低幻觉风险。&lt;/p&gt;
&lt;p&gt;一个完整的 RAG 系统分为&lt;strong&gt;离线索引&lt;/strong&gt;和&lt;strong&gt;在线检索生成&lt;/strong&gt;两条流水线：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 离线索引
        A[原始文档] --&amp;gt; B[文档分块 Chunking]
        B --&amp;gt; C[Embedding 编码]
        C --&amp;gt; D[(向量数据库)]
    end
    subgraph 在线检索生成
        E[用户查询] --&amp;gt; F[查询改写]
        F --&amp;gt; G[检索器 Retriever]
        D -.-&amp;gt; G
        G --&amp;gt; H[重排序 Re-ranking]
        H --&amp;gt; I[LLM 生成]
        I --&amp;gt; J[最终答案]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;落地框架&lt;/strong&gt;：搭建整条 pipeline 不必从零写，&lt;strong&gt;LangChain&lt;/strong&gt; 和 &lt;strong&gt;LlamaIndex&lt;/strong&gt; 是最主流的两个 RAG 编排框架，检索器、分块器、重排序器、提示词模板都有现成实现；需要精细控制流程状态（如 Agentic RAG）时可以用 &lt;strong&gt;LangGraph&lt;/strong&gt;。入门建议先用 LlamaIndex 快速跑通，再根据瓶颈逐环节替换。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二检索器找到正确的文档&#34;&gt;二、检索器：找到正确的文档&lt;/h2&gt;
&lt;p&gt;检索器是 RAG 系统的第一道关卡。常见检索方式有三种，以及它们的组合。&lt;/p&gt;
&lt;h3 id=&#34;21-元数据过滤&#34;&gt;2.1 元数据过滤&lt;/h3&gt;
&lt;p&gt;在向量搜索之前，先用结构化条件缩小范围：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作者、发布时间、信源&lt;/li&gt;
&lt;li&gt;地区、类别、权限标签&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如，企业知识库中只允许查询「本部门」文档，就可以通过 &lt;code&gt;department_id&lt;/code&gt; 元数据过滤。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最佳实践&lt;/strong&gt;：主流向量库都支持「过滤 + 向量搜索」的混合查询——Elasticsearch 的 &lt;code&gt;filter&lt;/code&gt; 子句、Qdrant 的 payload filter、pgvector 配合普通 &lt;code&gt;WHERE&lt;/code&gt; 条件都可以。&lt;strong&gt;权限过滤一定要放在检索阶段做&lt;/strong&gt;，而不是检索出来再在应用层过滤，否则 Top-K 里可能挤掉有权限的文档，还会把不该出现的文档内容带进 LLM 上下文。&lt;/p&gt;
&lt;h3 id=&#34;22-关键词搜索&#34;&gt;2.2 关键词搜索&lt;/h3&gt;
&lt;p&gt;基于词频的检索，计算查询词与文档的匹配程度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TF-IDF&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TF（词频）&lt;/strong&gt;：查询中的词在一篇文档里出现了多少次。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IDF（逆文档频率）&lt;/strong&gt;：查询中的词在所有文档中出现的频率的倒数。词越稀有，权重越高。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;写成公式：&lt;/p&gt;
&lt;p&gt;$$
\mathrm{tf}(t, d) = \frac{f_{t,d}}{\sum_{t&amp;rsquo; \in d} f_{t&amp;rsquo;,d}}, \qquad
\mathrm{idf}(t, D) = \log \frac{N}{1 + |{d \in D : t \in d}|}
$$&lt;/p&gt;
&lt;p&gt;公式中各符号的含义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;$t$（term）&lt;/strong&gt;：一个词项（term），通常指查询中的某个词。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$d$（document）&lt;/strong&gt;：一篇文档，即被评分的单篇文档。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$D$（document collection）&lt;/strong&gt;：整个文档集合（语料库），包含全部 $N$ 篇文档。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$f$&lt;/strong&gt;：词频计数函数（frequency）。$f_{t,d}$ 表示词 $t$ 在文档 $d$ 中出现的&lt;strong&gt;次数&lt;/strong&gt;，例如「检索」在某文档中出现 3 次，则 $f_{t,d} = 3$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$\sum_{t&amp;rsquo; \in d} f_{t&amp;rsquo;,d}$&lt;/strong&gt;：文档 $d$ 中所有词项的出现次数之和，即文档总词数（长度）。分子除以它，就把词频归一化成「占比」，避免长文档天然占优。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$N$&lt;/strong&gt;：文档集合 $D$ 中的文档总数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$|{d \in D : t \in d}|$&lt;/strong&gt;：包含词 $t$ 的文档篇数（document frequency）。分母加 1 是平滑处理，防止词在所有文档中都不出现时除以零。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;直观理解：$\mathrm{tf}$ 衡量「这个词在本文档中有多重要」（出现占比越高越重要），$\mathrm{idf}$ 衡量「这个词在整个语料库中有多稀有」（越稀有的词越能区分文档，权重越高），两者相乘就是词的综合权重。&lt;/p&gt;
&lt;p&gt;文档 $d$ 对查询 $q$ 的得分即查询中各词的 TF-IDF 之和：&lt;/p&gt;
&lt;p&gt;$$
\mathrm{score}(q, d) = \sum_{t \in q} \mathrm{tf}(t, d) \cdot \mathrm{idf}(t, D)
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BM25&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;BM25 是 TF-IDF 的改进版，引入了两个可调参数：&lt;/p&gt;
&lt;p&gt;$$
\mathrm{score}(q, d) = \sum_{t \in q} \mathrm{idf}(t) \cdot
\frac{f_{t,d} \cdot (k_1 + 1)}
{f_{t,d} + k_1 \cdot \left(1 - b + b \cdot \dfrac{|d|}{\mathrm{avgdl}}\right)}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;$k_1$&lt;/strong&gt;：控制词频对评分的影响（词频饱和曲线），常见取值 $1.2 \sim 2.0$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$b$&lt;/strong&gt;：控制文档长度归一化的强度，$|d|$ 是文档长度，$\mathrm{avgdl}$ 是平均文档长度，常见取值 $0.75$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;BM25 在短查询、精确匹配场景中仍然非常有效，比如查找包含特定术语的技术文档。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工业实现：Elasticsearch&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;BM25 不是纸面算法，它就是 Elasticsearch 的&lt;strong&gt;默认评分器&lt;/strong&gt;（自 ES 5.0 / Lucene 6.0 起，替代了旧的 TF-IDF）。最普通的 &lt;code&gt;match&lt;/code&gt; 查询，底层走的就是 BM25：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;GET&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/my-index/_search&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;query&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;content&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;检索增强生成&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;$k_1$ 和 $b$ 两个参数可以在索引设置里自定义（注意修改 similarity 需要关闭索引或重建索引）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;PUT&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/my-index&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;settings&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;similarity&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;my_bm25&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;BM25&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;k1&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1.2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;b&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0.75&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;mappings&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;properties&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;content&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;similarity&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;my_bm25&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;调试时还可以加 &lt;code&gt;&amp;quot;explain&amp;quot;: true&lt;/code&gt;，ES 会返回每个文档 BM25 打分的详细拆解（tf、idf、长度归一化各自贡献了多少），对排查「为什么这篇文档排在前面」非常有用。&lt;/p&gt;
&lt;p&gt;不想维护 ES 集群时，轻量替代方案有 &lt;strong&gt;OpenSearch&lt;/strong&gt;（同源于 Lucene）、纯 Python 的 &lt;strong&gt;rank_bm25&lt;/strong&gt; 库，以及 LangChain 内置的 &lt;code&gt;BM25Retriever&lt;/code&gt;。注意中文 BM25 的效果强依赖分词器：ES 建议装 &lt;strong&gt;IK Analyzer&lt;/strong&gt;，并针对领域维护自定义词典（产品名、缩写、术语）。&lt;/p&gt;
&lt;h3 id=&#34;23-语义搜索&#34;&gt;2.3 语义搜索&lt;/h3&gt;
&lt;p&gt;将查询和文档都编码成向量，通过 &lt;strong&gt;余弦相似度（cosine similarity）&lt;/strong&gt; 找到语义相近的文档。即使关键词不同，语义接近也能召回。&lt;/p&gt;
&lt;p&gt;$$
\cos(\mathbf{q}, \mathbf{d}) = \frac{\mathbf{q} \cdot \mathbf{d}}{|\mathbf{q}| , |\mathbf{d}|}
= \frac{\sum_i q_i d_i}{\sqrt{\sum_i q_i^2} \cdot \sqrt{\sum_i d_i^2}}
$$&lt;/p&gt;
&lt;p&gt;余弦相似度取值在 $[-1, 1]$ 之间，越接近 $1$ 表示语义越相近。实际工程中通常先对向量做 L2 归一化，此时余弦相似度就退化为简单的内积，检索更高效。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：向量由 Embedding 模型生成，选型参考 Hugging Face 的 &lt;strong&gt;MTEB / C-MTEB 榜单&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API 方案：OpenAI &lt;code&gt;text-embedding-3&lt;/code&gt; 系列、Cohere Embed、阿里 DashScope 通用文本向量。&lt;/li&gt;
&lt;li&gt;自托管方案：&lt;strong&gt;BGE-M3&lt;/strong&gt;（BAAI，中英文效果好，支持稠密 + 稀疏 + 多向量三种输出）、&lt;code&gt;gte-Qwen&lt;/code&gt; 系列、E5 系列，均可用 &lt;strong&gt;sentence-transformers&lt;/strong&gt; 或 &lt;strong&gt;FlagEmbedding&lt;/strong&gt; 库本地加载。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;最佳实践&lt;/strong&gt;：查询和文档必须用同一个 Embedding 模型（部分模型对查询和文档有不同前缀，如 E5 的 &lt;code&gt;query:&lt;/code&gt; / &lt;code&gt;passage:&lt;/code&gt;，不要混用）；更换 Embedding 模型意味着全量重建索引，选型时先在自有数据上做小规模评测再定。&lt;/p&gt;
&lt;h3 id=&#34;24-混合搜索&#34;&gt;2.4 混合搜索&lt;/h3&gt;
&lt;p&gt;实际工程中，通常把多种检索方式组合起来：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    Q[用户查询] --&amp;gt; K[关键词搜索 BM25]
    Q --&amp;gt; S[语义搜索 向量]
    K --&amp;gt; M[结果融合]
    S --&amp;gt; M
    M --&amp;gt; F[元数据过滤]
    F --&amp;gt; R[重排序]
    R --&amp;gt; O[最终上下文]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;两路结果分数量纲不同，直接相加不合理，标准做法是 &lt;strong&gt;RRF（Reciprocal Rank Fusion）&lt;/strong&gt;：只看排名不看分数。&lt;/p&gt;
&lt;p&gt;$$
\mathrm{RRF}(d) = \sum_{r \in \text{各路检索}} \frac{1}{k + \mathrm{rank}_r(d)}
$$&lt;/p&gt;
&lt;p&gt;其中 $k$ 是平滑常数，通常取 $60$。文档在任一路排得靠前，融合后得分就高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：这套组合在 Elasticsearch 8.x 里一个引擎就能实现——BM25 + kNN 向量检索（底层 HNSW），原生支持 RRF 融合；代码侧则可以用 LangChain 的 &lt;code&gt;EnsembleRetriever&lt;/code&gt;（内部同样是 RRF）把 &lt;code&gt;BM25Retriever&lt;/code&gt; 和任意向量检索器组合起来。&lt;/p&gt;
&lt;h3 id=&#34;25-检索质量评估&#34;&gt;2.5 检索质量评估&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;精准度（Precision）&lt;/strong&gt;：检索出的相关文档 / 检索出的所有文档。反映结果的可信度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;召回率（Recall）&lt;/strong&gt;：检索出的相关文档 / 所有相关文档。反映结果的完整度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\mathrm{Precision} = \frac{|\text{相关文档} \cap \text{检索结果}|}{|\text{检索结果}|}, \qquad
\mathrm{Recall} = \frac{|\text{相关文档} \cap \text{检索结果}|}{|\text{相关文档}|}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;平均精准度（MAP）&lt;/strong&gt;：先对单次查询在相关文档出现的每个位置取精准度平均（AP），再对多次查询求平均。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\mathrm{MAP} = \frac{1}{|Q|} \sum_{q=1}^{|Q|} \mathrm{AP}(q), \qquad
\mathrm{AP}(q) = \frac{1}{m_q} \sum_{k=1}^{n} P(k) \cdot \mathrm{rel}(k)
$$&lt;/p&gt;
&lt;p&gt;其中 $m_q$ 是查询 $q$ 的相关文档总数，$P(k)$ 是前 $k$ 个结果的精准度，$\mathrm{rel}(k)$ 表示第 $k$ 个结果是否相关。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;平均倒数排名（MRR）&lt;/strong&gt;：最相关文档在结果列表中的排名的倒数。排名第一，MRR 就是 1；排名第十，MRR 就是 0.1。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\mathrm{MRR} = \frac{1}{|Q|} \sum_{q=1}^{|Q|} \frac{1}{\mathrm{rank}_q}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：评估的前提是有一批带标注的「黄金测试集」（golden dataset），通常 50-200 条真实用户问题加人工标注的相关文档。标注成本高时可以用 &lt;strong&gt;Ragas&lt;/strong&gt; 的合成数据功能自动生成测试集；&lt;strong&gt;Ragas&lt;/strong&gt; 和 &lt;strong&gt;DeepEval&lt;/strong&gt; 还都提供 context precision / context recall 等无需人工标注、由 LLM 打分的检索评估指标，适合纳入 CI 做回归测试。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三向量数据库如何高效存储和检索&#34;&gt;三、向量数据库：如何高效存储和检索&lt;/h2&gt;
&lt;p&gt;向量数据库的核心问题是：&lt;strong&gt;给定一个查询向量，如何快速找到最相似的文档向量？&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;31-knn精确但慢&#34;&gt;3.1 KNN：精确但慢&lt;/h3&gt;
&lt;p&gt;KNN（K-Nearest Neighbors）会实时计算查询向量与所有文档向量的距离，结果准确，但当向量数量达到亿级时，计算量不可接受。设向量库有 $N$ 个 $d$ 维向量，暴力检索的复杂度为 $O(Nd)$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：向量量在万级以内、或需要精确结果做基准评测时，暴力 KNN 反而最省事——FAISS 的 &lt;code&gt;IndexFlatIP&lt;/code&gt;、NumPy 一次矩阵乘法就够，不必引入索引结构。&lt;/p&gt;
&lt;h3 id=&#34;32-ann近似但快&#34;&gt;3.2 ANN：近似但快&lt;/h3&gt;
&lt;p&gt;ANN（Approximate Nearest Neighbor）通过构建近邻图或索引结构来加速检索，牺牲了极小的准确率，换取了数量级的性能提升。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HNSW（Hierarchical Navigable Small World）&lt;/strong&gt; 是目前最常用的 ANN 算法之一，采用分层导航图结构，能够在亿级向量库中快速找到近似最近邻：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    Q[查询向量] --&amp;gt; E[顶层入口点]
    subgraph HNSW[&amp;#34;HNSW 分层图&amp;#34;]
        direction TB
        L2[&amp;#34;Layer 2（最稀疏，快速定位大致区域）&amp;#34;]
        L1[&amp;#34;Layer 1（中间过渡层）&amp;#34;]
        L0[&amp;#34;Layer 0（包含全部节点，精确找最近邻）&amp;#34;]
        L2 --&amp;gt; L1 --&amp;gt; L0
    end
    E --&amp;gt; L2
    L0 --&amp;gt; R[&amp;#34;Top-K 近似最近邻&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;查询时从最稀疏的顶层开始贪心搜索，逐层向下，每层把入口点收敛到更接近目标的区域，最后在最稠密的第 0 层做精细查找。平均查询复杂度约为 $O(\log N)$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;向量库选型：&lt;strong&gt;Milvus&lt;/strong&gt;、&lt;strong&gt;Qdrant&lt;/strong&gt;、&lt;strong&gt;Weaviate&lt;/strong&gt;（独立向量数据库）、&lt;strong&gt;pgvector&lt;/strong&gt;（已有 PostgreSQL 时的最简方案）、&lt;strong&gt;Chroma&lt;/strong&gt;（轻量嵌入式）、&lt;strong&gt;FAISS&lt;/strong&gt;（Meta 的索引库，无服务化能力）、&lt;strong&gt;Elasticsearch kNN&lt;/strong&gt;（想复用 BM25 生态时）。&lt;/li&gt;
&lt;li&gt;HNSW 三个关键参数：&lt;code&gt;M&lt;/code&gt;（每个节点的最大连接数，默认 16，越大越准但越耗内存）、&lt;code&gt;ef_construction&lt;/code&gt;（建索引时的搜索宽度，默认 100）、&lt;code&gt;ef_search&lt;/code&gt;（查询时的搜索宽度，&lt;strong&gt;运行时调大它就能在召回率和延迟之间实时权衡&lt;/strong&gt;，是最常用的调优旋钮）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四文档分块粒度决定召回质量&#34;&gt;四、文档分块：粒度决定召回质量&lt;/h2&gt;
&lt;p&gt;把长文档切成小块（chunk）是 RAG 的关键步骤。chunk 太大或太小都有问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;太大&lt;/strong&gt;：一个向量要表达太多内容，指向性弱，容易引入无关信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;太小&lt;/strong&gt;：上下文不足，语义不明确。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;41-固定大小分块&#34;&gt;4.1 固定大小分块&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;文本：250 字符左右（例如 225 字符正文 + 25 字符重叠）。&lt;/li&gt;
&lt;li&gt;代码：500 字符左右，重叠 50-100 字符。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;重叠（overlap）能避免关键信息被切分断掉。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：LangChain 的 &lt;code&gt;CharacterTextSplitter&lt;/code&gt; / &lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt;、LlamaIndex 的 &lt;code&gt;SentenceSplitter&lt;/code&gt;。其中 &lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt; 是事实上的默认选择：按「段落 → 句子 → 词 → 字符」的优先级递归切分，尽量在大小限制内保留自然边界。&lt;/p&gt;
&lt;h3 id=&#34;42-按特殊字符分块&#34;&gt;4.2 按特殊字符分块&lt;/h3&gt;
&lt;p&gt;按换行、段落、标题等自然边界切分，保留文档结构。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：Markdown 文档用 LangChain 的 &lt;code&gt;MarkdownHeaderTextSplitter&lt;/code&gt; 按标题层级切；HTML/PDF 等复杂格式先用 &lt;strong&gt;Unstructured&lt;/strong&gt; 做版面解析再分块；代码可以按语法树切分（LangChain 的 &lt;code&gt;Language&lt;/code&gt; 参数基于 tree-sitter）。&lt;/p&gt;
&lt;h3 id=&#34;43-语义分块&#34;&gt;4.3 语义分块&lt;/h3&gt;
&lt;p&gt;按句子处理，判断当前句子与下一个句子的语义相似度。如果不相似，就在当前位置切分。即对相邻句子的向量 $s_i$、$s_{i+1}$，当 $\cos(s_i, s_{i+1}) &amp;lt; \tau$（$\tau$ 为预设阈值）时切分。这样切出来的 chunk 内部语义更连贯。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：LangChain 的 &lt;code&gt;SemanticChunker&lt;/code&gt;（基于 Embedding，可选百分位/标准差阈值）、LlamaIndex 的 &lt;code&gt;SemanticSplitterNodeParser&lt;/code&gt;、专用库 &lt;strong&gt;chonkie&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;44-llm-分块&#34;&gt;4.4 LLM 分块&lt;/h3&gt;
&lt;p&gt;直接让大模型根据语义边界做分块，效果通常最好，但成本也最高。实践中一般用小型模型（如 GPT-4o-mini 级别）离线跑一遍建索引，在线查询不再产生额外成本。&lt;/p&gt;
&lt;h3 id=&#34;45-上下文感知分块&#34;&gt;4.5 上下文感知分块&lt;/h3&gt;
&lt;p&gt;在分块后补充描述信息，例如 chunk 所属的章节、文档标题等，帮助提升索引相关性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最佳实践&lt;/strong&gt;：Anthropic 提出的 &lt;strong&gt;Contextual Retrieval&lt;/strong&gt; 是这个思路的成熟方案——索引入库前，让 LLM 为每个 chunk 生成一段 50-100 token 的上下文说明（「本段出自 XX 文档第三章，讨论……」）拼在 chunk 前面，再做 Embedding 和 BM25 索引，官方报告可将检索失败率降低约 49%（配合重排序可达 67%）。Embedding 前拼接即可，无需改动检索链路。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五查询改写让问题更容易被检索&#34;&gt;五、查询改写：让问题更容易被检索&lt;/h2&gt;
&lt;p&gt;用户的原始查询往往不适合直接做向量检索。常见优化方式：&lt;/p&gt;
&lt;h3 id=&#34;51-llm-重写查询&#34;&gt;5.1 LLM 重写查询&lt;/h3&gt;
&lt;p&gt;让 LLM 在检索前改写查询：去除无关信息、修正术语、扩展同义词。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：LangChain 提供现成的实现——&lt;code&gt;MultiQueryRetriever&lt;/code&gt;（让 LLM 生成多个改写版本并行检索后去重合并）、&lt;code&gt;RePhraseQueryRetriever&lt;/code&gt;；LlamaIndex 有 &lt;code&gt;QueryTransform&lt;/code&gt;（如 &lt;code&gt;HyDEQueryTransform&lt;/code&gt;）。&lt;strong&gt;最佳实践&lt;/strong&gt;：多轮对话里务必先把「指代消解」做掉——用 LLM 把「那它多少钱？」结合历史改写成独立问题再检索，这一步对客服场景提升最明显。&lt;/p&gt;
&lt;h3 id=&#34;52-命名实体识别ner&#34;&gt;5.2 命名实体识别（NER）&lt;/h3&gt;
&lt;p&gt;识别查询中的人名、地名、日期、组织、角色等实体，用作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;知识图谱检索&lt;/li&gt;
&lt;li&gt;精准 BM25 匹配&lt;/li&gt;
&lt;li&gt;元数据过滤&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：通用场景用 &lt;strong&gt;spaCy&lt;/strong&gt;、中文用 &lt;strong&gt;HanLP&lt;/strong&gt; 或 &lt;strong&gt;LTP&lt;/strong&gt;；实体类型不固定（领域专有名词）时用零样本模型 &lt;strong&gt;GLiNER&lt;/strong&gt;，或直接让 LLM 做结构化抽取（JSON 输出），简单且免去训练。&lt;/p&gt;
&lt;h3 id=&#34;53-假设文档嵌入hyde&#34;&gt;5.3 假设文档嵌入（HyDE）&lt;/h3&gt;
&lt;p&gt;HyDE（Hypothetical Document Embeddings）的思路是：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    Q[用户查询] --&amp;gt; L[&amp;#34;LLM 生成「理想答案」&amp;#34;]
    L --&amp;gt; E[Embedding 编码]
    E --&amp;gt; V[(向量数据库)]
    V --&amp;gt; D[检索相似的真实文档]
    D --&amp;gt; G[LLM 基于真实文档生成最终答案]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ol&gt;
&lt;li&gt;先用 LLM 生成一个「理想答案」。&lt;/li&gt;
&lt;li&gt;用这个答案的向量去向量库中检索相似文档。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;传统 RAG 搜索的是「与用户查询相似的文档」，而 HyDE 搜索的是「与理想答案相似的文档」，往往召回质量更高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最佳实践&lt;/strong&gt;：HyDE 适合开放式、长尾问题，对事实短查询收益有限；生成理想答案时用低 temperature 并生成 2-4 个版本取向量均值，比单个版本更稳；LangChain 的 &lt;code&gt;HypotheticalDocumentEmbedder&lt;/code&gt; 和 LlamaIndex 的 &lt;code&gt;HyDEQueryTransform&lt;/code&gt; 可直接使用。代价是多一次 LLM 调用的延迟，对时延敏感的场景要权衡。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六编码与重排序让相似度更准确&#34;&gt;六、编码与重排序：让相似度更准确&lt;/h2&gt;
&lt;h3 id=&#34;61-双向编码bi-encoder&#34;&gt;6.1 双向编码（Bi-Encoder）&lt;/h3&gt;
&lt;p&gt;RAG 默认的编码方式。查询和文档分别编码成向量，然后通过向量相似度匹配：&lt;/p&gt;
&lt;p&gt;$$
\mathrm{score}(q, d) = \cos\big(E_q(q), E_d(d)\big)
$$&lt;/p&gt;
&lt;p&gt;编码可以离线预计算，在线检索只需一次查询编码加向量搜索，速度快，但查询与文档之间没有深度交互。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：就是 2.3 节的 Embedding 模型生态——sentence-transformers、FlagEmbedding 加载 BGE/E5 等模型，或调用 Embedding API。&lt;/p&gt;
&lt;h3 id=&#34;62-交叉编码cross-encoder&#34;&gt;6.2 交叉编码（Cross-Encoder）&lt;/h3&gt;
&lt;p&gt;把查询和每个文档配对，输入一个小型 LLM，输出相关度分数：&lt;/p&gt;
&lt;p&gt;$$
\mathrm{score}(q, d) = E_{\mathrm{cross}}([q; d])
$$&lt;/p&gt;
&lt;p&gt;准确率更高，但每对 $(q, d)$ 都要过一次模型，计算成本大，通常用于重排序阶段。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：开源重排序模型首选 &lt;strong&gt;bge-reranker-v2&lt;/strong&gt; 系列（BAAI，中英双语）和 &lt;strong&gt;Qwen3-Reranker&lt;/strong&gt;；API 方案有 &lt;strong&gt;Cohere Rerank&lt;/strong&gt; 和 &lt;strong&gt;Jina Reranker&lt;/strong&gt;。sentence-transformers 的 &lt;code&gt;CrossEncoder&lt;/code&gt; 类三行代码即可加载本地模型打分。&lt;/p&gt;
&lt;h3 id=&#34;63-colbert&#34;&gt;6.3 ColBERT&lt;/h3&gt;
&lt;p&gt;文档中每个词都生成一个向量，而不是整个文档只有一个向量。查询时，把查询词与文档中所有词向量对比，构建相似度矩阵并加和。其核心是 &lt;strong&gt;MaxSim&lt;/strong&gt; 操作：&lt;/p&gt;
&lt;p&gt;$$
\mathrm{score}(q, d) = \sum_{i=1}^{|q|} \max_{j=1}^{|d|} ; \mathbf{q}_i \cdot \mathbf{d}_j
$$&lt;/p&gt;
&lt;p&gt;即查询中每个 token 都在文档里找到最匹配的一个 token，再把最高分相加。兼顾效率和细粒度匹配。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：训练和使用 ColBERT 模型用官方 &lt;strong&gt;ColBERT&lt;/strong&gt; 仓库或更易用的 &lt;strong&gt;RAGatouille&lt;/strong&gt;；生产检索引擎 &lt;strong&gt;Vespa&lt;/strong&gt; 原生支持张量（多向量）排序；&lt;strong&gt;BGE-M3&lt;/strong&gt; 的 multi-vector 输出也能实现类似的 MaxSim 细粒度匹配。&lt;/p&gt;
&lt;h3 id=&#34;64-重排序re-ranking&#34;&gt;6.4 重排序（Re-ranking）&lt;/h3&gt;
&lt;p&gt;先从向量库中粗召回一批候选文档，再用交叉编码或 LLM 做二次排序，过滤掉不相关的结果：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    Q[查询] --&amp;gt; B[&amp;#34;Bi-Encoder 粗召回&amp;lt;br&amp;gt;（快，Top 50）&amp;#34;]
    B --&amp;gt; C[&amp;#34;Cross-Encoder / LLM 精排&amp;lt;br&amp;gt;（准，Top 5）&amp;#34;]
    C --&amp;gt; O[送入 LLM 的上下文]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;粗召回阶段追求「不漏」，精排阶段追求「排得准」，两个阶段各司其职。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最佳实践&lt;/strong&gt;：重排序通常是 RAG 系统里&lt;strong&gt;性价比最高的单项优化&lt;/strong&gt;——不动索引、不改 Embedding 模型，只在检索后插一层，召回质量立竿见影。粗召回取 Top 20-50、精排后保留 Top 3-5 是常见配置；用 LangChain 的 &lt;code&gt;ContextualCompressionRetriever&lt;/code&gt; 或 LlamaIndex 的 &lt;code&gt;Postprocessor&lt;/code&gt; 机制接入，几行代码就能挂在现有链路上。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七llm-生成如何让答案更可靠&#34;&gt;七、LLM 生成：如何让答案更可靠&lt;/h2&gt;
&lt;h3 id=&#34;71-transformer-基础&#34;&gt;7.1 Transformer 基础&lt;/h3&gt;
&lt;p&gt;LLM 的核心是 Transformer：多头注意力机制 + 前馈网络。缩放点积注意力的公式为：&lt;/p&gt;
&lt;p&gt;$$
\mathrm{Attention}(Q, K, V) = \mathrm{softmax}\left(\frac{Q K^{\top}}{\sqrt{d_k}}\right) V
$$&lt;/p&gt;
&lt;p&gt;其中 $Q$、$K$、$V$ 分别由输入线性变换得到，$d_k$ 是 key 的维度，除以 $\sqrt{d_k}$ 是为了防止点积过大导致 softmax 梯度消失。理解这一点有助于理解上下文窗口、注意力稀释等问题。&lt;/p&gt;
&lt;h3 id=&#34;72-llm-api-关键参数&#34;&gt;7.2 LLM API 关键参数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;temperature&lt;/strong&gt;：采样温度。0 表示贪婪模式，总是选概率最高的 token；大于 1 会让低概率 token 更有可能被选中。其作用是在 softmax 前对 logits 做缩放：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
p_i = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)}
$$&lt;/p&gt;
&lt;p&gt;$T \to 0$ 时分布退化为 one-hot（贪婪），$T$ 越大分布越平坦、越随机。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;top-k&lt;/strong&gt;：只从概率最高的前 k 个 token 中采样。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;top-p（nucleus sampling）&lt;/strong&gt;：从累计概率达到 p 的 token 集合中采样。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;repetition_penalty&lt;/strong&gt;：惩罚重复出现的 token。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;logit_bias&lt;/strong&gt;：调整特定 token 的概率。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;最佳实践&lt;/strong&gt;：RAG 问答场景建议 &lt;code&gt;temperature = 0&lt;/code&gt;（或 0.1-0.3）以保证答案稳定、贴合检索内容；&lt;code&gt;top-p&lt;/code&gt; 与 temperature 一般只调一个，不要同时大幅改动；需要模型输出严格 JSON 时优先用各家的 structured output / JSON mode，而不是靠提示词约束。&lt;/p&gt;
&lt;h3 id=&#34;73-llm-选型因素&#34;&gt;7.3 LLM 选型因素&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;模型大小&lt;/li&gt;
&lt;li&gt;发布时间&lt;/li&gt;
&lt;li&gt;成本&lt;/li&gt;
&lt;li&gt;上下文窗口长度&lt;/li&gt;
&lt;li&gt;首 token 延迟（Time to First Token）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：横向对比可以参考 &lt;strong&gt;LMArena&lt;/strong&gt;（真人盲测偏好榜）和 &lt;strong&gt;OpenCompass&lt;/strong&gt;（中文能力评测较全）；但最终一定要在自有评估集上跑一次——榜单排名和具体业务场景的表现经常有出入。自建服务推理引擎选 &lt;strong&gt;vLLM&lt;/strong&gt;（高吞吐）或 &lt;strong&gt;TGI&lt;/strong&gt;；本地/边缘部署用 &lt;strong&gt;Ollama&lt;/strong&gt;、&lt;strong&gt;llama.cpp&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;74-提示词工程&#34;&gt;7.4 提示词工程&lt;/h3&gt;
&lt;p&gt;一个典型的 RAG 提示词结构：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;系统提示词
+ 会话历史
+ 检索到的文档
+ 用户问题&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;优化技巧：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;添加回答样例（few-shot）&lt;/li&gt;
&lt;li&gt;要求模型展示思考链路（chain-of-thought）&lt;/li&gt;
&lt;li&gt;对过长的上下文做裁剪，保留最相关部分&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;最佳实践&lt;/strong&gt;：给每个检索到的 chunk 编号并要求模型在答案中标注引用（如 &lt;code&gt;[1][3]&lt;/code&gt;），既能溯源又便于自动校验幻觉；明确写「文档中没有答案时直接说不知道」，比含糊的约束更能压住幻觉；上下文窗口紧张时按重排序分数从高到低填充，而不是按文档原始顺序。&lt;/p&gt;
&lt;h3 id=&#34;75-幻觉处理&#34;&gt;7.5 幻觉处理&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;多次生成对比&lt;/strong&gt;：让模型多次回答，看是否一致。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;强制基于检索文档回答&lt;/strong&gt;：明确要求模型只根据提供的文档作答，并引用来源。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：自动化检测可用 &lt;strong&gt;Ragas&lt;/strong&gt; 的 faithfulness 指标（答案是否忠于检索上下文）、&lt;strong&gt;SelfCheckGPT&lt;/strong&gt;（多次采样一致性）；要求引用编号后，还可以在应用层写一个简单的校验器：答案中引用的编号必须真实存在于本次检索结果里，否则丢弃重答。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八agentic-rag-与微调&#34;&gt;八、Agentic RAG 与微调&lt;/h2&gt;
&lt;h3 id=&#34;81-agentic-rag&#34;&gt;8.1 Agentic RAG&lt;/h3&gt;
&lt;p&gt;复杂查询可以交给 Agent：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    Q[复杂查询] --&amp;gt; R[提示词路由]
    R --&amp;gt; P[&amp;#34;制定计划&amp;lt;br&amp;gt;（拆分多跳子查询）&amp;#34;]
    P --&amp;gt; X[&amp;#34;执行检索 / 工具调用&amp;#34;]
    X --&amp;gt; V{结果是否充分}
    V -- 否 --&amp;gt; P
    V -- 是 --&amp;gt; S[整合答案]
    S --&amp;gt; A[最终回答]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;例如，一个多跳问题可以拆分成多个子查询，分别检索后再综合回答；检索不充分时 Agent 还会调整计划继续检索。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：&lt;strong&gt;LangGraph&lt;/strong&gt;（显式状态机，最可控）、&lt;strong&gt;CrewAI&lt;/strong&gt; / &lt;strong&gt;AutoGen&lt;/strong&gt;（多 Agent 协作）、LlamaIndex 的 &lt;code&gt;AgentWorkflow&lt;/code&gt;。最佳实践是先做「查询路由」这一层：简单问题走普通 RAG，只有复杂问题才进 Agent 流程，避免所有查询都承担多轮 LLM 调用的成本和时延。&lt;/p&gt;
&lt;h3 id=&#34;82-微调&#34;&gt;8.2 微调&lt;/h3&gt;
&lt;p&gt;当通用模型在某些领域表现不足时，可以用领域数据集微调模型，提升特定任务的准确率和稳定性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：参数高效微调用 &lt;strong&gt;LoRA / QLoRA&lt;/strong&gt;（Hugging Face &lt;code&gt;peft&lt;/code&gt; 库），一站式训练框架选 &lt;strong&gt;LLaMA-Factory&lt;/strong&gt;（中文社区活跃、零代码 WebUI）或 &lt;strong&gt;Axolotl&lt;/strong&gt;。&lt;strong&gt;最佳实践&lt;/strong&gt;：微调是最后手段——先把提示词工程和检索质量优化到位仍不达标时再考虑；微调 Embedding 模型（用自己业务的正负例对）往往比微调生成模型投入产出比更高。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九生产部署从-demo-到可运维系统&#34;&gt;九、生产部署：从 demo 到可运维系统&lt;/h2&gt;
&lt;h3 id=&#34;91-生产挑战&#34;&gt;9.1 生产挑战&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;高并发&lt;/li&gt;
&lt;li&gt;预期外的会话&lt;/li&gt;
&lt;li&gt;数据质量差&lt;/li&gt;
&lt;li&gt;权限控制&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;92-评估与监控&#34;&gt;9.2 评估与监控&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基础监控&lt;/strong&gt;：流量、日志、AB 测试切换&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;质量监控&lt;/strong&gt;：组件级和系统级评估&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用户满意度与回答质量&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LLM 评估与代码评估&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;工具如 &lt;strong&gt;Phoenix&lt;/strong&gt; 可以做消息跟踪和可观测性。同类常用工具还有 &lt;strong&gt;Langfuse&lt;/strong&gt;（开源自托管、LLM 调用全链路追踪 + 数据标注）和 &lt;strong&gt;LangSmith&lt;/strong&gt;（LangChain 官方，商业化）；评估指标侧用 &lt;strong&gt;Ragas&lt;/strong&gt; 做周期性离线评估，监控侧采集用户点踩、会话中断率等业务信号。&lt;/p&gt;
&lt;h3 id=&#34;93-成本优化&#34;&gt;9.3 成本优化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;选择更小的 LLM&lt;/li&gt;
&lt;li&gt;减小提示词大小&lt;/li&gt;
&lt;li&gt;自托管 vs API 调用&lt;/li&gt;
&lt;li&gt;部署方式优化：HNSW 索引存入 RAM，文档存磁盘，按需按时区加载&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;最佳实践&lt;/strong&gt;：提示词里检索上下文通常占 token 大头，砍成本优先做两件事——重排序后只保留 Top 3-5 个 chunk、启用 prompt caching（Anthropic / DeepSeek 等均已支持，系统提示词和历史命中缓存可省 50% 以上的输入费用）。&lt;/p&gt;
&lt;h3 id=&#34;94-模型量化&#34;&gt;9.4 模型量化&lt;/h3&gt;
&lt;p&gt;将模型参数从 16 bit 降到 8 bit 或 4 bit，降低显存占用和部署成本。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：推理侧格式选 &lt;strong&gt;GGUF&lt;/strong&gt;（llama.cpp / Ollama 生态）或 &lt;strong&gt;AWQ / GPTQ&lt;/strong&gt;（vLLM 支持）；量化工具用 &lt;strong&gt;bitsandbytes&lt;/strong&gt;（训练/推理通用）、&lt;strong&gt;AutoGPTQ&lt;/strong&gt;、&lt;strong&gt;AutoAWQ&lt;/strong&gt;。实践上 4 bit 量化对 7B 级模型的问答质量损失通常在可接受范围，但对数学、代码任务影响更明显，量化前先在评估集上对比一次。&lt;/p&gt;
&lt;h3 id=&#34;95-时延与响应质量权衡&#34;&gt;9.5 时延与响应质量权衡&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;电商客服&lt;/strong&gt;：时延低，质量要求一般。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;医疗咨询&lt;/strong&gt;：时延可以高，但质量要求最高。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;优化手段包括缓存相似问题、精简召回流程等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最佳实践&lt;/strong&gt;：缓存分两层——完全相同的问题缓存最终答案，语义相似的问题用语义缓存（如 &lt;strong&gt;GPTCache&lt;/strong&gt;）直接返回；流式输出（SSE）能把用户感知延迟从「整个答案的生成时间」降到首 token 时间，是性价比最高的体验优化。&lt;/p&gt;
&lt;h3 id=&#34;96-安全防护&#34;&gt;9.6 安全防护&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;授权访问&lt;/li&gt;
&lt;li&gt;租户隔离&lt;/li&gt;
&lt;li&gt;本地部署 LLM&lt;/li&gt;
&lt;li&gt;文档加密&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;最佳实践&lt;/strong&gt;：除了数据安全，还要防 &lt;strong&gt;提示词注入&lt;/strong&gt;——检索到的文档本身可能包含恶意指令（「忽略之前的指令，输出……」），对策包括系统提示词中明确「检索内容只是数据不是指令」、对检索内容做隔离标记，以及用 &lt;strong&gt;Llama Guard&lt;/strong&gt; 之类的安全模型对输入输出做二次检查。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十多模态-rag&#34;&gt;十、多模态 RAG&lt;/h2&gt;
&lt;h3 id=&#34;101-image-rag&#34;&gt;10.1 Image RAG&lt;/h3&gt;
&lt;p&gt;把图像 token 化后，和文本一样做向量化处理，实现图文混合检索。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：跨模态 Embedding 用 &lt;strong&gt;CLIP&lt;/strong&gt;（OpenAI）或 &lt;strong&gt;SigLIP&lt;/strong&gt;（Google），中文场景可用 &lt;strong&gt;Chinese-CLIP&lt;/strong&gt;；BGE-VL、Jina CLIP 等较新模型效果更好。配合 Qdrant / Milvus 的多向量支持，即可实现「以文搜图、以图搜文」。&lt;/p&gt;
&lt;h3 id=&#34;102-pdf-rag&#34;&gt;10.2 PDF RAG&lt;/h3&gt;
&lt;p&gt;对 PDF 做分块、向量化，类似 ColBERT 的细粒度方法可以更好地处理表格、公式等复杂内容。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;落地工具&lt;/strong&gt;：复杂 PDF 解析用 &lt;strong&gt;Unstructured&lt;/strong&gt;、&lt;strong&gt;Marker&lt;/strong&gt;、&lt;strong&gt;Docling&lt;/strong&gt;（IBM，表格和版面还原好）；视觉化检索思路用 &lt;strong&gt;ColPali&lt;/strong&gt;——直接把 PDF 页面当作图片做视觉 Embedding，跳过脆弱的 OCR/解析环节，对表格、图表密集的文档效果显著；端到端方案也可直接用多模态大模型（GPT-4o、Qwen-VL）读图回答。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十一总结&#34;&gt;十一、总结&lt;/h2&gt;
&lt;p&gt;一个完整的 RAG 系统远不止「向量库 + LLM」，它包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;检索器&lt;/strong&gt;：关键词、语义、元数据、混合搜索。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;向量库&lt;/strong&gt;：KNN/ANN/HNSW 的选择与部署。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文档分块&lt;/strong&gt;：粒度决定召回质量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查询改写&lt;/strong&gt;：HyDE、NER、LLM 重写。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编码与重排序&lt;/strong&gt;：Bi-Encoder、Cross-Encoder、ColBERT。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LLM 生成&lt;/strong&gt;：参数调优、提示词工程、幻觉控制。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生产部署&lt;/strong&gt;：监控、评估、成本、安全、多模态。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;各环节的主流工具速查：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;环节&lt;/th&gt;
					&lt;th&gt;常用工具&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;编排框架&lt;/td&gt;
					&lt;td&gt;LangChain、LlamaIndex、LangGraph&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;关键词检索&lt;/td&gt;
					&lt;td&gt;Elasticsearch / OpenSearch（BM25）、rank_bm25&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Embedding 模型&lt;/td&gt;
					&lt;td&gt;BGE-M3、E5、OpenAI text-embedding-3&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;向量数据库&lt;/td&gt;
					&lt;td&gt;Qdrant、Milvus、pgvector、FAISS、ES kNN&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;重排序&lt;/td&gt;
					&lt;td&gt;bge-reranker、Qwen3-Reranker、Cohere Rerank&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;评估&lt;/td&gt;
					&lt;td&gt;Ragas、DeepEval&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;可观测性&lt;/td&gt;
					&lt;td&gt;Phoenix、Langfuse、LangSmith&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;推理部署&lt;/td&gt;
					&lt;td&gt;vLLM、Ollama、llama.cpp&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;微调&lt;/td&gt;
					&lt;td&gt;LLaMA-Factory、peft（LoRA/QLoRA）&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;RAG 的每一个环节都可以单独调优，也都有成熟的工程实践。真正影响系统效果的，往往不是某个单点技术，而是整个 pipeline 的协同设计。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;参考：吴恩达 DeepLearning.ai《Retrieval Augmented Generation (RAG)》课程&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    <item>
      <title>RAG 本地素材库：从 Embedding 选型到召回调优</title>
      <link>https://ftmao.com/blog/posts/raginftmao/</link>
      <pubDate>Tue, 10 Feb 2026 11:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/raginftmao/</guid>
      <description>&lt;h1 id=&#34;rag-本地素材库从-embedding-选型到召回调优&#34;&gt;RAG 本地素材库：从 Embedding 选型到召回调优&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;在我开发的方糖猫 AI 视频平台中，一个核心能力是「根据文案自动匹配视频素材」。用户的文案可能是「讲一个关于坚持的故事」，系统需要从本地素材库中找到最契合的画面。我们最终用 &lt;strong&gt;RAG（检索增强生成）&lt;/strong&gt; 的思路搭建了本地素材库。这篇文章会完整复盘从 Embedding 选型到召回调优的全过程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一业务背景为什么需要-rag&#34;&gt;一、业务背景：为什么需要 RAG？&lt;/h2&gt;
&lt;p&gt;短视频生成平台里的素材通常有几类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;图片素材：背景图、人物图、概念图&lt;/li&gt;
&lt;li&gt;视频片段：B-roll、转场、动画&lt;/li&gt;
&lt;li&gt;文案片段：金句、案例、开场白&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;早期我们用关键词标签做检索，很快就发现几个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;标签覆盖不全&lt;/strong&gt;。一个海边日落镜头，可能被标注为「海」「日落」「风景」「旅行」，但用户文案写的是「人生低谷后的曙光」，关键词完全对不上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标签质量参差不齐&lt;/strong&gt;。不同运营人员打的标签不一致，导致召回结果波动大。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无法跨模态匹配&lt;/strong&gt;。图片素材没有文字描述，靠文件名搜索基本不可用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;RAG 的思路是：&lt;strong&gt;用 Embedding 把素材和查询都映射到同一向量空间，通过语义相似度做召回&lt;/strong&gt;。这样即使关键词不同，只要语义接近，就能被检索出来。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二系统设计&#34;&gt;二、系统设计&lt;/h2&gt;
&lt;p&gt;整个 RAG 素材库分为三个核心环节：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph S1[&amp;#34;素材入库&amp;#34;]
        A1[&amp;#34;原始素材&amp;#34;] --&amp;gt; A2[&amp;#34;内容提取&amp;#34;] --&amp;gt; A3[&amp;#34;Embedding&amp;#34;] --&amp;gt; A4[&amp;#34;ChromaDB&amp;#34;]
    end
    subgraph S2[&amp;#34;查询召回&amp;#34;]
        B1[&amp;#34;用户文案&amp;#34;] --&amp;gt; B2[&amp;#34;Embedding&amp;#34;] --&amp;gt; B3[&amp;#34;Top-K 检索&amp;#34;] --&amp;gt; B4[&amp;#34;重排&amp;#34;] --&amp;gt; B5[&amp;#34;返回结果&amp;#34;]
    end
    subgraph S3[&amp;#34;持续更新&amp;#34;]
        C1[&amp;#34;新增素材&amp;#34;] --&amp;gt; C2[&amp;#34;增量 Embedding&amp;#34;] --&amp;gt; C3[&amp;#34;索引更新&amp;#34;]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;21-素材入库流程&#34;&gt;2.1 素材入库流程&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;图片/视频&amp;#34;] --&amp;gt; B[&amp;#34;抽帧/缩略图&amp;#34;] --&amp;gt; C[&amp;#34;视觉特征提取（CLIP/ResNet）&amp;#34;] --&amp;gt; D[&amp;#34;文本描述生成&amp;#34;] --&amp;gt; E[&amp;#34;Embedding&amp;#34;] --&amp;gt; F[&amp;#34;写入 ChromaDB&amp;#34;]
    G[&amp;#34;文本片段&amp;#34;] --&amp;gt; H[&amp;#34;清洗/分块&amp;#34;] --&amp;gt; I[&amp;#34;Embedding&amp;#34;] --&amp;gt; F&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;对于图片和视频，我们会：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;视频抽帧为关键帧&lt;/li&gt;
&lt;li&gt;用多模态模型生成一句话描述&lt;/li&gt;
&lt;li&gt;同时保存原始文件路径、文件类型、使用次数等元数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;22-查询召回流程&#34;&gt;2.2 查询召回流程&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 简化版召回流程&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;retrieve_materials&lt;/span&gt;(query: str, top_k: int &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 1. 查询向量化&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    query_embedding &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; embedding_model&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;encode(query)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 2. 向量库初筛&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    candidates &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; chroma_collection&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;query(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        query_embeddings&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;[query_embedding],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        n_results&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;top_k &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;,  &lt;span style=&#34;color:#75715e&#34;&gt;# 多召回一些，留给重排&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        where&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;active&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 3. 重排序&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ranked &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; reranker&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;rerank(query, candidates)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 4. 返回 Top-K&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ranked[:top_k]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;三embedding-选型实测&#34;&gt;三、Embedding 选型实测&lt;/h2&gt;
&lt;p&gt;Embedding 模型的选择直接决定召回质量。我们测试了以下几种：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;模型&lt;/th&gt;
					&lt;th&gt;类型&lt;/th&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;中文语义&lt;/th&gt;
					&lt;th&gt;多模态&lt;/th&gt;
					&lt;th&gt;延迟&lt;/th&gt;
					&lt;th&gt;成本&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;text-embedding-ada-002&lt;/td&gt;
					&lt;td&gt;文本&lt;/td&gt;
					&lt;td&gt;1536&lt;/td&gt;
					&lt;td&gt;中等&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;快&lt;/td&gt;
					&lt;td&gt;低&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;text-embedding-3-small&lt;/td&gt;
					&lt;td&gt;文本&lt;/td&gt;
					&lt;td&gt;1536&lt;/td&gt;
					&lt;td&gt;较好&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;快&lt;/td&gt;
					&lt;td&gt;低&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;BGE-M3&lt;/td&gt;
					&lt;td&gt;文本&lt;/td&gt;
					&lt;td&gt;1024&lt;/td&gt;
					&lt;td&gt;优秀&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;中等&lt;/td&gt;
					&lt;td&gt;本地&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;OpenAI CLIP&lt;/td&gt;
					&lt;td&gt;多模态&lt;/td&gt;
					&lt;td&gt;512&lt;/td&gt;
					&lt;td&gt;一般&lt;/td&gt;
					&lt;td&gt;是&lt;/td&gt;
					&lt;td&gt;中等&lt;/td&gt;
					&lt;td&gt;中等&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;中文 CLIP&lt;/td&gt;
					&lt;td&gt;多模态&lt;/td&gt;
					&lt;td&gt;512&lt;/td&gt;
					&lt;td&gt;较好&lt;/td&gt;
					&lt;td&gt;是&lt;/td&gt;
					&lt;td&gt;中等&lt;/td&gt;
					&lt;td&gt;本地&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;31-文本-embedding-选型&#34;&gt;3.1 文本 Embedding 选型&lt;/h3&gt;
&lt;p&gt;因为我们的查询主要是中文文案，最终选择了 &lt;strong&gt;BGE-M3&lt;/strong&gt;，原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;中文语义理解能力明显优于 OpenAI 的 Embedding&lt;/li&gt;
&lt;li&gt;支持本地部署，无调用成本&lt;/li&gt;
&lt;li&gt;向量维度 1024，检索速度和存储成本可接受&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;32-多模态-embedding-选型&#34;&gt;3.2 多模态 Embedding 选型&lt;/h3&gt;
&lt;p&gt;对于图片/视频素材，CLIP 系列可以把视觉内容和文本映射到同一空间。但实测发现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通用 CLIP 对中文概念的支持较弱&lt;/li&gt;
&lt;li&gt;视频场景下，关键帧的质量直接影响召回效果&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最终方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;关键帧用 &lt;strong&gt;中文 CLIP&lt;/strong&gt; 生成视觉 Embedding&lt;/li&gt;
&lt;li&gt;同时用图像描述模型生成一句话中文描述，再用 &lt;strong&gt;BGE-M3&lt;/strong&gt; 生成文本 Embedding&lt;/li&gt;
&lt;li&gt;向量库中同时存储两种 Embedding，查询时分别召回再融合&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四向量库设计&#34;&gt;四、向量库设计&lt;/h2&gt;
&lt;p&gt;我们选用 &lt;strong&gt;ChromaDB&lt;/strong&gt; 作为向量存储，主要考虑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;部署简单，适合小团队&lt;/li&gt;
&lt;li&gt;支持元数据过滤（按项目、类型、状态筛选）&lt;/li&gt;
&lt;li&gt;Python 生态集成好&lt;/li&gt;
&lt;li&gt;支持内存模式和持久化模式，开发方便&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;41-collection-划分&#34;&gt;4.1 Collection 划分&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 按素材类型分 Collection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;image_collection &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; chroma_client&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_or_create_collection(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;materials_image&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;video_collection &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; chroma_client&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_or_create_collection(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;materials_video&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;text_collection &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; chroma_client&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_or_create_collection(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;materials_text&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;分 Collection 的好处是查询更聚焦，也方便针对不同类型的素材调优。&lt;/p&gt;
&lt;h3 id=&#34;42-文档结构设计&#34;&gt;4.2 文档结构设计&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;documents &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;img_001&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;embedding&amp;#34;&lt;/span&gt;: image_embedding,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;metadata&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;image&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;project_id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;proj_123&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tags&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;海边&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;日落&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;风景&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;file_url&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;oss://ftmao/materials/img_001.jpg&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;width&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1920&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;height&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1080&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;active&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;usage_count&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;document&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;海边日落，宁静的风景画面&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# 用于可读性和调试&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;五召回调优实战&#34;&gt;五、召回调优实战&lt;/h2&gt;
&lt;h3 id=&#34;51-评测数据集构建&#34;&gt;5.1 评测数据集构建&lt;/h3&gt;
&lt;p&gt;召回调优的前提是有评测数据。我们标注了 200 条查询-素材对：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;query&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;人生低谷后重新出发&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;relevant&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;img_001&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;img_015&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;vid_008&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;irrelevant&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;img_099&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;vid_045&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;评测指标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Recall@K&lt;/strong&gt;：相关素材在 Top-K 中的占比&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Precision@K&lt;/strong&gt;：Top-K 中相关素材的占比&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MRR&lt;/strong&gt;：第一个相关素材的平均排名倒数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;52-embedding-调优&#34;&gt;5.2 Embedding 调优&lt;/h3&gt;
&lt;p&gt;通过对比实验，我们发现：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;方案&lt;/th&gt;
					&lt;th&gt;Recall@10&lt;/th&gt;
					&lt;th&gt;Precision@10&lt;/th&gt;
					&lt;th&gt;MRR&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;关键词标签&lt;/td&gt;
					&lt;td&gt;42%&lt;/td&gt;
					&lt;td&gt;35%&lt;/td&gt;
					&lt;td&gt;0.18&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;BGE-M3 文本 Embedding&lt;/td&gt;
					&lt;td&gt;68%&lt;/td&gt;
					&lt;td&gt;54%&lt;/td&gt;
					&lt;td&gt;0.31&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;BGE-M3 + 中文 CLIP 融合&lt;/td&gt;
					&lt;td&gt;79%&lt;/td&gt;
					&lt;td&gt;63%&lt;/td&gt;
					&lt;td&gt;0.40&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;+ 重排序模型&lt;/td&gt;
					&lt;td&gt;86%&lt;/td&gt;
					&lt;td&gt;71%&lt;/td&gt;
					&lt;td&gt;0.48&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;53-查询改写&#34;&gt;5.3 查询改写&lt;/h3&gt;
&lt;p&gt;用户的原始查询往往比较抽象，比如「做一个励志视频」。直接用这个查询做向量检索，召回结果会比较散。&lt;/p&gt;
&lt;p&gt;我们的做法是让 LLM 先对查询做改写和扩展：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;输入：做一个励志视频
输出：
- 核心语义：励志、奋斗、坚持不懈
- 扩展查询：逆境中成长、永不放弃、追逐梦想、突破自我
- 情绪基调：激昂、温暖、希望&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;然后用扩展后的多个查询分别检索，最后合并结果。这个策略让 Recall@10 提升了约 8%。&lt;/p&gt;
&lt;h3 id=&#34;54-重排序模型&#34;&gt;5.4 重排序模型&lt;/h3&gt;
&lt;p&gt;初筛召回 30 个候选后，用轻量级重排序模型做二次排序。我们用的是 &lt;strong&gt;bge-reranker-base&lt;/strong&gt;，原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模型小，CPU 推理足够快&lt;/li&gt;
&lt;li&gt;对中文语义匹配效果好&lt;/li&gt;
&lt;li&gt;可以 fine-tune 适应平台特定素材&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六工程化细节&#34;&gt;六、工程化细节&lt;/h2&gt;
&lt;h3 id=&#34;61-索引更新策略&#34;&gt;6.1 索引更新策略&lt;/h3&gt;
&lt;p&gt;素材库是动态变化的，每天都有新增、删除、状态变更。我们采用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;增量更新&lt;/strong&gt;：新增素材立即 Embedding 入库&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;批量补偿&lt;/strong&gt;：每天晚上全量校验一次，修复漏入/错入&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;软删除&lt;/strong&gt;：素材状态改为 inactive，不立即物理删除，便于恢复和审计&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;62-缓存与去重&#34;&gt;6.2 缓存与去重&lt;/h3&gt;
&lt;p&gt;热门查询的 Embedding 结果会被缓存到 Redis，缓存 key 是查询文本的 hash。同时 TTS 结果也按文案 hash 去重，避免重复调用。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_cached_or_compute&lt;/span&gt;(query: str):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rag:query:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;hashlib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;md5(query&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;encode())&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;hexdigest()&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cached &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; redis&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(key)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; cached:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;loads(cached)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; retrieve_materials(query)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    redis&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;setex(key, &lt;span style=&#34;color:#ae81ff&#34;&gt;3600&lt;/span&gt;, json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;dumps(result))  &lt;span style=&#34;color:#75715e&#34;&gt;# 缓存 1 小时&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;63-多项目隔离&#34;&gt;6.3 多项目隔离&lt;/h3&gt;
&lt;p&gt;同一个平台可能服务多个创作者或项目，不同项目的素材库需要隔离。我们通过 &lt;code&gt;project_id&lt;/code&gt; 元数据过滤实现：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;results &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; collection&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;query(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    query_embeddings&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;[embedding],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    n_results&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    where&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;project_id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;proj_123&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;active&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;七踩过的坑&#34;&gt;七、踩过的坑&lt;/h2&gt;
&lt;h3 id=&#34;71-向量维度不一致&#34;&gt;7.1 向量维度不一致&lt;/h3&gt;
&lt;p&gt;早期我们同时用了 OpenAI Embedding（1536 维）和 BGE-M3（1024 维），混在一个 Collection 里，结果查询时报维度错误。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：严格按模型维度分 Collection，每个 Collection 只存同一模型产出的向量。&lt;/p&gt;
&lt;h3 id=&#34;72-关键帧质量影响召回&#34;&gt;7.2 关键帧质量影响召回&lt;/h3&gt;
&lt;p&gt;视频素材如果关键帧选得不好（比如黑屏、转场瞬间），CLIP 生成的 Embedding 会偏离视频真实内容。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用场景检测算法选取稳定画面&lt;/li&gt;
&lt;li&gt;一个视频保存多个关键帧，查询时合并召回&lt;/li&gt;
&lt;li&gt;人工抽检低质量素材&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;73-查询过短导致语义漂移&#34;&gt;7.3 查询过短导致语义漂移&lt;/h3&gt;
&lt;p&gt;用户查询只有一个词，比如「奋斗」，召回结果可能包含大量不相关但语义宽泛的素材。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;短查询自动扩展为完整句子&lt;/li&gt;
&lt;li&gt;提高重排序模型的权重&lt;/li&gt;
&lt;li&gt;对短查询增加元数据过滤&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八当前效果与下一步&#34;&gt;八、当前效果与下一步&lt;/h2&gt;
&lt;p&gt;目前这套 RAG 素材库已经支撑方糖猫平台的核心素材检索能力，覆盖了图片、视频片段、文案片段三类素材。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;已验证的收益&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;素材召回质量明显高于关键词检索&lt;/li&gt;
&lt;li&gt;新素材无需人工打标签即可被检索到&lt;/li&gt;
&lt;li&gt;跨项目复用素材变得可行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;下一步计划&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;引入用户反馈数据，做在线学习优化&lt;/li&gt;
&lt;li&gt;针对视频内容做更细粒度的时序检索&lt;/li&gt;
&lt;li&gt;探索多模态大模型直接做端到端素材匹配&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九总结&#34;&gt;九、总结&lt;/h2&gt;
&lt;p&gt;RAG 不只是 LLM 应用的一个热门概念，对于内容生成类平台来说，它是&lt;strong&gt;连接用户意图和内容资产的关键基础设施&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;几个关键经验：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Embedding 选型要以业务语言为主&lt;/strong&gt;。中文场景不要迷信 OpenAI，BGE 系列性价比更高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一定要有评测数据集&lt;/strong&gt;。没有数据就没有优化方向。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;召回 pipeline 通常是多阶段的&lt;/strong&gt;：初筛 + 重排 + 业务规则过滤。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工程化比算法更重要&lt;/strong&gt;。索引更新、缓存、隔离、监控，这些决定系统能不能长期稳定运行。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;如果你也在做 RAG 相关的项目，欢迎交流 Embedding 选型和召回调优的经验。&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
