代码语言

知识点思维导图

33 个知识节点

Embedding(02) - 把文档向量化:基于向量实现语义搜索

读完后,你应能完成以下任务:

  • 绘制“RAG(11) - RAG:把文档向量化,基于向量实现真正的语义搜索 / 本篇定位”的关键对象与数据流,解释“这是 RAG 进阶线的入口。”,并用源码位置、日志或 Trace 标注证据。
  • 为“RAG(11) - RAG:把文档向量化,基于向量实现真正的语义搜索 / 核心拆解”设计正常与异常输入,验证“向量化不是为了让模型直接读数字,而是为了把语义相近的文本放到向量空间里相近的位置。”,输出首个偏差位置与回归测试结果。
  • 实现“RAG(11) - RAG:把文档向量化,基于向量实现真正的语义搜索 / 工程链路”的最小代码或配置,检验“为每个 chunk 生成 embedding。”,输出命令、结果与 Diff,并说明不适用边界。

一、把文档向量化的学习定位与边界

这是 RAG 进阶线的入口。20-26 已经讲过基础流程,这里开始把每一步拆到能落地调优的层级。

二、把文档向量化的真实应用场景

用户问“报销多久内提交”,文档写的是“费用产生后三十日内完成报支”。关键词检索可能搜不到,向量检索能通过语义相似把它召回。RAG 的第一步,就是把文档和问题都变成可比较的向量。

三、把文档向量化的核心对象与机制

  • 向量化不是为了让模型直接读数字,而是为了把语义相近的文本放到向量空间里相近的位置。
  • 入库时要保存三件东西:chunk 文本、embedding 向量、metadata。metadata 负责来源、章节、权限和时间。
  • 在线检索时,用户问题也要 embedding,再用余弦相似度或内积找 topK。topK 不是越大越好,太大会引入噪声。

四、把文档向量化的工程链路

  • 解析文档。
  • 按语义切 chunk。
  • 为每个 chunk 生成 embedding。
  • 向量和 metadata 一起写入库。
  • 提问时对 query 向量化。
  • 检索 topK 并拼进 prompt。

五、把文档向量化的落地建议

  • 先用一小批真实问题测试召回,再扩大知识库。
  • 每个 chunk 都保留 source、page、section、permission。
  • 记录检索分数,后面才能调阈值和坏 case。

六、把文档向量化的常见故障与误区

  • 只存向量不存原文。
  • 只看相似度最高的一条,不看 topK 里是否有噪声。
  • 换 embedding 模型后不重建索引,导致向量空间不一致。

七、把文档向量化在学习路线中的位置

20 是 RAG 总览,22 是 embedding 基础;58 把两者合成一个真实的向量化检索链路。

八、把文档向量化的核心结论

RAG 的语义搜索链路是:文档切块、每块生成 embedding、带 metadata 存入向量库;提问时 query 也生成 embedding,检索相似 chunk,再让模型基于证据回答。关键不是“用了向量库”,而是 chunk、metadata、topK 和阈值都可调可评测。

九、总结

  • 核心拆解:向量化不是为了让模型直接读数字,而是为了把语义相近的文本放到向量空间里相近的位置。
  • 常见坑:只看相似度最高的一条,不看 topK 里是否有噪声。
  • 和已有主线的关系:20 是 RAG 总览,22 是 embedding 基础;
  • 复述答法:RAG 的语义搜索链路是:文档切块、每块生成 embedding、带 metadata 存入向量库;
  • 实现源码与运行边界:其余文件保留真实本地项目结构,用于理解接口、部署和测试。

9.1 实现源码与运行边界

下方 sandbox.html 可直接在文章中运行;其余文件保留真实本地项目结构,用于理解接口、部署和测试。

sandbox.html

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Embedding Top-K</title>
    <style>
      :root {
        color-scheme: dark;
        font-family: Inter, system-ui, sans-serif;
        background: #0e1110;
        color: #e4e4e7;
      }

      * {
        box-sizing: border-box;
      }

      body {
        margin: 0;
        padding: 18px;
      }

      label,
      input,
      button {
        font: inherit;
      }

      label {
        display: grid;
        gap: 8px;
        color: #a1a1aa;
        font-size: 12px;
      }

      .controls {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 8px;
      }

      input,
      button {
        min-height: 38px;
        border: 1px solid #3f3f46;
        border-radius: 6px;
      }

      input {
        min-width: 0;
        padding: 0 12px;
        background: #181b1a;
        color: inherit;
      }

      button {
        padding: 0 16px;
        background: #78e2bf;
        color: #10221c;
        font-weight: 700;
        cursor: pointer;
      }

      button:disabled {
        cursor: wait;
        opacity: 0.65;
      }

      #status {
        min-height: 20px;
        margin: 14px 0 10px;
        color: #a1a1aa;
        font-size: 12px;
      }

      ol {
        display: grid;
        gap: 8px;
        margin: 0;
        padding: 0;
        list-style: none;
      }

      li {
        display: grid;
        grid-template-columns: 2rem minmax(0, 1fr) auto;
        gap: 10px;
        align-items: start;
        padding: 12px;
        border: 1px solid #2f3331;
        background: #151817;
      }

      .rank,
      .score {
        font-family: ui-monospace, monospace;
        color: #78e2bf;
        font-size: 12px;
      }

      .document-id {
        margin-bottom: 5px;
        font-family: ui-monospace, monospace;
        font-size: 11px;
        color: #a1a1aa;
      }

      .document-text {
        line-height: 1.6;
        font-size: 13px;
      }
    </style>
  </head>
  <body>
    <label>
      查询文本
      <span class="controls">
        <input id="query" value="退款多久能到账" />
        <button id="search" type="button">运行 Top-K</button>
      </span>
    </label>
    <p id="status">首次运行会加载同源部署的 bge-small-zh-v1.5 量化模型。</p>
    <ol id="results"></ol>

    <script type="module">
      import { env, pipeline } from '/vendor/transformers/transformers.min.js'

      /** 强制使用站点同源的模型与 WASM 资产,避免运行时依赖第三方 CDN。 */
      env.allowRemoteModels = false
      env.allowLocalModels = true
      env.useBrowserCache = false
      env.localModelPath = '/models/'
      env.backends.onnx.wasm.wasmPaths = '/vendor/transformers/'

      /** 实验中待召回的候选文档。 */
      const documents = [
        { id: 'refund#1', text: '退款审核通过后三个工作日内原路退回。' },
        { id: 'shipping#1', text: '订单发货后可在物流页面查看进度。' },
        { id: 'invoice#1', text: '电子发票开具后会发送到下单邮箱。' }
      ]
      /** 避免每次查询重复初始化的特征提取 Pipeline。 */
      let extractorPromise

      /** 对查询和文档生成 512 维语义向量并渲染 Top-K。 */
      async function runSearch() {
        /** 用户当前需要检索的查询。 */
        const query = document.querySelector('#query').value.trim()
        /** 运行按钮用于防止重复提交。 */
        const searchButton = document.querySelector('#search')
        /** 当前模型下载或计算状态。 */
        const status = document.querySelector('#status')
        /** Top-K 结果列表容器。 */
        const results = document.querySelector('#results')

        if (!query) {
          status.textContent = '请先输入查询文本。'
          return
        }

        searchButton.disabled = true
        results.replaceChildren()
        status.textContent = '正在加载模型并生成 Embedding……'

        try {
          extractorPromise ||= pipeline('feature-extraction', 'bge-small-zh-v1.5', {
            dtype: 'q8', // 使用仓库内 24 MB 量化模型,降低首次加载成本。
            device: 'wasm' // 优先保证浏览器覆盖率,不强制要求 WebGPU。
          })
          /** 已加载的中文 Embedding 特征提取器。 */
          const extractor = await extractorPromise
          /** 查询和候选文档的归一化 512 维向量。 */
          const embeddings = await extractor([query, ...documents.map((document) => document.text)], {
            pooling: 'cls', // bge 模型使用 CLS 向量作为句子表示。
            normalize: true // 归一化后点积即余弦相似度。
          })
          /** 按输入顺序切分后的向量数组。 */
          const vectors = embeddings.tolist()
          /** 当前查询的语义向量。 */
          const queryVector = vectors[0]
          /** 按余弦相似度从高到低排序的候选。 */
          const rankedDocuments = documents
            .map((document, index) => ({
              ...document,
              score: queryVector.reduce(
                (similarity, value, dimensionIndex) => similarity + value * vectors[index + 1][dimensionIndex],
                0
              )
            }))
            .sort((left, right) => right.score - left.score)
            .slice(0, 2)

          rankedDocuments.forEach((rankedDocument, index) => {
            /** 单条召回结果的语义容器。 */
            const item = document.createElement('li')
            /** 单条结果的排名序号。 */
            const rank = document.createElement('span')
            /** 文档信息的布局容器。 */
            const documentSummary = document.createElement('span')
            /** 被召回文档的稳定标识。 */
            const documentId = document.createElement('div')
            /** 被召回文档的正文。 */
            const documentText = document.createElement('div')
            /** 查询与文档的余弦相似度。 */
            const score = document.createElement('span')

            rank.className = 'rank'
            rank.textContent = `#${index + 1}`
            documentId.className = 'document-id'
            documentId.textContent = rankedDocument.id
            documentText.className = 'document-text'
            documentText.textContent = rankedDocument.text
            score.className = 'score'
            score.textContent = rankedDocument.score.toFixed(4)
            documentSummary.append(documentId, documentText)
            item.append(rank, documentSummary, score)
            results.append(item)
          })
          status.textContent = `已使用 ${queryVector.length} 维真实 Embedding 完成 Top-2。`
        } catch (error) {
          status.textContent = `模型加载或向量计算失败:${error instanceof Error ? error.message : String(error)}`
        } finally {
          searchButton.disabled = false
        }
      }

      document.querySelector('#search').addEventListener('click', runSearch)
    </script>
  </body>
</html>

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“把文档向量化:基于向量实现语义搜索”中,需要同时满足“把文档向量化的学习定位与边界”与“把文档向量化的真实应用场景”。给定正文约束“20-26 已经讲过基础流程,这里开始把每一步拆到能落地调优的层级。”,哪些判断保持了原有处理机制?多选
2“把文档向量化:基于向量实现语义搜索”出现偏差:“在“把文档向量化:基于向量实现语义搜索 / 把文档向量化的核心对象与机制”中,即使不满足“向量化不是为了让模型直接读数字,而是为了把语义相近的文本放到向量空间里相近的位置”,结果与副作用仍会保持不变。”已成为实际行为。围绕“把文档向量化的核心对象与机制”与“把文档向量化的工程链路”,哪些判断能定位被改变的职责或边界?多选
3评审“把文档向量化:基于向量实现语义搜索”方案时,验收条件包含“每个 chunk 都保留 source、page、section、permission。”。关于“把文档向量化的落地建议”与“把文档向量化的常见故障与误区”的哪些决策符合正文机制?多选