---
createdAt: 2025-09-10
updatedAt: 2025-09-10
title: 构建一个基于RAG的文档助手（分块、嵌入和搜索）
description: 构建一个基于RAG的文档助手（分块、嵌入和搜索）
keywords:
  - RAG
  - 文档
  - 助手
  - 分块
  - 嵌入
  - 搜索
slugs:
  - blog
  - rag-powered-documentation-assistant
---

# 构建一个基于RAG的文档助手（分块、嵌入和搜索）

## 你将获得什么

我构建了一个基于RAG的文档助手，并将其打包成一个你可以立即使用的模板。

- 附带一个可直接使用的应用程序（Next.js + OpenAI API）
- 包含一个可用的RAG流程（分块、嵌入、余弦相似度）
- 提供一个用React构建的完整聊天机器人UI
- 所有UI组件均可使用Tailwind CSS完全编辑
- 记录每个用户查询，帮助识别缺失的文档、用户痛点和产品机会

👉 [在线演示](https://intlayer.org/doc/why) 👉 [代码模板](https://github.com/aymericzip/smart_doc_RAG)

## 介绍

如果你曾在文档中迷失，滚动寻找一个答案，你就知道这有多痛苦。文档是有用的，但它们是静态的，搜索起来常常感觉笨拙。

这就是**RAG（检索增强生成）**的用武之地。我们不必强迫用户去翻找文本，而是结合**检索**（找到文档中正确的部分）和**生成**（让大型语言模型自然地解释它）。

在这篇文章中，我将带你了解我是如何构建一个基于RAG的文档聊天机器人的，以及它不仅帮助用户更快地找到答案，还为产品团队提供了一种全新的方式来理解用户痛点。

## 为什么为文档使用RAG？

RAG之所以成为流行的方法，是有原因的：它是让大型语言模型真正发挥作用的最实用方式之一。

对于文档来说，优势非常明显：

- 即时回答：用户用自然语言提问，得到相关的回复。
- 更好的上下文：模型只看到最相关的文档部分，减少了幻觉现象。
- 更人性化的搜索：更像是Algolia + FAQ + 聊天机器人三合一。
- 反馈循环：通过存储查询，你可以发现用户真正遇到的困难。

最后一点至关重要。RAG 系统不仅仅是回答问题，它还能告诉你人们在问什么。这意味着：

- 你会发现文档中缺失的信息。
- 你会看到新功能请求的出现。
- 你会发现一些模式，甚至可以指导产品策略。

所以，RAG 不仅仅是一个支持工具，它还是一个**产品发现引擎**。

## RAG 流程是如何工作的

![RAG Pipeline](https://github.com/aymericzip/intlayer/blob/main/docs/assets/rag_flow.svg)

从高层次来看，我使用的流程如下：

1. **文档分块** 大型 Markdown 文件被拆分成多个块。分块可以让上下文只包含文档中相关的部分。
2. **生成嵌入向量** 每个块通过 OpenAI 的嵌入 API（text-embedding-3-large）或向量数据库（Chroma、Qdrant、Pinecone）转换成向量。
3. **索引与存储** 嵌入向量被存储在一个简单的 JSON 文件中（用于我的演示），但在生产环境中，你可能会使用向量数据库。
4. **检索（RAG 中的 R）** 用户查询被转换为嵌入向量，计算余弦相似度，并检索最匹配的文本块。
5. **增强与生成（RAG 中的 AG）** 这些文本块被注入到 ChatGPT 的提示中，使模型能够基于实际文档上下文进行回答。
6. **查询日志反馈** 每个用户查询都会被存储。这对于理解痛点、缺失文档或新机会非常宝贵。

## 第一步：读取文档

第一步很简单：我需要一种方法扫描 docs/ 文件夹中的所有 .md 文件。使用 Node.js 和 glob，我将每个 Markdown 文件的内容加载到内存中。

这保持了流程的灵活性：你可以不是从 Markdown，而是从数据库、CMS，甚至 API 中获取文档。

## 第2步：将文档分块

为什么要分块？因为语言模型有**上下文限制**。给它们整本文档是行不通的。

所以思路是将文本拆分成可管理的块（例如每块500个token），并且有重叠部分（例如100个token）。重叠确保连续性，这样你不会在块的边界处丢失意义。

**示例：**

- 块1 → “…那座许多人已遗忘的旧图书馆。它高耸的书架上摆满了书籍…”
- 块2 → “…书架上摆满了各种想象得到的书籍，每本都在低语故事…”

重叠确保两个块包含共享的上下文，因此检索保持连贯。

这种权衡（块大小与重叠）是 RAG 效率的关键：

- 太小 → 会产生噪音。
- 太大 → 会导致上下文大小爆炸。

## 第三步：生成嵌入向量

文档被分块后，我们生成**嵌入向量**——表示每个块的高维向量。

我使用了 OpenAI 的 text-embedding-3-large 模型，但你也可以使用任何现代嵌入模型。

**嵌入示例：**

```js
[
  -0.0002630692, -0.029749284, 0.010225477, -0.009224428, -0.0065269712,
  -0.002665544, 0.003214777, 0.04235309, -0.033162255, -0.00080789323,
  //...+1533 个元素
];
```

每个向量都是文本的数学指纹，支持相似度搜索。

## 第四步：索引与存储嵌入向量

为了避免多次重新生成嵌入，我将它们存储在 embeddings.json 中。

在生产环境中，你可能会想使用以下向量数据库：

- Chroma
- Qdrant
- Pinecone
- FAISS、Weaviate、Milvus 等等。

向量数据库负责索引、可扩展性和快速搜索。但对于我的原型，使用本地 JSON 就足够了。

## 第五步：使用余弦相似度进行检索

当用户提出问题时：

1. 为查询生成一个嵌入向量。
2. 使用**余弦相似度**将其与所有文档嵌入向量进行比较。
3. 只保留最相似的前 N 个块。

余弦相似度衡量两个向量之间的夹角。完美匹配的得分为**1.0**。

通过这种方式，系统能够找到与查询最接近的文档段落。

## 第六步：增强 + 生成

现在进入关键步骤。我们将最相关的块注入到 ChatGPT 的**系统提示**中。

这意味着模型的回答就像那些文档片段是对话的一部分一样。

结果是：准确的、**基于文档的响应**。

## 第7步：记录用户查询

这是隐藏的超级能力。

每个提问都会被存储。随着时间推移，你会构建一个数据集，包含：

- 最常见的问题（非常适合常见问题解答）
- 未回答的问题（文档缺失或不清晰）
- 伪装成问题的功能请求（“它能与X集成吗？”）
- 你未曾预料到的新兴用例

这将你的RAG助手变成一个**持续的用户研究工具**。

## 成本是多少？

对RAG的一个常见反对意见是成本。实际上，它出乎意料地便宜：

- 为大约200个文档生成嵌入大约需要**5分钟**，成本为**1–2欧元**。
- 文档搜索功能是100%免费的。
- 对于查询，我们使用 gpt-4o-latest，不启用“思考”模式。在 Intlayer 上，我们每月大约处理 **300 次聊天查询**，OpenAI API 账单很少超过 **10 美元**。

除此之外，你还需要考虑托管成本。

## 实现细节

技术栈：

- Monorepo：pnpm 工作区
- 文档包：Node.js / TypeScript / OpenAI API
- 前端：Next.js / React / Tailwind CSS
- 后端：Node.js API 路由 / OpenAI API

`@smart-doc/docs` 包是一个 TypeScript 包，负责文档处理。当添加或修改 markdown 文件时，该包包含一个 `build` 脚本，用于重新构建每种语言的文档列表，生成嵌入向量，并将其存储在 `embeddings.json` 文件中。

在前端，我们使用一个 Next.js 应用程序，提供：

- Markdown 转换为 HTML 渲染
- 一个用于查找相关文档的搜索栏
- 一个用于提问文档问题的聊天机器人界面

为了执行文档搜索，Next.js 应用程序包含一个 API 路由，该路由调用 `@smart-doc/docs` 包中的函数以检索与查询匹配的文档块。利用这些文档块，我们可以返回与用户搜索相关的文档页面列表。

对于聊天机器人功能，我们遵循相同的搜索流程，但额外将检索到的文档块注入到发送给 ChatGPT 的提示中。

以下是发送给 ChatGPT 的提示示例：

系统提示：

```txt
你是一个能够回答关于 Intlayer 文档问题的有用助手。

相关文档块：

-----
docName: "getting-started"
docChunk: "1/3"
docUrl: "https://example.com/docs/zh/getting-started"
---

# 如何开始

...

-----
docName: "another-doc"
docChunk: "1/5"
docUrl: "https://example.com/docs/zh/another-doc"
---

# 另一个文档

...
```

用户查询：

```txt
如何开始？
```

我们使用 SSE 来流式传输来自 API 路由的响应。

如前所述，我们使用无“思考”模式的 gpt-4-turbo。响应相关且延迟低。
我们尝试过 gpt-5，但延迟过高（有时回复需要长达 15 秒）。但我们将来会重新考虑。

👉 [在这里试用演示](https://intlayer.org/doc/zh/why) 👉 [在 GitHub 查看代码模板](https://github.com/aymericzip/smart_doc_RAG)

## 进一步探索

该项目是一个最小实现。但你可以通过多种方式扩展它：

- MCP 服务器 → 将文档检索功能迁移到 MCP 服务器，以便将文档连接到任何 AI 助手

- 向量数据库 → 扩展到数百万文档块
- LangChain / LlamaIndex → 现成的 RAG 流水线框架
- 分析仪表盘 → 可视化用户查询和痛点
- 多源检索 → 不仅检索文档，还能拉取数据库条目、博客文章、工单等
- 改进的提示技术 → 重新排序、过滤和混合搜索（关键词 + 语义）

## 我们遇到的限制

- 分块和重叠是经验性的。正确的平衡（分块大小、重叠比例、检索块数量）需要反复迭代和测试。
- 当文档发生变化时，嵌入不会自动重新生成。我们的系统仅在文件的分块数量与存储的不同情况下才会重置该文件的嵌入。
- 在这个原型中，嵌入向量存储在 JSON 中。这适用于演示，但会污染 Git。在生产环境中，使用数据库或专用向量存储更为合适。

## 这不仅仅是文档的重要性

有趣的部分不仅仅是聊天机器人，而是**反馈循环**。

通过 RAG，你不仅仅是在回答问题：

- 你了解用户的困惑所在。
- 你发现他们期望的功能。
- 你根据真实查询调整产品策略。

**示例：**

想象一下发布一个新功能后，立即看到：

- 50% 的问题都集中在同一个不清晰的设置步骤上
- 用户反复请求你尚未支持的集成
- 人们搜索的词汇揭示了一个新的使用场景

这就是直接来自用户的**产品智能**。

## 结论

RAG 是使大型语言模型（LLM）实用的最简单且最强大的方法之一。通过结合**检索 + 生成**，您可以将静态文档转变为**智能助手**，同时获得持续的产品洞察流。

对我来说，这个项目表明 RAG 不仅仅是一个技术技巧。它是一种将文档转变为：

- 交互式支持系统
- 反馈渠道
- 产品策略工具

👉 [在此试用演示](https://intlayer.org/doc/why) 👉 [在 GitHub 查看代码模板](https://github.com/aymericzip/smart_doc_RAG)

如果您也在尝试使用 RAG，我很想了解您是如何使用它的。
