这道题问的是[[RAG]]的两阶段架构,说白了就是给大模型外挂一个知识库,让它在生成答案时能参考外部文档,而不是完全依赖自己的参数记忆。我把它拆成离线和在线两个阶段来讲。
先说离线阶段,目标是把原始文档转成机器能快速检索的[[向量库]]。第一步是文档解析,处理PDF、Word、网页这些乱七八糟的格式,把文本抽出来。这一步看着简单,但PDF里经常有表格、图片、页眉页脚,处理不好就会带进很多噪声。第二步是[[分块]],把长文档切成小块。这里有个核心矛盾:块太大,一个块里混了多个主题,检索时容易带进不相关的信息;块太小,上下文不完整,LLM没法理解。所以分块策略很关键,常见做法是按固定长度切,但更鲁棒的是按语义边界切,比如自然段或者章节。第三步是把每个块用 [[Embedding]] 模型转成向量,比如用BGE或者M3E。第四步是存到 Vector Database 里,像Milvus、[[Faiss]]或者Elasticsearch,同时建好索引方便快速检索。离线阶段追求的是覆盖全、更新快,通常用定时任务或者事件驱动来增量更新,而不是全量重建。
在线阶段就是用户来了一个Query,怎么用这个知识库生成答案。流程是这样的:用户Query进来,先做Query理解,比如改写或者扩展,因为用户可能输入得很随意,比如“退款多久到账”和“退货钱什么时候退”其实是一个意思。然后去向量库做检索,用向量相似度召回Top-K个候选文档。这里有个坑:纯向量检索容易漏掉精确匹配的场景,比如订单号或者错误码。所以落地时我通常会把向量检索和关键词检索结合起来做 [[Hybrid Search]],比如用 [[BM25]] 做关键词召回,再融合排序。召回的文档不一定都相关,所以下一步是[[重排]],用 [[Cross-Encoder]] 模型精排,把最相关的文档排到前面。重排之后就是上下文融合,把检索到的文档按相关性排序,截断到LLM能接受的token长度,然后组装成Prompt。最后交给LLM生成答案。这里要特别注意拒答场景,如果检索到的文档跟Query完全不相关,LLM应该明确说“没有找到相关信息”,而不是硬编一个答案。
说到边界划分,RAG适合场景是知识库频繁更新、需要引用外部证据的,比如企业内部的SOP文档或者合规政策;而微调适合让模型学会固定的格式或风格,比如客服话术模板。检索层面,关键词搜索适合精确匹配,比如订单号;向量搜索适合语义匹配,比如“退款政策”和“退货规则”意思相近。分块层面,固定切分简单但容易断掉语义,语义切分更准但计算成本高。真正落地的系统往往是混合策略,比如用固定长度切块,同时保留段落边界做软约束。
落地时我特别关注几个风险。一是分块策略必须跟业务场景匹配,比如客服场景里,退款政策是一个完整段落,如果切成两半,LLM可能只看到一半的政策,回答就不准。二是检索质量,如果Top-K召回的文档里混了噪声,LLM会被带偏。所以上线前我会用一批历史Query做回放,对比Recall@K和答案准确率。三是延迟,在线阶段每一步都有开销,尤其是重排和LLM生成,所以我会用缓存和异步处理来优化。
还有一个细节值得深挖,就是分块粒度对检索的影响。比如一份技术文档,按固定256字切块,可能把一个API说明切成两半,检索时只召回前半段,LLM就不知道完整用法。而如果按章节切,块太大,检索时又可能带进不相关的内容。所以我会根据文档类型动态调整分块策略,比如代码文档按函数切,政策文档按条款切。
所以整体上,我会把RAG系统看成两个解耦的管道,离线管道保证数据新鲜度和覆盖率,在线管道保证召回质量和响应速度。两者独立迭代,但最终都要用业务指标来验证,比如客服场景里客户满意度有没有提升。