楼主: yinzhipeng
251 1

[其他] 破解大模型“知识盲区”——RAG技术原理与实践 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

学前班

40%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
20 点
帖子
1
精华
0
在线时间
0 小时
注册时间
2018-10-10
最后登录
2018-10-10

楼主
yinzhipeng 发表于 2025-11-24 18:55:00 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币
在上一篇文章中,我们探讨了AI的本质是概率模型,其核心机制在于通过计算概率分布来生成最优输出。同时,借助Dify等开源平台,我们也初步了解了大模型交互如何实现工程化落地。然而在实际应用过程中,虽然大模型能轻松回答“李白出生于哪一年”这类常识性问题,但一旦面对诸如“2025年某行业的最新政策解读”或“某企业内部产品研发规范”等专业性强、时效性高的问题时,往往会出现答非所问,甚至“一本正经地胡说八道”的情况。 这并非意味着大模型的能力下降,而是其内在存在固有的“知识短板”。接下来,我们将深入剖析这一问题的根源,并介绍一项关键解决方案—— RAG(检索增强生成)技术,它为大模型提供了如同“专业知识外挂”般的强大支持。

一、大模型的“知识盲区”:为何会“胡说八道”?

要真正理解RAG的价值,必须先认清大模型“知识缺失”的本质。很多人误以为大模型像一个无所不知的“活字典”,实际上它的“知识”完全来源于训练数据,而这些数据本身存在三个难以克服的局限:
  1. 知识的时间滞后性:无法获取实时信息
    大模型的训练数据都有明确的截止时间点。尽管厂商会定期更新模型版本,但由于训练成本极高,不可能频繁进行全量训练。因此,模型对新发生的事件、政策变化或行业动态缺乏感知能力,导致其在处理时效性强的问题时表现不佳。
  2. 垂直领域数据匮乏:专业内容覆盖不足
    训练数据多以通用语料为主,涵盖日常对话、百科知识、新闻资讯等广泛主题,但在医疗、法律、金融等高度专业化领域,相关高质量数据占比极低。例如,当询问“某种罕见病的前沿治疗方案”时,模型只能基于过往公开资料作答,难以引用近期发表于权威期刊的临床研究成果,因为这些内容可能未被纳入原始训练集。
  3. 私有数据不可见:企业内部信息无法触达
    对企业和组织而言,最关键的业务知识往往存储在内部系统中,如产品代理政策、客户服务响应标准、研发流程文档等。这些数据不会出现在公开网络中,自然也不会进入大模型的训练体系,导致AI无法访问和利用这些关键信息。
有人可能会提出:“是否可以通过微调(Fine-tuning)将这些知识注入模型?”理论上可行,但实践中代价高昂:需要大量标注样本、强大的算力支撑,且每次知识更新都需重新训练,维护成本高、灵活性差。相比之下,RAG提供了一种更高效、灵活的替代路径——不改变模型本身,而是通过外部检索机制为其补充精准知识。

二、RAG的核心原理:从依赖记忆到实时查证的跃迁

RAG的基本思想非常直观,类似于人类写作时的“查阅资料+撰写结论”过程:先查找权威来源的信息,再据此组织语言输出。在技术层面,这个过程被自动化为两个步骤—— 检索 → 生成。 [此处为图片1]

1. 核心机制:用“精确检索结果”替代“模糊记忆推断”

传统模式下,大模型的回答依赖于训练阶段形成的“概率记忆”。比如被问及“某疾病的治疗方式”,它会根据训练数据中高频出现的内容推测最可能的答案。而引入RAG后,整个流程发生根本转变:
  • 用户提问:“2025年某行业最新的税收优惠政策是什么?”
  • 系统执行检索:自动从预构建的“2025年行业政策知识库”中提取与“税收优惠”相关的原文段落。
  • 模型生成回答:基于检索到的真实政策文本,整合成清晰易懂的答复,并可附带信息出处说明。
这一机制的关键在于:模型不再凭“印象”作答,而是依据“真实可查”的资料生成回应,从而从根本上避免了虚构和误导。

2. 技术流程详解:从数据准备到答案输出的完整链条

看似简单的“检索+生成”背后,实则包含三大核心技术环节。下面我们逐层拆解:
环节一:知识库构建 —— 实现专业内容的“机器可读化”
RAG的前提是有可供检索的专业知识源。该阶段的目标是将分散的文档(如政策文件、操作手册、技术白皮书)转化为结构化、可检索的形式。主要分为以下三步: 第一步:文档切分(Chunking)—— 将长文本拆解为语义完整的片段
直接将数百页的PDF丢给系统无法实现精准定位。因此需将文档切割成若干小块(Chunk),每个块承载一个独立的知识单元。切分讲究策略:过细会导致上下文断裂,过粗则影响检索精度。通常建议按段落或小节划分,每块控制在200–500字之间,确保语义完整性和检索效率。 第二步:向量化嵌入(Embedding)—— 赋予文本“语义坐标”
计算机无法直接理解文字含义,因此需使用Embedding模型(如BGE、Sentence-BERT)将每个文本块转换为一组数字向量。其核心逻辑是:语义相近的句子,其向量距离也更接近。例如,“减税措施”与“税收减免”会被映射到相似的空间位置,而与“天气预报”相距甚远。这种表示方式实现了基于语义而非关键词的匹配能力。 第三步:向量数据库存储 —— 搭建“智能知识书架”
所有向量化后的文本片段被存入专用的向量数据库(如Milvus、Pinecone、Weaviate),形成一个支持快速语义搜索的知识仓库。就像图书馆给书籍编号上架,这套系统能让AI在毫秒级时间内找到最相关的内容。 [此处为图片2]

环节一:向量存储——为何选择向量数据库?

经过转化的文本向量需存入专用的“向量数据库”中,例如 Milvus、Chroma 或 Pinecone,而非传统的 MySQL 等关系型数据库。这是因为向量数据库具备高效计算“相似度”的能力——当用户提问被转化为向量后,系统可从百万甚至千万级别的向量数据中,迅速定位最相近的若干个文本片段(Chunk)。这就像一个智能化的书架,当你提出“查找有关税收优惠的信息”时,它能立即找出所有相关的“便签条”内容。

[此处为图片1]

环节二:信息检索——精准匹配相关结果

在用户发起提问后,系统首先使用与构建知识库相同的 Embedding 模型,将问题转换为向量形式,随后利用该向量在向量数据库中进行“相似性搜索”。这一过程并非精确匹配关键词,而是基于语义相似度进行排序,通常返回 Top5 至 Top10 最相关的 Chunk 作为候选结果。

为了进一步提升检索准确率,常采用多种优化策略:

  • 多轮检索:若首轮检索效果不佳,系统可根据原始问题和初步结果生成更精准的查询表达式,进行二次或多次检索,逐步逼近最优答案。
  • 权重调节机制:对最新发布的文档或权威来源赋予更高的检索权重,使其在排序中优先出现,从而增强结果的时效性与可信度。

环节三:生成控制——引导大模型基于事实作答

检索到的相关文本片段不能直接呈现给用户,还需通过大语言模型进行整合与润色,输出自然流畅的回答。关键在于“Prompt 工程”的设计:将用户问题与检索出的多个 Chunk 内容组织成结构化提示词,并明确指令:“请依据以下参考资料作答,禁止虚构信息;若资料中无相关内容,请如实说明‘未查询到相关信息’。”

典型的 Prompt 模板如下:

参考资料:
[Chunk1内容] 来源:《2025年某行业税收优惠政策通知》
[Chunk2内容] 来源:某税务局官方解读文件

用户问题:2025年某行业最新税收优惠政策是什么?

请基于上述参考资料,用简洁的语言回答用户问题,并在回答末尾标注信息来源。如果参考资料中没有相关信息,请勿编造,直接说明“未查询到相关信息”。

通过这种机制,大模型的回答将严格依赖于检索所得的真实资料,同时附带引用出处,显著增强了输出内容的可验证性与专业可信度。

3. RAG 与 微调对比:为何 RAG 更适合专业领域应用?

关于“RAG 和微调如何选择”的疑问较为常见,以下是两者的详细对比,帮助明确各自的适用边界:

对比维度 RAG(检索增强生成) 微调(Fine-tuning)
数据成本 较低,无需标注数据,可直接使用原始文档 较高,需大量高质量标注训练样本
算力消耗 较低,仅需完成向量化及检索计算 较高,依赖大规模模型训练资源
知识更新速度 快,只需更新知识库即可实时生效 慢,需重新准备数据并执行完整微调流程
可解释性 高,回答内容可追溯至具体参考资料 低,输出基于模型内部“记忆”,难以溯源
典型应用场景 专业知识问答、时效性强的问题响应、企业内部知识查询 模型风格定制、特定任务优化(如翻译、摘要生成等)

三、基于 LangChain4j 的 RAG 实现方案

在理解了 RAG 的核心原理之后,接下来我们将借助 LangChain4j——一款专为 Java 生态打造的大模型开发框架,更适合集成进企业级业务系统——来实现完整的 RAG 流程。相较于 Python 生态中的 LangChain,LangChain4j 能更好地兼容 Spring Boot 等主流企业级开发框架,便于后续系统对接与部署。

3.1 整体架构设计

RAG 的实现分为两个主要阶段:

  1. 索引阶段(Indexing):对原始文档进行处理,并将其向量化后存入向量数据库。
  2. 检索阶段(Retrieval):在接收到用户查询时,从数据库中检索相关内容,并注入 Prompt 中供模型生成回答。

核心组件包括:

  • EmbeddingStoreIngestor:负责索引阶段的数据摄入与分段处理。
  • RetrievalAugmentor:主导检索阶段的协调工作。
  • DefaultRetrievalAugmentor:默认实现类,用于整合各模块协同运行。

3.2 索引阶段的具体实现

核心类为 EmbeddingStoreIngestor,其主要处理流程如下:

public IngestionResult ingest(List<Document> documents) {
    log.debug("Starting to ingest {} documents", documents.size());
    if (documentTransformer != null) {
        documents = documentTransformer.transformAll(documents);
        log.debug("Documents were transformed into {} documents", documents.size());
    }
    List<TextSegment> segments;
    if (documentSplitter != null) {
        segments = documentSplitter.splitAll(documents);
        log.debug("Documents were split into {} text segments", segments.size());
    } else {
// 文档转换阶段(可选)
if (documentTransformer != null) {
    documents = documentTransformer.transformAll(documents);
}
[此处为图片1]

对原始文档进行清洗、标准化处理,并补充或优化元数据信息,提升后续处理的准确性与一致性。

文档分割处理(关键步骤)

if (documentSplitter != null) { segments = documentSplitter.splitAll(documents); } else { segments = documents.stream().map(Document::toTextSegment).collect(java.util.stream.Collectors.toList()); } 将较大的文档切分为更小的文本片段(TextSegment),便于后续处理。 主要原因包括: - 适应大模型有限的上下文窗口; - 提高检索时的精确度; - 有效控制计算资源消耗和使用成本。 常用分割策略:基于段落或句子进行递归切分,支持设置重叠区域以保留上下文连贯性。

文本片段后处理(可选)

if (textSegmentTransformer != null) { segments = textSegmentTransformer.transformAll(segments); } [此处为图片2] 此步骤可用于增强文本段的信息表达,例如在每个片段前添加所属文档的标题、章节名或生成简要摘要,从而提升向量检索的相关性和效果。

生成向量表示(核心环节)

log.debug("开始对 {} 个文本片段进行向量化", segments.size()); Response<List<Embedding>> embeddingsResponse = embeddingModel.embedAll(segments); log.debug("完成对 {} 个文本片段的向量化", segments.size()); 利用指定的 EmbeddingModel 将文本内容转化为高维向量。 该向量能够捕捉文本的语义特征,语义相近的内容在向量空间中的距离也更接近。

存入向量数据库

log.debug("开始将 {} 个文本片段及其向量存储至向量库", segments.size()); embeddingStore.addAll(embeddingsResponse.content(), segments); log.debug("已完成向量数据存储"); 将生成的向量与对应的原始文本段一并写入 EmbeddingStore 中,以便后续快速检索。 支持多种主流向量数据库实现,如 Pinecone、Milvus、Qdrant 等。

3.3 检索阶段(Retrieval)实现说明

核心组件:DefaultRetrievalAugmentor 检索执行流程如下: @Override public AugmentationResult augment(AugmentationRequest augmentationRequest) { ChatMessage chatMessage = augmentationRequest.chatMessage(); String queryText; if (chatMessage instanceof UserMessage userMessage) { queryText = userMessage.singleText(); } else { throw new IllegalArgumentException("不支持的消息类型: " + chatMessage.type()); } } [此处为图片3]
ChatMessage augmentedChatMessage = contentInjector.inject(contents, chatMessage);
List<Content> contents = contentAggregator.aggregate(queryToContents);
Map<Query, Collection<List<Content>>> queryToContents = process(queries);
Collection<Query> queries = queryTransformer.transform(originalQuery);
Query originalQuery = Query.from(queryText, augmentationRequest.metadata());

return AugmentationResult.builder()
    .chatMessage(augmentedChatMessage)
    .contents(contents)
    .build();

执行流程解析

阶段一:查询变换(Query Transformation)

系统首先对原始查询进行转换处理:

Collection<Query> queries = queryTransformer.transform(originalQuery);

基础实现类 DefaultQueryTransformer 不做额外扩展,仅保留原查询:

public Collection<Query> transform(Query query) {
    return singletonList(query);  // 返回单一元素的列表,内容为原始查询
}

而更复杂的实现如 ExpandingQueryTransformer,则利用大语言模型(LLM)生成多个语义相近但表达不同的查询变体。这种方式有助于覆盖更多潜在匹配信息,从而提升内容召回的广度与准确性。

[此处为图片1]

阶段二:查询分发与内容获取(Query Routing & Retrieval)

接下来进入核心处理逻辑 process(queries) 方法,该方法根据查询数量和路由策略决定如何检索内容:

private Map<Query, Collection<List<Content>>> process(Collection<Query> queries) {
    if (queries.size() == 1) {
        Query query = queries.iterator().next();
        Collection<ContentRetriever> retrievers = queryRouter.route(query);
        
        if (retrievers.size() == 1) {
            ContentRetriever retriever = retrievers.iterator().next();
            List<Content> contents = retriever.retrieve(query);
            return singletonMap(query, singletonList(contents));
        } 
        else if (retrievers.size() > 1) {
            Collection<List<Content>> contents = retrieveFromAll(retrievers, query).join();
            return singletonMap(query, contents);
        } 
        else {
            return emptyMap();
        }
    } 
    else if (queries.size() > 1) {
        Map<Query, CompletableFuture<Collection<List<Content>>>> queryToFutureContents = new ConcurrentHashMap<>();
        queries.forEach(query -> {
            CompletableFuture<Collection<List<Content>>> futureContents =
                supplyAsync(() -> queryRouter.route(query), executor)
                .thenCompose(retrievers -> retrieveFromAll(retrievers, query));
            queryToFutureContents.put(query, futureContents);
        });
        return join(queryToFutureContents);
    } 
    else {
        return emptyMap();
    }
}

此过程根据传入的查询集合大小分为三种情况处理:

  • 单个查询:通过 queryRouter 确定一个或多个内容检索器(ContentRetriever)。若存在多个检索器,则并行调用各检索器获取结果,并合并返回;若无匹配检索器,则返回空映射。
  • 多个查询:对每个查询独立启动异步任务,分别完成路由与内容拉取,最终统一等待所有任务完成后再汇总结果。这种设计提升了整体吞吐效率,尤其适用于扩展后的多变体查询场景。
  • 无查询输入:直接返回空结果集,避免无效操作。

整个流程充分考虑了灵活性与性能平衡,支持同步与异步混合模式,确保在复杂查询环境下仍能高效响应。

[此处为图片2]

步骤 3:内容检索(Content Retrieval)

在内容检索阶段,核心实现类为 EmbeddingStoreContentRetriever,其主要职责是通过向量相似度匹配从存储中获取最相关的内容。以下是该类的关键方法:

public List<Content> retrieve(Query query) {
    Embedding embeddedQuery = embeddingModel.embed(query.text()).content();
    EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder()
        .queryEmbedding(embeddedQuery)
        .maxResults(maxResultsProvider.apply(query))
        .minScore(minScoreProvider.apply(query))
        .filter(filterProvider.apply(query))
        .build();
    EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(searchRequest);
    return searchResult.matches().stream()
        .map(embeddingMatch -> Content.from(
            embeddingMatch.embedded(),
            Map.of(
                ContentMetadata.SCORE, embeddingMatch.score(),
                ContentMetadata.EMBEDDING_ID, embeddingMatch.embeddingId()
            )
        ))
        .collect(Collectors.toList());
}

执行流程如下:

  1. 将输入的查询文本转换为嵌入向量:embeddingModel.embed(query.text())
  2. 构建向量搜索请求,配置最大返回结果数、最低相似度阈值以及过滤条件
  3. 调用向量数据库进行相似性搜索:embeddingStore.search(searchRequest)
  4. 将匹配到的结果转换为 Content 对象列表,并按相似度得分排序返回
[此处为图片1]

支持的检索器类型

系统提供多种检索器实现,可根据不同场景选择使用:

  • EmbeddingStoreContentRetriever:基于向量数据库的语义检索
  • WebSearchContentRetriever:通过网络搜索引擎获取实时信息
  • AzureAiSearchContentRetriever:支持混合模式的高级检索,结合关键词与向量搜索

当存在多个检索源时,系统可并行调用各检索器以提升响应效率和召回率。

智能路由机制

LanguageModelQueryRouter 利用大语言模型(LLM)分析用户查询意图,自动判断并选择最合适的数据源或检索策略,实现智能化的查询分发。

而默认的路由策略由 DefaultQueryRouter 实现,负责将查询请求定向至预设的目标检索器。

步骤 4:内容聚合(Content Aggregation)

为了整合来自多个检索通道的结果,系统采用 Reciprocal Rank Fusion (RRF) 算法进行融合排序。

public static List<Content> fuse(Collection<List<Content>> listsOfContents, int k) {
    ensureBetween(k, 1, Integer.MAX_VALUE, "k");
    Map<Content, Double> scores = new LinkedHashMap<>();
    for (List<Content> singleListOfContent : listsOfContents) {
        for (int i = 0; i < singleListOfContent.size(); i++) {
            Content content = singleListOfContent.get(i);
            double currentScore = scores.getOrDefault(content, 0.0);
            int rank = i + 1;
            double newScore = currentScore + 1.0 / (k + rank);
            scores.put(content, newScore);
        }
    }
    List<Content> fused = new ArrayList<>(scores.keySet());
    fused.sort(Comparator.comparingDouble(scores::get).reversed());
    return fused;
}

RRF 融合算法的核心逻辑包括:

  • 评分公式为:score = 1.0 / (k + rank),其中 k 是调节参数(通常取值为 60),rank 表示内容在单个结果列表中的位置(从 1 开始计数)
  • 同一内容若出现在多个检索结果中,其得分将累加
  • 最终结果按总得分降序排列,确保高相关性内容排在前列

示例说明:

假设有两个检索结果列表:

  • 列表1: [cat, dog]
  • 列表2: [cat, parrot]

经 RRF 融合后,"cat" 因在两个列表中均靠前出现,获得更高综合得分,最终排序优先级最高。

[此处为图片2]

cat: 1/(60+1) + 1/(60+1) = 0.0328

dog: 1/(60+2) = 0.0161

parrot: 1/(60+2) = 0.0161

最终排序结果为:[cat, dog, parrot](由于 cat 出现两次,其排名更靠前)

DefaultContentAggregator 采用两阶段的 RRF 融合策略:

@Override
public List<Content> aggregate(Map<Query, Collection<List<Content>>> queryToContents) {
    // 第一阶段:针对每个查询,融合来自多个检索源的内容
    Map<Query, List<Content>> fused = fuse(queryToContents);
    // 第二阶段:将所有查询对应的结果进行统一融合
    return ReciprocalRankFuser.fuse(fused.values());
}

第一阶段任务:对每一个独立的查询,整合不同来源返回的内容列表。

第二阶段任务:将各个查询所获得的内容结果再次融合,生成全局排序后的最终内容序列。

步骤 5:内容注入(Content Injection)

DefaultContentInjector 负责将检索到的相关内容嵌入原始用户消息中,具体实现如下:

@Override
public ChatMessage inject(List<Content> contents, ChatMessage chatMessage) {
    if (contents.isEmpty()) {
        return chatMessage;
    }
    Prompt prompt = createPrompt(chatMessage, contents);
    if (chatMessage instanceof UserMessage userMessage) {
        return userMessage.toBuilder()
            .contents(List.of(TextContent.from(prompt.text())))
            .build();
    } else {
        return prompt.toUserMessage();
    }
}

系统默认使用的提示模板定义如下:

public static final PromptTemplate DEFAULT_PROMPT_TEMPLATE = PromptTemplate.from(
"""
{{userMessage}}
Answer using the following information:
{{contents}}""");

经过注入处理后生成的实际提示示例如下:

用户问题:什么是 RAG?
请使用以下信息回答:
[检索到的相关内容1]
[检索到的相关内容2]
[检索到的相关内容3]

[此处为图片1]

3.4 完整数据流图

四、总结

RAG 技术的核心机制在于:通过“从外部知识库中检索相关信息”(通常基于向量相似度匹配),并将这些相关片段补充进用户的原始输入提示中。这种方式使得大语言模型能够访问并利用原本不具备的新知识,有效弥补其在专业领域知识、时效性信息以及私有数据方面的不足。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:Augmentation Aggregation information transformed embeddings

沙发
老马识途99 发表于 2025-11-25 10:59:26
感谢分享。

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
加好友,备注jltj
拉您入交流群
GMT+8, 2025-12-5 23:17