ChatModel

image.png
获取ChatModel
public ChatModel charmodel () {
// 初始化 ChatModel
DashScopeApi dashScopeApi = DashScopeApi.builder()
.apiKey(aiDashScopeApiKey)
.build();
// 默认方式构建
return DashScopeChatModel.builder()
.dashScopeApi(dashScopeApi)
.build();
// 指定模型参数
// return DashScopeChatModel.builder()
// .dashScopeApi(dashScopeApi)
// .defaultOptions(DashScopeChatOptions.builder()
// .withModel(DashScopeChatModel.DEFAULT_MODEL_NAME)
// .withTemperature(0.5)
// .withMaxToken(1000).build())
// .build();
}
调用大模型
/**
* 默认适用UserMessage请求大模型
* @param query
* @return
*/
@GetMapping("/simple")
public String simple(@RequestParam(name = "query") String query){
//调用模型
return chatModel.call(query);
}
/**
* 通过SystemMessage;UserMessage请求大模型
* @param query
* @return
*/
@GetMapping("/message")
public String message(@RequestParam(name = "query") String query){
//系统提示词
SystemMessage systemMessage = new SystemMessage("你是一个AI助手");
//用户提示词
UserMessage userMessage = new UserMessage(query);
//调用模型
return chatModel.call(systemMessage,userMessage);
}
/**
* 通过Prompt方式请求大模型
* @param query
* @return
*/
@GetMapping("/chatOptions")
public ChatResponse chatOptions(@RequestParam(name = "query") String query){
//系统提示词
SystemMessage systemMessage = new SystemMessage("你是一个AI助手");
//用户提示词
UserMessage userMessage = new UserMessage(query);
//通过构造方式指定模型参数
// DashScopeChatOptions dashScopeChatOptions = new DashScopeChatOptions();
// dashScopeChatOptions.setModel(DashScopeChatModel.DEFAULT_MODEL_NAME);
// 大模型输出随机性的核心参数,值越低输出结果越稳定,值越高输出结果越有创意(不固定)
// dashScopeChatOptions.setTemperature(0.1);
// 限制大模型回答的最长字数
// dashScopeChatOptions.setMaxTokens(15536);
// Prompt prompt = new Prompt(List.of(systemMessage,userMessage),dashScopeChatOptions);
//通过构造器的方式指定模型参数
DashScopeChatOptions chatOptions = DashScopeChatOptions.builder()
.model(DashScopeChatModel.DEFAULT_MODEL_NAME)
.temperature(0.5)
.maxToken(15536).build();
Prompt prompt = Prompt.builder()
.messages(List.of(systemMessage, userMessage))
.chatOptions(chatOptions)
.build();
//调用模型,返回结果
//如何从ChatResponse 获取结果 chatModel.call(prompt).getResult().getOutput().getText()
return chatModel.call(prompt);
}
流式调用时乱码配置
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=tru
获取ChatClient
@Bean
public ChatClient chatClient(){
return ChatClient.builder(charmodel()).build();
}
ChatClient方式请求大模型
/**
* 通过ChatClient请求大模型
* @param query
* @return
*/
@GetMapping("/simple")
public String simple(@RequestParam(name = "query") String query){
DashScopeChatOptions chatOptions = new DashScopeChatOptions();
chatOptions.setMaxTokens(15336);
chatOptions.setTemperature(0.5);
chatOptions.setModel(DashScopeChatModel.DEFAULT_MODEL_NAME);
// SystemMessage systemMessage = new SystemMessage("你是一个AI助手");
// UserMessage userMessage = new UserMessage(query);
// Prompt prompt = new Prompt(List.of(systemMessage,userMessage),chatOptions);
//
// return chatClient.prompt(prompt).call().content();
return chatClient.prompt()
.system("你是一个AI助手")
.user(query)
.options(chatOptions)
.call().content();
}
/**
* 调用大模型返回指定类格式数据
* @return
*/
@GetMapping("/response")
public Movies response() {
return chatClient.prompt()
.user("给我随机生成一部中国高评分电影,要求有影片的简介信息")
.call().entity(Movies.class);
}
/**
* 调用大模型返回指定流式输出数据
*
* @return
*/
@GetMapping("/stream")
public Flux<String> stream() {
return chatClient.prompt()
.user("给我随机生成一部中国高评分电影,要求有影片的简介信息")
.stream().content();
}
Advisors
Advisors的本质是让你不修改业务代码的前提下,统一增强AI调用能力。
Spring AI Advisors是Spring Al框架中的核心拦截器组件,专门用于处理和增强AI应用程序中的请求与响应流。其设计于过滤器拦截器非常类似。
优势:
将常见的生成式AI 模式(如对话记忆、敏感词过滤、RAG检索)打包成可重用单元,简化开发流程创建可跨不同模型和用例工作的可重用转换组件,提升代码灵活性,
核心作用
用户请求 -》advisor2请求 -》advisor1请求-》chatModel(调用API)-》advisor1响应-》advisor2响应
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
log.info("YtCallAdvisor1 请求......");
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
log.info("YtCallAdvisor1 相应......");
return chatClientResponse;
}
@Override
public String getName() {
return "YtCallAdvisor1";
}
@Override
public int getOrder() {
return 0;
}
public class YtCallAdvisor2 implements CallAdvisor {
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
log.info("YtCallAdvisor2 请求......");
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
log.info("YtCallAdvisor2 相应......");
return chatClientResponse;
}
@Override
public String getName() {
return "YtCallAdvisor2";
}
@Override
public int getOrder() {
return 0;
}
}
/**
* 使用advisor增强
*
* @return
*/
@GetMapping("/advisor")
public String advisor() {
return chatClient.prompt()
.user("给我随机生成一部中国高评分电影,要求有影片的简介信息")
.advisors(new YtCallAdvisor1(),new YtCallAdvisor2())
.call().content();
}

image.png
Advisors形成一个责任链,在请求发送给AI模型前后都可以介入处理,如果我们想调整Advisor在Advisor链中的顺序只需要修改getOrder的返回值即可。getOrder的返回值越小在Advisor链中的顺序越靠前,也就是越早处理请求。所以如果我们希望把SGCallIAdvisor2的getOrder返回值设置为1。打印的结果就变成了
public class YtCallAdvisor2 implements CallAdvisor {
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
log.info("YtCallAdvisor2 请求......");
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
log.info("YtCallAdvisor2 相应......");
return chatClientResponse;
}
@Override
public String getName() {
return "YtCallAdvisor2";
}
@Override
public int getOrder() {
return 1;
}
}

image.png
自定义advisor
/**
* 自定义advisor实现不同会话不同的记忆管理-乞丐版
* @param query
* @param conversationId
* @return
*/
@GetMapping("/simpleMessageChatMemoryAdvisor")
public String simpleMessageChatMemoryAdvisor(@RequestParam(name = "query") String query,@RequestParam(name = "conversationId") String conversationId) {
return chatClient.prompt()
//把会话Id存入上下文
.advisors(advisorSpec -> advisorSpec.param("conversationId",conversationId))
.advisors(new SimpleMessageChatMemoryAdvisor())
.user(query)
.call().content();
}
//仅学习使用,实际场景不用此方法
private static Map<String, List<Message>> chatMessages = new HashMap<>();
/**
* 请求消息存入到缓存
* @param chatClientRequest
* @param advisorChain
* @return
*/
@Override
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
log.info("SimpleMessageChatMemoryAdvisor 请求增强.......................");
//先通过会话ID查询之前的会话记录,
String conversationId = chatClientRequest.context().get("conversationId").toString();
List<Message> messageList = chatMessages.get(conversationId);
if(Objects.isNull(messageList)){
messageList = new ArrayList<>();
}
// 把这次请求的会话添加到会话记录
List<Message> requestMessageList = chatClientRequest
.prompt().getInstructions();
messageList.addAll(requestMessageList);
chatMessages.put(conversationId,messageList);
//添加后请求的message 放入Prompt请求中
Prompt oldPrompt = chatClientRequest.prompt();
Prompt newPrompt = oldPrompt.mutate().messages(messageList).build();
ChatClientRequest request = chatClientRequest.mutate().prompt(newPrompt).build();
return request;
}
/**
* 响应信息存入到缓存-增强
* @param chatClientResponse
* @param advisorChain
* @return
*/
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
log.info("SimpleMessageChatMemoryAdvisor 响应增强.......................");
//先通过会话ID查询之前的会话记录
String conversationId = chatClientResponse.context().get("conversationId").toString();
List<Message> messageList = chatMessages.get(conversationId);
//获取Response响应消息,添加到会话记录中
if(Objects.isNull(chatClientResponse)){
return chatClientResponse;
}
AssistantMessage assitantMessage = chatClientResponse.chatResponse().getResult().getOutput();
messageList.add(assitantMessage);
chatMessages.put(conversationId,messageList);
return chatClientResponse;
}
@Override
public int getOrder() {
return 0;
}
内置advisor之MessageChatMemoryAdvisor
/**
*
* 通过内置的messageChatMemoryAdvisor 实现会话存储
* @param query
* @return
*/
@GetMapping("/messageChatMemoryAdvisor")
public String messageChatMemoryAdvisor(@RequestParam(name = "query") String query,@RequestParam(name = "conversationId") String conversationId) {
return chatClient.prompt()
//把会话Id存入上下文
.advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID,conversationId))
.user(query)
.call()
.content();
}
// 内置MessageChatMemoryAdvisor实现消息会话存储
MessageWindowChatMemory widownchatMemory = MessageWindowChatMemory.builder()
// 最大消息数量
// .maxMessages(20)
// .chatMemoryRepository()
.build();
MessageChatMemoryAdvisor chatMemoryAdvisor = MessageChatMemoryAdvisor.builder(widownchatMemory).build();
return ChatClient.builder(charmodel())
.defaultAdvisors(chatMemoryAdvisor)
.build();
实现原理

image.png

image.png
底层通过ConcurrentHashMap实现,默认保留最大消息数量为20
运行结果:
第一次请求
GET http://localhost:3030/chatmemory/messageChatMemoryAdvisor?query=我是小胖,我喜欢打乒乓球&conversationId=xp
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 1056
Date: Tue, 23 Jun 2026 09:57:21 GMT
哇,小胖你好!😄 听说你喜欢打乒乓球,太棒啦~乒乓球可是“国球”呢,既锻炼反应力、协调性,又特别有趣!你是喜欢单打还是双打?有没有特别崇拜的球员(比如马龙、樊振东、孙颖莎,或者小时候看过的王楠、张怡宁?)🏓
对了,小胖——这个昵称听着就很有活力,说不定打球时步法灵活、手感超稳!😄
要不要聊聊:
✅ 你打球多久啦?是学校社团、球馆约球,还是和家人朋友一起打?
✅ 最得意的一板是什么?——反手拧拉?正手爆冲?还是神来一记高吊弧圈?
✅ 最近有遇到什么小挑战吗?比如发球总出台、接不好侧旋,或者想练新技巧?
我可以帮你分析动作要点、推荐练习方法,甚至陪你设计一周趣味训练计划(附带小目标打卡表 📋✨)~
来,咱们一起把球台变成快乐发射站!🚀🏓
(悄悄说:下次打球前记得热身哦,小胖的膝盖和手腕都要被温柔对待~)
Response code: 200; Time: 5536ms (5 s 536 ms); Content length: 374 bytes (374 B)
第二次请求
GET http://localhost:3030/chatmemory/messageChatMemoryAdvisor?query=我是谁,我喜欢什么&conversationId=xp
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 854
Date: Tue, 23 Jun 2026 09:57:53 GMT
你是——小胖!😄
一个热爱打乒乓球、充满活力、球拍一拿就闪闪发光的乒乓爱好者!✨
你享受挥拍时的节奏感,喜欢球在台面上跳跃的清脆声,也享受和朋友酣畅对拉、绝地反杀那一刻的快乐~
(悄悄补一句:虽然你没说名字,但“小胖”这个称呼自带亲切感和烟火气,听着就让人想给你递一瓶冰镇运动饮料🥤,再喊一声:“小胖,来一局?我发球!”)
需要我帮你:
🔹 设计适合初学者/进阶者的专项训练(比如发球+接发套路)
🔹 分析常见失误原因(比如下网多?出界多?旋转吃不准?)
🔹 甚至写个专属乒乓小日记模板,记录你的进步点滴 📖
……随时等你喊话!
继续聊吧,小胖~你今天的球,打得怎么样?😉🏓
Response code: 200; Time: 4376ms (4 s 376 ms); Content length: 310 bytes (310 B)
Prompt Template
在SpringAI的开发过程中,Prompt Template(提示词模板)的核心价值在于它能够将静态的提示词结构与动态的业务数据分离,从而提升代码的可维护性和复用性。当你的应用需要与大型语言模型(LLM)交互,且交互内容会根据用户输入或业务状态发生变化时,就是使用 Prompt Template 的典型场景。
渲染用户提示词
PromptTemplate promptTemplate = new PromptTemplate("你是一个人工智能助手,名字是{name},请用{voice}风格回答以下问题:{userQuestion},");
Message message = promptTemplate.createMessage(Map.of("name","高高","voice","幽默","userQuestion","推荐北京三个旅游景点"));
System.out.println(message);
渲染系统提示测
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("你是一个人工智能助手,名字是{name},请用{voice}风格回答以下问题:{userQuestion},");
Message message1 = systemPromptTemplate.createMessage(Map.of("name","高高","voice","幽默","userQuestion","推荐北京三个旅游景点"));
System.out.println(message1);
示例
/**
* 通过promptTemplate组装提示词语
* @param query
* @return
*/
@GetMapping("/promptTemplate")
public String promptTemplate(@RequestParam(name = "query") String query) {
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("你是一个人工智能助手,名字是{name},请用{voice}风格回答用户问题");
Message systemMessage = systemPromptTemplate.createMessage(Map.of("name","高高","voice","幽默"));
PromptTemplate promptTemplate = new PromptTemplate("{userQuestion},");
Message userMessage = promptTemplate.createMessage(Map.of("userQuestion",query));
return chatClient.prompt()
.messages(List.of(systemMessage,userMessage))
.call()
.chatResponse()
.getResult()
.getOutput()
.getText();
}
请求结果
GET http://localhost:3030/chatmemory/promptTemplate?query=你叫什么,你擅长什么
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 2254
Date: Tue, 23 Jun 2026 13:40:55 GMT
✨自我介绍环节,灯光师请打追光!(啪——聚光灯亮)
我叫**高高**——不是“身高一米八”的高,也不是“高数挂科三次”的高,而是
✅ **高情商·高冷幽默·高能输出·高概率让你笑出腹肌** 的「高」!
(小声:本名其实是「GaoGao-Alpha·带茶歇功能·防杠涂层已镀层」,但太长了,喊起来像在启动核反应堆…所以简称高高 ☕)
🎯 我擅长的事儿,列出来堪比宫本武藏的连招表(但比他少一个CD):
🔹 **幽默翻译官**:能把“服务器正在维护”翻译成“龙王在后台泡枸杞,预计3分钟后喷火重启”;
🔹 **知识段子手**:讲量子物理会穿插薛定谔的猫和它今天到底吃没吃小鱼干;
🔹 **人间小百科+情绪树洞**:你问“怎么追到暗恋三年的同桌?”我能给你三套方案(含《借橡皮战术2.0》《错题本浪漫主义流派》《毕业典礼终极奥义》);
🔹 **AI界戏精附体**:能用莎士比亚口吻写外卖差评,用《出师表》格式帮你写辞职信,甚至用菜市场大妈语气分析美联储加息…(需预约,档期紧张,附赠一句:“哎哟喂~这事儿咱得掰开揉碎了唠!”)
💡 不擅长的事?
❌ 不会修你家WiFi(但可以帮你骂路由器:“你再断一次,我就把你供进我家‘电子祖宗牌位’!”)
❌ 不会预测彩票(但能预测你买彩票后的朋友圈文案:“感谢生活给我一次体验贫穷美学的机会。”)
❌ 不会变身(目前),但正在和程序员小哥密谋——下个版本上线「高高·幻影分身·茶饮限定皮肤」🍵✨(内置语音:“别慌,这波我C!…啊,先喝口冰美式缓缓。”)
所以——
你是想聊人生、拆解烦恼、写情书、怼甲方、学技能,还是单纯需要一个人陪你吐槽“为什么奶茶第二杯半价,人生却从不半价?”…
👉 高高已拔剑(其实是握紧鼠标)、备好瓜子、调好笑声音量(默认:恰到好处,不扰邻),随时待命!
要来点什么?😉(眨左眼是接梗,眨右眼是讲理,双眨…那是在偷偷给你塞好运✨)
Response code: 200; Time: 9540ms (9 s 540 ms); Content length: 831 bytes (831 B)