用 RAG 做项目文档问答:从零搭一个 Demo
目录
之前写过一篇「文档问答助手」的早期尝试,这次想更系统地从 RAG 角度,把整个过程讲清楚。
RAG(Retrieval-Augmented Generation)= 检索增强生成
简单说就是:先从你自己的文档里检索相关内容,再让大模型基于这些内容生成答案。
这篇文章以「项目文档问答」为例,从零搭一个小 Demo。
1. 目标:让大模型「读得懂」我们的项目文档
设定目标:
- 知识来源:代码仓库中的 README、设计文档、接口说明等;
- 使用方式:在一个 Web 界面里提问,例如:
- 「私募投资人小程序支持哪些登陆方式?」
- 「电子合同模块的签署流程有哪些关键步骤?」
- 期望回答:
- 基本准确;
- 回答中能指出「来自哪份文档」。
约束条件:
- 不追求「端到端生产可用」,先搭一个可演示、可验证的 Demo;
- 优先用云端 Embedding + 向量库,而不是自己训模型。
2. 文档准备与切分
2.1 文档来源
- Git 仓库中的 Markdown:
docs/目录、各项目 README; - 内部 Wiki 导出的 HTML/Markdown;
- 部分 API 文档(Swagger/OpenAPI 转文本)。
统一先转成 Markdown/纯文本,方便处理。
2.2 切分策略
RAG 很关键的一步是「chunking」,即把长文档切成小段。
我们采用的是:
- 按标题结构 + 字数混合切分;
- 每段控制在 300–600 字;
- 保留上级标题用于提供上下文。
示例结构:
{
"docId": "fund-investor-miniapp",
"docTitle": "信e募投资人小程序技术方案",
"sectionPath": ["2. 技术架构", "2.1 前端技术栈"],
"content": "前端采用 Vue3 + uni-app...",
"index": 12
}
经验:
- 纯按字数硬切容易把一个完整概念拆散;
- 适当利用 Markdown 标题结构,会让回答更连贯。
3. 向量化:Embedding 与向量库
3.1 生成 Embedding
选一个支持中文的 Embedding API:
const embedding = await embeddingApi.embed({
input: chunk.content,
});
得到一串向量(例如 1536 维)。
我们把每个 chunk 存成:
{
"id": "fund-investor-miniapp#12",
"vector": [0.12, -0.03, ...],
"metadata": {
"docId": "fund-investor-miniapp",
"docTitle": "信e募投资人小程序技术方案",
"sectionPath": ["2. 技术架构", "2.1 前端技术栈"],
"content": "前端采用 Vue3 + uni-app..."
}
}
3.2 向量库选择
Demo 阶段可以很简单:
- 用支持向量字段的 Postgres/SQLite 扩展;
- 或者用专门的向量数据库(如 Milvus、Pinecone 等);
- 实在简单一点也可以内存向量搜索(适合小数据集)。
关键是支持:
给定一个向量,按余弦相似度/点积,返回 Top K 相似文本。
4. 问答流程:从问题到答案
用户在前端问:
「电子合同的签署流程有哪些关键步骤?」
流程大致是:
- 把问题本身做 Embedding;
- 在向量库中检索 Top K 文档片段;
- 组合这些片段 + 原问题,构造大模型 Prompt;
- 得到回答后返回给前端,同时附上引用片段。
4.1 检索相关片段
const qEmbedding = await embeddingApi.embed({ input: question });
const chunks = await vectorStore.search(qEmbedding, {
topK: 5,
filter: { project: 'private-fund-platform' }, // 可选过滤条件
});
得到的 chunks 包含内容 + 相似度。
4.2 构造 Prompt
你是项目文档助手,只能根据给定的文档内容回答问题。
如果文档中没有相关信息,请明确回答「文档中没有找到相关说明」。
【文档片段】
[1] 来自《电子合同模块设计》 - 小节「签署流程概览」:
...
[2] 来自《电子合同模块设计》 - 小节「签署方与签署顺序」:
...
【问题】
电子合同的签署流程有哪些关键步骤?
请使用简洁的中文回答,并在合适时标注引用的文档片段编号。
4.3 渲染回答
例如模型返回:
电子合同签署流程主要包含以下步骤:
- 合同模版选择与变量渲染;
- 根据业务方配置生成合同实例;
- 按签署顺序依次通知各签署方完成签名/盖章;
- 所有签署方完成后生成最终版合同,并推送回业务系统。
(参考文档片段 [1][2])
前端展示时:
- 上方显示这段回答;
- 下方列出 [1][2] 对应的原文片段,方便用户点击查看。
5. 前端界面:简单但好用就行
页面布局可以很简单:
- 左侧:问答对话区;
- 右侧:文档列表/知识库切换;
- 回答下方:引用片段列表。
交互小细节:
- 支持「引用片段展开」,方便查看上下文;
- 显示当前检索匹配的项目/文档范围;
- 当模型明确「找不到答案」时,要如实展示。
6. 一些工程化与产品化思考
6.1 权限控制
项目文档往往有访问控制:
- 某些项目只对特定团队开放;
- 某些文档包含敏感信息。
在检索层就要做权限过滤:
- 按用户 ID / 角色过滤可见的 docId;
- 只在这些文档范围内做向量检索。
6.2 文档更新与向量同步
文档会变化,向量库要跟上:
- 对接文档存储的变更 webhook(如 Git commit hook);
- 文档更新时重新切分 → 重新 Embedding → 更新向量库;
- 旧版本保留或删除视情况而定。
6.3 错误与「幻觉」处理
- 对置信度较低(相似度较低)的检索结果,干脆返回「文档中未明确说明」;
- 对某些敏感领域(合规/法律/风控)建议在回答中加免责声明。
7. 实战经验 & 坑
-
切分真的很重要
- 一开始切得太粗,导致命中片段包含大量无关信息,回答容易跑偏;
- 后来改成「标题 + 段落」混合切分,效果明显好很多。
-
Embedding 模型的选择有影响,但不是唯一关键
- 换更强的模型会有提升,但如果切分/检索逻辑不好,收益有限;
- 可以先用中等水平 Embedding,重点打磨流程。
-
文档质量决定上限
- 文档过时、内容模糊,RAG 也无能为力;
- 反而可以用 RAG 的表现倒推:哪些地方需要补文档。
8. 小结
用 RAG 做项目文档问答,从整体上看并不复杂:
- 准备文档 → 清洗 & 切分;
- 用 Embedding 转成向量,存入向量库;
- 提问时先检索,再让大模型基于检索结果生成回答;
- 前端界面展示答案 + 引用片段;
- 在权限、文档更新、质量评估方面逐步打磨。
它的价值不在于「多高级的模型」,而在于:
让团队的现有文档真正「活起来」,被更多人、以更自然的方式使用。