当前位置: 首页 > backend >正文

基于RAG实现下一代的企业智能客服系统

以下网站是某企业国内机票当中的常见问题:某企业-国内机票-退票、更改和签转,我们希望利用它和大模型的自然语言理解能力来打造一套企业智能客服系统。
首先,我们如果直接在DeepSeek中问:xxx中我想取消预订的机票,需要支付费用吗?
在这里插入图片描述

我们如果直接在ChatGpt中问:xxx中我想取消预订的机票,需要支付费用吗?
在这里插入图片描述

而网站中的答案为:
在这里插入图片描述

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

整理数据
首先,我们需要把现有的常见文件整理成文档,可以是txt、pdf、xlsx、markdown等格式都可以,我们这里将某企业-国内机票-退票、更改和签转网页中的问题和答案转成txt文件
在这里插入图片描述
功能实现

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

<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-redis</artifactId><version>${langchain4j.version}</version>
</dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai</artifactId><version>${langchain4j.version}</version>
</dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-spring-boot-starter</artifactId><version>${langchain4j.version}</version> <!-- 使用最新版本 -->
</dependency>     

以及slf4j的依赖:

<dependency><groupId>org.tinylog</groupId><artifactId>slf4j-tinylog</artifactId><version>${tinylog.version}</version>
</dependency>
<dependency><groupId>org.tinylog</groupId><artifactId>tinylog-impl</artifactId><version>${tinylog.version}</version>
</dependency>

在main方法中进行简单测试
在这里插入图片描述
定义Agent
我们可以定义一个智能客服专门的Agent,比如CustomerServiceAgent,后续就可以直接这个Agent来充当客服回答问题了,比如:

package com.example.agent;import dev.langchain4j.service.spring.AiService;import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;/*** @author Administrator*/
@AiService(wiringMode = EXPLICIT, chatModel="deepSeekChatModel")
public interface CustomerServiceAgent {// 用来回答问题的方案String answer(String question);}

我这里配置了open-aideepseek

package com.example.config;import com.example.record.DeepSeekConfig;
import com.example.record.OpenAiConfig;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author Administrator*/
@Configuration
public class AiModelConfiguration {@Beanpublic OpenAiChatModel openAiChatModel(OpenAiConfig openAiConfig){return OpenAiChatModel.builder().baseUrl(openAiConfig.baseUrl()).apiKey(openAiConfig.apiKey()).modelName(openAiConfig.modelName())// .httpClientBuilder(new SpringRestClientBuilder()).logRequests(true).logResponses(true).build();}@Beanpublic OpenAiChatModel deepSeekChatModel(DeepSeekConfig deepSeekConfig){return OpenAiChatModel.builder().baseUrl(deepSeekConfig.baseUrl()).apiKey(deepSeekConfig.apiKey()).modelName(deepSeekConfig.modelName())// .httpClientBuilder(new SpringRestClientBuilder()).logRequests(true).logResponses(true).build();}}

以上CustomerServiceAgent接口,提供了一个answer方法用来回答问题,同时提供了配置类AiModelConfiguration 来利用@Bean返回两个注册为Spring应用上下文中的bean的对象 ,然后在注解@AiService(wiringMode = EXPLICIT, chatModel=“deepSeekChatModel”)上使用,这样我们可以直接这么来创建并使用CustomerServiceAgent:
在这里插入图片描述
换一个模型
在这里插入图片描述

导入知识库
上面方法创建出来的CustomerServiceAgent目前来说只拥有普通大模型的功能,此时的它还没有企业内部的信息,要想让它成为一个智能客服系统,需要将前面整理出来的问答数据送给CustomerServiceAgent。
加载并解析文件
我们需要这么来做,首先加载并解析问答txt文件

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

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

切分文件
然后需要对文件进行切分,把"ctrip-qa.txt"文件中的内容切分成问答对,"ctrip-qa.txt"文件内容格式已经被我整理好了,比如:
在这里插入图片描述
所以我们可以使用正则表达式Pattern.compile("(\\R{2,})", Pattern.MULTILINE)来进行切分,我们自定义一个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) {Pattern pattern = Pattern.compile("(\\R{2,})", Pattern.MULTILINE);return pattern.split(text);
}

然后使用以下代码对Document对象进行加载和切分就可以了:
在这里插入图片描述
其中每个TextSegment对象就表示切分之后的一段文本,在本项目中就是一个问答对。

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

package com.example.record;import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = OpenAiEmbeddingConfig.PREFIX)
public record OpenAiEmbeddingConfig(String baseUrl, String apiKey, String modelName,int dimension) {public static final String PREFIX = "langchain4j.open-ai.embedding-model";public OpenAiEmbeddingConfig(String baseUrl, String apiKey, String modelName,int dimension) {this.baseUrl = baseUrl;this.apiKey = apiKey;this.modelName = modelName;this.dimension=dimension;}@Overridepublic int dimension() {return dimension;}@Overridepublic String baseUrl() {return baseUrl;}@Overridepublic String apiKey() {return apiKey;}@Overridepublic String modelName() {return modelName;}
}
@Bean
public OpenAiEmbeddingModel openAiEmbeddingModel(OpenAiEmbeddingConfig openAiEmbeddingConfig){return OpenAiEmbeddingModel.builder().baseUrl(openAiEmbeddingConfig.baseUrl()).apiKey(openAiEmbeddingConfig.apiKey()).modelName(openAiEmbeddingConfig.modelName())// .httpClientBuilder(new SpringRestClientBuilder()).logRequests(true).logResponses(true).build();
}

准备工作做好后,进行向量化

 List<TextSegment> textSegments = documentSplitter.split(documentSplitter.load(""));Response<List<Embedding>> resp = openAiEmbeddingModel.embedAll(textSegments);redisEmbeddingStore.addAll(resp.content());

运行结果:
在这里插入图片描述
每个TextSegment,也就是每个问答对,对应了一个向量,而向量就是一个数字数组,如果简化一下数组的大小,比如大小为2,那么一个向量相当于一个(x,y)坐标点,放在坐标中就可以两个坐标点的距离,距离越近就表示坐标点越相似,也就是表示两个向量越相似。

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

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

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


AllMiniLmL6V2QuantizedEmbeddingModel allMiniLmL6V2QuantizedEmbeddingModel1 = new AllMiniLmL6V2QuantizedEmbeddingModel();
List<TextSegment> textSegments = documentSplitter.split(documentSplitter.load("ctrip-qa.txt"));
Response<List<Embedding>> resp = allMiniLmL6V2QuantizedEmbeddingModel1.embedAll(textSegments);
redisEmbeddingStore.addAll(resp.content());

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

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

存储向量的代码大致为

Response<List<Embedding>> resp = allMiniLmL6V2QuantizedEmbeddingModel1.embedAll(textSegments);
redisEmbeddingStore.addAll(resp.content(),textSegments);

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

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

组装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:电子机票如何签转?
电子机票签转按航空公司规定操作。折扣票不能签转。

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

Query query = new Query("机票如何签转?");List<Content> retrieve = contentRetriever.retrieve(query);

运行结果如下:
在这里插入图片描述
发现答案就比较理想了,"机票如何签转?"排在了第一个,说明和原始问题最匹配。

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

List<TextSegment> textSegments = documentSplitter.split(documentSplitter.load("ctrip-qa.txt"));
// 将问题抽取出来单独进行向量化
List<TextSegment> questions = new ArrayList<>();
for (TextSegment segment : textSegments) {questions.add(TextSegment.from(segment.text().split("\r\n")[0]));
}
Response<List<Embedding>> resp = openAiEmbeddingModel.embedAll(questions);
redisEmbeddingStore.addAll(resp.content(),textSegments);
 ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder().embeddingStore(redisEmbeddingStore).embeddingModel(openAiEmbeddingModel).maxResults(5) // 最相似的5个结果.minScore(0.8) // 只找相似度在0.8以上的内容.build();Query query = new Query("机票如何签转?");
List<Content> retrieve = contentRetriever.retrieve(query);

运行结果如下:
在这里插入图片描述
效果比上一次提升了,只显示了我们想要的

整合大模型
当我们能根据用户问题匹配到原始问题和答案后,该如何将问题的答案返回给用户呢?
现在我们再调用下CustomerServiceAgent:
在这里插入图片描述
发现内容根本就不是ContentRetriever返回的内容

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

@Bean
public ContentRetriever contentRetriever(OpenAiEmbeddingModel openAiEmbeddingModel,RedisEmbeddingStore redisEmbeddingStore){return EmbeddingStoreContentRetriever.builder().embeddingStore(redisEmbeddingStore).embeddingModel(openAiEmbeddingModel).maxResults(5) // 最相似的5个结果.minScore(0.8) // 只找相似度在0.8以上的内容.build();
}
@Bean
public ChatMemory chatMemory(){return MessageWindowChatMemory.withMaxMessages(100);
}

把bean注入给AiService 生成CustomerServiceAgent的代理对象

package com.example.agent;import dev.langchain4j.service.spring.AiService;import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;/*** @author Administrator*/
@AiService(wiringMode = EXPLICIT, chatModel="openAiChatModel",chatMemory="chatMemory",contentRetriever="contentRetriever")
public interface CustomerServiceAgent {// 用来回答问题的方案String answer(String question);}

再次执行 输出
在这里插入图片描述
符合预期了

Tool
定义一个Tool:

package com.example.tool;import dev.langchain4j.agent.tool.Tool;import java.time.LocalDateTime;public class DateTool {@Tool("获取当前日期")public static String dateUtil(){System.out.println("获取当前日期");return LocalDateTime.now().toString();}}
 @Beanpublic DateTool dateTool(){return new DateTool();}
@AiService(wiringMode = EXPLICIT, chatModel="openAiChatModel",chatMemory="chatMemory",contentRetriever="contentRetriever",tools = {"dateTool"})
public interface CustomerServiceAgent {//其他代码
}

再运行:
在这里插入图片描述
符合预期,这样涉及当前日期的一些任务也可以比较精确的进行回答

到这,一个智能客服系统算是初具雏形了,这中间也涉及到了langchain4j中最为关键的几个核心组件,大家可以基于以上流程在自己公司内部也搭建这么一套系统,当然,可能需要针对实际情况做各种优化。
【zwpflc】
各位看官 有任何问题可以加我 请备注csdn

http://www.xdnf.cn/news/12798.html

相关文章:

  • 2025年6月|注意力机制|面向精度与推理速度提升的YOLOv8模型结构优化研究:融合ACmix的自研改进方案
  • 当SAP系统内计划订单转换为生产订单时发生了什么?
  • 混合策略实现 doc-doc 对称检索局限性与失败案例
  • 基于算法竞赛的c++编程(21)cin,scanf性能差距和优化
  • 在 Windows 11 或 10 上删除、创建和格式化分区
  • tableau 实战工作场景常用函数与LOD表达式的应用详解
  • 操作系统进程管理解析:从 fork 到 exec 的全流程实战与底层原理
  • Python Robot Framework【自动化测试框架】简介
  • OTF字体包瘦身,保留想要的字
  • vector使用及模拟
  • python并发编程
  • 【AI系列】BM25 与向量检索
  • 并行硬件环境及并行编程
  • 【Java学习】Spring Security登录认证流程通俗版总结归纳
  • 【西门子杯工业嵌入式-4-什么是外部中断】
  • Cursor生成Java的架构设计图
  • 第二十六章 流程控制: case分支
  • 一键亮灯高级和弦触发自动鼓机:特伦斯自动挡钢琴开启音乐创作的全新时代
  • B站Miachael_ee——蓝牙教程笔记
  • 【论文解读】Toolformer: 语言模型自学使用工具
  • C++图书管理
  • MySQL 8.0 绿色版安装和配置过程
  • 属于我的“龙场悟道”
  • 桌面图标无法对齐!
  • 解密LSTM(长短期记忆网络):让机器拥有记忆力的魔法网络
  • 软件测试与军用标准详细框架
  • Java异步编程难题拆解与技术实践
  • 【AI论文】推理健身房(REASONING GYM):基于可验证奖励的强化学习推理环境
  • vue3 创建图标 按钮
  • Kafka 消息模式实战:从简单队列到流处理(一)