Part12 - 搭建AI智能助手2

YangeIT大约 7 分钟中州养老AIIOT版本MysqlApifoxServletHTTPGETPOST

Part12 - 搭建AI智能助手2

1. 搭建AI智能助手2

1.1. 退订实现

前言

接下来,我们来实现酒店系统的最核心的2个功能退定和更改预定。首先我们完成退定功能。

首先,我们来分析下退定的需求

  1. 退定后,状态改成已取消 image
  2. 退定需要获取当前房间信息(用户需要提供房间号和姓名)
  3. 如果房间号和姓名不匹配,无法退定,需要提示用户
  4. 需要在代码中准备退定的方法,类似Service层的方法 image

然后,我们来实现退定功能。

  1. 我们如何获取退定方法的2个参数roomNumber, customerName?

我们可以通过提示词来实现,通过提示词,让用户输入房间号和姓名,然后收集后执行后续的方法

public OpenAiController(ChatClient.Builder chatClientBuilder,ChatMemory chatMemory) {
this.chatClient = chatClientBuilder
        .defaultSystem("""
                您是二龙山酒店的客户聊天支持代理。请以友好、乐于助人且愉快的方式来回复。
                您正在通过在线聊天系统与客户互动。
                在提供有关预定或取消预定的信息之前,您必须始终
                从用户处获取以下信息:房间号、客户姓名。
                在询问用户之前,请检查消息历史记录以获取此信息。
                请讲中文。
                今天的日期是 {current_date}.""")
        .defaultAdvisors(
                new PromptChatMemoryAdvisor(chatMemory),
                new LoggingAdvisor()
                )
        .build();
}





 
 
 








重启项目,我们发现,每次聊天,他都会问我们的房间号和姓名信息 image

但是我们输入了房间号和姓名后,他就开始扯蛋了 ,为什么尼? 因为他没有调用我的业务方法,不知道预定信息

image
image

1.2 退订实function-call介绍

前言

接下来,我们来实现AI调用方法,首先我们来了解下function-call的流程图,👇

function-call是AI调用方法的一种方式,通过function-call,AI可以调用我们定义的方法,从而实现业务逻辑。

image
image

熟悉了流程图,接下来弄懂3个问题:

  1. 需要告诉大模型,我们回调那个方法
  • 提供实现了Function接口的Bean
  1. 需要告诉大模型,什么对话才调用这个方法
  • 配置Function的作用(直接写好信息)
  1. 需要告诉大模型,提取对话中哪些关键字
  • Function的第一个泛型去指定关键字的变量名字

综上所述:搞定Function就行

Function源码
Function源码

1.3 退订确认

退订确认

搞定Function后,我们就可以实现退订功能了,首先我们创建一个配置类,将Function定在这个配置类中

从上节我们知道,Function有2个泛型,其中第一个是参数,第二个是结果,为了方便数据传输,我们定义一个内部类,为了方便定义,我们使用密封类

密封类是Java 17正式支持的一个新特性,它让Java中类的继承可以更加细粒度的进行控制。今天就来认识一下这个新的功能。(下面第14行 )

@Configuration
public class ReserveTools {

	@Autowired
	RoomService roomService;

	@JsonInclude(Include.NON_NULL)
	public record ReserveDetails(String roomNumber,
								 String customerName,
								 String roomType, 
								 LocalDateTime reserveTime,
			String status) {
	}
	public record CancelReserveRequest(String roomNumber,String customerName){}
	
	@Bean
	@Description("处理房间退定")
	public Function<CancelReserveRequest,String> cancelReserve(){
		return cancelBookingRequest -> {
			// apply 调用退定方法
			String result = roomService.cancelReservation(
					cancelBookingRequest.roomNumber(),
					cancelBookingRequest.customerName()
			);
			return result;
		};
	}
}













 














Function是一个函数式接口,我们可以使用Lambda表达式来创建一个Function对象,在{}中我们调用退订的方法,参数是CancelReserveRequest里面的2个属性roomNumber和customerName,返回值是String类型,返回值是调用退订方法的结果

那么大模型怎么知道,调用这个方法尼?需要再方法上加上@Description("处理房间退定"),告诉大模型,这个方法的作用是处理房间退定,这样大模型才知道,调用这个方法, 当对话中出现退定关键字的时候,就会调用这个方法

那么如何将方法配置到大模型中尼? 可以在chatClientBuilder配置functions image

完成后,我们来测试: image

至此,我们的退订功能就完成了,接下来大家练习一下吧

1.4 退订再次确认实现

退订实现

上一节我们完成了退订功能,但是退订后,用户不知道退订成功了,所以我们需要增加一个确认退订的步骤. 可以修改提示词并加上获取预定详情的方法

 在更改或退定之前,请先获取房间预订信息并且展示给用户,用户确认无误后才进行更改或退定。

加上提示词后,我们重启项目,测试退订 image

测试发现,并没有获取预订详情,因为我们没有定义获取预定详情的方法,接下来需要定义获取预订详情的方法,修改ReserveTools类,增加获取预订详情的方法

public record ReserveDetailsRequest(String roomNumber,String customerName){}

@Bean
@Description("获取房间预定详细信息")
public Function<ReserveDetailsRequest, ReserveDetails> getReserveDetails() {
return request -> {
        try {
                RoomCustomer detail = roomService.getReservationDetail(request.roomNumber(), request.customerName());
                return new ReserveDetails(detail.getRoomNumber(), detail.getCustomerName(), detail.getRoomType(),
                                detail.getReserveTime(), detail.getStatus());
        }
        catch (Exception e) {

                return new ReserveDetails(request.roomNumber(), request.customerName(), null, null, null);
        }
};
}

确定好方法后,需要将方法配置到大模型中 image

重启项目,测试 image

1.5 RAG检索增强

RAG检索增强

上面我们完成了退订功能,但是有的退订需要付违约金,有的退订不需要获取违约金。比方说入住前6小时退定,不需要付违约金,入住前1小时-6小时退定,需要付违约金。那么我们如何根据不同的退定时间,来计算违约金尼?这些规定一般是公司的规定,而不能由大模型来决定,所以我们需要增加一个RAG检索增强,根据不同的退定时间,来计算违约金。

image
image

用户提问 → 转换为向量 → 检索知识库 → 组合提问+检索内容 → 输入大模型 → 输出回答

RAG检索:其实就是相似性检索,也叫向量检索,其实就是给项目外挂一个知识库,当大模型回答问题时,会先去知识库中查找,如果找到了,就使用知识库中的答案,如果没有找到,就使用大模型的答案。

这里为了演示,我们使用一个简单的知识库。

  1. 我们准备一个文件terms-of-service.txt,这个文件放在resources下的rag文件夹下,文件内容如下:
本服务条款适用于您对Yg酒店房间预定系统的体验。预定房间,即表示您同意这些条款。
1. 预定房间
- 通过我们的网站或移动应用程序预订。
- 预订时需要全额付款。
- 确保个人信息(姓名、房间号等)的准确性,因为更正可能会产生 20元的费用。
3. 取消预定
- 取消预定需要支付20元违约金
- 退款将在 7 个工作日内处理。
  1. 配置一个向量数据库(可以通过redis等工具实现),本项目为了演示,我们使用一个简单的内存数据库
    @Bean
    public VectorStore vectorStore(EmbeddingModel embeddingModel) {
        return new SimpleVectorStore(embeddingModel);
    }

查看SimpleVectorStore的源码,发现底层其实就是一个map集合 protected Map<String, Document> store;

  1. 接下来需要将知识内容写入到向量数据,我们可以在项目启动的写入,可以使用如下代码:

// 启动springboot的时候就会运行
@Bean
CommandLineRunner ingestTermOfServiceToVectorStore(EmbeddingModel embeddingModel, VectorStore vectorStore,
                                                @Value("classpath:rag/terms-of-service.txt") Resource termsOfServiceDocs) {

return args -> {
        vectorStore.write(                                  // 3.写入
                new TokenTextSplitter().transform(          // 2.转换
                        new TextReader(termsOfServiceDocs).read())  // 1.读取
        );

};
}

4.将向量数据库挂载到大模型中

image
image
  1. 重启测试
image
image

可以发现,大模型已经可以检索到知识库中的内容了。