Spring AI 连续对话

本文介绍了如何在SpringAIChat中实现连续对话,通过传递消息历史记录上下文,以及讨论了预设上下文、减少token数以降低调用成本和扩展至多人对话的设计。作者还提到了使用RAG技术增强上下文理解和处理特殊需求的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Comfy UI

在前面 Spring AI Chat 简单示例 中我们只调用了一次请求,返回了一个结果,我们见过的各种 chat 都是支持连续对话的,AI 需要记住我们的上下文才能让对话连贯起来,通过 API 调用的时候每次对话都是一次无状态的独立请求,想要实现连续对话就需要我们自己记住对话的历史,在每次调用 API 的时候将对话历史传递给 API。

本文就简单实现连续对话,并且引申一些相关的扩折或者优化。

基本功能

首先要知道 Spring AI 中交互的消息对象,在 Prompt 中存在 public Prompt(List<Message> messages) 构造方法,可以传递多个消息,Message 中存在消息类型 MessageType,包含下面几种类型:

public enum MessageType {
	USER("user"),
	ASSISTANT("assistant"),
	SYSTEM("system"),
	FUNCTION("function");

这四种类型分别对应的 Message 实现,如 UserMessageSystemMessage

在 API 调用返回的 ChatResponse 包含的 Generation 中返回的是 AssistantMessage 消息,因此只需要通过 List<message> 按顺序记录消息历史就可以实现。

var openAiApi = ...//创建 API
//创建客户端,注意withMaxTokens参数,可以不限制
ChatClient chatClient = new OpenAiChatClient(openAiApi, OpenAiChatOptions.builder()
		.withModel("gpt-3.5-turbo")
		.withTemperature(0.4F).build());
//使用集合记录消息历史
List<Message> messages = new ArrayList<>();
//可以预设系统消息
messages.add(new SystemMessage("你是一个聊天机器人,所有回复请使用中文。"));
//演示从控制台输入内容进行对话
Scanner scanner = new Scanner(System.in);
while (true) {
	System.out.print(">>>>> ");
	String message = scanner.nextLine();
	//退出命令
	if (message.equals("exit")) {
		break;
	}
	//添加到消息历史
	messages.add(new UserMessage(message));
	//发送所有消息
	ChatResponse response = chatClient.call(new Prompt(messages));
	//将返回的助手消息添加到消息历史
	messages.add(response.getResult().getOutput());
	//输出返回的内容
	System.out.println("<<<<< "  + response.getResult().getOutput().getContent());
}

最近在看《大道朝天》,所以问几个相关的问题进行测试:

>>>>> 大道朝天的作者是谁?
<<<<< 大道朝天的作者是明代著名文学家冯梦龙。
>>>>> 你知道猫腻是谁吗?
<<<<< 猫腻是中国知名网络小说作家,代表作品有《择天记》、《将夜》等。
>>>>> 大道朝天的作者是谁?
<<<<< 大道朝天的作者是明代著名文学家冯梦龙。
>>>>> exit

可以看到 AI 的回复是错误的,尤其是有历史后,回复会变得更固定。当我去掉对话历史时,回复的内容会有所不同:

>>>>> 大道朝天的作者是谁?
<<<<< 大道朝天的作者是唐代诗人李白。
>>>>> 大道朝天的作者是谁?
<<<<< 大道朝天的作者是唐代诗人杜甫。
>>>>> 大道朝天的作者是谁?
<<<<< 大道朝天的作者是庄子。
>>>>> 大道朝天的作者是谁?
<<<<< 大道朝天的作者是李白。

接下来是几个基于对话的扩展。

预设上下文

当前很火的 RAG 技术实际上就是将已有知识向量化之后通过向量查询,将查询的结果作为上下文提供给 AI,在此基础上进行回答,上面的问题中,我们可以增加一个上下文,让 AI 回答的更准确,修改前面示例增加上下文:

List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个聊天机器人,所有回复请使用中文。"));
messages.add(new SystemMessage("猫腻 ,本名晓峰,1977年出生于湖北省宜昌市夷陵区,网络作家,橙瓜见证·网络文学20年十大玄幻作家,百强大神作家,百位行业人物。\n" +
                "猫腻曾就读于四川大学(未毕业),后从事网络文学创作,现为阅文集团白金作家。" +
                "代表作《朱雀记》《庆余年》《间客》《将夜》《择天记》《大道朝天》、《映秀十年事》等,其中《朱雀记》于2007年获得新浪第四届原创大赛·奇幻武侠奖一等奖;" +
                "《间客》于2013年获得首届“西湖·类型文学双年奖”银奖 ,于2018年入选“中国网络文学20年20部作品”,位列榜首 ;《将夜》于2015年获得首届网络文学双年奖金奖 。"));

把百度的一段内容放到了上下文,再重新进行上面的对话:

>>>>> 大道朝天的作者是谁?
<<<<< 《大道朝天》的作者是网络作家猫腻。
>>>>> 你知道猫腻是谁吗?
<<<<< 猫腻是网络作家的笔名,本名是晓峰,是一位知名的网络文学作家,代表作品包括《朱雀记》、《庆余年》、《间客》、《将夜》、《择天记》、《大道朝天》等。
>>>>> 猫腻获得过哪些奖?
<<<<< 猫腻曾获得过多个奖项,其中包括:
1. 《朱雀记》于2007年获得新浪第四届原创大赛·奇幻武侠奖一等奖;
2. 《间客》于2013年获得首届“西湖·类型文学双年奖”银奖;
3. 《将夜》于2015年获得首届网络文学双年奖金奖。

有了上下文后 AI 的回答更准确了。

减少 token 数

AI 的调用价钱和 token 数相关,所以如果支持无限长度的对话会使得成本快速升高,所以一般的对话会限制连续对话的次数,或者通过 AI 将对话总结后减少需要传递的内容。

如果想减少次数,最简单的就是判断对话数,然后减少前面的内容,在原始代码中修改如下:

messages.add(new UserMessage(message));
// messages 保留最近的10次对话
if (messages.size() > 10) {
	messages.remove(0);
}

如果通过总结的方式,还可以额外调用一次 AI,然后去掉前面几次的 user 和 assistant 内容,将总结的内容追加到最后,再加上本次的用户消息进行发送。

支持多人对话

示例中只能单线程的一个人进行对话,如果通过 http 提供服务,想要支持多人对话,最简单的方式就是给每个对话分配一个对话id,然后将对话id和对应的对话历史联系起来,例如使用 Map

Map<String, List<Message>> chatMessage = new ConcurrentHashMap<>();

对话时都带着对话id即可将多人的对话区分开。

连续对话的实现比较简单,我们能额外做的就是在 API 调用时如何获取并提供预设信息,如何处理额外的 API 调用实现一些特殊需求,在消息最终展示给用户时需要做的各种处理。

### 插件实现连续对话的方法 在构建支持连续对话的插件时,核心在于维护会话状态并有效地处理上下文信息。通过结合Spring Boot框架和OpenAI API工具包中的`OpenAiUtils`,能够创建一个具备持续交互能力的应用程序[^1]。 #### 维护会话状态 为了使应用程序能够在多个回合内记住之前的交流内容,通常采用两种主要策略: - **基于内存的状态保存**:适用于较短时间内的简单对话场景,在服务器端利用变量存储临时数据。 - **持久化存储方案**:针对更复杂的长期互动需求,则需借助数据库或其他形式的数据仓库来记录过往消息及其解析后的意图等重要参数。 #### 处理上下文信息 当涉及到自然语言处理部分时,可运用先进的神经网络架构(比如LSTM或Transformer),它们擅长于执行序列到序列的任务——即把输入的一系列词映射成另一组有意义的文字输出,从而帮助机器更好地理解和回应人类用户的表达方式[^2]。 ```java // Java代码片段展示如何配置Spring Boot项目以集成OpenAI服务 import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class ChatController { private final OpenAiService openAiService; // 假设这是已经初始化好的OpenAI客户端实例 public ChatController() { this.openAiService = new OpenAiServiceImpl(); // 实现细节省略 } @PostMapping("/chat") String chat(@RequestBody Map<String, Object> body) throws Exception { String messageText = (String)body.get("message"); // 调用OpenAI接口获取回复 CompletionRequest completionRequest = CompletionRequest.builder() .prompt(messageText) .maxTokens(150) .build(); CompletionResult result = openAiService.createCompletion(completionRequest); return result Choices().get(0).text(); } } ``` 此段Java代码展示了怎样设置RESTful Web Service端点用于接收来自前端的消息请求,并转发给OpenAI平台寻求智能化应答;同时简化了实际业务逻辑以便专注于说明重点概念。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

isea533

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值