做RAG项目的人最常犯的错,不是embedding选得不对,也不是没用Rerank,而是根本没搞明白自己手里的数据到底该往哪个方向上处理。我接手过几个知识库项目,第一轮联调时表现不错,一到真实文档集就塌了,最后排查下来基本都是数据类型识别出了问题——有人把一堆扫描版PDF直接当成纯文本切块,有人把几十列的业务表硬塞进向量库,还有人拿重复度很高的监控日志去建索引,结果召回的全是同一条信息。这篇文章就是想从“数据类型”这个最底层但最容易被忽视的视角,把RAG的设计决策重新捋一遍。不管你是刚开始接触RAG,还是已经在调优线上系统,弄清楚数据类型与切块、索引、检索、生成之间的关系,很多问题根本不用费力排查。
1. 为什么数据类型决定了RAG的天花板
1.1 一次失败的RAG项目复盘
之前有个内部文档问答需求,原始材料是几十份产品说明书,PDF格式,每份大概二三十页。当时图省事,直接用PyPDF2抽文本,按固定512字符切块,塞进向量库完事。首轮测试看起来不错,能回答“设备最大功率是多少”这类问题。但换成一问“第三章节中关于故障排除的步骤是什么”,答案就开始乱来,经常把不同产品的故障码混在一起。
复盘时发现问题出在两个地方:一是PDF里大量文字其实是表格和流程图标注,PyPDF2抽出来的文本顺序是乱的;二是固定长度切块把章节标题和小节内容拆散了,导致召回时丢失上下文边界。这两个问题本质上是数据类型没有在切块之前被识别和处理。如果当时先把文档分类——纯文本段落、表格、图片注释分开处理,结果会完全不同。
1.2 数据类型的四个核心维度
很多人听到“数据类型”第一反应是int、string、json这些编程概念,但RAG场景里看的是另外四个维度。
第一是结构化程度。结构化数据(数据库表、CSV)有明确字段和关系,半结构化数据(JSON、HTML、XML)有嵌套结构但不完全规整,非结构化数据(PDF文本、邮件正文、聊天记录)没有固定模式。结构化程度直接决定了你是做向量检索、关键词检索、还是text-to-SQL。
第二是文本长度。一句话和一篇1万字的合同,切块逻辑完全不同。短文本可能不需要切,长文本必须考虑边界。问题往往在长文本,因为切片切得不好,语义就碎了。
第三是语义密度。一段技术文档和一段营销文案,同样1000字,前者每个句子都可能承载多个关键概念,后者可能几句话才有一个实体。语义密度越高,对切块和embedding的粒度要求就越高。
第四是模态复杂度。同一份材料里可能混着文字、图片、表格、音视频,处理器差异很大。比如图片里的文字,不做OCR就相当于这种信息凭空消失。
这四个维度组合起来,基本能决定你的RAG系统是“简单向量检索+LLM”就行,还是必须上混合检索、重排、多路召回这一整套工程化方案。
1.3 从数据类型推导出“检索方式”和“生成策略”
我自己习惯先画一个决策判断表,来定RAG的基础形态:
| 数据类型特征 | 推荐的检索方式 | 生成策略 |
|---|---|---|
| 结构化表格、字段明确 | 关键词过滤 + text-to-SQL | LLM直接生成SQL或直接查库 |
| 半结构化JSON/HTML | 结构解析 + 向量检索/图检索 | 按结构字段召回后组装上下文 |
| 非结构化长文本 | 向量召回 + BM25关键词召回 + Rerank | 切片重新排序后送入LLM |
| 多模态图文混排 | OCR + 多模态embedding | 文本+视觉特征拼接 |
| 代码仓库/日志 | 语法解析 + 标识符分块 | 按函数/类级别召回 |
这张表不是一个严格的规则,但能把很多项目方案从一开始就拉到正确方向。比如数据是标准的关系型业务表格,你却非要把它转成自然语言文档再去做向量检索,效果肯定差。反过来,如果数据是几十万字的技术手册,你只靠BM25关键词匹配,很多同义改写就匹配不到。
2. 打底:常见数据类型图谱与RAG应对方案
2.1 非结构化长文本(PDF/Word/网页)——切块是核心难点
非结构化长文本是RAG最普遍的输入,也是翻车重灾区。难点主要在解析和切块两件事上。
先讲解析。PDF分两种:数字版PDF能直接复制文字,扫描版PDF本质是图片。很多项目在第一步就把扫描版当成普通PDF处理,抽出来全是乱码或空文本。正确做法是先用OCR(比如PaddleOCR、Tesseract)识别,然后再进文本流程。Word文档还要注意页眉页脚、文本框、批注里的内容,这些如果不过滤,会污染向量索引。
再讲切块。固定长度切块最简单,但经常破坏语义边界。更可靠的做法是“结构感知切块”:按标题层级、段落、句子边界来切。举例来说,对于Markdown源文档,可以把H1/H2/H3作为切分点;对于PDF,先抽取出文档大纲,再按“标题-内容块”切。切完后建议保留一个结构元数据字段,比如“source_section = 3.2”,这样后来做引用溯源和权限过滤都容易。
切块大小怎么定?我的经验是:通用领域,300到500个字比较稳;技术文档,可以放大到600到800个字;代码或结构化字段,应该更小,甚至一条函数一个块。不过这个参数不是拍脑袋定的,要结合你的embedding模型的最大输入长度。如果用OpenAI的text-embedding-3-small,单次输入token限制是8191,但超过512或1024个token后,语义精度会下降。所以一般控制在300到800字之间。
另一个常见问题是“重叠(overlap)”。如果两个相邻块完全无重叠,查询“介绍系统架构和它如何实现高可用”时,可能“高可用”这个词落在了前一块末尾,“系统架构”落在了后一块开头,结果两块都召不全。设重叠为10%到20%,可以在一定程度上缓解。重叠不是越多越好,太多会放大重复内容,导致召回结果冗余。
2.2 结构化表格数据——不能硬塞向量库
表格类的结构化数据,最常见的错误是把它序列化成“第一行是...,第二行是...”然后交给向量检索。表格的价值在于行列关系,直接用embedding会把这种关系打平,查询“上季度华东区销售额最高的产品”时,向量检索只能靠字面相似度去猜,极容易混淆行列。
对表格数据,主流方案有三类。第一种是text-to-SQL,让LLM根据用户问题生成SQL,然后在真实数据库上执行。这种方案依赖schema描述和示例,需要对表结构做一层语义层映射。第二种是表格转JSON或自然语言描述后做成混合索引:先用关键词匹配找到相关表,再用LLM做字段对齐。第三种是把表格按“行+表头”或“列+列名”切块,作为辅助召回信息,再配合SQL查询结果一起送入LLM。真实项目里,第三种通常效果不错,因为它既保留了结构化查询的精确性,又能给LLM足够上下文。
当然也有纯表格组成的知识库。处理时我会先把表头、单位、枚举值这些信息抽取出来做成元数据,然后在检索阶段先用元数据过滤掉无关表,再在候选表内做细节匹配。比如一张“2024年销售明细表”和一张“员工考勤表”,如果问题跟销售相关,第一步就应该通过元数据把考勤表过滤掉,而不是让embedding去大海捞针。
2.3 半结构化JSON/知识图谱——GraphRAG和Hybrid RAG
半结构化数据最常见形态是JSON、HTML、XML,也包括带层级关系的知识库节点。这类数据的处理思路是保留结构,而不是强行拍平。比如一个JSON对象,“用户”和“订单”之间存在明确的嵌套关系,用普通文本切块会失去这层语义。更合理的做法是先把JSON解析成树状结构,每个节点一个块,同时在元数据里记录父节点路径。这样查询“这个用户最近的订单状态”时,可以先定位用户节点,再按父路径召回订单节点。
如果数据本身实体关系复杂,比如多个业务对象互相引用,GraphRAG会更合适。构建知识图谱时,先把实体和关系抽取出来,存到图数据库,再在检索时用“先向量召回实体、再图遍历扩展”的方式。GraphRAG特别适合“关系查询”和“多跳推理”,比如“A部门的项目涉及哪些供应商,这些供应商有没有被处罚记录”。但GraphRAG的项目门槛不低,实体抽取质量直接影响下游,建议只有在数据实体关系确实复杂、且普通向量检索明显不够用时才上。
Hybrid RAG是另一种实用组合,本质是“向量检索+关键词检索”的多路召回融合。向量检索擅长语义相似,关键词检索擅长精确匹配,比如产品型号、错误码、编号这类信息,向量模型经常会把它们当成噪声,但BM25能精准命中。实际项目中,我会把两种结果做加权融合(RRF或简单加权),再统一交给Rerank。这样既能找回同义改写,又能保证精确词不丢失。
2.4 图片与多模态数据——OCR+多模态embedding
现在很多知识库不仅包含文字,还包含图片、截图、流程图、扫描件。如果图片里的文字是关键信息,必须先做OCR。OCR完成后的文本可以走普通RAG流程,但要注意坐标信息——有时需要把识别出的文本块和它在图片中的位置对应起来,否则如果用户问“这张图的标题是什么”,我们无法回答。
如果图片内容本身是重要的视觉信息,比如产品设计图、趋势折线图,则必须用多模态embedding模型,比如CLIP、SigLIP,把图片编码成向量和文字向量对齐。查询时,用户问“图片里库房布局是怎样的”,通过视觉embedding能找到图片,但回答时LLM并不能直接“看”图,除非用多模态LLM(如GPT-4o、Gemini、Qwen-VL)。所以多模态RAG的完整链路是:图片先经过视觉编码进索引,检索出图片候选后,再把图片本身传给支持视觉输入的生成模型。
做这个方向时,容易忽略一个细节:图片切块。一张很大的架构图只切一块,embedding可能只捕捉到全局风格,细节就丢了。实践上,可以结合目标检测把图片中关键区域截出来,每个区域单独做向量,同时保留原图引用。这样用户问“图中负载均衡器后面有几个后端服务”时,能定位到具体区域而不是整张图。
2.5 代码与日志——特殊解析器和召回策略
代码和日志在RAG场景里经常出现,比如企业内部研发助手、运维知识库。这类数据的类型特征和自然语言差异很大。代码不能简单按字符数切块,否则很容易把一个函数切成两半。正确做法是按语法树切块,以函数、类、代码块为最小单元,同时保留文件路径和注释信息。如果项目用了LangChain,可以组合使用特定语言的解析器,或者调用tree-sitter这类语法分析库。
日志数据的难点在于高重复度和高噪声。同一台服务器的报错信息可能一天出现几万次,直接建向量索引会让召回结果高度集中。处理时先做聚合去重,把相同pattern的日志聚合成一条样本,并保留出现时间、频次等作为过滤条件。召回策略上也建议先用时间范围+关键词过滤,再进入向量检索,否则用户问“最近一次OOM是什么时候”,向量检索并不擅长按时间排序这类操作。
3. 从数据类型推导RAG系统架构
3.1 单路召回什么时候够用
很多科普RAG的文章一上来就推一套完整的“切块-向量化-向量检索-重排-生成”链路,对很多小项目来说是杀鸡用牛刀。单路向量召回在什么场景够用?我总结了几个条件:数据总量不大(比如百万字以下)、问题语义比较丰富(不是精确词匹配)、文档结构相对规整(没有太多表格和图片)、权限控制不复杂。满足这些条件,直接单路向量召回也能跑得不错。
但单路召回最怕的是“双关词”和“同义词歧义”。比如“苹果”既可能是水果也可能是品牌,向量模型会把两种含义搅在一起。如果业务场景里这种词特别多,至少应该加上词典限制或关键词纠偏。
3.2 多路召回(向量召回+BM25)为什么是标配
虽然单路召回能用,但生产级RAG我强烈建议上多路召回。原因很简单:embedding模型不是万能的。准确编号、专有名词、型号、缩写,这些对向量模型不友好,比如“CU-3042”和“CU-3043”在向量空间里距离非常近,但它们是两个完全不同的部件。BM25对这类精确token匹配却非常准。
多路召回常见结构是两个或更多检索通道并行,比如“向量召回Top20 + BM25召回Top20”,然后用RRF算法合并。RRF公式不复杂:score = 1/(60+rank),把每个通道的排名映射成分数再相加。这么做的好处是让精确匹配通道里排名靠前的结果有更高的权重,同时保留语义通道的结果作为补充。
多路召回还包含一种特殊场景:元数据过滤。如果权限卡控是强需求,比如不同部门只能看本部门文档,那么先通过元数据过滤缩小候选集,再在多路召回里选结果,比召回后再硬过滤效果稳定得多。后面章节会单独讲权限卡控。
3.3 Rerank的必要性与触发条件
Rerank(重排)是一个让人又爱又恨的组件。它的作用是把召回回来的几十个候选片段,用更精细的模型(如bge-reranker、Cohere Rerank)重新打分排序,把真正和问题相关的文本提到前面。它对长文本、强指代、多意图问题特别有效。但Rerank会引入额外延迟和成本,不是所有项目都必须要上。
我判断是否要上Rerank,主要看三个条件:数据文档是否很长(长文档中相关性分布在局部,容易漏排);问题是否包含大量限定词(如“2024年第四季度华东区的销售额”);以及召回结果是否经常出现“相关但顺序不对”的现象。如果都中,就上。如果只是简单FAQ,Top1基本正确,Rerank意义不大。
Rerank还有个容易被忽略的作用:它可以把“上下文窗口”用得更高效。比如向量召回Top20,每段500字,全塞给LLM可能超长。用Rerank先选Top5,再拼装上下文,能省很多token,也能减少噪声。我在政务类知识库里常这么干,因为文档长、查询点多,直接送Top20效果反而不稳。
3.4 权限卡控:元数据过滤与检索层隔离
RAG的权限卡控有时比检索效果还重要,尤其在企业内部或政务场景。最基本方案是在文档入库时给每份文档打上权限标签,比如部门编码、密级、可见范围。检索时,先从用户身份中解析出权限集合,然后用权限过滤条件或安全查询层从向量库中预过滤。
实现上,有几种选择。简单场景下,在向量数据库的过滤条件里写上权限字段,召回前先减掉无权访问的文档;复杂场景下,把文档和用户权限分开在两个系统里,RAG服务先向权限服务查询用户可见的文档ID列表,再把这些ID作为过滤项传给向量库。后者更稳,但每次请求要多一次权限查询,需要缓存。
我踩过的坑是权限标签打错粒度。如果只把权限打到文档级,而文档内包含跨部门内容,那么检索结果仍然可能泄露片段信息。更稳妥的做法是权限打到“章节或块”粒度,但运维成本会很高。折中方案是:文档级权限过滤 + 生成阶段用提示约束“只回答当前用户有权访问的内容”,并按需对生成结果做二次校验。
3.5 查询改写:从“用户说的”到“数据表达的”
数据类型不同,用户问法可能和数据表达方式完全不同。比如用户问“最近有没有设备报警”,数据里存的是“告警记录”或“AlarmEvent”。如果不做查询改写,向量检索可能匹配不上。查询改写常见做法是让LLM先对用户问题进行扩展、去指代或转换成语料中更可能出现的形式。
具体操作时,我会先定义一个改写模板,比如“把用户问题转成更适合检索的查询语句,保留专有名词和编号,必要时拆分成多个子查询”。子查询用于多路召回中,每条子查询分别跑检索,再合并结果。这个过程很依赖提示词设计,建议在冷启动阶段人工标注30到50条改写前后的例子,再用Few-shot方式让LLM模仿。
查询改写要注意不要过度改写。用户问“系统卡顿怎么办”,你改成“系统卡顿原因分析解决方案”,语义没变,但可能引入更多无关候选。改写的目标不是变得更长,而是更接近文档里的表达习惯。
4. 实操:一套可复用的RAG构建流程
4.1 第一步:数据摸底与分类
不要一上来就写代码。先花半天时间把数据源梳理清楚,建一个数据清单,列表至少包含:文件名称、数据格式、大概大小、是否有表格/图片、是否有扫描页、更新频率、是否需要权限控制、语言类型。这个清单就是后续一切方案的基础。
举个例子,我做过一个政务知识库项目,输入是政策文件、办事指南和咨询工单。只看表面都是PDF和Word,实际上政策文件是长文本,办事指南是半结构化流程,咨询工单是短文本带高重复度。如果统一用一种切块方式,效果必定参差不齐。我们最后把数据分成三种类型,分别用不同parser,再合并进统一索引,才把效果提上来。
4.2 第二步:分块参数怎么定
分块策略不是全局统一的,但也不是每个文档一套参数,尽量控制在3到5套模板以内。比如“长文本模板:分隔符一标题或段落,块大小600字,重叠10%”“短文本模板:每条FAQ或每条聊天记录单独一块”“表格模板:按行+表头切块,不做上下文重叠”。
实操时先用测试集验证。选20到50个典型问题,把分块后的文档建立索引,跑一轮检索,看Top3召回是否包含正确答案。如果答案总是被切散,就增加块大小或让分隔符优先级更高;如果召回结果包含大量无关片段,就缩小块大小。这个实验要多跑几轮,直到找到在你的数据和模型下的平衡点。
4.3 第三步:embedding模型选择与向量化
embedding选择要考虑语言、领域和硬件。中文场景下,bge-series、m3e、text-embedding都是常见选择;如果追求更强领域效果,可以用领域微调的模型。硬件上,如果并发不高、模型量不大,普通CPU就能跑,但延迟会高,建议还是上GPU或直接调用API。
向量化时要特别注意batch大小和文本长度。很多模型在超过512或1024 token时精度明显下降,因此切块大小不应超过模型上限。我还习惯在向量化之前做文本清洗,比如去除重复空行、标准查引号、过滤HTML标签,这些脏数据不会让模型崩溃,但会稀释向量质量。清洗前后我对Top5召回准确率做过对比,普遍能提升3到8个百分点。
4.4 第四步:索引设计与召回调试
索引设计不是简单建个collection就完事。以Milvus或Qdrant为例,我会按业务维度设计Field:id、text、metadata、permission、source_type、created_at。其中permission和source_type要设置为可过滤字段。召回调用时,先加过滤条件,比如“permission IN [user_permissions] AND source_type IN ['policy','guide']”,再做向量检索。
调试召回时,不要只看准确率,还要看“召回结果多样性”。有时Top10全是同一篇文档的不同片段,说明切块重叠太高或embedding区分度不够。这时可以试试MMR(Maximum Marginal Relevance)算法,在后处理时让结果在相关性和多样性之间平衡。MMR参数lambda通常取0.7到0.9,lambda越大越偏相关性,越小越偏多样性。
4.5 第五步:生成策略与提示词模板
RAG的生成阶段不是简单把召回片段拼起来就完事。提示词里至少要包含三个要素:用户原始问题、召回片段、输出约束。输出约束包括“如果召回内容中没有明确答案,请回答不知道”“只使用给定内容回答,不要编造”“回答问题前,先总结相关片段”。
我一般还会在提示词中加入“引用来源”要求,让LLM按照“内容(参考文献:文件名/章节)”格式回答。这样既方便用户判断答案可信度,也方便后续做答案审计。实践中,这对降低幻觉率帮助很大,因为模型知道要标注来源时,会更倾向于引用已有内容而不是自由发挥。
4.6 第六步:评估指标与回归测试
RAG评测比一般模型评测麻烦,因为检索和生成都要测。建议至少建立两组评估:一组是“检索评估”,用人工标注问题到标准答案片段关联,指标可以看Recall@K;另一组是“生成评估”,看答案是否准确、完整、有来源。生成评估最好让领域专家打分,而不是只用自动指标,因为很多表面答案正确,关键细节却错了。
每次修改分块参数、召回策略或提示词后,都要跑一遍回归测试。我习惯把历史所有测试问题的结果记录下来,做对比。如果新方案在某个问题上效果变差了,要能快速定位是检索环节还是生成环节的问题,避免“改了一个参数,别的环节崩了”这种连锁反应。
5. 常见问题与排查实录
5.1 问题速查表
| 问题现象 | 可能原因 | 排查建议 |
|---|---|---|
| 答案张冠李戴 | 切块切碎章节边界,召回内容跨章节 | 改用结构感知切块,按标题层级分割 |
| 精确编号查不到 | 向量模型对短编码不敏感 | 加BM25关键词召回,或做编号增强 |
| 表格数据答错列 | 表格被打平成自然语言 | 用text-to-SQL或按“行+表头”切块 |
| 图片文字无法检索 | 扫描件未OCR | 接入OCR流程,再把识别文本入索引 |
| 召回结果全是同篇文档片段 | 重叠比例过高或embedding区分度不足 | 降低重叠,使用MMR提高多样性 |
| 用户有权限但搜不到内容 | 权限过滤字段没正确设置 | 检查过滤条件是否与权限标签一致 |
| 长文档答案漏细节 | 块太小或Rerank没上 | 增大块大小,增加Rerank模型 |
| 日志问题答案重复 | 日志重复度太高 | 先聚合去重,再建索引 |
这张表不是万能的,但绝大多数RAG性能问题,最后都能倒推到“数据形态”和“检索策略”的匹配度上。
5.2 咬文嚼字:为什么召回结果总是乱序
很多人遇到过这个问题:向量检索Top5看起来都对,但排第一的不是最相关的。原因通常是embedding模型的匹配粒度比较粗,用户问题短,向量空间里“语义相近”和“问题可回答”并不完全等价。比如问“设备坏了怎么修”,文档里一段完整维修步骤可能和另一个案例里的“设备坏了”字样更相似,反而比正版维修手册片段排名更靠前。
这个问题的解法不是只调embedding,而是组合方案。一是查询改写,让问题更接近文档表达。二是引入Rerank,让精排模型在粗排结果中重新打一遍分数。三是把召回数量从Top5调到Top20,这样即使粗排顺序不对,候选里也包含正确答案,靠Rerank再提上来。顺序不对并不可怕,怕的是正确答案压根没进候选集。
5.3 实战中的小技巧
分享几个我实际用下来很简单但有效的小技巧。
第一个是给每个块打上唯一ID和来源路径。所有后续的权限过滤、引用溯源、结果展示都依赖这两个字段,一定要提前设计好。
第二个是embedding之前把文本里的超长字符串或连续数字替换成占位符。比如把所有长串数字替换成“NUM”,可以减少噪声,但在替换前要把原始值存到元数据里,否则答案里没法保留真实编号。
第三个是测试集不只准备标准问法,还要准备口语化问法。比如文档里写“注销登记”,用户可能会问“取消注册”,RAG系统是不是能把两者关联上,测试一次就知道。
第四个是定期下线无效文档。数据源会更新,旧版本和重复内容如果不清理,会不断污染检索结果。我做了一个简单的文档指纹去重(SimHash),每周跑一次,效果立竿见影。
结尾
最后说点个人体会。RAG项目的复杂度不在模型,而在数据。很多时候把数据分类、清洗、切块、索引这四件事做扎实了,模型用最普通的也有不错表现。反过来,如果数据类型没认清,堆再多Rerank和查询改写都是在修补一个错误的底座。我每次启动新项目,都会先逼自己回答一个问题:“这批数据到底是结构化、半结构化、还是非结构化?里面的表格和图该单独处理吗?”回答清楚了,后面每一步都有据可依。这个习惯救了我很多次,也希望对你有用。