Spring AI 入门级学习 2
接下来我们重学习 Advisors
首先是它的定义:一系列可插拔的拦截器,在调用 AI 前和调用 AI 后可以执行一些额外的操作,在前面也有使用的例子:
// 创建 ChatClient时,并注册 Advisor
ChatClient chatClient = ChatClient
.builder(chatModel)
.defaultAdvisors( new MessageChatMemoryAdvisor(chatMemory), // 对话记忆 advisor
QuestionAnswerAdvisor(vectorStore) // RAG 检索增强 advisor )
.build();
// 调用时,设定Advisor参数
String response = this.chatClient.prompt() // 指定对话记忆的 id 和长度
.advisors(advisor -> advisor
.param("chat_memory_conversation_id", "678")
.param("chat_memory_response_size", 100))
.user(userText)
.call()
.content();
上面都是已经内置的Advisor,为了更好的理解,我们来学习自定义 Advisor
自定义 Advisor 步骤
1)接口实现,实现以下接口之一或同时实现两者:
- CallAroundAdvisor:用于处理同步请求和响应(非流式)
- StreamAroundAdvisor:用于处理流式请求和响应
(Around = 你把 before + 调用 + after 包成一个整体,自己控制执行顺序)
public class MyCustomAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
// 实现方法...
}
2)实现核心方法
// 对于非流式处理 (CallAroundAdvisor),实现 aroundCall 方法
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
// 1. 处理请求(前置处理)
AdvisedRequest modifiedRequest = before(advisedRequest); // 自定义的before方法,返回新的advisedRequest
// 2. 调用链中的下一个Advisor,也就是执行调用方法了,因为有很多Advisor
AdvisedResponse response = chain.nextAroundCall(modifiedRequest);
// 3. 处理响应(后置处理)
return processResponse(response);
}
// 对于流式处理 (StreamAroundAdvisor),实现 aroundStream 方法
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
// 1. 处理请求
AdvisedRequest modifiedRequest = before(advisedRequest);
// 2. 调用链中的下一个Advisor并处理流式响应
return chain.nextAroundStream(modifiedRequest)
.map(response -> processResponse(response)); // 每一段流式返回处理
}
3)还有其他方法设置
@Override
public int getOrder() {
// 值越小优先级越高,越先执行
return 100;
}
@Override
public String getName() {
return "为每个 Advisor 提供一个唯一标识符";
}
接下来我们尝试自己实现一个日志Advisor
@Slf4j
public class MyLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
@Override
public String getName() {
return this.getClass().getSimpleName();
}
@Override
public int getOrder() {
return 0;
}
// 定义前置方法
private AdvisedRequest before(AdvisedRequest request) {
log.info("AI Request: {}", request.userText());
return request;
}
// 定义后置方法
private void after(AdvisedResponse response) {
log.info("AI Response: {}", response.response().getResult().getOutput().getText());
}
// 重写CallAroundAdvisor中的方法
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
advisedRequest = this.before(advisedRequest); // 执行所定义的前置方法
AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest); // 执行被拦截方法
this.after(advisedResponse); // 执行后置方法
return advisedResponse;
}
// 重写StreamAroundAdvisor中的方法
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
advisedRequest = this.before(advisedRequest);
Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);
// 通过 MessageAggregator 工具类将 Flux 响应聚合成单个 AdvisedResponse
// 与map的一段段处理不同,是等全部返回完,再变成一个完整结果处理,即后面的after后置处理
return (new MessageAggregator()).aggregateAdvisedResponse(advisedResponses, this::after);
}
}
// 使用自定义的日志Advisor
chatClient = ChatClient.builder(dashscopeChatModel)
.defaultSystem(SYSTEM_PROMPT)
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory),
// 自定义日志 Advisor,可按需开启
new MyLoggerAdvisor(),
)
.build();
补充:
- 更复杂处理的流式场景,可以使用 Reactor 的操作符(比如请求本身是异步/流式 Mono/Flux时,就要使用Mono响应式方法(Mono.just、.flatMapMany等))
public Flux aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { return Mono.just(request) // “开启一个响应式流水线”,后面可以接 .map() / .flatMap() 等操作 .publishOn(Schedulers.boundedElastic()) // 切换执行线程,确保后续操作在一个可伸缩的异步线程池里执行 .map(request -> { // 请求前处理逻辑 return before(request); }) .flatMapMany(request -> chain.nextAroundStream(request)) // 把 Mono(request是单值)变成 Flux(多值流)来调用方法 .map(response -> { // 响应处理逻辑 return after(response); }); }
接下来是结构化输出
为保证大模型输出我们规定的格式,有两种约束:
-
只靠 Prompt 本质:自然语言约束 问题:AI 可以“偏离”
-
模型原生结构化输出,比如现在很多模型支持:
JSON mode、Function calling、Tool calling
这些不是普通 prompt,而是通过 API 参数,强制模型按结构输出
首先先了解一个定义:结构化输出转换器(Structured Output Converter)是 Spring AI 提供的一种实用机制,用于将大语言模型返回的文本输出转换为结构化数据格式,如 JSON、XML 或 Java 类,这对于需要可靠解析 AI 输出值的下游应用程序非常重要
那 Spring AI 的 Converter 用了哪个约束呢?
答案是:优先使用模型的“结构化能力”(比如:JSON mode、Function / Tool calling),如果模型不支持才会用 prompt + schema 去“尽量约束”
使用示例:
// 定义一个记录类
record Actors(String actor, List movies) {}
// 使用高级 ChatClient API
Actors actors = ChatClient.create(chatModel).prompt()
.user("Generate 5 movies for Tom Hanks.")
.call()
.entity(actors.class); // 这一步是把“创建 converter + 转换结果”这件事帮你隐藏掉了,
// 当然为避免泛型擦除可用以下
.entity(new ParameterizedTypeReference<List>() {});
对话记忆持久化
之前我们使用了基于内存的对话记忆来保存对话上下文,但是服务器一旦重启了,对话记忆就会丢失
如果我们要将对话持久化到数据库中,优先用官方内置 JDBC 方案,只有当默认策略不满足时,再自定义
步骤如下:
- 先加依赖
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId> </dependency> - 配置,自动配置会在启动时创建 SPRING_AI_CHAT_MEMORY 表,并且会根据数据库厂商选择对应 SQL 脚本
spring: datasource: url: jdbc:mysql://localhost:3306... username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver ai: chat: memory: repository: jdbc: initialize-schema: always // 这个配置控制是否初始化表结构
PromptTemplate 模板
PromptTemplate 是 Spring AI 框架中用于构建和管理提示词的核心组件。允许开发者创建带有占位符的文本模板,然后在运行时动态替换这些占位符
使用示例
// 定义带有变量的模板,{{即为转义的{
String template = "你好,{name}。今天是{day},天气{weather}。";
// 创建模板对象
PromptTemplate promptTemplate = new PromptTemplate(template);
// 准备变量映射
Map<String, Object> variables = new HashMap<>();
variables.put("name", "kui");
variables.put("day", "星期一");
variables.put("weather", "晴朗");
// 生成最终提示文本
String prompt = promptTemplate.render(variables);
// 结果: "你好,kui。今天是星期一,天气晴朗。"
如何从文件加载模板
这样才是更真实的项目写法了,(基本不会再写死字符串了)
-
在 resources 里建一个文件 movie.st
给我推荐一部关于 {topic} 的电影。
请按如下 JSON 返回:
{{ "title": "", "year": "" }} - 用 Resource 读取文件
Resource resource = new ClassPathResource("prompts/movie.st"); - 创建 PromptTemplate
PromptTemplate template = new PromptTemplate(resource); - 填参数生成 Prompt(create()返回Prompt 对象,render()返回String 字符串)
Prompt prompt = template.create(Map.of("topic", "科幻")); - 交给 ChatClient
chatClient.prompt(prompt).call().content();
还有对应不同角色消息的模版类
SystemPromptTemplate:用于系统消息,设置 AI 的行为和背景;AssistantPromptTemplate:用于助手消息,用于设置 AI 回复的结构
SystemPromptTemplate systemTemplate =
new SystemPromptTemplate("你是一个{role}专家");
PromptTemplate userTemplate =
new PromptTemplate("请解释 {topic}");
Map<String, Object> params = Map.of(
"role", "Java",
"topic", "Spring Boot"
);
Prompt prompt = new Prompt(List.of(
systemTemplate.createMessage(params),
userTemplate.createMessage(params)
));
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)