想象你是一家大型连锁餐厅的老板,每天要接待上千位客人。客人们会问:“水煮鱼里有没有花椒?”“退单流程是怎样的?”为了让服务员在店里的任何一个角落都能飞快找到正确答案,你整理了一本500页的员工手册——可它有的是PDF、有的是Word、还有扫描图片,服务员在客人面前翻上十分钟可不行。于是你建了一座“中央厨房”:先把各种格式的资料统一拆包、清洗成标准食材(文档解析),再把大块食材切成适合一口吃下的小片(文本切分),给每片食材贴上一串只有后厨才懂的二维码标签(向量化),存进井井有条的冷库(向量存储),客人点菜时配菜师傅按需挑出最合适的食材(检索增强),最后送到大厨手里做成菜端上桌。这套“备菜流水线”,就是RAG(检索增强生成)客服系统的核心——文档处理与向量化。今天我们就走进这间中央厨房,看看每个环节到底怎么运转。

岗位关联

对AI从业者而言,文档处理与向量化是搭建RAG系统的地基工程。掌握它,你就能为企业搭起真正能用的大模型问答服务——这也是当前AI落地最热门的岗位技能之一。

目录
  1. 可视化演示
  2. 文档解析——食材的验收与清洗
  3. 文本切分——大块食材切适口小片
  4. 向量化模型——给食材贴上味道标签
  5. 向量存储——井井有条的冷库与智能货架
  6. 检索增强——配菜师傅的高阶智慧
  7. 系统优化——厨房的持续运营
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示文档解析分块向量化管道、Embedding模型选择与Top-N检索上下文拼接,理解RAG客服系统从文档到可检索知识库的核心环节。

本节配有交互动效(进入视口后自动加载,约 3KB)。

处理管道 解析PDF转结构化文本 固定512 token分块 分块策略直接影响检索精度与生成质量。

文档解析——食材的验收与清洗

餐厅后厨每天要收各种包装的食材:真空袋、纸箱、泡沫箱。每一件都有“包装”要拆,有“泥巴”要洗,有“烂叶”要摘。在RAG系统里,这些“包装”就是PDF、Word、HTML等文件格式,“泥巴烂叶”就是页眉、页脚、导航栏、广告等噪音。如果验收不严,脏东西混进来,会影响后续所有环节——就像一块带沙的肉,无论厨艺多好,客人都会吃到沙子。

文档解析(Parsing)的任务,正是把这些非结构化文件转成干净的纯文本。具体包括三大任务。格式识别是判断食材的包装类型:PDF分为文本型(可直接提取)和扫描型(需OCR识别图片中的文字),Word内部有段落与表格结构,HTML有标签嵌套。内容抽取是取出能吃的部分:表格转为Markdown或CSV保留结构语义,多栏布局按阅读顺序拼接,图片文字用OCR补充。数据清洗是扔掉烂叶:去掉页眉页脚、重复空行、特殊控制字符,只保留业务需要的信息。

在实际项目中,一份PDF可能混合了文字层和扫描图,需要逐页判断处理策略;HTML则要先剥离script和style标签,再按正文区域抽取。清洗的严格程度直接决定后续向量化的质量——一段混入“© 2024 版权所有 京ICP备XXXXXXXX号”的文本,在检索时可能被当成有效内容返回给用户。

为了量化解析效果,我们可以用“有效信号占比”来评估:

解析完成,食材验收与清洗结束。如果这段没做好,后面所有环节都会被拖累——这正是很多RAG项目“检索结果乱七八糟”的第一大根因。

SignalRatio=1−NnoiseNtotal\displaystyle \text{SignalRatio} = 1 - \frac{N_{\text{noise}}}{N_{\text{total}}}
有效信号占比 = 1 − 噪音字符数 ÷ 总字符数,用来衡量文档解析后文本的“干净程度”。
  1. N_total(总字符数)原始文档的全部字符数量,对应厨房收到的食材总重量。
    NtotalN_{\text{total}}
  2. N_noise(噪音字符数)解析后识别出的噪音字符数量,包括HTML标签、页眉页脚、乱码等无效信息,相当于需要摘掉的烂叶。
    NnoiseN_{\text{noise}}
  3. SignalRatio(有效信号占比)用1减去噪音比例,得到干净文本占全文的比例。比值越接近1,说明解析越彻底,后续切分和向量化的“原料”越纯净。
    SignalRatio=1−NnoiseNtotal\displaystyle \text{SignalRatio} = 1 - \frac{N_{\text{noise}}}{N_{\text{total}}}

文本切分——大块食材切适口小片

一块五斤重的整牛肉直接下锅没法吃。大厨会先切片,太厚咬不动,太薄容易碎。更有经验的师傅切的时候会让相邻两片之间留一点重叠,保证肉的纹理不断开。在RAG系统里,一段5000字的文档包含多个主题,一个向量根本“装不下”,所以必须切成chunk(文本块)。

文本切分由三个要素决定。切分粒度:按固定字符数切(如256或512)实现简单,但容易切断句子;按句子或段落切语义完整,但chunk长度不齐。重叠窗口:相邻chunk保留10%-20%的重叠字符,确保被切断位置在另一侧仍有上下文,避免“断章取义”。语义边界:优先在句子结束、段落结束处下刀,遇到Markdown标题或PDF章节标志时,把标题和正文放进同一个chunk,保持内容归属清晰。

切分的效果直接决定检索召回的质量。如果一刀切断了关键语义——就像把“不要加香菜”切成“不要加”和“香菜”——客户明明说了不要,系统却检索出了加香菜的做法。重叠窗口正是为了缓解这个问题而存在,它相当于给每片肉留了一段“重复的纹理”。

给定文档长度、块大小和重叠长度,chunk数量可以这样计算:

切得好不好,直接决定“下锅”后的味道。好的切分策略能让检索精度提升近一倍,这也是为什么资深工程师会花大量时间测试不同的chunk大小和overlap组合。

Nchunks=⌈L−OC−O⌉\displaystyle N_{\text{chunks}} = \left\lceil \frac{L - O}{C - O} \right\rceil
文本块数量 = 向上取整(文档总长 − 重叠长度)÷(块大小 − 重叠长度),用来估算切分后需要向量化的片段个数。
  1. L(文档总长度)等待切分的全部文本字符数。
    LL
  2. C(块大小)每个文本块的目标长度,例如512字符。
    CC
  3. O(重叠长度)相邻文本块共享的字符数,值越大上下文衔接越紧密,但会增加总块数。
    OO
  4. C − O(实际步长)切分时窗口每次向前移动的距离,只包含“新出现”的字符。
    C−OC - O
  5. L − O(待覆盖距离)从第一个块起点到最后一个块起点之间的总距离。
    L−OL - O
  6. N_chunks(文本块数)两者相除后向上取整,保证最后一段文本也能被覆盖到。
    Nchunks=⌈L−OC−O⌉\displaystyle N_{\text{chunks}} = \left\lceil \frac{L - O}{C - O} \right\rceil

向量化模型——给食材贴上味道标签

走进一间黑漆漆的冷库,你看不清每份食材。聪明的仓库管理员会给每个盒子贴二维码,扫码就知道这是“咸鲜五花肉”还是“甜香提拉米苏”。向量化做的就是这件事:把每个文本块变成一个几百维的“二维码”——一串数字向量,让语义相近的文本在向量空间中彼此靠近。

向量化(Embedding)的核心是让计算机理解“语义距离”。“怎么退钱”和“退款流程如何办理”用词完全不同,但语义一致,对应的向量就应该很近。深度学习模型(如BERT系列)通过在海量语料上的预训练,学到了这种语义映射:Transformer编码器将文本压缩成一个固定长度的向量,把“语义”分散编码到每个维度里。

在实际选型中,有几个关键考量。Embedding原理:模型输出的向量维度常见有384、768、1024、1536等,维度越高语义区分度越高,但存储与计算开销也越大。模型选型:通用领域常用BGE、text-embedding-ada-002等开源/商业模型;垂直领域(医疗、法律、客服)建议在领域数据上微调或选择领域特化模型,否则会出现“报修”被理解成“保修”的尴尬。维度权衡:10万条768维向量大约占用3GB内存,如果资源有限,可以考虑384维或降维压缩。

向量之间的相似程度,最常使用余弦相似度来衡量:

二维码打得准不准,决定了冷库取菜快不快。如果你用的Embedding模型是“通用型”,它扫出来的标签可能与你的行业“方言”对不上。选择或微调领域模型,就是让标签系统学会你的行业语言——这是向量化效果提升最立竿见影的一步。

cos⁡(q,d)=∑i=1nqidi∑i=1nqi2⋅∑i=1ndi2\displaystyle \cos(q, d) = \frac{\sum_{i=1}^{n} q_i d_i}{\sqrt{\sum_{i=1}^{n} q_i^2} \cdot \sqrt{\sum_{i=1}^{n} d_i^2}}
余弦相似度 = 两个向量的点积 ÷ 两个向量模长的乘积,值越接近1表示语义越相似,越接近-1表示越相悖。
  1. q_i 与 d_i(向量分量)查询文本和文档文本在第i个维度的取值。比如第17个维度可能代表“与退款相关”的语义强度。
    qi,diq_i, d_i
  2. 分子(点积)两个向量各维度乘积之和,衡量它们在同一方向上的“共同强度”。
    ∑i=1nqidi\sum_{i=1}^{n} q_i d_i
  3. 分母(模长乘积)两个向量长度相乘,用于归一化,消除文本长度带来的偏差。
    ∑i=1nqi2⋅∑i=1ndi2\sqrt{\sum_{i=1}^{n} q_i^2} \cdot \sqrt{\sum_{i=1}^{n} d_i^2}
  4. cos(余弦值)约等于两个向量的夹角余弦。夹角越小越相似,客服场景中通常取阈值0.7以上才算相关。
    cos⁡(q,d)\cos(q, d)

向量存储——井井有条的冷库与智能货架

冷库里光有标签还不够,还得有货架。如果食材随便堆在地上,客人点菜时配菜师傅要满仓库翻找。有了按类别分区、按热度排序的智能货架,就能快速锁定位置。向量数据库就是这间智能冷库——它不仅“存”向量,更负责“找”向量。

生产环境中,向量数据库(如Milvus、Qdrant、FAISS)主要做三件事。存向量:把每一条文本块的向量连同元数据(文档ID、章节、时间戳)一起存入。建索引:暴力扫描在百万级数据下太慢,所以引入ANN(近似最近邻)索引,其中HNSW(层级小世界图)兼顾召回率与速度,是当前主流;IVF(倒排文件)则适合超大规模场景。算相似度:常用余弦相似度、内积和欧氏距离,归一化后余弦与内积等价。

单靠语义向量并不万能。用户问“订单号20240815001的退款状态”,这种精确匹配场景下,关键词检索(BM25/倒排索引)准确率更高。因此生产系统普遍采用混合召回:语义向量通道负责泛化理解,关键词通道负责精确命中,同时结合元数据过滤(如先限定在“退款政策”章节下检索),把搜索范围缩小一个数量级。

混合召回的核心是给两个通道的分数量一个权重:

冷库布局决定了出菜速度。HNSW索引结构选得好,百万级向量几十毫秒就能返回结果;混合召回则像一位经验老到的配菜师傅——“你给我订单号,我按编号找;你描述口感,我按味道标签找”,两手都硬才是生产级方案。

scorehybrid=α⋅simkeyword+(1−α)⋅simsemantic\displaystyle \text{score}_{\text{hybrid}} = \alpha \cdot \text{sim}_{\text{keyword}} + (1 - \alpha) \cdot \text{sim}_{\text{semantic}}
混合分数 = 关键词通道分数 × 权重 + 语义通道分数 ×(1 − 权重)。α控制“精确”与“泛化”的偏好。
  1. sim_keyword(关键词分数)来自BM25或倒排索引的匹配分数,擅长订单号、型号、政策编号等精确匹配。
    simkeyword\text{sim}_{\text{keyword}}
  2. sim_semantic(语义分数)来自向量检索的余弦相似度,擅长“没记住产品名但记得用途”的模糊表达。
    simsemantic\text{sim}_{\text{semantic}}
  3. α(混合权重)取值范围0到1。α靠近1时系统更像“关键词搜索引擎”,靠近0时更像“聊天机器人记忆库”。实际场景通常取0.2~0.4。
    α\alpha
  4. score_hybrid(混合总分)融合后的最终分数,按分数降序取top-k块送往下游生成环节。
    scorehybrid\text{score}_{\text{hybrid}}

检索增强——配菜师傅的高阶智慧

客人一句“上次那个有点辣但也不能太辣的菜再来一份”,普通服务员听得一头雾水。经验丰富的配菜师傅会先确认:“您说的是水煮鱼吧?微辣,去香菜,多加豆芽?”然后迅速下单。RAG的检索增强环节正是如此:理解Query、优化Query、精排结果,把最相关的内容送到大模型手里。

Query改写是第一步。用户真实输入往往口语化、有指代:“那个怎么弄?”“我需要它尽快到。”如果直接拿去检索,效果很差。改写组件会把query补全为信息更充分的检索语句,如“如何申请退款”;也可以利用多轮对话历史,把“它”替换成之前提到的商品名。改写可以用大模型完成,也可以用规则+模板降低延迟。

重排序(Rerank)是第二步。初召回可能拿到50条候选,但大模型上下文窗口最多放5~10条。Rerank模型(如CrossEncoder)将query与每条候选拼接后精细打分,比Embedding的粗粒度相似度更准。一个典型的线上配置是:向量检索召回50条 → Rerank精排取Top-5 → 拼接成上下文送大模型。

结果融合是第三步。多路召回(向量、关键词、元数据)的结果需要合并去重。这里常用RRF(Reciprocal Rank Fusion),它不看绝对分数大小,只看排名,因此对不同通道的分数尺度天然鲁棒:

配菜师傅不会把客人的模糊需求原封不动转给后厨,而是先确认、再挑菜、最后按最合适的顺序递给大厨。Query改写、重排序、结果融合,就是这套“确认—挑菜—排队”的流程,让大模型拿到的永远是“精挑细选过的食材”。

RRF(d)=∑r∈R1k+rankr(d)\displaystyle \text{RRF}(d) = \sum_{r \in R} \frac{1}{k + \text{rank}_r(d)}
RRF分数 = 各通道排名倒数的累加和,排名越靠前的文档融合分数越高,k是平滑常数。
  1. R(召回通道集合)所有参与召回的通道,如向量通道、关键词通道、元数据通道。
    RR
  2. rank_r(d)(通道内排名)文档d在通道r中的排名位置,从第1名开始。
    rankr(d)\text{rank}_r(d)
  3. k(平滑常数)通常取60。它防止排名为1的文档分数过高,同时让排名靠后的文档仍有非零贡献。
    kk
  4. RRF(d)(融合分数)遍历所有通道,把每个通道的排名倒数累加,得到最终的融合排序分数。
    RRF(d)=∑r∈R1k+rankr(d)\displaystyle \text{RRF}(d) = \sum_{r \in R} \frac{1}{k + \text{rank}_r(d)}

系统优化——厨房的持续运营

开业三个月后,菜谱更新了,新食材不断到货,有些食材过期要下架;午市高峰顾客排队,厨师忙不过来。餐厅若不优化运营流程,好菜也会上得慢。RAG系统上线后同样要面对数据时效、查询成本和资源效率三大挑战。

增量更新解决数据时效问题。当文档新增、修改或删除时,没必要全量重建向量库——只对变化的文档重新解析→切分→向量化,更新对应条目即可。实现上可以用文档哈希(如MD5)判断内容是否变化;加上版本号字段,查询时按“最新版本”过滤,避免新旧内容混淆。产品手册每周更新,增量更新让新知识几秒内生效,而全量重建可能要跑几个小时。

缓存策略解决查询成本问题。热门问题(“怎么退货”“运费谁出”)命中率极高,直接把答案缓存起来,或者至少缓存检索结果,可以大幅降低大模型调用费用和时延。常用LRU淘汰策略,把不常问的旧缓存让位给新热点。性能调优解决资源效率问题。向量维度可以用PCA降维,存储精度可以从FP32降到INT8,HNSW的M和efConstruction参数也要在召回率与延迟之间调平衡。

缓存策略的效果用命中率来度量:

持续优化的厨房永远不会“开一次张就关门”。增量更新像每日补货,缓存像招牌菜提前备好,性能调优像高峰期加派人手。RAG系统的生命力,就在这一天天细水长流的运营中积累出来。

HitRate=NhitNtotal×100%\displaystyle \text{HitRate} = \frac{N_{\text{hit}}}{N_{\text{total}}} \times 100\%
命中率 = 缓存命中的查询次数 ÷ 总查询次数 × 100%。在客服场景中,头部100个热门问题的回答往往能覆盖60%以上的用户请求。
  1. N_hit(缓存命中次数)用户查询直接命中缓存的次数,相当于点了“招牌菜”——后厨已经备好的半成品,秒出。
    NhitN_{\text{hit}}
  2. N_total(总查询次数)系统启动以来接收到的所有查询。
    NtotalN_{\text{total}}
  3. HitRate(命中率)命中率越高,系统平均响应越快、成本越低。如果命中率低于30%,通常需要增大缓存池、优化淘汰策略,或者将问题改写后再匹配缓存。
    HitRate=NhitNtotal×100%\displaystyle \text{HitRate} = \frac{N_{\text{hit}}}{N_{\text{total}}} \times 100\%

AI 应用场景

企业客服知识库冷启动

阶段:文档处理与向量化全流程

解决的问题:某电商平台的客服FAQ、产品手册、售后政策散落在几十份PDF和Word里。新人客服培训周期长,老客服检索历史文档效率极低。传统做法是人工整理FAQ并对关键词建索引,但自然语言的千变万化让关键词匹配频频失效。

具体案例:团队将这些文档统一解析为干净文本,按“问题-答案”结构切分并向量化,存入Milvus向量库。上线后,客服只需输入自然语言描述(如“顾客说充电器坏了怎么赔”),系统就能在1秒内从3000多条知识中检索出最相关的3条处理方案,准确率从关键词方案的68%提升到91%。

实时工单相似问题推荐

阶段:检索增强(Query改写 + 混合召回)

解决的问题:客服处理新工单时,往往要翻查历史工单寻找类似案例。人工翻找效率低下,且不同的客服检索习惯不同,结果参差不齐。传统工单系统的全文搜索只能按标签精确匹配,找不到语义相似的隐藏案例。

具体案例:系统先将历史工单的“问题描述+解决方案”切分向量化。新工单到达时,系统自动改写工单标题(识别“进水”“开不了机”等口语描述),做向量与关键词混合检索,返回Top-5相似历史工单,并标注相似度分数。客服一键查看解决方案,单张工单处理时长从15分钟降至6分钟。

产品文档快速迭代的知识库

阶段:增量更新 + 缓存策略

解决的问题:智能硬件公司的产品手册每周更新一版,旧版知识如果不及时下架,会造成错误回答。传统做法是每周日凌晨全量重建向量库,耗时4小时且占用大量服务器资源。

具体案例:团队改为增量更新方案:用文档哈希检测变更,只对新版本文档重新解析和向量化,并在向量元数据中记录“版本号”和“生效时间”。查询时默认过滤旧版本。同时将最常被问到的50个问题的答案缓存到Redis,线上命中率78%,平均响应时延从1.8秒降至0.3秒。

Python 演示

python98 行
import mathimport refrom collections import Counter  class DocProcessor:    """RAG客服系统文档处理与向量化完整流程演示"""     def __init__(self, chunk_size=40, overlap=8):        self.chunk_size = chunk_size        self.overlap = overlap        self.vocab = {}        self.chunk_vectors = []        self.chunk_texts = []     def parse_document(self, raw_text):        # 步骤一:文档解析,去掉HTML标签和多余空白        text = re.sub(r'<[^>]+>', '', raw_text)        text = re.sub(r'\s+', ' ', text).strip()        return text     def chunk_text(self, text):        # 步骤二:文本切分,固定大小加重叠窗口        chunks = []        step = self.chunk_size - self.overlap        for i in range(0, len(text), step):            chunk = text[i:i + self.chunk_size]            if len(chunk) >= self.chunk_size // 2:                chunks.append(chunk)        return chunks     def build_vocab(self, chunks):        # 构建词表,给每个词一个唯一编号        words = set()        for chunk in chunks:            words.update(re.findall(r'[\w\u4e00-\u9fff]+', chunk.lower()))        self.vocab = {w: idx for idx, w in enumerate(words)}        return self.vocab     def vectorize(self, chunks):        # 步骤三:向量化,词袋模型加归一化        vectors = []        for chunk in chunks:            vec = [0.0] * len(self.vocab)            counter = Counter(re.findall(r'[\w\u4e00-\u9fff]+', chunk.lower()))            for w, cnt in counter.items():                if w in self.vocab:                    vec[self.vocab[w]] = cnt            norm = math.sqrt(sum(v * v for v in vec))            if norm > 0:                vec = [v / norm for v in vec]            vectors.append(vec)        return vectors     def retrieve(self, query, top_k=3):        # 步骤五:检索,查询向量化加余弦相似度        q_vec = [0.0] * len(self.vocab)        for w in set(re.findall(r'[\w\u4e00-\u9fff]+', query.lower())):            if w in self.vocab:                q_vec[self.vocab[w]] = 1.0        norm = math.sqrt(sum(v * v for v in q_vec))        if norm > 0:            q_vec = [v / norm for v in q_vec]         scores = []        for vec in self.chunk_vectors:            sim = sum(a * b for a, b in zip(q_vec, vec))            scores.append(sim)         top_indices = sorted(range(len(scores)),                             key=lambda i: scores[i], reverse=True)[:top_k]        return [(idx, scores[idx]) for idx in top_indices]     def process(self, raw_text, query):        # 完整流程:解析 -> 切分 -> 向量化 -> 存储 -> 检索        text = self.parse_document(raw_text)        chunks = self.chunk_text(text)        self.build_vocab(chunks)        self.chunk_texts = chunks        self.chunk_vectors = self.vectorize(chunks)        return self.retrieve(query)  if __name__ == "__main__":    doc = (        "<html><body>"        "尊敬的客户,欢迎使用智能客服系统。本系统提供退款、换货、物流查询服务。"        "如需退款,请在订单页点击退款按钮,填写原因并提交。"        "物流查询请提供订单号,系统将自动匹配物流状态。"        "换货申请需在签收后7天内完成,逾期无法受理。"        "</body></html>"    )    processor = DocProcessor(chunk_size=30, overlap=5)    query = "如何申请退款"    results = processor.process(doc, query)    print("查询:", query)    for idx, score in results:        print(f"[相似度 {score:.4f}] {processor.chunk_texts[idx][:40]}...")
目的:用纯Python实现RAG的“解析→切分→向量化→存储→检索”完整链路,无第三方依赖,可直接运行感受文档处理与向量化的核心流程。
  1. text = re.sub(r'<[^>]+>', '', raw_text)用正则表达式剥离HTML标签,对应文档解析中的数据清洗
  2. step = self.chunk_size - self.overlap计算切分步长,每次只向前移动“新增部分”,重叠处自然覆盖
  3. self.vocab = {w: idx for idx, w in enumerate(words)}构建词表,给每个词一个编号,向量中该位置的数字代表这个词的权重
  4. vec = [0.0] * len(self.vocab)创建一个与词表等长的全零向量,等待填充词频
  5. counter = Counter(re.findall(r'[\w\u4e00-\u9fff]+', chunk.lower()))统计当前文本块中每个词的出现次数,中文按连续汉字片段切词
  6. norm = math.sqrt(sum(v * v for v in vec))计算向量的模长,用于归一化,让不同长度文本的向量可比
  7. sim = sum(a * b for a, b in zip(q_vec, vec))计算余弦相似度分子(向量已归一化,省略了分母)
  8. top_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:top_k]按分数从高到低排序,取前top_k个结果,这就是最原始的“召回”

常见误区

误区我以为只要用`pdfplumber`或`docx`库把文件内容读出来,文档处理就完成了。后面检索不准时完全没往解析环节想。

正解解析的目标是提取“结构化、干净”的文本,不是简单地读字符串。真实PDF里有表格、多栏、页眉页脚、扫描图片,都需要逐项处理。就像前面说的,不摘掉烂叶,整盘菜都是沙。生产级解析还涉及OCR路由、版面分析、表格结构还原,这是一项独立的工程能力。

为何常见:本地文本文件确实直接读就行,容易让人低估异构格式的复杂度。加上很多RAG教程跳过了解析细节,初学者自然以为“读取=解析”。

误区我觉得把整篇文档作为向量存起来最完整,小块的chunk会丢失上下文,所以把chunk_size设成2000甚至更大。

正解块越大,embedding要“压缩”的信息越多,单条向量的语义越模糊,检索精确度反而下降。块太小又会让语义碎片化。更合理的做法是:先按语义边界切(段落/标题),再限制块长度在300~800字符,配合10%-20%重叠窗口。根据业务测试调参,而不是拍脑袋往大设。

为何常见:直觉上“内容越多信息越多”,但忽略了向量检索是以“相似度匹配”为核心的——当一块文本包含三四个主题时,任何单一query都只能匹配其中一小部分,检索精度自然被稀释。

误区我在HuggingFace上随便选了一个下载量最高的Embedding模型,直接对文本调用,以为这样就能得到好用的向量。

正解通用模型在垂直领域往往表现不佳。客服场景里,“报修”和“保修”、“拒收”和“退货”这些词的语义关系,通用模型未必理解到位。正确做法是:先用领域语料评估效果,必要时在业务数据上微调Embedding模型,或在向量搜索前加一层领域词典扩充。另外还要注意模型的输入长度上限(如512 token),超长文本必须先切分再向量化。

为何常见:Embedding模型的“开箱即用”体验太好,让人低估了领域适配的重要性。模型距离你的业务语料越远,语义偏差越大,而且这种偏差在检索阶段才会暴露,问题定位成本很高。

误区我做完向量检索后发现它能理解“帮我把不要的东西退掉”这种一句话,于是把公司的全文搜索直接换成了纯向量方案。

正解纯向量在精确匹配上极不靠谱。用户输入“订单号83492021的运费险怎么算”,向量检索很难精确命中这串数字对应的记录。生产系统必须做混合召回:关键词通道(BM25/倒排索引)负责数字、编号、专有名词的精确匹配,向量通道负责语义泛化,再用Rerank或RRF融合两路结果,才能在“精确”和“泛化”之间取得平衡。

为何常见:被“语义理解”的效果震撼后容易走极端。向量检索擅长的是“意思相近”,不是“字符一致”。客服场景中大量查询恰恰包含数字、型号等需要精确匹配的信息。

误区我把所有文档向量化存入向量库后,就安心上线了。过了两周发现某些旧政策被检索出来,用户得到的答案已经过时。

正解文档是持续演进的生命体。新政策要进来,旧政策要下线,修改过的段落要重新向量化。生产级方案需要给每条向量存元数据(版本号、生效时间、来源文档ID),用文档哈希判断变更,只对变化的部分做增量更新,查询时按版本过滤,甚至要处理“新旧版本过渡期”的语义冲突。

为何常见:静态demo中的文档一次入库再不变更,这种开发体验让人忽略了线上环境的动态性。等到过期内容被用户截图投诉,才意识到向量数据也需要“运维”。

面试题

Q1. RAG系统中文档解析的核心目的是什么?

单选题 · easy

  • A.将非结构化文档转为结构化干净文本← 正确答案
  • B.压缩文档以节省存储空间
  • C.对文档内容进行加密保护
  • D.将文档翻译成多种语言

答案:A。文档解析的目标是把PDF、Word、HTML等格式的文件“拆包装、洗泥沙”,转换成干净的纯文本——就像中央厨房里洗菜摘菜,为后续切分和向量化提供合格原料。压缩、加密、翻译都不是解析的目标。

Q2. 文本切分时引入重叠窗口(overlap)的主要作用是什么?

单选题 · mid

  • A.减少向量库的存储空间
  • B.加速向量化的计算速度
  • C.保持跨块语义的连贯性← 正确答案
  • D.增加文本块的总数量

答案:C。重叠窗口让相邻文本块共享一段内容,被切断的语义在另一侧仍有上下文兜底。就像肉片之间的“纹理重叠”,能避免检索时出现“断章取义”(比如“不要加香菜”被切成“不要加”和“香菜”)。存储、速度、数量都不是它的核心目的。

Q3. 词袋模型在文本向量化时丢失了哪类关键信息?

单选题 · mid

  • A.词语的出现频率
  • B.词序与上下文语义← 正确答案
  • C.文档的总长度
  • D.词汇表的覆盖范围

答案:B。词袋模型只统计“哪些词出现、出现了几次”,完全忽略词的先后顺序和组合语境。就像只看菜谱的“食材清单”却不知道“烹饪步骤”——“肉炒青椒”和“青椒炒肉”在词袋眼里是同一道菜。频率、长度、覆盖范围都能体现,唯独词序和语义丢失了。

Q4. 以下哪些属于常用的文本向量化方案?

多选题 · mid

  • A.词袋模型(Bag of Words)← 正确答案
  • B.TF-IDF加权表示← 正确答案
  • C.预训练语言模型Embedding← 正确答案
  • D.关系型数据库外键关联

答案:ABC。词袋模型、TF-IDF和预训练Embedding都是把文本映射为数值向量的方法。词袋最简单,TF-IDF增加了词的重要性加权,Embedding能捕捉语义关联。关系型数据库外键是数据表之间的关联方式,与文本向量化无关。

Q5. 两个归一化向量的余弦相似度取值范围是什么?

单选题 · hard

  • A.只能取0到1之间的值
  • B.可以取-1到1之间的任意值← 正确答案
  • C.最小值为0最大值为正无穷
  • D.取值范围没有上下界限制

答案:B。经过归一化后,向量模长为1,余弦相似度等于点积,由柯西-施瓦茨不等式可知其范围必在[-1,1]之间。1代表方向完全一致,-1代表方向相反。注意归一化前余弦相似度同样落在[-1,1],与向量长度无关,这是它常用作文本相似度度量的重要原因。

Q6. HNSW等ANN索引在向量数据库中的核心目标是什么?

单选题 · easy

  • A.减少向量存储的磁盘占用
  • B.提升Embedding模型的精度
  • C.替代文本清洗与切分环节
  • D.加速近似最近邻检索← 正确答案

答案:D。ANN(近似最近邻)索引的核心是“以轻微精度损失换数量级的速度提升”。HNSW通过多层小世界图,让查询在几十毫秒内从百万级向量中返回近邻,而不必做全量暴力扫描。它不负责存储压缩、模型精度和文本处理。

Q7. RAG系统的混合检索通常融合哪些检索通道?

多选题 · hard

  • A.关键词精确匹配(BM25)← 正确答案
  • B.语义向量检索← 正确答案
  • C.用户登录状态校验
  • D.全文倒排索引← 正确答案

答案:ABD。混合检索的目的是兼顾“精确”和“泛化”:BM25和倒排索引负责订单号、型号等精确匹配,语义向量负责意图理解。用户登录状态属于权限控制,与文本检索通道无关,不参与检索分数融合。

Q8. 重排序(Rerank)环节在RAG流水线中的位置与作用是什么?

单选题 · mid

  • A.在文档切分之前压缩文本长度
  • B.在初召回后对候选精排取top-k← 正确答案
  • C.在向量化前选择Embedding模型
  • D.在大模型生成后修正错别字

答案:B。Rerank位于初召回(粗排)之后、大模型生成之前。初召回可能返回50条候选,但大模型上下文窗口只能装5~10条。Rerank模型(如CrossEncoder)把query和每条候选拼接后精细打分,挑出最相关的几条,充当“配菜师傅最后把关”的角色。

Q9. RAG系统中增量更新策略主要解决什么问题?

单选题 · hard

  • A.向量维度灾难导致的存储膨胀
  • B.新文档频繁入库时全量重建效率低← 正确答案
  • C.大模型生成时的token超长截断
  • D.Embedding模型的过拟合风险

答案:B。生产环境文档每天都会变化,若每次更新都全量重建向量库,几百万条向量要跑数小时且占用大量资源。增量更新只对变化的文档重新解析、切分、向量化,并配合文档哈希与版本号管理,实现“几分钟内新知识生效”。它不解决维度灾难、token截断和模型过拟合问题。

Q10. 以下哪些属于RAG系统文档预处理的必要步骤?

多选题 · mid

  • A.清洗HTML标签等噪音← 正确答案
  • B.按语义边界或重叠窗口切分← 正确答案
  • C.用Embedding模型编码向量← 正确答案
  • D.对用户进行实名认证

答案:ABC。RAG文档预处理的核心链路是“解析清洗 → 文本切分 → 向量化编码”,三者缺一不可,直接决定检索质量。用户实名认证属于系统安全与合规层面,与文档内容的处理链路无关,不会影响语义检索的效果。