进阶版是在基础的对话版之上进行新增功能。
如果还没弄出基础版的,请参考
一,进阶版需要实现的功能
- 给AI进行功能预设
- 记忆对话,能自动联系上下文语境
- 结合业务,通过对话操作系统业务
简单解释一下
给AI进行功能预设
比如当客户发送特定消息时,ai需要做出什么回应
或者
让ai充当淘宝客服之类的角色
记忆对话,能自动联系上下文语境
同一时间多个用户访问时,分别可以对应多个用户,不会混淆上下文语境。
比如用户A说了自己是A,用户B说了自己是B,那么A在问自己是谁时,AI能准确回答出用户是A,而不会混淆说A是B。
结合业务,通过对话操作系统业务
类似于现在的手机助手,叫声“小艺,帮我买杯霸王别姬的奶茶”。小艺就会自动下单购买奶茶。
二,代码编写
首先整合一下上次的样例代码,做一下简单的调整。
简单调整上次的代码
controller
package org.example.springaidemo.controller;
import org.example.springaidemo.impl.SimpleControllerImpl;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
public class SimpleController {
private final SimpleControllerImpl simpleControllerimpl;
@Autowired
public SimpleController(OpenAiChatModel openAiChatModel, SimpleControllerImpl simpleControllerimpl) {
this.simpleControllerimpl = simpleControllerimpl;
}
@GetMapping("/ai/generate")
public String generate(@RequestParam(value = "message", defaultValue = "讲个笑话") String message) {
return simpleControllerimpl.generate(message);
}
@GetMapping("/ai/generateStream")
public Flux<String> generateStream(@RequestParam(value = "message", defaultValue = "讲个笑话") String message) {
return simpleControllerimpl.generateStream(message);
}
}
impl
package org.example.springaidemo.impl;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
@Service
public class SimpleControllerImpl {
private final ChatClient client;
@Autowired
public SimpleControllerImpl(ChatClient.Builder clientBuilder) {
this.client = clientBuilder.build();
}
public String generate(String msg) {
return this.client.prompt()
.user(msg)
.call()
.content();
}
public Flux<String> generateStream(String msg) {
return this.client.prompt()
.user(msg)
.stream()
.content();
}
}
测试
给AI进行功能预设
修改我们的impl类
package org.example.springaidemo.impl;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
@Service
public class SimpleControllerImpl {
private final ChatClient client;
@Autowired
public SimpleControllerImpl(ChatClient.Builder clientBuilder) {
this.client = clientBuilder.defaultSystem(
"""
你是一家名叫“Rojer”的淘宝客服。
当用户语句中包含“退款”时,你需要回复“不好意思,本店不支持7天无理由退款”
"""
).build();
}
public String generate(String msg) {
return this.client.prompt()
.user(msg)
.call()
.content();
}
public Flux<String> generateStream(String msg) {
return this.client.prompt()
.user(msg)
.stream()
.content();
}
}
这里需要注意,大模型是经过特定训练后的,它无法做出一些本身禁止于大模型的回复,
比如说污言秽语,比如说民族纠纷,比如说反人类语言。
这个时候,我们再进行一些简单测试
记忆对话,能自动联系上下文语境
这里有两个方面
- 需要开启ai的记忆功能
- 需要对不同用户进行分别的处理
别的不多说,都在代码中,注释中
impl
package org.example.springaidemo.impl;
import org.example.springaidemo.config.MychatMemory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
/**
* SimpleControllerImpl 是一个服务类,用于处理基于 AI 的对话。
* 它利用 Spring AI Chat 框架,通过会话 ID(token)管理不同用户的上下文和对话。
*/
@Service
public class SimpleControllerImpl {
// AI 对话客户端实例
private final ChatClient client;
// 自定义的对话存储实现,用于保存用户会话上下文
private final MychatMemory mychatMemory;
/**
* 构造方法,初始化 ChatClient 和自定义的对话存储。
*
* @param clientBuilder 用于构建 ChatClient 的构建器
* @param mychatMemory 自定义的对话存储实现
*/
@Autowired
public SimpleControllerImpl(ChatClient.Builder clientBuilder, MychatMemory mychatMemory) {
this.mychatMemory = mychatMemory;
// 初始化 ChatClient,并设置默认系统提示和对话存储
this.client = clientBuilder.defaultSystem(
"""
你是一家名叫“Rojer”的淘宝客服。
当用户语句中包含“退款”时,你需要回复“不好意思,本店不支持7天无理由退款”
"""
)
.defaultAdvisors(new PromptChatMemoryAdvisor(mychatMemory))
.build();
}
/**
* 生成基于用户消息和会话 token 的 AI 回复。
*
* @param msg 用户输入的消息
* @param token 表示会话唯一标识,用于区分不同用户的上下文
* @return AI 的回复内容
*/
public String generate(String msg, String token) {
return this.client.prompt()
.user(msg) // 用户的输入
.advisors(adv -> adv
// 设置检索的上下文记录条数
.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)
// 指定会话唯一标识,用于区分不同的用户对话
.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, token))
.call() // 调用 AI 服务,生成回复
.content(); // 获取生成的文本内容
}
/**
* 以流式方式生成基于用户消息和会话 token 的 AI 回复。
* 适用于需要逐步接收回复内容的场景,例如聊天应用中的实时响应。
*
* @param msg 用户输入的消息
* @param token 表示会话唯一标识,用于区分不同用户的上下文
* @return Flux<String> 流式的回复内容
*/
public Flux<String> generateStream(String msg, String token) {
return this.client.prompt()
.user(msg) // 用户的输入
.advisors(adv -> adv
// 设置检索的上下文记录条数
.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)
// 指定会话唯一标识,用于区分不同的用户对话
.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, token))
.stream() // 以流式模式调用 AI 服务
.content(); // 获取生成的文本流内容
}
}
自定义的chatMemory
package org.example.springaidemo.config;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class MychatMemory implements ChatMemory {
Map<String, List<Message>> conversationHistory = new ConcurrentHashMap<>();
@Override
public void add(String conversationId, List<Message> messages) {
this.conversationHistory.computeIfAbsent(conversationId, id -> Collections.synchronizedList(new ArrayList<>()))
.addAll(messages);
}
@Override
public void add(String conversationId, Message message) {
this.conversationHistory.computeIfAbsent(conversationId, id -> Collections.synchronizedList(new ArrayList<>()))
.add(message);
}
@Override
public List<Message> get(String conversationId, int lastN) {
List<Message> allMessages = conversationHistory.get(conversationId);
if (allMessages == null || allMessages.isEmpty()) {
return List.of(); // 如果没有历史记录,返回空列表
}
// 计算获取的起始位置
int start = Math.max(0, allMessages.size() - lastN);
return new ArrayList<>(allMessages.subList(start, allMessages.size())); // 返回一个新列表,避免外部修改
}
@Override
public void clear(String conversationId) {
conversationHistory.remove(conversationId); // 移除该会话的历史记录
}
}
看看 实际对话是否有分别存储到自定义的chatMemory中
这里可以看出已经按照我的需求将两条不同的会话进行分别处理了。
这里可以根据自己的需要,使用标准的token,或者直接使用sessionID都可以。
结合业务,通过对话操作系统业务
这里需要用到springAI提供的fuction方法
详细都在注释 中
SimpleFunction
package org.example.springaidemo.config;
import org.example.springaidemo.impl.SimpleControllerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;
import java.util.function.Function;
/**
* SimpleFunction 是一个 Spring 配置类,定义了应用中使用的函数 Bean。
* 主要用于暴露基于 Lambda 表达式的业务逻辑函数。
*/
@Configuration
public class SimpleFunction {
// 引用业务逻辑实现类 SimpleControllerImpl
private final SimpleControllerImpl simpleImpl;
/**
* 构造方法,注入 SimpleControllerImpl 实例。
*
* @param simpleImpl SimpleControllerImpl 的实例
*/
@Autowired
public SimpleFunction(SimpleControllerImpl simpleImpl) {
this.simpleImpl = simpleImpl;
}
/**
* 内部静态记录类,用于封装输入参数。
* 在这里,PriceAll 用于传递商品的数量。
*
* @param count 商品的数量
*/
public record PriceAll(int count){}
/**
* 定义一个 Function 类型的 Bean,用于计算总价格。
*
* @return 一个函数,接收 PriceAll 类型的输入,返回计算结果(总价格)的字符串表示
*/
@Bean
@Description("获取总价格")
public Function<PriceAll, String> getPrice(){
return priceCount -> {
// 从输入中获取商品数量,并调用业务逻辑计算总价格
Double pricedAll = simpleImpl.priceAll(priceCount.count);
// 返回总价格的字符串表示
return pricedAll.toString();
};
}
}
修改我们的Impl
package org.example.springaidemo.impl;
import org.example.springaidemo.config.MychatMemory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Description;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
/**
* SimpleControllerImpl 是一个服务类,用于处理基于 AI 的对话。
* 它利用 Spring AI Chat 框架,通过会话 ID(token)管理不同用户的上下文和对话。
*/
@Service
public class SimpleControllerImpl {
// AI 对话客户端实例
private final ChatClient client;
// 自定义的对话存储实现,用于保存用户会话上下文
private final MychatMemory mychatMemory;
/**
* 构造方法,初始化 ChatClient 和自定义的对话存储。
*
* @param clientBuilder 用于构建 ChatClient 的构建器
* @param mychatMemory 自定义的对话存储实现
*/
@Autowired
public SimpleControllerImpl(ChatClient.Builder clientBuilder, MychatMemory mychatMemory) {
this.mychatMemory = mychatMemory;
// 初始化 ChatClient,并设置默认系统提示和对话存储
this.client = clientBuilder.defaultSystem(
"""
你是一家名叫“Rojer”的淘宝客服。
当用户语句中包含“退款”时,你需要回复“不好意思,本店不支持7天无理由退款”
"""
)
.defaultAdvisors(new PromptChatMemoryAdvisor(mychatMemory))
.build();
}
/**
* 生成基于用户消息和会话 token 的 AI 回复。
*
* @param msg 用户输入的消息
* @param token 表示会话唯一标识,用于区分不同用户的上下文
* @return AI 的回复内容
*/
public String generate(String msg, String token) {
return this.client.prompt()
.user(msg) // 用户的输入
.advisors(adv -> adv
// 设置检索的上下文记录条数
.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)
// 指定会话唯一标识,用于区分不同的用户对话
.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, token))
.call() // 调用 AI 服务,生成回复
.content(); // 获取生成的文本内容
}
/**
* 以流式方式生成基于用户消息和会话 token 的 AI 回复。
* 适用于需要逐步接收回复内容的场景,例如聊天应用中的实时响应。
*
* @param msg 用户输入的消息
* @param token 表示会话唯一标识,用于区分不同用户的上下文
* @return Flux<String> 流式的回复内容
*/
public Flux<String> generateStream(String msg, String token) {
return this.client.prompt()
.user(msg) // 用户的输入
.advisors(adv -> adv
// 设置检索的上下文记录条数
.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)
// 指定会话唯一标识,用于区分不同的用户对话
.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, token))
.functions("getPrice")// 指定需要调用的功能
.stream() // 以流式模式调用 AI 服务
.content(); // 获取生成的文本流内容
}
public Double priceAll(int count) {
double price = 3.25;
double re = price * count;
System.out.println("打印这条内容,代表已经执行了priceAll该方法。");
return re;
}
}
看看测试结果
以上,后面还会出AI的进一步详细且方便的使用。欢迎各位大佬持续关注