Part12 - 搭建AI智能助手2
Part12 - 搭建AI智能助手2
1. 搭建AI智能助手2
1.1. 退订实现
前言
接下来,我们来实现酒店系统的最核心的2个功能退定和更改预定。首先我们完成退定功能。
首先,我们来分析下退定的需求
- 退定后,状态改成已取消
- 退定需要获取当前房间信息(用户需要提供房间号和姓名)
- 如果房间号和姓名不匹配,无法退定,需要提示用户
- 需要在代码中准备退定的方法,类似Service层的方法
然后,我们来实现退定功能。
- 我们如何获取退定方法的2个参数roomNumber, customerName?
我们可以通过提示词来实现,通过提示词,让用户输入房间号和姓名,然后收集后执行后续的方法
public OpenAiController(ChatClient.Builder chatClientBuilder,ChatMemory chatMemory) {
this.chatClient = chatClientBuilder
.defaultSystem("""
您是二龙山酒店的客户聊天支持代理。请以友好、乐于助人且愉快的方式来回复。
您正在通过在线聊天系统与客户互动。
在提供有关预定或取消预定的信息之前,您必须始终
从用户处获取以下信息:房间号、客户姓名。
在询问用户之前,请检查消息历史记录以获取此信息。
请讲中文。
今天的日期是 {current_date}.""")
.defaultAdvisors(
new PromptChatMemoryAdvisor(chatMemory),
new LoggingAdvisor()
)
.build();
}
重启项目,我们发现,每次聊天,他都会问我们的房间号和姓名信息
但是我们输入了房间号和姓名后,他就开始扯蛋了 ,为什么尼? 因为他没有调用我的业务方法,不知道预定信息

1.2 退订实function-call介绍
前言
接下来,我们来实现AI调用方法,首先我们来了解下function-call的流程图,👇
function-call是AI调用方法的一种方式,通过function-call,AI可以调用我们定义的方法,从而实现业务逻辑。

熟悉了流程图,接下来弄懂3个问题:
- 需要告诉大模型,我们回调那个方法
- 提供实现了Function接口的Bean
- 需要告诉大模型,什么对话才调用这个方法
- 配置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
完成后,我们来测试:
至此,我们的退订功能就完成了,接下来大家练习一下吧
1.4 退订再次确认实现
退订实现
上一节我们完成了退订功能,但是退订后,用户不知道退订成功了,所以我们需要增加一个确认退订的步骤. 可以修改提示词并加上获取预定详情的方法
在更改或退定之前,请先获取房间预订信息并且展示给用户,用户确认无误后才进行更改或退定。
加上提示词后,我们重启项目,测试退订
测试发现,并没有获取预订详情,因为我们没有定义获取预定详情的方法,接下来需要定义获取预订详情的方法,修改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);
}
};
}
确定好方法后,需要将方法配置到大模型中
重启项目,测试
1.5 RAG检索增强
RAG检索增强
上面我们完成了退订功能,但是有的退订需要付违约金,有的退订不需要获取违约金。比方说入住前6小时退定,不需要付违约金,入住前1小时-6小时退定,需要付违约金。那么我们如何根据不同的退定时间,来计算违约金尼?这些规定一般是公司的规定,而不能由大模型来决定,所以我们需要增加一个RAG检索增强,根据不同的退定时间,来计算违约金。

用户提问 → 转换为向量 → 检索知识库 → 组合提问+检索内容 → 输入大模型 → 输出回答
RAG检索:其实就是相似性检索,也叫向量检索,其实就是给项目外挂一个知识库,当大模型回答问题时,会先去知识库中查找,如果找到了,就使用知识库中的答案,如果没有找到,就使用大模型的答案。
这里为了演示,我们使用一个简单的知识库。
- 我们准备一个文件
terms-of-service.txt
,这个文件放在resources下的rag文件夹下,文件内容如下:
本服务条款适用于您对Yg酒店房间预定系统的体验。预定房间,即表示您同意这些条款。
1. 预定房间
- 通过我们的网站或移动应用程序预订。
- 预订时需要全额付款。
- 确保个人信息(姓名、房间号等)的准确性,因为更正可能会产生 20元的费用。
3. 取消预定
- 取消预定需要支付20元违约金
- 退款将在 7 个工作日内处理。
- 配置一个向量数据库(可以通过redis等工具实现),本项目为了演示,我们使用一个简单的内存数据库
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
return new SimpleVectorStore(embeddingModel);
}
查看SimpleVectorStore的源码,发现底层其实就是一个map集合
protected Map<String, Document> store;
- 接下来需要将知识内容写入到向量数据,我们可以在项目启动的写入,可以使用如下代码:
// 启动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.将向量数据库挂载到大模型中

- 重启测试

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