基于RAG(检索增强生成)实现一套企业智能客服系统

news2024/12/23 22:19:53

大型语言模型(LLM)相较于传统的语言模型具有更强大的能力,然而在某些情况下,它们仍可能无法提供准确的答案。为了解决大型语言模型在生成文本时面临的一系列挑战,提高模型的性能和输出质量,研究人员提出了一种新的模型架构:检索增强生成(RAG, Retrieval-Augmented Generation)。该架构巧妙地整合了从庞大知识库中检索到的相关信息,并以此为基础,指导大型语言模型生成更为精准的答案,从而显著提升了回答的准确性与深度。

RAG流程分为数据处理、检索、增强、生成:
在这里插入图片描述

以下网站是美团外卖中的常见问题:美团外卖 - 常见问题,我们希望利用它和大模型的自然语言理解能力来打造一套企业智能客服系统。

首先,我们如果直接在ChatGPT中问:美团外卖中在线支付取消订单后钱怎么返还?

它给的答案是:
image.png

而网站中的答案为:
image.png

所以,直接利用ChatGPT来作为智能客服系统行不通,它能够理解你的问题,但是它并不能给你确切的答案,因为对于ChatGPT来说,它并不知道企业内部的专有数据,而这个时候,我们就可以利用langchain4j来给企业内部搭一套智能客服系统。

整理数据

首先,我们需要把现有的常见文件整理成文档,可以是txt、pdf、xlsx、markdown等格式都可以,我们这里将美团外卖 - 常见问题网页中的问题和答案转成txt文件,文件为:meituan-qa.txt

功能实现

创建一个工程

直接创建一个普通的Maven工程就可以了,然后引入langchain4j的依赖和你选择的大模型依赖,我这里使用open-ai:

<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j</artifactId>
  <version>0.27.1</version>
</dependency>

<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-open-ai</artifactId>
  <version>0.27.1</version>
</dependency>

以及slf4j的依赖:

<dependency>
  <groupId>org.tinylog</groupId>
  <artifactId>tinylog-impl</artifactId>
  <version>2.6.2</version>
</dependency>
<dependency>
  <groupId>org.tinylog</groupId>
  <artifactId>slf4j-tinylog</artifactId>
  <version>2.6.2</version>
</dependency>

在main方法中进行简单测试:
image.png

定义Agent

我们可以定义一个智能客服专门的Agent,比如CustomerServiceAgent,后续就可以直接这个Agent来充当客服回答问题了,比如:

public interface CustomerServiceAgent {

    // 用来回答问题的方案
    String answer(String question);

    // 利用AiServices创建一个CustomerServiceAgent的代理对象
    static CustomerServiceAgent create() {

        // 创建模型
        ChatLanguageModel model = OpenAiChatModel.builder()
                .apiKey(MY_API_KEY)
                .baseUrl(MY_BASE_URL)
                .build();

        // 指定模型,创建并返回代理对象
        return AiServices.builder(CustomerServiceAgent.class)
                .chatLanguageModel(model)
                .build();
    }
}

以上CustomerServiceAgent接口,提供了一个answer方法用来回答问题,同时提供了create方法用来利用AiServices生成CustomerServiceAgent代理对象,比如我们可以直接这么来创建并使用CustomerServiceAgent:
image.png

导入知识库

上面create方法创建出来的CustomerServiceAgent目前来说只拥有普通大模型的功能,此时的它还没有企业内部的信息,要想让它成为一个智能客服系统,需要将前面整理出来的问答数据送给CustomerServiceAgent。

加载并解析文件

我们需要这么来做,首先加载并解析问答txt文件:

// 加载并解析文件
Document document;
try {
    Path documentPath = Paths.get(CustomerServiceAgent.class.getClassLoader().getResource("meituan-qa.txt").toURI());
    DocumentParser documentParser = new TextDocumentParser();
    document = FileSystemDocumentLoader.loadDocument(documentPath, documentParser);
} catch (URISyntaxException e) {
    throw new RuntimeException(e);
}

以上代码我们使用FileSystemDocumentLoader来加载本地文件,利用TextDocumentParser来解析txt文件,最终得到"meituan-qa.txt"文件所对应的Document对象。

切分文件

然后需要对文件进行切分,把"meituan-qa.txt"文件中的内容切分成问答对,"meituan-qa.txt"文件内容格式已经被我整理好了,比如:
image.png

所以我们可以使用正则表达式"\s*\R\s*\R\s*"来进行切分,我们自定义一个DocumentSplitter来实现:

public class CustomerServiceDocumentSplitter implements DocumentSplitter {

    @Override
    public List<TextSegment> split(Document document) {

        List<TextSegment> segments = new ArrayList<>();

        String[] parts = split(document.text());
        for (String part : parts) {
            segments.add(TextSegment.from(part));
        }

        return segments;
    }

    public String[] split(String text) {
        return text.split("\\s*\\R\\s*\\R\\s*"); 
    }
    
}

然后使用以下代码对Document对象进行切分就可以了:

// 切分文件
DocumentSplitter splitter = new CustomerServiceDocumentSplitter() ;
List<TextSegment> segments = splitter.split(document);

切分结果为:
image.png
其中每个TextSegment对象就表示切分之后的一段文本,在本项目中就是一个问答对。

文本向量化

得到切分之后的文本后,就可以对文本进行向量化处理了,比如你可以直接使用open-ai的向量化模型接口来进行向量化:

EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
    .apiKey(MY_API_KEY)
    .baseUrl(MY_BASE_URL)
    .build();
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();

得到的向量化结果为:
image.png
每个TextSegment,也就是每个问答对,对应了一个向量,而向量就是一个数字数组,如果简化一下数组的大小,比如大小为2,那么一个向量相当于一个(x,y)坐标点,放在坐标中就可以两个坐标点的距离,距离越近就表示坐标点越相似,也就是表示两个向量越相似。

当然,我们也可以使用其他的向量模型来对文本进行向量化,比如使用AllMiniLmL6V2QuantizedEmbeddingModel这个向量化模型,使用它就需要通过网络请求去进行向量化了,因为这个模型可以直接部署在你当前的应用进程内,不过需要额外添加依赖:

<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-embeddings-all-minilm-l6-v2-q</artifactId>
  <version>0.25.0</version>
</dependency>

然后使用以下代码即可得到文本的向量:

EmbeddingModel embeddingModel = new AllMiniLmL6V2QuantizedEmbeddingModel();
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();

不同的向量化模型效果肯定是有区别,比如A1、A2两个文本,open-ai计算出来的向量可能是比较相似的,而AllMiniLmL6V2QuantizedEmbeddingModel则可能计算出来的向量之间差别较大,所以在实际工作中还是建议使用效果更好的向量化模型。

向量存储

正对拆分后的文本得到向量后,就需要把文本和向量之间的映射关系存储下来,使用CustomerServiceAgent在回答问题时,能够根据向量相似度找到和用户问题相似的知识库问题。

存储向量的代码大致为:

EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
embeddingStore.addAll(embeddings, segments);

这是把向量和文本数据直接存在了JVM内存中,本质上就是一个CopyOnWriteArrayList,该List中存储的是Entry对象,而Entry对象则分别存储了文本和向量。

实际工作中,我们肯定需要文本和向量存储可持久化的向量数据库中,你可以选择Chroma、Milvus这种专门的向量数据库,也可以使用Elasticsearch、Redis、PostgreSQL、MongoDb来存储,比如使用Redis需要这么来做。

首先普通的Redis是不支持向量存储和查询的,需要额外的redisearch模块,我这边是直接使用docker来运行一个带有redisearch模块的redis容器的,命令为:

docker run -p 6379:6379 redis/redis-stack-server:latest

注意端口6379不要和你现有的Redis冲突了。

然后就可以使用以下代码把向量存到redis中了:

EmbeddingStore<TextSegment> embeddingStore = RedisEmbeddingStore.builder()
    .host("127.0.0.1")
    .port(6379)
    .dimension(384)
    .build();
embeddingStore.addAll(embeddings, segments);

这里的dimension表示向量维度,也就是上面数组的大小,执行完以上代码后向量和文本就会存储到Redis中了。

可以使用以下命令来查看:

redis-cli FT.SEARCH embedding-index "*" LIMIT 0 10

能得到结果就证明是正常的:
image.png
如果想删除某个index和对应数据,可以:

redis-cli FT.DROPINDEX embedding-index DD

组装ContentRetriever

当把向量存入向量数据库后,就可以组装一个ContentRetriever用来后续进行内容查找了,组装代码为:

ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
        .embeddingStore(embeddingStore)
        .embeddingModel(embeddingModel)
        .maxResults(5) // 最相似的5个结果
        .minScore(0.8) // 只找相似度在0.8以上的内容
        .build();

以上代码将向量数据库和向量模型组装成了一个ContentRetriever,并指定ContentRetriever后续查找内容时,只返回相似度在0.8以上的前5个结果。

我现在针对以下原始问题来进行提问:

Q:余额提现到账时间是多久?
1-7个工作日内可退回您的支付账户。由于银行处理可能有延迟,具体以账户的到账时间为准。

我的问题和原始问题并不完全相同,但是我希望ContentRetriever能根据我的问题找到和问题相似的原始问题和答案:

Query query = new Query("余额提现什么时候到账?");
List<Content> retrieve = contentRetriever.retrieve(query);

但是得到的答案为:
image.png

效果优化

发现答案不太理想,我们预期的原始问题并没有在这5个中,我们换成open-ai的向量化模型来试试,注意使用open-ai得到的向量维度为1536,所以记得修改dimension:

ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
                .embeddingStore(embeddingStore)
                .embeddingModel(embeddingModel)
                .maxResults(5) // 最相似的5个结果
                .minScore(0.8) // 只找相似度在0.8以上的内容
                .build();

改为之后得到的结果为:
image.png
发现答案就比较理想了,"余额提现到账时间是多久?"排在了第一个,说明和原始问题最匹配,这里也能看出不同向量化模型之间的差距。

当然,我们也可以从另外一个角度来进行优化,由于我们在做文本向量化时,使用的是“问题+答案”一起做的向量化,而查询的时候只使用了“问题”做向量化,由于两者不一致,导致某些较弱的向量化模型生成出来的向量偏离的更远,导致在做向量匹配时出现了差距,那能不能在做文本向量化时,也只使用“问题”来做向量化呢?

我们之前是把整个TextSegment对象一起做的向量化,相当于“问题+答案”一起做的向量化:

List<Embedding> embeddings = embeddingModel.embedAll(segments).content();

所以我们只需要将TextSegment中的问题提取出来然后做向量化就可以了,可以这么做:

EmbeddingModel embeddingModel = new AllMiniLmL6V2QuantizedEmbeddingModel();
// 将问题抽取出来单独进行向量化
List<TextSegment> questions = new ArrayList<>();
for (TextSegment segment : segments) {
    questions.add(TextSegment.from(segment.text().split("\n")[0]));
}
List<Embedding> embeddings = embeddingModel.embedAll(questions).content();

记得将dimension改回384,然后我们来看效果:
image.png
效果比上一次提升了,至少原始问题已经出现了,之所以还没有出现在第一个,那就是AllMiniLmL6V2QuantizedEmbeddingModel这个向量化模型确实效果不咋地,比较它比较小,而不想open-ai那种大的向量化模型。

整合大模型

当我们能根据用户问题匹配到原始问题和答案后,该如何将问题的答案返回给用户呢?比如今天是2024年3月17号,假如用户问“今天的余额提现,最晚什么时候能到账?”,作为智能客服系统,能不能直接告诉客户具体的日期呢,而不是只返回一个“1-7个工作日内可退回您的支付账户”让客户来算日期,实现这个功能就可以结合大模型来实现了。

在创建了ContentRetriever之后,我们可以通过AiServices来整合它与大模型:

// 构造ChatMemory,用来保存历史聊天记录
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);

// 指定模型,创建并返回代理对象
return AiServices.builder(CustomerServiceAgent.class)
    .chatLanguageModel(model)
    .contentRetriever(contentRetriever)
    .chatMemory(chatMemory)
    .build();

通过AiServices指定了大模型、ContentRetriever,以及一个用来记录历史聊天记录的ChatMemory,这样AiServices就可以创建出来一个CustomerServiceAgent代理对象进行使用了:

// 创建
CustomerServiceAgent customerServiceAgent = CustomerServiceAgent.create();

// 使用
String result = customerServiceAgent.answer("今天的余额提现,最晚什么时候能到账?");
System.out.println(result);

让我们来看看结果:

根据以上信息,余额提现到账时间为1-7个工作日内,具体以账户的到账时间为准。因此,最迟可能在7个工作日内到账。您可以在美团账户的“账号管理——我的账号”中查看是否到账。

没有达到我们想要的效果,我们可以这么问:

// 创建
CustomerServiceAgent customerServiceAgent = CustomerServiceAgent.create();

// 使用
String result = customerServiceAgent.answer("今天的余额提现,最晚哪天能到账?给我具体的日期");
System.out.println(result);

给的答案是:

如果提现是在工作日内进行的话,最晚最晚会在7个工作日内到账。如果提现是在周五进行的话,最晚会在下周的周五到账。

我继续问:
image.png

继续问:
image.png

继续问:
image.png

通过这个过程,我们发现虽然整合了大模型,但是大模型似乎对今天是不是周末、是不是工作日这些不太智能,那这就需要利用langchain4j的Tools技术了。

Tool

定义一个Tool:

public class DateCalculator {

    @Tool("计算指定天数后的具体日期")
    String date(Integer days) {
        return LocalDate.now().plusDays(days).toString();
    }
}

@Tool就表示定义了一个Tool,这个Tool需要绑定到AiService中:

return AiServices.builder(CustomerServiceAgent.class)
                .chatLanguageModel(model)
                .contentRetriever(contentRetriever)
                .chatMemory(chatMemory)
                .tools(new DateCalculator())
                .build();

这样大模型在得到初步答案后,会自动匹配到这个Tool,因为我问的是“最晚”,所以大模型能把“1-7”中的“7”提出来并传给我定义的Tool,从而算出具体的日期:
image.png

到这,一个智能客服系统算是初具雏形了,这中间也涉及到了langchain4j中最为关键的几个核心组件,大家可以基于以上流程在自己公司内部也搭建这么一套系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1891317.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试面试必杀篇:【2024软件测试面试八股文宝典】

800道软件测试面试真题&#xff0c;高清打印版打包带走&#xff0c;横扫软件测试面试高频问题&#xff0c;涵盖测试理论、Linux、MySQL、Web测试、接口测试、App测试、Python、Selenium、性能测试、LordRunner、计算机网络、数据结构与算法、逻辑思维、人力资源等模块面试题&am…

ssm三农产品助推网站-计算机毕业设计源码91990

目录 摘要 1 绪论 1.1选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2网站分析 2.1 可行性分析 2.2 网站流程分析 2.2.1 数据流程 2.2.2 业务流程 2.3 网站功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 网站用例分析 2.5本章小结 3 网站总体设…

怎么在线打开AI文件?推荐使用这款免费白板软件!

在我们的日常生活和工作中&#xff0c;AI文件的使用频率越来越高。但是&#xff0c;对于许多非设计从业者来说&#xff0c;如何打开AI文件仍然是一个经常遇到的问题。 别担心&#xff0c;免费的在线白板软件就是你的解决方案。这种工具不仅可以轻松打开AI文件&#xff0c;还可…

AI 与数据的智能融合丨大模型时代下的存储系统

WOT 全球技术创新大会2024北京站于 6 月 22 日圆满落幕。本届大会以“智启新纪&#xff0c;慧创万物”为主题&#xff0c;邀请到 60 位不同行业的专家&#xff0c;聚焦 AIGC、领导力、研发效能、架构演进、大数据等热门技术话题进行分享。 近年来&#xff0c;数据和人工智能已…

springboot+vue+mybatis前台点菜系统+PPT+论文+讲解+售后

21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存储达到…

【操作系统】进程管理——调度基础(个人笔记)

学习日期&#xff1a;2024.7.3 内容摘要&#xff1a;调度的概念、层次&#xff0c;进程调度的时机&#xff0c;调度器和闲逛进程&#xff0c;调度算法的评价指标 调度的基本概念 有一堆任务需要处理&#xff0c;但由于资源有限&#xff0c;有的事情不能同时处理&#xff0c;这…

virtualbox+Ubuntu部分窗口显示错乱

如下图&#xff1a; 窗口标题显示错乱&#xff0c;跟一般乱码不一样。 解决办法&#xff1a; 在virtualbox设置中&#xff0c;显示选项卡&#xff0c;取消勾选启用3D加速 也可参考此链接&#xff1a;linux ubuntu 中vscode中央窗口显示出现异常/显示错误_开发工具-CSDN问答

替换数据库是换肤还是换心?

上周末参加了自主可控数据库的沙龙。在会上我进行了主题演讲《围追堵截下&#xff0c;Oracle还能在国内立足么》 先听结论吧 我个人认为在很长一段时间还是你可以立足的。IDC最近公布的数据如下。本地部署中&#xff0c;Oracle居然是第一位的。这个数据是超出我想象的。去O都…

【机器学习】Datawhale-AI夏令营分子性质AI预测挑战赛

参赛链接&#xff1a;零基础入门 Ai 数据挖掘竞赛-速通 Baseline - 飞桨AI Studio星河社区 一、赛事背景 在当今科技日新月异的时代&#xff0c;人工智能&#xff08;AI&#xff09;技术正以前所未有的深度和广度渗透到科研领域&#xff0c;特别是在化学及药物研发中展现出了巨…

[我靠升级逆袭成为大师]韩漫日漫无删减完整版,免费在线观看漫画

[我靠升级逆袭成为大师]韩漫日漫无删减完整版&#xff0c;免费在线观看漫画 不能多说&#xff0c;怕审-核不过&#xff0c;自己看图吧。 目前统计【统计日期&#xff1a;2024-07-03】&#xff1a; 完结的有&#xff1a;420部。 连载的有&#xff1a;308部&#xff0c;持续更…

关于工时表软件,知道这四点就够了

在当今商业环境日益加快的节奏中&#xff0c;分秒必争。对于企业而言&#xff0c;了解内部时间的使用情况是确保效率、优化资源分配和提高生产力的关键。工时表在这里发挥着不可或缺的作用&#xff0c;它不仅是行政工作的组成部分&#xff0c;更是明确运营、加强项目管理和简化…

程序员的加油站,各类技术文章,可视化技术,在线源码资源,在线实用工具,数据爬虫接口持续集成更新中

先挂网址&#xff1a;https://wheart.cn 可视化大屏模板与设计&#xff0c;在线预览 上百例可视化模板 技术文章、资源下载等各类资源导航页 echart在线实用demo 各种在线工具提升开发效率 echart在线代码模板

Raylib 坐标系适应与GPU绘制参数

通过750 - 鼠标坐标&#xff0c;把原点在左上角的鼠标坐标变成左下角 实现输入数据后的坐标系同GPU原点在左下角坐标相同&#xff0c; 比数组0&#xff0c;0对应左上角好&#xff0c; 此时实际上数组0&#xff0c;0对应左下角 #include <raylib.h> // 感受&#xff1a…

8624 多项式系数累加和

这个问题可以通过使用数学的导数规则来解决。对于一个多项式&#xff0c;它的导数可以通过将每一项的系数乘以它的指数&#xff0c;然后降低该项的指数来得到。这个过程可以重复M次来得到多项式的M阶导数。然后&#xff0c;我们可以简单地将所有项的系数相加来得到结果。 以下…

Android Studio上传新项目到Gitee

一、在Gitee上创建仓库 首先需要再Gitee上创建仓库 1、在Gitee中新建仓库 2、输入仓库信息 3、生成仓库地址 创建成功会生成一个仓库地址&#xff0c;格式如下&#xff1a; https://gitee.com/test/compose_mvi_demo.git二、Android Studio 上传项目到Gitee 1、在Android …

GPT-4预测股票涨跌更更更准了!东京大学新框架LLMFactor提升显著 | ACL 2024

花一秒钟就看透事物本质的人&#xff0c;和花一辈子都看不清的人&#xff0c;注定是截然不同的命运。——唐柯里昂 除了少数天纵奇才&#xff0c;大多数人都是通过知识和阅历的不断积累&#xff0c;才逐渐锻炼出观察和判断事物变化规律的能力。而如果说有一件事&#xff0c;可以…

数据库系统概论 | MySQL | 数据定义 | 单表查询 | 嵌套查询 | 连接查询 | 带有谓词的查询

数据定义 模式的定义与删除 定义模式与删除模式&#xff1a; CREATE SCHEMA S_C_SC; DROP SCHEMA S_C_SC;进入模式&#xff1a; USE S_C_SC;建立学生表&#xff1a; CREATE TABLE Student (Sno CHAR(8) PRIMARY KEY, Sname VARCHAR(20) UNIQUE, Ssex CHAR(6), Sbirthdate …

从零构建vue3+ts项目(三):vite plugin与打包配置

一、Svg配置 每次引入一张 SVG 图片都需要写一次相对路径&#xff0c;并且对 SVG 图片进行压缩优化也不够方便。 vite-svg-loader插件加载SVG文件作为Vue组件&#xff0c;使用SVGO进行优化。 插件网站https://www.npmjs.com/package/vite-svg-loader 1. 安装 pnpm i vite-svg…

反射(通俗易懂)

一、反射(Reflection) 反射就是:加载类&#xff0c;并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等) 动态语言&#xff0c;是一类在运行时可以改变其结构的语言&#xff1a;例如新的函数、对象、甚至代码可以被引进&#xff0c;已有的函数可以被删除或是其他…

【软件测试】快速定位bug,编写测试用例

作为一名测试人员如果连常见的系统问题都不知道如何分析&#xff0c;频繁将前端人员问题指派给后端人员&#xff0c;后端人员问题指派给前端人员&#xff0c;那么在团队里你在开发中的地位显而易见 &#xff0c;口碑、升值、加薪那应该是你遥不可及的梦 但是作为测试人员来说&…