RAG 的知识库摄取与 Chunking 怎么做:从原始文档到可检索证据单元

系列:生产级 LLM 应用方法论 06
日期:2026-06-20
适合读者:正在建设企业知识库、文档问答、客服助手、合规问答、研究助理或内部 Agent 的工程师、产品负责人和技术负责人。
摘要
第五篇讲了 RAG 的引用与归因:答案里的关键结论必须能追溯到证据。
这一篇回到更底层的问题:这些证据单元从哪里来?
很多 RAG 项目失败,不是因为模型不够强,也不是因为向量数据库不好,而是知识库摄取阶段已经把材料切坏了。PDF 页眉页脚混进正文,表格行列关系丢失,旧版本文档没有废止,权限信息没有进入 metadata,chunk 太长导致召回不准,chunk 太短导致上下文断裂。后面再怎么调 prompt、reranker 和引用格式,都只能在坏证据上修补。
所以知识库摄取不是“把文件上传到向量库”。它是一条生产流水线:盘点来源、解析文档、规范化文本、保留结构、切分证据、补元数据、去重和版本管理、写入索引、跑检索评测、持续回流失败样本。
本文给出一套可落地的摄取与 chunking 方法,包含证据单元 JSONL 契约、chunk 策略选择、参数建议、表格和代码处理、权限和版本字段、最小结构化 chunker 示例,以及上线前自检清单。
目录
- 1. 为什么摄取质量决定 RAG 上限
- 2. 摄取流水线的 9 个阶段
- 3. 证据单元应该长什么样
- 4. Chunking 不是固定长度切文本
- 5. 常见 chunk 策略怎么选
- 6. Chunk 大小和 overlap 怎么定
- 7. 不同文档类型怎么处理
- 8. Metadata 是检索质量的一半
- 9. 去重、版本和增量更新
- 10. 一个最小结构化 chunker 示例
- 11. 如何评测 chunking 是否有效
- 12. 生产落地架构
- 13. 常见误区
- 14. 落地路线图
- 15. 发布前自检清单
- 16. 总结
- 参考资料
1. 为什么摄取质量决定 RAG 上限
RAG 的后半段看起来更“智能”:检索、重排、生成、引用校验。但真正决定系统上限的,经常是最前面的摄取。
如果摄取阶段把文档处理成这样:
退款政策
页脚:内部资料,仅供参考
表 2
人工审核 跨境支付 大额退款
1-5 个工作日
页眉:退款政策
模型后面很难稳定回答:
跨境订单是否需要人工审核?退款多久到账?
因为原始关系已经丢了。表格的行列、标题层级、版本、页码、权限、更新时间都没有进入证据单元。
RAG 摄取质量差,会带来六类问题。
| 问题 | 表现 | 根因 |
|---|---|---|
| 召回不到 | 用户问对了,但正确内容不在 top-k | 文档没入库、切分断裂、标题丢失 |
| 召回太泛 | top-k 都相关但不精确 | chunk 太长、噪声多、metadata 缺失 |
| 答案断章取义 | 只引用一句话,漏了例外条件 | chunk 太短、上下文断裂 |
| 旧版本覆盖新版本 | 回答用过期政策 | 版本字段和废止状态缺失 |
| 权限泄漏 | 召回用户无权访问内容 | access scope 没进检索过滤 |
| 引用无法核查 | 用户点开来源找不到原文 | locator、page、span、source hash 缺失 |
所以摄取流水线的目标不是“生成 embedding”,而是生成可检索、可引用、可过滤、可审计的证据单元。
2. 摄取流水线的 9 个阶段
一条可靠的知识库摄取流水线可以拆成 9 个阶段。

| 阶段 | 目标 | 产物 |
|---|---|---|
| 1. Source Inventory | 盘点知识来源 | source registry |
| 2. Permission Mapping | 建立权限和租户边界 | access policy |
| 3. Parsing | 从 PDF/HTML/Docx/表格中抽取内容 | parsed blocks |
| 4. Normalization | 清洗页眉页脚、乱码、重复空白 | normalized text |
| 5. Structure Recovery | 保留标题、页码、表格、列表、代码块 | structured document |
| 6. Segmentation | 切成可检索证据单元 | evidence chunks |
| 7. Metadata Enrichment | 添加版本、权限、来源、hash、locator | chunk metadata |
| 8. Indexing | embedding、稀疏索引、向量库写入 | vector/search index |
| 9. Evaluation Loop | 用 gold set 检查召回和引用质量 | eval report |
很多团队跳过前 5 步,直接把文档丢进默认 chunker。这在 demo 里可以工作,但在企业知识库里会很快遇到边界:
- PDF 的两栏排版被读错;
- 表格被拉平成无意义文本;
- 页眉页脚重复污染 embedding;
- 同一政策多个版本同时命中;
- 文档标题和段落内容分离;
- 权限只能在回答后提醒模型,而不能在检索前过滤。
摄取阶段越认真,后面的检索和归因越简单。
3. 证据单元应该长什么样
上一篇讲引用与归因时提到 Evidence Span。这一篇把它展开成一个可写入索引的 evidence_chunk。
推荐每个 chunk 至少包含这些字段:
{
"chunk_id": "refund_policy_2026_03#sec_4#chunk_02",
"source_id": "refund_policy_2026_03",
"source_title": "退款政策 2026-03 版",
"source_type": "policy_doc",
"version": "2026-03",
"status": "active",
"updated_at": "2026-03-18",
"owner": "finance_ops",
"tenant_id": "global",
"access_scope": ["customer_service", "finance"],
"language": "zh-CN",
"hierarchy_path": ["退款政策", "4. 跨境退款", "4.2 人工审核"],
"locator": {
"page": 4,
"section": "4.2",
"start_char": 1832,
"end_char": 2150
},
"text": "跨境支付订单需进入人工审核。审核完成后,退款通常在 1-5 个工作日内退回原支付渠道。",
"context_prefix": "退款政策 > 跨境退款 > 人工审核",
"content_hash": "sha256:...",
"source_hash": "sha256:...",
"ingested_at": "2026-06-20T22:18:24+08:00"
}
这里有几个关键点。
3.1 text 不是全部
向量索引可能主要使用 text,但系统不能只保存 text。没有 source_id、version、access_scope、locator,后续就做不了权限过滤、版本控制和引用高亮。
3.2 context_prefix 很重要
很多 chunk 本身只有一句话:
需进入人工审核。
如果不带标题路径,它的语义很弱。可以在索引文本里加入轻量上下文:
退款政策 > 跨境退款 > 人工审核
需进入人工审核。
这不是给用户看的原文,而是帮助检索理解上下文。展示时仍然应该回到原文 span。
3.3 content_hash 和 source_hash 用来做增量更新
知识库会变。没有 hash,你很难判断:
- 文档是否真的变了;
- 哪些 chunk 需要重算 embedding;
- 旧 chunk 是否应废止;
- 线上事故发生时用的是哪个版本。
4. Chunking 不是固定长度切文本
最常见的错误是把 chunking 理解成:
每 800 tokens 切一段,重叠 200 tokens。
固定长度切分有价值,尤其适合纯文本和快速基线。但它不是生产 RAG 的全部。
好的 chunk 应该满足五个条件:
| 条件 | 说明 |
|---|---|
| 语义完整 | 一个 chunk 能表达一个相对完整的事实或步骤 |
| 可召回 | 用户问题里的词或语义能命中它 |
| 可引用 | 用户点开后能理解它支持什么 |
| 可过滤 | 带权限、版本、来源和时间 metadata |
| 可组合 | 多个 chunk 能组成完整答案,不互相冲突 |
如果 chunk 太大,检索会泛:
整章退款政策都被召回,但答案只需要跨境退款一条规则。
如果 chunk 太小,证据会断:
chunk A: 跨境支付订单需进入人工审核。
chunk B: 审核完成后按原支付渠道退款。
chunk C: 退款通常在 1-5 个工作日内完成。
模型可能只看到 A,漏掉 B 和 C。
所以 chunking 的本质是证据建模,而不是字符串切片。
5. 常见 chunk 策略怎么选
5.1 固定 token chunk
按 token 数切分,并设置 overlap。
适合:
- 快速基线;
- 结构简单的纯文本;
- 大量 FAQ;
- 对引用粒度要求不高的内部搜索。
优点是简单稳定。缺点是容易切断标题、表格、步骤和例外条件。
5.2 结构化 chunk
按标题、段落、列表、表格、代码块切分。
适合:
- 政策文档;
- 产品手册;
- 合同;
- API 文档;
- 研究报告;
- 标准操作流程。
这是企业 RAG 最常用的生产策略。它会保留文档结构:
H1: 退款政策
H2: 跨境退款
H3: 人工审核
Paragraph: 跨境支付订单需进入人工审核...
5.3 语义 chunk
按语义边界切分,例如话题变化、段落相似度、句群聚类。
适合:
- 长报告;
- 访谈记录;
- 会议纪要;
- 没有清晰标题的文本。
风险是实现更复杂,而且边界不一定稳定。生产系统里最好保留原始 locator,避免语义切分后无法回到原文。
5.4 Parent-child chunk
索引小 chunk,展示或生成时带 parent context。
例子:
- child chunk: 一个条款;
- parent chunk: 所在 section;
- source: 整份合同。
适合需要精确召回但又怕上下文断裂的场景。检索时用 child 命中,生成时把 parent 或相邻 chunk 一起带入。
5.5 Hierarchical chunk
把文档构造成多层:
文档摘要
章节摘要
段落 chunk
原文 span
RAPTOR 这类方法把文本组织成树状摘要和检索结构,适合长文档、多跳问题和总结型问题。工程上可以先做简化版:文档级摘要、section 摘要、原文 chunk 三层。
5.6 Late chunking
常规做法是先切 chunk,再分别 embedding。Late chunking 的思路是先利用更长上下文生成 token 表征,再对 chunk 聚合,从而让每个 chunk embedding 保留更多上下文信息。
它适合长上下文 embedding 能力较强、文档内部指代关系多的场景。落地时要注意成本、模型支持和可复现性,不要在没有 eval 的情况下直接替换基线。
5.7 Graph-aware chunk
如果文档之间存在实体、关系和社区结构,可以在 chunk 外再建图。GraphRAG 这类方法适合跨文档综合、组织知识图谱和 query-focused summarization。
但它不替代普通 chunking。图结构依赖底层证据单元,如果 chunk 本身质量差,图上只是组织了坏证据。
6. Chunk 大小和 overlap 怎么定
OpenAI Retrieval 文档给出一个实用基线:默认 max_chunk_size_tokens 是 800,chunk_overlap_tokens 是 400;可以通过 chunking_strategy 调整。当前限制是 max_chunk_size_tokens 需要在 100 到 4096 之间,overlap 不能超过 chunk size 的一半。
这个默认值适合做第一版,但不要把它当成永远正确。
6.1 先按任务类型定范围
| 场景 | 建议 chunk 范围 | overlap | 说明 |
|---|---|---|---|
| FAQ / 短问答 | 100-300 tokens | 0-50 | 一个问答就是一个证据单元 |
| 政策条款 | 300-800 tokens | 50-200 | 保留条件、例外和步骤 |
| 合同条款 | 400-1000 tokens | 100-250 | 条款和定义可能互相引用 |
| 技术文档 | 300-900 tokens | 50-200 | 保留标题路径和代码块 |
| 长报告 | 600-1500 tokens | 150-400 | 结合 section 摘要 |
| 表格 | 按行/区域/语义块 | 少用 token overlap | 保留行列关系比 token 数更重要 |
| 代码 | 按函数/类/文件结构 | 少量上下文 | 不要切断函数签名和实现 |
6.2 overlap 不是越大越好
overlap 的作用是防止边界切断语义。但 overlap 太大,会造成:
- 重复 chunk 过多;
- top-k 被同一段内容占满;
- embedding 存储成本增加;
- 引用结果冗余;
- MRR 和 Recall 看起来提升,但答案覆盖没有真正提升。
一个实用做法是限制同一 source 在 top-k 中的数量,或者对相邻 chunk 做去重。
6.3 用 eval 选参数
不要凭感觉选 chunk size。至少比较这些组合:
300 / 50
600 / 100
800 / 200
800 / 400
1200 / 300
对每组跑第四篇说过的检索 eval:
- Hit Rate@k;
- Recall@k;
- MRR;
- nDCG;
- Duplicate Rate;
- Citation Support;
- token 成本;
- p95 延迟。
最终选择通常不是最高 Recall 的配置,而是召回、精度、成本和引用可读性的折中。
7. 不同文档类型怎么处理
7.1 PDF
PDF 是企业 RAG 的重灾区。常见问题包括:
- 两栏排版顺序错;
- 页眉页脚重复;
- 脚注混入正文;
- 表格丢行列;
- 扫描件 OCR 错;
- 页码和原文 locator 丢失。
建议:
- 保存页码和字符/坐标位置;
- 去掉重复页眉页脚;
- 对表格单独抽取;
- OCR 结果要标记置信度;
- 扫描件低质量页面进入人工修复队列。
7.2 Word / Markdown / HTML
这些格式结构较好,优先按标题树切分。
保留:
- H1/H2/H3;
- 列表层级;
- 表格;
- 代码块;
- 链接;
- 更新时间;
- 文档路径。
不要把 HTML 导航、页脚、侧边栏一起入库。
7.3 表格和电子表格
表格不要简单转成连续文本。
错误做法:
企业版 10% 专业版 5% 免费版 0%
更好的证据单元:
{
"chunk_id": "pricing_2026_q2#table_sla#row_enterprise",
"source_id": "pricing_2026_q2",
"table": "SLA Credit",
"row_key": "enterprise",
"columns": {
"plan": "企业版",
"sla_credit": "10%",
"notice_window": "30 天"
},
"text": "SLA Credit 表中,企业版的 SLA credit 为 10%,通知窗口为 30 天。"
}
检索文本可以是自然语言化后的行,审计时仍然回到行列坐标。
7.4 代码文档和源码
代码不要按固定 token 粗暴切。
适合的边界:
- 文件;
- class;
- function;
- method;
- docstring;
- 注释块;
- 测试用例。
保存:
- repo;
- branch/commit;
- file path;
- symbol name;
- start line/end line;
- language;
- imports;
- dependency relation。
代码问答的引用最好能跳到具体行号。
7.5 FAQ 和客服知识
FAQ 最适合一问一答作为 chunk。
但要注意同义问题:
{
"canonical_question": "退款多久到账?",
"aliases": ["钱什么时候退回来?", "退款几天到?"],
"answer": "退款通常在 1-5 个工作日内退回原支付渠道。",
"tags": ["refund", "payment"]
}
aliases 可以进入检索文本,但展示时不要混淆成原文。
8. Metadata 是检索质量的一半
第四篇已经讲过,权限和时效性必须在检索层处理。metadata 是实现这个目标的基础。
OpenAI Retrieval 文档里提到,vector store file 可以带 attributes,用于 attribute filtering。文档也说明 attributes 字典有键数和长度限制。这提醒我们:metadata 不能随便堆,要设计成少而关键的字段。
推荐最小字段:
| 字段 | 用途 |
|---|---|
source_id | 关联原始文档 |
chunk_id | 稳定证据单元 |
tenant_id | 租户隔离 |
access_scope | 权限过滤 |
updated_at | 时效性 |
version | 多版本策略 |
status | active/deprecated/draft |
source_type | policy/contract/faq/code/table |
language | 多语言检索 |
owner | 责任人和审核 |
content_hash | 增量更新 |
hierarchy_path | 结构上下文 |
如果平台的 metadata key 数有限,可以把过滤必需字段放到 attributes,把展示和审计字段放到自有数据库。
一个常见架构是:
Vector Store:
- chunk_id
- searchable text
- embedding
- minimal filter attributes
Metadata DB:
- source registry
- locator
- permissions
- version history
- source hash
- full quote
- audit info
不要把所有治理信息塞进向量库,也不要让向量库成为唯一真相源。
9. 去重、版本和增量更新
9.1 去重
知识库里经常有重复内容:
- 同一 PDF 被上传多次;
- Word 和 PDF 是同一文档不同格式;
- FAQ 复制了政策条款;
- 新旧版本高度相似;
- chunk overlap 造成大量重复片段。
去重可以分三层:
| 层级 | 方法 |
|---|---|
| 文件级 | source hash |
| 段落级 | normalized text hash |
| 语义级 | embedding 相似度或 MinHash |
但不要把所有重复都删掉。新旧版本相似但都需要保留时,应该保留版本关系,而不是去掉旧版。
9.2 版本
版本字段要回答:
- 这是 draft、active 还是 deprecated?
- 哪个版本当前生效?
- 生效日期是什么?
- 被哪个版本替代?
- 查询
as_of某个日期时应该命中哪个版本?
示例:
{
"source_id": "refund_policy_2026_03",
"version": "2026-03",
"status": "active",
"valid_from": "2026-03-18",
"valid_to": null,
"supersedes": "refund_policy_2025_10"
}
9.3 增量更新
每次知识库更新,不应该全量重建,除非规模很小。
更好的流程:
- 计算 source hash;
- 未变化文档跳过;
- 变化文档重新解析;
- 对比 chunk hash;
- 新增 chunk 写入索引;
- 删除或废止旧 chunk;
- 记录变更日志;
- 对受影响主题跑回归 eval。
OpenAI Retrieval 文档提到,移除 vector store 文件后,搜索结果在短时间内仍可能出现已移除内容。这类最终一致性细节在生产里要考虑:如果是高风险知识库,删除或废止后要有短暂保护策略,例如过滤 status=active,而不只依赖物理删除。
10. 一个最小结构化 chunker 示例
下面示例演示如何把 Markdown 文档按标题结构切成 evidence chunks。它不是完整生产实现,但展示了关键思路:
- 保留标题路径;
- 给每个 chunk 生成稳定 id;
- 限制最大长度;
- 添加 metadata;
- 输出 JSONL。
import hashlib
import json
import re
from datetime import datetime, timezone
from pathlib import Path
def sha256(text):
return hashlib.sha256(text.encode("utf-8")).hexdigest()
def approx_tokens(text):
# 粗略估算,生产环境应使用目标模型 tokenizer。
chinese_chars = len(re.findall(r"[\u4e00-\u9fff]", text))
ascii_words = len(re.findall(r"[A-Za-z0-9_]+", text))
return chinese_chars + ascii_words
def split_markdown_blocks(markdown):
blocks = []
hierarchy = []
buffer = []
def flush():
if buffer:
blocks.append({
"hierarchy_path": hierarchy[:],
"text": "\n".join(buffer).strip()
})
buffer.clear()
for line in markdown.splitlines():
heading = re.match(r"^(#{1,6})\s+(.+)$", line)
if heading:
flush()
level = len(heading.group(1))
title = heading.group(2).strip()
hierarchy[:] = hierarchy[: level - 1] + [title]
continue
if line.strip():
buffer.append(line)
else:
flush()
flush()
return [b for b in blocks if b["text"]]
def pack_blocks(blocks, max_tokens=700):
chunks = []
current = []
current_path = []
current_tokens = 0
for block in blocks:
block_text = block["text"]
block_tokens = approx_tokens(block_text)
if current and current_tokens + block_tokens > max_tokens:
chunks.append({
"hierarchy_path": current_path,
"text": "\n\n".join(current)
})
current = []
current_tokens = 0
current_path = block["hierarchy_path"]
current.append(block_text)
current_tokens += block_tokens
if current:
chunks.append({
"hierarchy_path": current_path,
"text": "\n\n".join(current)
})
return chunks
def build_chunks(path, source_meta):
raw = Path(path).read_text(encoding="utf-8")
source_hash = sha256(raw)
blocks = split_markdown_blocks(raw)
packed = pack_blocks(blocks)
ingested_at = datetime.now(timezone.utc).isoformat()
for index, chunk in enumerate(packed, start=1):
context_prefix = " > ".join(chunk["hierarchy_path"])
text = chunk["text"].strip()
chunk_id = f"{source_meta['source_id']}#chunk_{index:04d}"
yield {
"chunk_id": chunk_id,
"source_id": source_meta["source_id"],
"source_title": source_meta["source_title"],
"source_type": source_meta["source_type"],
"version": source_meta["version"],
"status": source_meta["status"],
"updated_at": source_meta["updated_at"],
"owner": source_meta["owner"],
"tenant_id": source_meta["tenant_id"],
"access_scope": source_meta["access_scope"],
"language": source_meta.get("language", "zh-CN"),
"hierarchy_path": chunk["hierarchy_path"],
"context_prefix": context_prefix,
"text": text,
"index_text": f"{context_prefix}\n{text}" if context_prefix else text,
"content_hash": "sha256:" + sha256(context_prefix + "\n" + text),
"source_hash": "sha256:" + source_hash,
"ingested_at": ingested_at
}
if __name__ == "__main__":
meta = {
"source_id": "refund_policy_2026_03",
"source_title": "退款政策 2026-03 版",
"source_type": "policy_doc",
"version": "2026-03",
"status": "active",
"updated_at": "2026-03-18",
"owner": "finance_ops",
"tenant_id": "global",
"access_scope": ["customer_service", "finance"],
"language": "zh-CN"
}
with Path("evidence_chunks.jsonl").open("w", encoding="utf-8") as f:
for row in build_chunks("refund_policy.md", meta):
f.write(json.dumps(row, ensure_ascii=False) + "\n")
生产版本还需要:
- 真正 tokenizer;
- PDF/HTML/Docx parser;
- 表格结构处理;
- locator;
- ACL 映射;
- source registry;
- embedding 写入;
- 删除和废止策略;
- eval 自动化。
但这个例子已经体现核心原则:chunk 是带上下文和治理信息的证据对象,不是纯文本片段。
11. 如何评测 chunking 是否有效
评测 chunking 不能只看“切出来多少块”。要回到业务问题。
11.1 离线检索评测
用第四篇的 gold set:
{
"query_id": "refund_001",
"query": "跨境订单退款多久到账?什么情况需要人工审核?",
"gold_chunk_ids": [
"refund_policy_2026_03#sec_4#chunk_02"
],
"required_facts": ["到账时间", "跨境支付需人工审核"]
}
对不同 chunk 策略跑相同查询,比较:
- Hit Rate@k;
- Recall@k;
- MRR;
- nDCG;
- Duplicate Rate;
- Citation Support;
- token 成本;
- 检索延迟。
11.2 引用可读性评测
chunk 命中不等于适合引用。还要问:
- 用户点开 citation 后,是否能直接看懂?
- quote 是否过长?
- 是否包含无关段落?
- 是否缺少必要上下文?
- 是否能高亮到原文位置?
11.3 失败样本归因
把错误分成几类:
| 失败类型 | 说明 | 可能修复 |
|---|---|---|
| Missing source | 文档没入库 | source inventory |
| Parse failure | 解析错或 OCR 错 | parser/OCR 修复 |
| Bad boundary | chunk 切断语义 | 结构化切分 |
| Too broad | chunk 太大 | 缩小 chunk 或按 section 切 |
| Too narrow | chunk 太小 | parent-child 或 overlap |
| Lost table relation | 表格行列丢失 | 表格结构化 |
| Stale version | 旧文档命中 | version filter |
| Permission leak | 越权召回 | metadata filter |
没有失败归因,chunking 实验会变成调参玄学。
12. 生产落地架构
推荐把摄取系统拆成几个模块。
Source Connectors
-> Parser Workers
-> Normalizer
-> Structure Extractor
-> Chunk Builder
-> Metadata Enricher
-> Quality Gate
-> Vector Index + Keyword Index
-> Metadata Store
-> Retrieval Eval
12.1 Source Connectors
负责拉取:
- 文件夹;
- CMS;
- Notion/Confluence;
- Google Drive/SharePoint;
- 数据库;
- Git repo;
- 工单系统;
- 网页。
Connector 要记录 source id、路径、更新时间、owner 和权限。
12.2 Parser Workers
不同格式用不同 parser。不要用一个通用 read_text 解决所有问题。
Parser 输出 blocks:
{
"block_id": "b_0012",
"type": "paragraph",
"text": "...",
"page": 4,
"section": "4.2",
"bbox": [72, 120, 520, 188]
}
12.3 Quality Gate
入库前做质量检查:
- 空文本比例;
- OCR 置信度;
- 重复页眉页脚;
- 表格解析失败;
- chunk 太短/太长;
- 缺少权限字段;
- 缺少版本字段;
- source hash 异常;
- 非 UTF-8 文本。
质量失败的文档不要静默入库。标记为 ingestion_failed,进入修复队列。
12.4 双索引
不要只依赖向量索引。很多企业问题包含精确术语、编号、合同条款、错误码、API 名称。hybrid search 通常更稳。
OpenAI Retrieval 文档也提到 ranking options 里可以调 hybrid search 中 embedding 和 text 的权重。工程上应该把 semantic 和 keyword 的表现分开观察。
12.5 Metadata Store
向量库负责找相似文本,metadata store 负责治理和审计。两者通过 chunk_id 和 source_id 关联。
13. 常见误区
13.1 上传文件等于建知识库
上传只是开始。生产知识库需要解析质量、权限、版本、引用定位和评测闭环。
13.2 默认 chunk 参数永远够用
默认参数适合起步,不适合所有文档。FAQ、合同、表格、代码、长报告的切分策略不同。
13.3 只保存 chunk 文本
没有 metadata 的 chunk 不能过滤、不能审计、不能可靠引用。
13.4 表格转文本后就完事
表格最重要的是行列关系。转成散文后,模型可能把专业版和企业版的字段混在一起。
13.5 overlap 越大越好
overlap 会增加冗余。它解决边界问题,也制造重复问题。必须结合 Duplicate Rate 和 Citation Support 看。
13.6 不做版本废止
删除旧文档不等于生产安全。要有 active/deprecated 状态、valid_from/valid_to 和查询时过滤。
13.7 不保留 locator
没有页码、section、行号或 span,引用 UI 就只能跳到整篇文档,用户很难核查。
14. 落地路线图
第 1 周:建立 source registry
- 盘点知识来源;
- 定义 source id;
- 保存 owner、权限、版本、更新时间;
- 标记哪些来源可以入库。
第 2 周:做结构化解析
- 对 PDF/Docx/HTML/Markdown/表格分别解析;
- 去掉页眉页脚和导航噪声;
- 保留标题树、页码、表格、代码块。
第 3 周:生成 evidence chunks
- 定义
evidence_chunks.jsonlschema; - 按文档类型选择 chunk 策略;
- 添加 context_prefix;
- 生成 chunk hash 和 source hash。
第 4 周:写入索引和 metadata store
- embedding 写入向量索引;
- 精确字段写入 keyword index;
- 权限、版本、locator 写入 metadata store;
- 建立删除和废止流程。
第 5 周:跑 chunking eval
- 用 50-100 条业务问题标注 gold chunk;
- 比较不同 chunk size、overlap 和策略;
- 输出失败归因;
- 固化一版上线阈值。
15. 发布前自检清单
上线前至少问完这些问题:
- 是否有 source registry,而不是散落的文件路径?
- 每个 source 是否有 owner、更新时间、版本和权限?
- PDF 是否处理页眉页脚、页码、两栏、OCR 和表格?
- 表格是否保留行列关系和单元格 locator?
- 每个 chunk 是否有稳定
chunk_id和source_id? - 每个 chunk 是否有
hierarchy_path、context_prefix和原文 locator? - 是否有
status=active/deprecated/draft和版本过滤? - 是否记录
content_hash和source_hash? - 是否有增量更新和废止策略?
- 是否针对 chunk 参数跑过 retrieval eval?
- 是否看了 Duplicate Rate、Citation Support、token 成本和延迟?
- 是否能从 citation 跳回原文位置?
- 是否有 ingestion failed 队列,而不是静默入库坏文档?
如果这些问题答不上来,知识库还只是“文件集合”,不是生产级 RAG 知识库。
16. 总结
RAG 的摄取与 chunking,核心不是把文本切成多段,而是把原始材料变成可检索、可引用、可过滤、可审计的证据单元。
最小可用链路是:
Source Registry -> Parser -> Normalizer -> Structure Recovery -> Evidence Chunk -> Metadata -> Index -> Eval -> Fix Loop
做得好的摄取系统,会让后面的检索、引用和评测都变简单。做得差的摄取系统,会把所有问题推给 prompt 和模型。
参考资料
- OpenAI Developers: Retrieval
- OpenAI Developers: File search
- OpenAI Developers: Retrieval chunking section
- Sarthi et al., RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval
- Edge et al., From Local to Global: A Graph RAG Approach to Query-Focused Summarization
- Jina AI, Late Chunking: Contextual Chunk Embeddings Using Long-Context Embedding Models
1564

被折叠的 条评论
为什么被折叠?



