6. LangChain4j 基于RAG实现一套企业智能客服系统

RAG介绍

        LLM的知识仅限于它所训练的数据。 如果你想让 LLM 了解特定领域的知识或专有数据,你可以使用 RAG。

        什么是RAG?

        简而言之,RAG(检索增强生成) 是从数据中查找和注入相关信息的方法 再将其发送到 LLM 之前添加到提示符中。 这样,LLM将获得相关信息,并能够使用这些信息进行回复, 这应该会降低幻觉的可能性。

        再简单一点,就是用户输入问题时, 我们先从我们的知识库查看答案, 再把用户输的问题和我们知识库的答案,一起发送给大模型,让大模型根据我们的答案回复用户问题。

RAG阶段

RAG 过程分为 2 个不同的阶段:索引和检索。检索我们使用向量搜索,它使用余弦相似度或者其他相似度进行搜索。还有一种是全文(关键字)搜索,但现在暂时不支持。

索引

索引,此过程需要我们将我们公司的知识库内容,生成向量后,存在向量数据库中,关于向量可以参考我向量篇,文档的预处理方式是为了在检索阶段可以进行高效向量搜索。

向量搜索,这通常涉及清理文档,使用其他数据和元数据丰富它们, 将它们拆分为更小的段(又称分块),嵌入这些段,最后将它们存储在嵌入存储(又称向量数据库)中。

下面是索引阶段的简化图:

检索

检索阶段通常发生在在线,此时用户提交了应使用索引文档回答的问题。

向量搜索,这通常涉及嵌入用户的查询(问题) 并在嵌入存储中执行相似性搜索。 然后将相关片段(原始文档的片段)注入提示并发送到 LLM。

下面是检索阶段的简化图:

RAG的工作原理

RAG的工作原理可以分为以下几个步骤:

1.接收请求:首先,系统接收到用户的请求(例如提出一个问题)。

2.信息检索(R):系统从一个大型文档库中检索出与查询最相关的文档片段。这一步的目标是找到那些可能包含答案或相关信息的文档。

3.生成增强(A):将检索到的文档片段与原始查询一起输入到大模型(如chatGPT)中,注意使用合适的提示词,比如原始的问题是XXX,检索到的信息是YYY,给大模型的输入应该类似于:请基于YYY回答XXXX。

4.输出生成(G):大模型基于输入的查询和检索到的文档片段生成最终的文本答案,并返回给用户。

用户,RAG和大模型交互原理图:

RAG各个组件

        关于其他的组件,请看我前几篇关于LangChain4j的文章。

Document(文档):
  • DocumentLoader:文档加载
    • 您可以创建一个 DocumentString,但更简单的方法是使用库中包含的文档加载器之一
      • FileSystemDocumentLoaderlangchain4j 模块
      • UrlDocumentLoaderlangchain4j 模块
      • AmazonS3DocumentLoaderlangchain4j-document-loader-amazon-s3 模块
      • AzureBlobStorageDocumentLoaderlangchain4j-document-loader-azure-storage-blob 模块
      • GitHubDocumentLoaderlangchain4j-document-loader-github 模块
      • TencentCosDocumentLoaderlangchain4j-document-loader-tencent-cos 模块
  • DocumentParser:文档解析
    • Documents 可以表示各种格式的文件,例如 PDF、DOC、TXT 等。 要解析这些格式中的每一种,有一个 DocumentParser 接口与库中包含的多个实现。
      • extDocumentParserlangchain4j 模块,可以解析纯文本格式(e.g. TXT、HTML、MD 等)的文件
      • ApachePdfBoxDocumentParserlangchain4j-document-parser-apache-pdfbox 模块,可以解析PDF文件
      • ApachePoiDocumentParserlangchain4j-document-parser-apache-poi 模块,可以解析 MS Office 文件格式 (e.g. DOC、DOCX、PPT、PPTX、XLS、XLSX等)
      • ApacheTikaDocumentParserlangchain4j-document-parser-apache-tika 模块 它可以自动检测和解析几乎所有现有的文件格式
  • DocumentSplitter:文档切割
    • 文档拆分的方案langchain4j中提供了6种
      • DocumentByCharacterSplitter 基于字符,逐个字符(含空白字符)分割
      • DocumentByRegexSplitter 基于正则的,按照自定义正则表达式分隔
      • DocumentByParagraphSplitter 基于段落的,按照连续的两个换行符(\n\n)分割
      • DocumentBySentenceSplitter 基于句子的(使用Apache OpenNLP,只支持英文,所以可以忽略)
      • DocumentByLineSplitter 基于行的,按照换行符(\n)分割
      • DocumentByWordSplitter 基于字的,将文本按照空白字符分割
RetrievalAugmentor(检索增强器):
  • ContentRetriever: 内容检索器, 根据向量模型和向量数据库,生成向量,检索向量(相似度匹配)和存储向量,以及删除向量. 这个组件可以替代我们生成向量和搜索向量的两个步骤.
  • QueryTransformer: 查询转换器,可以将我们的问题,生成多个语义相似,但语法和结构不同的问题.将给定的 Query 转换为一个或多个 Query 。我们的目标是通过修改或扩展原始 Query 来提高检索质量。
  • QueryRouter: 查询路由器,我们的问答会路由到匹配度更高的向量库中,获取内容.负责将 Query 路由至适当的 ContentRetriever
  • ContentAggregator: 内容增强器,得到多个知识点(答案)时, 会根据算分,分数最高的放在最前面.
  • ContentInjector: 内容注入器,拼接提示词, 将问题和向量得到的结果,拼成UserMessage传给大模型.

文档拆分- 注意事项

由于与LLM交互的时候输入的文本对应的token长度是有限制的,输入过长的内容,LLM会无响应或直接该报错,因此不能将所有相关的知识都作为输入给到LLM,需要将知识文档进行拆分,存储到向量库,每次调用LLM时,先找出与提出的问题关联度最高的文档片段,作为参考的上下文输入给LLM。

入参过长,LLM报错:

虽然根据响应,允许输入1048576个字符=1024K个字符=1M个字符,但官网文档给的32K tokens,而一般1个中文字符对应1-2个Token,因此字符串建议不大于64K,实际使用中,为了保障性能,也是要控制输入不要过长。

如下是常见LLM给定的token输入上限:

模型名称Token 输入上限(最大长度)
GPT-3 (davinci)4096 tokens
GPT-3.5 (text-davinci-003)4096 tokens
GPT-4 (8k context)8192 tokens
GPT-4 (32k context)32768 tokens
LLaMA (7B)2048 tokens
LLaMA (13B)2048 tokens
LLaMA (30B)2048 tokens
LLaMA (65B)2048 tokens
讯飞星火(SparkDesk)8192 tokens
文心一言(Ernie 3.0)4096 tokens
智源悟道(WuDao 2.0)2048 tokens
阿里巴巴 M62048 tokens
华为盘古(Pangu-Alpha)2048 tokens
京东言犀大模型(ChatJd)2048 tokens

案例实现

基于RAG实现美团智能客服

1.导入maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gorgor</groupId>
    <artifactId>ai-demo</artifactId>
    <version>1.0-SNAPSHOT</version>


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.1</version>
    </parent>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <langchain4j.version>0.31.0</langchain4j.version>
        <jackson.version>2.16.1</jackson.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
        <!-- open Ai -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
        <!-- 智普 Ai  -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-zhipu-ai</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-redis</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>


        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
    </dependencies>
</project>

2. 在项目resources目录下导入,美团外卖-常见问题。文件名为 meituan-qa.txt。

Q:在线支付取消订单后钱怎么返还?
订单取消后,款项会在一个工作日内,直接返还到您的美团账户余额。

Q:怎么查看退款是否成功?
退款会在一个工作日之内到美团账户余额,可在“账号管理——我的账号”中查看是否到账。

Q:美团账户里的余额怎么提现?
余额可到美团网(meituan.com)——“我的美团→美团余额”里提取到您的银行卡或者支付宝账号,另外,余额也可直接用于支付外卖订单(限支持在线支付的商家)。

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

Q:申请退款后,商家拒绝了怎么办?
申请退款后,如果商家拒绝,此时回到订单页面点击“退款申诉”,美团客服介入处理。

Q:怎么取消退款呢?
请在订单页点击“不退款了”,商家还会正常送餐的。

Q:前面下了一个在线支付的单子,由于未付款,订单自动取消了,这单会计算我的参与活动次数吗?
不会。如果是未支付的在线支付订单,可以先将订单取消(如果不取消需要15分钟后系统自动取消),订单无效后,此时您再下单仍会享受活动的优惠。

Q:为什么我用微信订餐,却无法使用在线支付?
目前只有网页版和美团外卖手机App(非美团手机客户端)订餐,才能使用在线支付,请更换到网页版和美团外卖手机App下单。

Q:如何进行付款?
美团外卖现在支持货到付款与在线支付,其中微信版与手机触屏版暂不支持在线支付。

Q:如何查看可以在线支付的商家?
你可以在商家列表页寻找带有“付”标识的商家,提交订单时可以选择支付方式。

Q:美团外卖支持哪些支付方式?
现已支持美团余额、支付宝、网银(储蓄卡、信用卡)。

Q:在线支付订单如何退款?
商家接单前,您可以直接取消订单,订单金额会自动退款到美团余额;商家接单后,您在点击“申请退款”,在线申请。提交退款申请之后,商家有24小时处理您的退款申请。商家同意退款,或24小时内没有处理您的退款申请,您的支付金额会退款至您的美团余额。

Q:在线支付的过程中,订单显示未支付成功,款项却被扣了,怎么办?
出现此问题,可能是银行/支付宝的数据没有即时传输至美团,请您不要担心,稍后刷新页面查看。 如半小时后仍显示"未付款",请先联系银行/支付宝客服,获取您扣款的交易号,然后致电美团外卖客服4008507777,我们会协助您解决。

Q:哪些商家有优惠?都有些什么优惠?
有优惠的商家在商家列表页均含有优惠标识;具体的优惠可以查看活动详情或者商家详情页中的描述。

Q:在新用户享受的优惠中,新用户的条件是什么?
新用户是指第一次在美团外卖下单的用户(同一设备、同一手机号、同一账户仅可享受一次)。

Q:我达到了满赠、满减的优惠的金额,为什么没有享受相关的优惠?
满赠与满减优惠是以订单内菜品的总额来计算的,不包含配送费与包装费。

Q:超时赔付是什么意思?
超时赔付模式即:商家承诺一个送达时间和一个折扣,从用户下单时间开始计算,如果外卖超过了承诺时间才送到,该份外卖按照折扣价收取费用。由于恶劣天气、某些美食烹调时间过长、或者其他因素,商家会选择性的延长承诺时间或者不做承诺。预订单不参与超时赔付。
订单问题

Q:为什么提示我“账户存在异常,无法下单”?
包含(但不仅限于)以下行为者,系统将自动予以封禁(客服无权解封):
i)有过虚假交易(编造不存在真实买卖的订单);
ii)有过恶意下单行为;

Q:如何取消订单?
如果商家尚未接单,您可以在订单详情页通过“取消订单”功能进行取消;如果商家已接单,则需要您电话联系后由商家取消订单。

Q:我的订单为什么被取消了?
如果商家5分钟未接您的订单,为了保障您的权益,系统将会为您自动取消订单;商家接单后可能由于无法联系到您、菜品售完等原因无法配送,因而取消了您的订单,具体原因可查看系统发送的短信或通知。

Q:如何进行催单?
您可以在订单状态页面点击“电话催单”按钮向商家商家催单。

Q:刚下单发现信息填错了怎么办?
如果商家尚未接单,您可以自主取消订单;如果商家已经接单,您可以电话联系商家后由对方取消订单。然后重新下一单。

Q:我的订单是否被商家确认?
App用户可以在商家确认您的订单时收到推送通知,并且订单状态会实时更新;手机触屏版及微信用户可以刷新订单状态页查看。

Q:预计送达的时间为什么与我实际收餐的时间不符?
预计送达时间是系统根据用户评价的时间进行综合计算而得到的参考时间,餐厅的实际配送时间会受到当天的天气、订单量等外界因素影响。

Q:为什么会出现无法下单的情况?
无法下单有很多情况,可能是菜品售完、餐厅不在营业时间等,请查看无法下单时给的提示。

Q:为什么提示下单次数过多,已无法下单?
同一手机号在同一设备上一天最多可以成功提交7次订单(在线支付以完成支付为准,货到付款以提交订单为准)。

Q:如果对商家服务不满意如何进行投诉?
您可以在该订单完成评价后点击订单详情页中的意见反馈向客服投诉,或者拨打美团外卖的客服电话(400-850-7777)向客服进行投诉。

Q:如何联系客服解决问题?
您可以拨打美团外卖的客服电话(400-850-7777)或者在“我的”——>“意见反馈”页面提交反馈。

Q:我用的是手机客户端,为什么无法定位?
请先检查手机的网络以及是否开启定位功能。若确认正常,请试着在户外或wifi环境下进行定位。

Q:如何修改自己的账户信息?
美团外卖使用的是美团账号,您可以在“我的”页面修改个人账号信息,也可以在美团网账号页面修改个人账号。

Q:为什么有时需要输入短信验证码?
为了保障您的账号安全及商家利益,对于新用户及一些有异常行为的下单系统会提示您输入短信验证码。如果迟迟未接收到短信验证码,您同样可以选择接听语音验证码。

3. 定义Document切割器

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*"); 
    }
    
}

4.使用【基础组件】实现美团智能客服

        具体流程可以参考上面 用户,RAG和大模型交互原理图

public class RagDemo {
    public static void main(String[] args) {
        //向量模型
        OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
                .baseUrl("http://langchain4j.dev/demo/openai/v1")
                .apiKey("demo")
                .build();
        //向量存储
        RedisEmbeddingStore embeddingStore = RedisEmbeddingStore.builder()
                .host("127.0.0.1")
                .port(6379)
                .dimension(1536)
                .build();
        //内容检索器
        EmbeddingStoreContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
                .embeddingModel(embeddingModel)
                .embeddingStore(embeddingStore)
                .maxResults(3)
                .minScore(0.8)
                .build();
        // 创建模型
        ChatLanguageModel chatModel = OpenAiChatModel.builder()
                .apiKey("demo")
                .build();

        //加载文件
        Document document = getDocument();
        // 切分文件
        DocumentSplitter splitter = new CustomerServiceDocumentSplitter();
        List<TextSegment> segments = splitter.split(document);
        //生成向量
        List<Embedding> content = embeddingModel.embedAll(segments).content();
        //存储向量
        embeddingStore.addAll(content, segments);

        Query query = new Query("余额提现什么时候到账?");
        //查询转换器
        QueryTransformer queryTransformer = new ExpandingQueryTransformer(chatModel, 3);
        Collection<Query> queries = queryTransformer.transform(query);

        //查询路由器
        QueryRouter queryRouter = new LanguageModelQueryRouter(chatModel, Map.of(contentRetriever, "美团外卖-常见问题"));
        Map<Query, Collection<List<Content>>> queryToContents = Maps.newHashMap();
        for (Query query1 : queries) {
            //内容检索器
            Collection<ContentRetriever> retrievers = queryRouter.route(query1);
            for (ContentRetriever retriever : retrievers) {
                List<Content> contents = retriever.retrieve(query1);
                queryToContents.put(query1, Collections.singleton(contents));
            }
        }

        //内容增强器
        DefaultContentAggregator defaultContentAggregator = new DefaultContentAggregator();
        List<Content> contents = defaultContentAggregator.aggregate(queryToContents);
        //内容注入器,提示词
        ContentInjector contentInjector = new DefaultContentInjector();
        dev.langchain4j.data.message.UserMessage userMessage = contentInjector.inject(contents, dev.langchain4j.data.message.UserMessage.userMessage(query.text()));
        //调用大模型,获取内容
        Response<AiMessage> response = chatModel.generate(userMessage);
        System.out.println(response.content().text());
    }


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

代码执行结果为

余额提现一般会在1-7个工作日内到达您的支付账户,具体到账时间可能会受银行处理延迟影响。
您可以登录美团网,进入“我的美团→美团余额”查看提现状态,
或在“账号管理——我的账号”中查看余额是否已到账。

5. 使用AiService来实现美团智能客服(逻辑和第四步骤一样,只是这里用了高级组件Aiservice做代理简化代码)

  此代码加入了Tools工具, 可以让大模型知道最新的日期。

Tools工具代码

public class DateCalculator {

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

核心逻辑代码:

public class RagUserService {
    public interface CustomerServiceAgent {
        // 用来回答问题的方案
        Response<AiMessage> answer(@MemoryId Integer userId, @UserMessage String question);
    }

    public static void main(String[] args) {
        //向量模型
        OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
                .baseUrl("http://langchain4j.dev/demo/openai/v1")
                .apiKey("demo")
                .build();
        //向量存储
        RedisEmbeddingStore embeddingStore = RedisEmbeddingStore.builder()
                .host("127.0.0.1")
                .port(6379)
                .dimension(1536)
                .build();
        //内容检索器
        EmbeddingStoreContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
                .embeddingModel(embeddingModel)
                .embeddingStore(embeddingStore)
                .maxResults(3)
                .minScore(0.8)
                .build();
        // 创建模型
        ChatLanguageModel chatModel = ZhipuAiChatModel.builder()
                .apiKey("智普apikey")
                .build();

        DefaultRetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
                .contentRetriever(contentRetriever)
                .build();
        //创建AiService 代理
        CustomerServiceAgent agent = AiServices.builder(CustomerServiceAgent.class)
                .chatLanguageModel(chatModel)
                .retrievalAugmentor(retrievalAugmentor)
                .chatMemoryProvider(userId -> MessageWindowChatMemory.withMaxMessages(10))
                .tools(new DateCalculator())
                .build();

        //加载文件
        Document document = getDocument();
        // 切分文件
        DocumentSplitter splitter = new CustomerServiceDocumentSplitter();
        List<TextSegment> segments = splitter.split(document);
        //生成向量
        List<Embedding> content = embeddingModel.embedAll(segments).content();
        //存储向量
        embeddingStore.addAll(content, segments);


        Query query = new Query("今天的余额提现,最晚哪天能到账?给我具体的日期,什么时间?");
        //调用大模型
        Response<AiMessage> answer = agent.answer(1, query.text());
        System.out.println(answer.content().text());
        System.out.println(answer.tokenUsage());
    }

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

     代码执行结果为

根据您提供的信息,今天的余额提现,最晚将在7个工作日内到账。由于银行处理可能有延迟,
具体的到账时间可能会有所不同。如果按照最长的7个工作日计算,那么最晚的到账日期将是2024年7月24日。
不过,请注意,这个日期是工作日计算的,如果遇到周末或节假日,到账时间可能会进一步延迟。
具体的到账时间请以您的支付账户实际到账时间为准。

总结

大模型开发 vs. 传统JAVA开发

大模型开发——大模型实现业务逻辑:

开发前,开发人员关注数据准备(进行训练)、选择和微调模型(得到更好的效果,更能匹配业务预期),

开发过程中(大多数时候),重点在于如何有效地与大模型(LLM)进行沟通,利用LLM的专业知识解决特定的业务问题,

开发中更关注如何描述问题(提示工程 Propmt Engineering)进行有效的推理,关注如何将大模型的使用集成到现有的业务系统中。

传统的JAVA开发——开发者实现业务逻辑:

开发前,开发人员关注系统架构的选择(高并发、高可用),功能的拆解、模块化等设计。

开发过程中(大多数时候)是根据特定的业务问题,设计特定的算法、数据存储等以实现业务逻辑,以编码为主。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值