Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >AI大模型企业应用实战-“消灭”LLM幻觉的利器 - RAG介绍

AI大模型企业应用实战-“消灭”LLM幻觉的利器 - RAG介绍

原创
作者头像
JavaEdge
修改于 2025-05-26 07:52:01
修改于 2025-05-26 07:52:01
3160
举报
文章被收录于专栏:AIGC大模型应用AIGC大模型应用

本文已收录在Github关注我,紧跟本系列专栏文章,咱们下篇再续!

  • 🚀 魔都架构师 | 全网30W技术追随者
  • 🔧 大厂分布式系统/数据中台实战专家
  • 🏆 主导交易系统百万级流量调优 & 车联网平台架构
  • 🧠 AIGC应用开发先行者 | 区块链落地实践者
  • 🌍 以技术驱动创新,我们的征途是改变世界!
  • 👉 实战干货:编程严选网

1 LLM的问题

1.1 幻觉

LLM是预训练模型,已有一些知识储备,我们提的问题跟他的知识储备不相符时,就会产生幻觉,也就是看上去正确的回答。

1.2 新鲜度

LLM预训练后,不能感知到实时更新的业界发展数据,还有企业内部私域数据。

1.3 数据安全

LLM训练依赖很多训练数据集,为保证LLM效果更好,训练集质量及数据量越多,对LLM训练最终效果更好,但又期望LLM帮解决一些垂类问题,又希望在数据安全有些防范,如企业内部敏感数据不能暴露,让公有LLM去进行训练。

2 RAG是啥?

为解决LLM刚提到问题,提出RAG,将企业内部私域数据及实时更新的一些公域数据,通过一些处理后,变成可进行相似性搜索的向量数据,然后存储到向量数据库

和LLM交互时,用户提问。先在我们的相同数据库中进行相似性检索,检索与提问相关的知识内容,检索后交给LLM,连同用户的提问一起让 LLM 去生成回复。

RAG帮助我们个人及用户去把企业内部的一些知识数据,很快构建出一个庞大知识库,然后结合目前已有LLM能力,可快速制作智能问答机器人应用。

小结

为LLM提供来自外部知识源的额外信息的概念。这允许它们生成更准确和有上下文的答案,同时减少幻觉

  • 检索:外部相似搜索
  • 增强:提示词更新
  • 生成:更详细的提示词输入LLM

3 RAG应用咋构建?

3.1 无RAG的传统AI问答

就像这样:

代码语言:java
AI代码解释
复制
public class TraditionalAI {
    private StaticKnowledge knowledgeBase; // 训练时固化的知识
    
    public String answer(String question) {
        // 只能基于内置知识回答
        return knowledgeBase.search(question);
    }
}

就像一个只依赖内存的Java应用:

  • AI模型就像一个巨大的静态数据结构,所有知识都"硬编码"在模型参数中
  • 当用户问问题时,AI只能基于训练时学到的知识回答
  • 图中显示:对于OpenAI CEO的问题,没有RAG的系统回答"我无法提供评论...目前我没有关于CEO解雇和重新雇佣的信息"
代码语言:java
AI代码解释
复制
// 类比:没有RAG就像这样
public class TraditionalAI {
    private StaticKnowledge knowledgeBase; // 训练时固化的知识
    
    public String answer(String question) {
        // 只能基于内置知识回答
        return knowledgeBase.search(question);
    }
}

3.2 有RAG的知识库问答系统

RAG步骤:

  1. 知识切片成Chunk
  2. 向量化Chunk入库

前两步都是去知识库生成。

  1. Query检索知识Chunk
  2. 构建Prompts
  3. 调用LLM生成回答

后三步都是知识库生成后,在检索方面需要做的。

就像一个连接了实时数据库的Java应用:

① 索引阶段(Indexing)
代码语言:java
AI代码解释
复制
// 类比:文档预处理和索引
public class DocumentIndexer {
    public void indexDocuments(List<Document> docs) {
        for (Document doc : docs) {
            // 1. 切分文档为chunks
            List<String> chunks = splitIntoChunks(doc);
            
            // 2. 生成向量embeddings
            for (String chunk : chunks) {
                Vector embedding = embeddingModel.encode(chunk);
                vectorDB.store(chunk, embedding);
            }
        }
    }
}
② 检索阶段(Retrieval)
代码语言:java
AI代码解释
复制
public class RAGSystem {
    private VectorDatabase vectorDB;
    private LLM llm;
    
    public String answer(String question) {
        // 1. 将问题转换为向量
        Vector questionVector = embeddingModel.encode(question);
        
        // 2. 从向量数据库检索相关文档
        List<String> relevantChunks = vectorDB.similaritySearch(
            questionVector, topK = 3
        );
        
        // 3. 结合检索到的信息和问题生成答案
        String context = String.join("\n", relevantChunks);
        String prompt = buildPrompt(question, context);
        
        return llm.generate(prompt);
    }
}

3.3 关键技术差异

向量化检索
代码语言:java
AI代码解释
复制
// RAG的核心:语义相似性搜索
public List<String> findRelevantInfo(String query) {
    Vector queryVector = embeddingModel.encode(query);
    
    // 不是关键词匹配,而是语义相似度
    return vectorDB.cosineSearch(queryVector, threshold = 0.8);
}
动态上下文注入
代码语言:java
AI代码解释
复制
public String generateAnswer(String question, List<String> context) {
    String prompt = String.format(
        "基于以下信息回答问题:\n%s\n\n问题:%s", 
        String.join("\n", context), 
        question
    );
    return llm.complete(prompt);
}

3.4 效果对比

没有RAG:

  • ❌ 无法回答训练数据之后的新信息
  • ❌ 知识更新需要重新训练模型
  • ❌ 无法处理私有/企业内部数据

有RAG:

  • ✅ 能够回答基于最新文档的问题
  • ✅ 图中显示:系统检索到相关的OpenAI新闻chunks,生成了详细分析
  • ✅ 可以随时更新知识库而不需要重训模型
  • ✅ 支持企业私有数据查询

简单类比:

  • 没有RAG = 一个只能背书本的学生
  • 有RAG = 一个可以随时查阅图书馆和互联网的学生

这就是为什么RAG成为企业AI应用的主流架构 - 它让AI能够访问和利用实时、私有的数据源。

  • 使用RAG链路后,用户先去构建好的知识库,即向量数据库里进行相似性检索,再带出一部分知识文档。这部分知识文档会跟用户的query结合
  • 再通过prompt技术组装成一个最终完成的一个输入给到LLM,让LLM回复

最关键就是知识库生成这步,主要涉及把知识文档去做内容提取及拆分。还要进行量化,入库。

4 基于Langchain构建 RAG 应用

4.1 Langchain中RAG实现

各种文档 - 各种 loader - 文本切片 - 嵌入向量化 - 向量存储 - 各种检索链。

4.2 设计思想

RAG五步拆成不同组件,再由不同节点处理。让用户去编写业务逻辑代码,再把这整个过程串起。

4.3 优势

  • 可快速构建一个demo,助开发者理解RAG应用
  • 庞大社区支持,如一些插件或它的一个版本更新迭代都很快

4.4 痛点

本质上通用性很强。为保证强通用性,效果层面不一定做到最好,需企业或个人投入较大精力,把整体的RAG在召回层的效果提升到最佳。

5 bad case

构建整个RAG应用过程中会遇到的问题解决方案。

5.1 拒答

用户提问:请问A产品分析报告多久分析一次?

召回的相关知识:A产品的分析报告信息近30天的数据分析结果。

因为用户的问题,在相关知识没明确提到,只是有一定相似度,但跟我们用户问题不直接相关。这样的相关知识及用户问题,组装后交给LLM回答,本质上是人为制造干扰。

对此,有个工程化实践叫拒答。

5.2 消歧

提问:A课程适合多大年龄小孩。

知识库召回两条数据:

  • 其中一条是期望的一个知识,就在A课程文档,有一段话跟提问相关
  • 但还会召回其他的一个干扰知识。如其他文档里一些内容,像该课程适合3到7岁的小孩,适合6到8岁的女孩。这种知识内容也会被召回

期望的召回内容携带一部分干扰信息,这干扰信息没有A课程这关键字,然后也不会召回。在这两个知识内容交给大源模型处理,他也无法理解哪个字内容正确。

更希望在召回层,就有较好手段处理。工程化实践里,会对用户提问进行改写,增强query的一个效果。

也用到类似BM25这种倒排索引,提升关键字的权重。如干扰知识里没生成这个关键字,其相似度分数较低,就不会召回。

5.3 分类

可能有用户的提问类似:服务器连接不上,应当如何解决?

现在给知识库里面注入的文档,都是类似连接服务器应该有哪些步骤。

将这些知识内容召回,交给LLM也能引导用户。但不能直切要害,用户更希望,我现在连接不上,有啥排查手段。更好的还是通过提供一些专门QA文档,增强整个知识召回内容准确性。

用户可能问一些跟他实例相关的问题。如CPU占用变高或内存变高,实际响应可能是技术支持文档里的一些处理方案,就是我现在内存变更咋处理。但用户想知道为啥变高。有一个意图识别模型,判断用户他想要的问题具体是一个什么类的,需不需要用到RAG,也会判断他是否需要用到诊断引擎。类似问题2,需要用到诊断引擎,那我们会调用其他RAG无关的诊断相关技术为用户排查问题,并且给用户反馈一个结果。

6 咋提升RAG应用效果?

$$

整体效果 = 文档处理效果 Embedding效果 Retrieval效果 * LLM效果

$$

demo易,但上手难,主要因为LangChain、LLamIndex框架盛行。很快接入就是初级的一个状态,可能只做到35%。

想提高整体准确率,在拆分那儿会拆更合理、提取内容时,把整个内容提取更好。做向量化时,去选择我们的向量,更好的一个embedding模型。

最终跟LLM交流时,选择效果更好的LLM,把这效果提升到更高。

但60%的准确率还是达不到生产期望。希望准确率90%,在RAG应用构建各阶段,都有很多工程化手段。

目前RAG整体应用在界内的比较关注的一个地方就是在召回。因为涉及知识文档,思考方向:

  • 优先保证召回率
  • 优先保证精度

RAG召回是希望获得更多和用户提问相关的知识内容,还是只需更关键的知识内容排在最顶。某云厂商相关数据库AI套件选择前路,期望召回更多跟用户相关的提问的内容。

精度尽量保证召回内容在top3、top5位置出现,因为召回的一些内容确实有一部分干扰信息。但目前LLM能力尚可,对这种干扰性信息的排除能力较好。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Groovy】Groovy 脚本调用 ( Groovy 脚本中调用另外一个 Groovy 脚本 | 调用 evaluate 方法执行 Groovy 脚本 | 参数传递 )
在 【Groovy】Groovy 脚本调用 ( Groovy 脚本编译 | Groovy 脚本字节码文件分析 ) 博客中 , 已经分析了 Groovy 脚本的本质 , Groovy 脚本继承了 groovy.lang.Script 类 ;
韩曙亮
2023/03/30
2.3K0
【Groovy】Groovy 脚本调用 ( Groovy 脚本中调用另外一个 Groovy 脚本 | 调用 evaluate 方法执行 Groovy 脚本 | 参数传递 )
【Groovy】Groovy 脚本调用 ( Groovy 脚本中调用另外一个 Groovy 脚本 | 绑定作用域 binding 变量分析 | Binding 类 variables 成员分析 )
分析 groovy.lang.Script 类的 evaluate 方法源码 , 在该方法中 , 创建了一个 GroovyShell 对象 ;
韩曙亮
2023/03/30
1.4K0
【Groovy】Groovy 脚本调用 ( Groovy 脚本中调用另外一个 Groovy 脚本 | 绑定作用域 binding 变量分析 | Binding 类 variables 成员分析 )
【Groovy】Groovy 脚本调用 ( Java 类中调用 Groovy 脚本 )
Java 类中调用 Groovy 脚本 , 与 Groovy 类中调用 Groovy 脚本 , 代码基本类似 ;
韩曙亮
2023/03/30
2.3K0
【Groovy】Groovy 脚本调用 ( Java 类中调用 Groovy 脚本 )
【Groovy】Groovy 脚本调用 ( Groovy 类中调用 Groovy 脚本 | 创建 GroovyShell 对象并执行 Groovy 脚本 | 完整代码示例 )
首先 , 创建 GroovyShell 对象 , 在构造函数中 , 需要传入 Binding 对象 ;
韩曙亮
2023/03/30
2.4K0
【Groovy】Groovy 脚本调用 ( Groovy 类中调用 Groovy 脚本 | 创建 GroovyShell 对象并执行 Groovy 脚本 | 完整代码示例 )
14. Groovy 语言结构-脚本和类知识学习
本篇内容为Groovy学习第十四篇内容。本篇内容为Groovy语言中的脚本(Script)和类(classes)的知识。
zinyan.com
2022/12/07
1.3K0
Groovy 笔记
Groovy 是基于 JVM 的 动态脚本语言,可以与 Java 无缝对接。Groovy 并不替代 Java,而是与 Java 互补
tonglei0429
2019/07/24
1.3K0
21. Groovy 面向对象编程-Traits特性学习-第二篇
本篇内容为Groovy学习第二十一篇,上篇学习了Traits的一些使用。本篇继续深入学习Traits相关知识。
zinyan.com
2023/02/23
5190
21. Groovy 面向对象编程-Traits特性学习-第二篇
Gradle Avoiding traps
对于 Groovy DSL 的用户来说,了解 Groovy 如何处理脚本变量非常重要。 有两种类型的脚本变量。 一个具有本地作用域,另一个具有脚本范围。
acc8226
2022/05/17
1220
23. Groovy 面向对象编程-Traits特性学习-第四篇 高级功能
例如方法继承与Java8的区别。与Mixins的差异。以及静态方法属性和字段等相关知识点,
zinyan.com
2023/02/23
5340
23. Groovy 面向对象编程-Traits特性学习-第四篇 高级功能
【Groovy】Groovy 脚本调用 ( 命令行执行 Groovy 脚本并传入参数 | 获取 Groovy 脚本执行参数 )
在 Groovy 脚本 , Groovy 类 , Java 类中 , 可以调用 Groovy 脚本 ;
韩曙亮
2023/03/30
2.4K0
【Groovy】Groovy 脚本调用 ( 命令行执行 Groovy 脚本并传入参数 | 获取 Groovy 脚本执行参数 )
34. Groovy 语法 类型知识详解-第一篇
本篇内容开始介绍Groovy中的各种类型知识。将会分多篇文章详细介绍和学习Groovy中的有关于类型的相关知识点。
zinyan.com
2023/02/23
8780
34. Groovy 语法 类型知识详解-第一篇
第一节预解释、作用域、this原理
--------------------------------------------预解释---------------------------------------------------- 数据类型的分类: 基本数据类型 number、string、boolean、null、undefined 引用数据类型 object:{} [] /^$/ Date function 基本数据类型和引用数据类型的本质区别: 基本数据类型是按照值来操作的,引用数据类型是按照内存地址来操作的 思考1: var
河湾欢儿
2018/09/06
5220
JS知识点梳理之作用域、作用域链、柯里化、闭包
作用域是指 js 变量使用时所存在的一个区域,分为全局作用域(window)和局部作用域(function、setTimeout...等都会产生局部作用域)。当局部作用域变量名与全局作用域变量名重复时,局部变量会覆盖全局变量。
hellocoder2029
2022/10/21
5080
16. Groovy 面向对象编程-类成员学习-第一篇
Groovy学习笔记第16篇。接着上一篇介绍的类和数据类型,继续学习面向对象的相关知识。
zinyan.com
2022/12/08
5080
一门语言的作用域和函数调用是如何实现的
上次利用 Antlr 重构一版 用 Antlr 重构脚本解释器 之后便着手新增其他功能,也就是现在看到的支持了作用域以及函数调用。
crossoverJie
2022/10/27
7140
一门语言的作用域和函数调用是如何实现的
【Groovy】Groovy 脚本调用 ( Groovy 类中调用 Groovy 脚本 | 参考 Script#evaluate 方法 | 创建 Binding 对象并设置 args 参数 )
可以参考 groovy.lang.Script 类的 evaluate 方法 , 通过 GroovyShell 在类方法中调用 Groovy 脚本 ;
韩曙亮
2023/03/30
2.2K0
【Groovy】Groovy 脚本调用 ( Groovy 脚本编译 | Groovy 脚本字节码文件分析 )
Groovy 脚本编译后生成的是 groovy.lang.Script 类 ; 该类继承自 GroovyObjectSupport 类 ;
韩曙亮
2023/03/30
2.2K0
【Kotlin】扩展函数作用域分析 ( 扩展函数导入 | 扩展函数重载 | 扩展函数作用域优先级 )
① 扩展函数作用域 : 扩展函数使用需要导入包 , 如果在本包中使用 , 可以默认不导入包 ;
韩曙亮
2023/03/27
9670
【Kotlin】扩展函数作用域分析 ( 扩展函数导入 | 扩展函数重载 | 扩展函数作用域优先级 )
【JavaScript】作用域 ② ( JavaScript 块级作用域 | ES6 之前 等同于 全局/局部作用域 | ES6 使用 let / const 声明变量 / 常量 有 块级作用域 )
在 JavaScript 中 , 块级作用域 指的是 在一对大括号 {} 内 声明的变量 只在这对大括号内部可见 ;
韩曙亮
2024/04/09
6420
【JavaScript】作用域 ② ( JavaScript 块级作用域 | ES6 之前 等同于 全局/局部作用域 | ES6 使用 let / const 声明变量 / 常量 有 块级作用域 )
JS与ES6高级编程学习笔记(二)——函数与作用域
开发者常戏称"函数是JavaScript中的一等公民",这足以体现了函数的重要性,为了更好的掌握函数我们需要学习函数的构造器Function等相关内容。
张果
2022/05/31
1.5K0
JS与ES6高级编程学习笔记(二)——函数与作用域
推荐阅读
【Groovy】Groovy 脚本调用 ( Groovy 脚本中调用另外一个 Groovy 脚本 | 调用 evaluate 方法执行 Groovy 脚本 | 参数传递 )
2.3K0
【Groovy】Groovy 脚本调用 ( Groovy 脚本中调用另外一个 Groovy 脚本 | 绑定作用域 binding 变量分析 | Binding 类 variables 成员分析 )
1.4K0
【Groovy】Groovy 脚本调用 ( Java 类中调用 Groovy 脚本 )
2.3K0
【Groovy】Groovy 脚本调用 ( Groovy 类中调用 Groovy 脚本 | 创建 GroovyShell 对象并执行 Groovy 脚本 | 完整代码示例 )
2.4K0
14. Groovy 语言结构-脚本和类知识学习
1.3K0
Groovy 笔记
1.3K0
21. Groovy 面向对象编程-Traits特性学习-第二篇
5190
Gradle Avoiding traps
1220
23. Groovy 面向对象编程-Traits特性学习-第四篇 高级功能
5340
【Groovy】Groovy 脚本调用 ( 命令行执行 Groovy 脚本并传入参数 | 获取 Groovy 脚本执行参数 )
2.4K0
34. Groovy 语法 类型知识详解-第一篇
8780
第一节预解释、作用域、this原理
5220
JS知识点梳理之作用域、作用域链、柯里化、闭包
5080
16. Groovy 面向对象编程-类成员学习-第一篇
5080
一门语言的作用域和函数调用是如何实现的
7140
【Groovy】Groovy 脚本调用 ( Groovy 类中调用 Groovy 脚本 | 参考 Script#evaluate 方法 | 创建 Binding 对象并设置 args 参数 )
2.2K0
【Groovy】Groovy 脚本调用 ( Groovy 脚本编译 | Groovy 脚本字节码文件分析 )
2.2K0
【Kotlin】扩展函数作用域分析 ( 扩展函数导入 | 扩展函数重载 | 扩展函数作用域优先级 )
9670
【JavaScript】作用域 ② ( JavaScript 块级作用域 | ES6 之前 等同于 全局/局部作用域 | ES6 使用 let / const 声明变量 / 常量 有 块级作用域 )
6420
JS与ES6高级编程学习笔记(二)——函数与作用域
1.5K0
相关推荐
【Groovy】Groovy 脚本调用 ( Groovy 脚本中调用另外一个 Groovy 脚本 | 调用 evaluate 方法执行 Groovy 脚本 | 参数传递 )
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档