SpringAI
SpringAI 深度进阶¶

[Advisor 机制、VectorStore 集成、Function Calling、多模型降级]¶
🧭 1. SpringAI 的 Advisor 机制是什么?如何用 Advisor 实现 RAG?¶
一句话:Advisor 是 Spring AI 中拦截和增强请求/响应的切面(AOP)机制,可以在调用 LLM 前后插入自定义逻辑。
它借鉴了 Spring AOP 的思想,但专门针对 AI 调用链设计。每个 Advisor 都可以在 Prompt 发送给模型之前做预处理,在模型返回之后做后处理,甚至完全替换请求。

Advisor 接口定义(简化):
public interface RequestResponseAdvisor {
// 前置拦截:修改 Prompt
AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context);
// 后置拦截:修改响应
ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context);
// 优先级
int getOrder();
}
如何用 Advisor 实现 RAG?
核心思路是编写一个 RetrievalAugmentationAdvisor,在请求发送前,从向量数据库中检索相关文档,插入到 Prompt 的上下文中。
代码示例:实现一个简单的 RAG Advisor
@Component
public class RagAdvisor implements RequestResponseAdvisor {
private final VectorStore vectorStore;
public RagAdvisor(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
@Override
public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
// 1. 获取用户原始输入
String userQuery = request.userText();
// 2. 从向量库检索相关文档
List<Document> docs = vectorStore.similaritySearch(SearchRequest.query(userQuery).withTopK(3));
// 3. 拼接检索结果
String contextText = docs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n\n"));
// 4. 构建增强后的系统提示词
String augmentedSystem = String.format(
"根据以下参考资料回答用户问题。如果资料中没有答案,请如实告知。\n\n参考资料:\n%s", contextText
);
// 5. 修改请求,注入上下文
return AdvisedRequest.from(request)
.withSystemText(augmentedSystem + "\n\n" + request.systemText())
.build();
}
@Override
public ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context) {
// 这里可做后处理,如提取引用、记录日志
return response;
}
@Override
public int getOrder() { return 0; }
}
在 ChatClient 中使用:
@Bean
public ChatClient chatClient(ChatClient.Builder builder, RagAdvisor ragAdvisor) {
return builder
.defaultAdvisors(ragAdvisor)
.build();
}
// 调用时
String answer = chatClient.prompt().user("公司去年的营收是多少?").call().content();
Advisor 链的执行模型:
Spring AI 支持多个 Advisor 串联,形成一个链条。比如你可以同时启用:
-
RagAdvisor:检索增强 -
LoggingAdvisor:记录请求响应日志 -
ContentGuardAdvisor:过滤输出中的敏感词
它们按 getOrder() 的值顺序执行,像一个 Servlet Filter 链,但专门服务于 AI 调用。
Advisor vs LangChain 的 Runnable:
两者目标相似——都是“在 LLM 调用前后插入逻辑”。但实现哲学不同:
-
Advisor 是 Spring 的 AOP 风格,面向切面,无侵入式配置。
-
Runnable 是管道式组合,你需要显式地用
|连接各个步骤。
在 Spring 生态中,Advisor 更符合 Java 开发者的习惯,且天然集成 Spring 的 Bean 管理和配置体系。
🗄️ 2. SpringAI 如何集成向量数据库?VectorStore 的抽象设计是什么?¶
Spring AI 对向量数据库的集成遵循 “统一接口 + 适配器实现” 的经典 Spring 风格。
核心抽象:VectorStore 接口
public interface VectorStore {
void add(List<Document> documents);
Optional<Boolean> delete(List<String> idList);
List<Document> similaritySearch(SearchRequest request);
default List<Document> similaritySearch(String query) {
return similaritySearch(SearchRequest.query(query));
}
}
设计要点:
-
Document是通用载体,包含文本内容、元数据 Map、以及可选的 Embedding 向量。 -
SearchRequest封装查询参数:查询文本、TopK、相似度阈值、过滤表达式等。 -
add方法内部会自动调用 Embedding 模型将文档向量化,再存入实际后端。 -
通过
@ConditionalOnClass和@EnableConfigurationProperties实现自动配置,只要引入对应的 Starter,Spring Boot 就自动装配。
支持的向量数据库(部分):
-
Chroma:轻量级,适合本地开发。
-
Pinecone:全托管,高性能。
-
Weaviate:支持混合搜索。
-
Milvus:开源,适合大规模生产。
-
PGVector:基于 PostgreSQL,DBA 友好。
-
Redis:支持向量索引的缓存。
-
MongoDB Atlas:文档 + 向量一体化。
集成示例:以 Chroma 为例
依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-chroma-store-spring-boot-starter</artifactId>
</dependency>
配置:
spring:
ai:
vectorstore:
chroma:
host: http://localhost:8000
collection-name: my-knowledge-base
embedding:
openai:
api-key: ${OPENAI_API_KEY}
使用:
@RestController
public class KnowledgeController {
@Autowired
private VectorStore vectorStore;
// 入库
@PostMapping("/ingest")
public void ingest(@RequestBody String text) {
Document doc = new Document(text, Map.of("source", "user_upload"));
vectorStore.add(List.of(doc));
}
// 查询
@GetMapping("/search")
public List<String> search(@RequestParam String q) {
return vectorStore.similaritySearch(
SearchRequest.query(q).withTopK(5).withSimilarityThreshold(0.7)
).stream().map(Document::getContent).toList();
}
}
高级特性:元数据过滤
在检索时可以按元数据条件过滤,比如只搜索某个来源的文档:
SearchRequest request = SearchRequest.query("营收")
.withFilterExpression("source == 'financial_report'")
.withTopK(3);
List<Document> docs = vectorStore.similaritySearch(request);
向量库选择指南:
Spring AI 的 VectorStore 抽象让切换向量数据库几乎零代码改动——改个配置和依赖即可,这正是 Spring 生态最擅长的事情:“换实现,不换代码”。
🔧 3. SpringAI 的 Function Calling 如何实现?和 LangChain4J 的 @Tool 有什么区别?¶
Spring AI 的 Function Calling 实现:基于 @Description 注解和 FunctionCallback 接口。
核心流程:定义一个 Spring Bean,用 @Description 注解标注参数,框架自动将其转换为 OpenAI 兼容的 tools 参数,填入 API 请求中。模型返回调用指令后,Spring AI 自动执行函数,并将结果追加到对话上下文。
1. 定义函数 → 2. 自动注册为 FunctionCallback → 3. API调用时附带 tools 参数
→ 4. 模型返回 tool_calls → 5. 框架自动执行 → 6. 结果追加 → 7. 再次调用LLM
代码示例:定义一个天气查询函数
@Configuration
public class WeatherConfig {
@Bean
@Description("获取指定城市当前天气信息,包括温度、湿度和天气状况")
public Function<WeatherRequest, WeatherResponse> weatherFunction() {
return new WeatherService();
}
public static class WeatherService implements Function<WeatherRequest, WeatherResponse> {
@Override
public WeatherResponse apply(WeatherRequest request) {
// 实际调用天气API
return new WeatherResponse(request.city(), 25.5, "晴", 65);
}
}
// 输入参数记录,用 @JsonProperty 和 @JsonPropertyDescription 注解
public record WeatherRequest(
@JsonProperty(required = true)
@JsonPropertyDescription("城市名称,如 Beijing") String city
) {}
// 输出
public record WeatherResponse(String city, double temperature, String condition, int humidity) {}
}
使用(框架自动发现并调用):
@RestController
public class ChatController {
@Autowired
private ChatClient chatClient;
@GetMapping("/ask")
public String ask(@RequestParam String q) {
return chatClient.prompt()
.user(q)
.call()
.content();
}
}
当用户问“北京天气怎么样?”,Spring AI 会自动:
-
将
weatherFunction包装为FunctionCallback,生成 JSON Schema。 -
调用 OpenAI API 时带上这个工具定义。
-
模型返回
tool_calls: [{function: {name: "weatherFunction", arguments: {city: "Beijing"}}}]。 -
框架自动调用
weatherFunction.apply(new WeatherRequest("Beijing"))。 -
将结果
WeatherResponse(...)转为文本追加到 messages 中。 -
再次调用 LLM 生成最终回答。
与 LangChain4J 的 @Tool 对比:
核心区别深入分析:
-
类型安全与复用性 Spring AI 的函数就是一个标准的
java.util.function.Function,不依赖任何 AI 框架接口。这意味着你可以把这个 Bean 直接用于其他场景(如 REST 端点、消息队列消费),它是真正的“普通 Java 函数”。而 LangChain4J 的@Tool只能用在AiServices上下文中。 -
自动发现机制 Spring AI 通过 Spring 的 Bean 扫描机制,自动找到所有带有
@Description注解的FunctionBean,注册为FunctionCallback。这比 LangChain4J 的显式@Tool注解更符合 Spring 的“约定大于配置”哲学——不需要额外配置类,直接定义 Bean 即可。 -
参数元数据 Spring AI 使用 Jackson 的
@JsonProperty和自定义的@JsonPropertyDescription来生成 JSON Schema 中的字段描述。这充分利用了 Java 生态中 JSON 序列化的成熟基础设施。LangChain4J 则需要单独维护@P注解,生态系统更封闭。 -
与 Spring 生态的融合 Spring AI 的 Function Calling 天然集成 Spring 的依赖注入、AOP、事务管理。你可以在函数里使用
@Transactional、@Cacheable等 Spring 注解,而 LangChain4J 做不到这一点。例如:
@Bean
@Description("查询订单详情")
public Function<OrderRequest, OrderResponse> orderFunction(OrderService orderService) {
return request -> {
// 这里可以使用 Spring 的事务、安全、缓存等
return orderService.getOrder(request.orderId());
};
}
- 工具的交互性(Tool Interaction)
Spring AI 1.0 之后引入了
ToolCallback和ToolCallbackProvider,使得工具注册更加灵活。你可以动态地从注册中心拉取工具列表,或者对不同用户暴露不同工具,这比 LangChain4J 的静态绑定更适应多租户或 SaaS 场景。
总结一句话:
Spring AI 的 Function Calling 是 “用 Spring 的方式写 AI 应用” ——把 LLM 的函数调用能力无缝嫁接到 Java 的函数式编程和 Bean 管理体系中,让 AI 集成变得像加一个 @Service 一样自然。LangChain4J 的 @Tool 更像是 Python 生态中 @tool 装饰器的 Java 移植,虽然简洁,但丧失了 Spring 的深度整合优势。
4、场景题:SpringAI 项目中如何做多模型切换和降级?¶
难度:⭐⭐⭐(多模型管理、降级策略、高可用设计)
1️⃣ Common Answer:
多模型切换就是配置多个 ChatModel,根据需要选择用哪个。降级的话就是如果主模型挂了,就切换到备用模型。可以用配置文件管理多个模型的 key,然后在代码里根据条件选择。也可以用 Sentinel 做熔断降级,主模型不行就切到备用模型。
2️⃣ Impressive Answer:
多模型切换和降级需要从模型管理、路由策略、降级机制三个层面设计。
架构设计:
graph TD
A[请求] --> B[ChatService]
B --> C{路由策略}
C -->|正常| D[主模型 GPT-4]
C -->|降级| E[备用模型 GPT-3.5]
C -->|兜底| F[国产模型 Qwen-Max]
D --> G[响应]
E --> G
F --> G
实现代码:
@Configuration
public class MultiModelConfig {
@Primary
@Bean("primaryChatModel")
public ChatModel primaryChatModel() {
return OpenAiChatModel.builder()
.apiKey(primaryApiKey)
.modelName("gpt-4")
.build();
}
@Bean("fallbackChatModel")
public ChatModel fallbackChatModel() {
return OpenAiChatModel.builder()
.apiKey(primaryApiKey)
.modelName("gpt-3.5-turbo")
.build();
}
}
@Service
public class ChatService {
@Qualifier("primaryChatModel")
private final ChatModel primaryModel;
@Qualifier("fallbackChatModel")
private final ChatModel fallbackModel;
@Retry(name = "chatRetry", fallbackMethod = "fallbackChat")
@CircuitBreaker(name = "chatCircuitBreaker")
public String chat(String prompt) {
return primaryModel.call(prompt);
}
// 降级方法:主模型失败时自动调用
private String fallbackChat(String prompt, Exception error) {
log.warn("主模型调用失败,降级到备用模型: {}", error.getMessage());
return fallbackModel.call(prompt);
}
}
Resilience4j 配置:
resilience4j:
circuitbreaker:
instances:
chatCircuitBreaker:
failure-rate-threshold: 50 # 失败率超过 50% 触发熔断
wait-duration-in-open-state: 30s # 熔断后等待 30 秒
sliding-window-size: 10 # 滑动窗口大小
retry:
instances:
chatRetry:
max-attempts: 3
wait-duration: 1s
降级策略设计:
-
熔断降级:连续失败达到阈值后熔断,自动切换备用模型
-
超时降级:设置合理的超时时间(如 30 秒),超时后切换
-
成本路由:根据请求复杂度动态选择模型,简单问题用便宜模型
-
监控告警:实时监控各模型的可用性、延迟、成本,及时发现异常
3️⃣ Key Differences
🧩 5. Spring AI 的核心抽象是什么?如何与不同模型集成?¶
Spring AI 的设计思想和 Spring 一贯的理念一致:定义统一接口,通过适配器屏蔽底层差异。它的核心抽象围绕着 AI 应用最常见的几个能力展开:

① ChatModel —— 统一对话入口¶
ChatModel 是最常用的接口,它代表一个能理解 Prompt 并返回 ChatResponse 的语言模型。不管底层是 OpenAI、Azure、Ollama、还是本地 HuggingFace 模型,调用方式完全一致。
public interface ChatModel {
ChatResponse call(Prompt prompt);
// 默认流式方法
default Flux<ChatResponse> stream(Prompt prompt) {
throw new UnsupportedOperationException();
}
}
集成不同模型的步骤:引入对应的 Starter,在 application.yml 里配好认证信息,框架就会自动创建 ChatModel Bean。
-
OpenAI:
spring-ai-openai-spring-boot-starter -
Azure OpenAI:
spring-ai-azure-openai-spring-boot-starter -
Ollama(本地):
spring-ai-ollama-spring-boot-starter -
HuggingFace:
spring-ai-huggingface-spring-boot-starter
配置示例:
# 主模型
spring.ai.openai.api-key: ${OPENAI_KEY}
spring.ai.openai.chat.options.model: gpt-4o
# 本地模型
spring.ai.ollama.chat.model: llama3
使用时只需注入 ChatModel:
@Autowired
private ChatModel chatModel;
public String ask(String question) {
return chatModel.call(new Prompt(question)).getResult().getOutput().getContent();
}
多模型并存:当需要同时使用多个模型时,可以用 @Qualifier 或自定义 Bean 名来区分,或者实现一个路由 ChatModel,内部持有多个模型并根据策略选择。
② EmbeddingModel —— 把文本转为向量¶
接口简洁,输入一段文本或 Document,返回浮点数组:
public interface EmbeddingModel {
EmbeddingResponse embedForResponse(List<String> texts);
default float[] embed(String text) { ... }
}
同样,换底层模型只需改配置,代码不动。支持 OpenAI、Azure、Ollama、HuggingFace 等。
③ VectorStore —— 统一向量数据库访问¶
VectorStore 接口抽象了向量存储的增删查:
public interface VectorStore {
void add(List<Document> docs);
List<Document> similaritySearch(SearchRequest request);
default List<Document> similaritySearch(String query) { ... }
}
Spring AI 提供了 Chroma、Pinecone、Milvus、PGVector、Redis 等十几种实现,切换时只需换 Starter 和配置,代码零修改。
④ Advisor —— AOP 风格的拦截器¶
Advisor 可以在请求发送前和响应返回后插入自定义逻辑,是实现 RAG、日志、内容过滤等功能的利器。它让 AI 调用链和 Spring 的 AOP 体系无缝融合。
核心设计思想:Spring AI 不发明新轮子,而是让 AI 能力变成 Spring 容器中普通的 Bean,用 DI、AOP、自动配置这些开发者熟悉的方式来使用。这种“Spring 方式”的 AI 集成,正是它相比 LangChain 等框架的独特优势。
⚙️ 6. 在 Spring AI 中如何实现 Function Calling?如何处理异步调用?¶
Function Calling 是让 LLM 能“办事”的关键。Spring AI 的实现非常简洁:把 Java 函数声明为 Bean,框架自动包装成 OpenAI 兼容的工具定义,并在模型返回调用指令后自动执行该函数。
2.1 实现 Function Calling¶
分三步:
第一步:定义函数 Bean
@Configuration
public class ToolsConfig {
@Bean
@Description("获取指定城市的当前天气,包括温度、湿度和天气状况")
public Function<WeatherRequest, WeatherResponse> weatherFunction() {
return new WeatherService();
}
// 函数内部可以用 Spring 管理的依赖
public static class WeatherService implements Function<WeatherRequest, WeatherResponse> {
@Override
public WeatherResponse apply(WeatherRequest request) {
// 实际调用天气 API
return new WeatherResponse(request.city(), 25.6, "晴", 60);
}
}
// 使用 @JsonProperty 和 @JsonPropertyDescription 生成精确的 JSON Schema
public record WeatherRequest(
@JsonProperty(required = true)
@JsonPropertyDescription("城市名称,如 Beijing") String city
) {}
public record WeatherResponse(
String city, double temperature, String condition, int humidity
) {}
}
第二步:框架自动发现并注册
Spring AI 会扫描所有 @Description 注解的 Function Bean,自动生成 FunctionCallback,在调用 LLM 时将其放入 tools 参数。
第三步:通过 ChatClient 调用,自动完成工具调用闭环
@RestController
public class ChatController {
@Autowired
private ChatClient chatClient;
@GetMapping("/ask")
public String ask(@RequestParam String q) {
return chatClient.prompt()
.user(q)
.call()
.content();
}
}
当用户问“北京天气怎么样?”,Spring AI 自动执行以下流程:
用户输入 → [框架] 构造 API 请求 (带 tools 定义) → LLM 返回 tool_calls
→ [框架] 解析 tool_calls,反射调用 weatherFunction → 拿到结果
→ [框架] 将结果追加到消息历史 → 再次调用 LLM → 生成最终回答
整个过程业务代码只需要定义函数 Bean,其他的全由框架托管。
2.2 处理异步调用¶
LLM 调用本身是网络 IO 密集型操作,生产环境必须异步化。Spring AI 提供了以下几个层面的异步支持:
① Flux<ChatResponse> stream(Prompt prompt)
这是最常用的异步流式方法,底层基于 Reactor。调用后立即返回 Flux,你可以用它来逐 token 推送给前端。
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(@RequestParam String q) {
return chatModel.stream(new Prompt(q))
.map(resp -> resp.getResult().getOutput().getContent());
}
② 结合 @Async 注解
把 AI 调用放在 Spring 管理的线程池中执行,避免阻塞主线程(如 Tomcat 的 worker 线程)。
@Service
public class AiAsyncService {
@Async("aiTaskExecutor")
public CompletableFuture<String> askAsync(String question) {
String answer = chatModel.call(new Prompt(question)).getResult().getOutput().getContent();
return CompletableFuture.completedFuture(answer);
}
}
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("aiTaskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("ai-async-");
executor.initialize();
return executor;
}
}
③ 处理 Function Calling 中的长耗时工具
如果一个工具函数本身是异步的(如需要等待外部服务回调),可以让函数返回 CompletableFuture,或在函数内部使用 DeferredResult 等方式。Spring AI 的 FunctionCallback 支持返回 Future 类型,框架会处理好等待和结果提取。
④ 并行工具调用
当 LLM 返回多个独立的 tool_calls 时,Spring AI 会默认用线程池并行执行这些工具,汇总结果后再统一返回给 LLM。这大幅减少了多工具调用场景的延迟。
// 框架内部逻辑伪代码
List<ToolCall> toolCalls = response.getToolCalls();
List<CompletableFuture<ToolResult>> futures = toolCalls.stream()
.map(tc -> CompletableFuture.supplyAsync(() -> execute(tc), executor))
.toList();
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
总的来说,Spring AI 的 Function Calling 是“声明式”的——你声明函数,框架处理调用、序列化、错误重试、并行执行。这种设计让业务逻辑和 AI 调用完全解耦,业务函数甚至可以脱离 AI 框架独立测试和部署。
📚 7. Spring AI 的 EmbeddingClient 与 VectorStore 如何配合实现 RAG?¶
RAG(检索增强生成)的流程可以抽象为一条数据流水线:文档 → 切分 → 向量化 → 存入向量库 → 用户提问 → 向量化 → 检索相关文档 → 注入 Prompt → LLM 生成答案。

Spring AI 为每一步都提供了对应组件,让整个链路能在 Spring 容器内无缝串联。
第一步:文档读取与切分
// 读取 PDF、TXT 等文件
DocumentReader reader = new TextReader("classpath:knowledge.txt");
List<Document> rawDocs = reader.read();
// 按语义或固定长度切分
TextSplitter splitter = new TokenTextSplitter(800, 100, 5, 1000, true);
List<Document> chunks = splitter.apply(rawDocs);
第二步:向量化并存入向量库
@Autowired
private EmbeddingModel embeddingModel;
@Autowired
private VectorStore vectorStore;
public void ingest() {
// 批量向量化
List<String> texts = chunks.stream().map(Document::getContent).toList();
List<float[]> embeddings = embeddingModel.embed(texts);
// 将向量赋给 Document
for (int i = 0; i < chunks.size(); i++) {
chunks.get(i).setEmbedding(embeddings.get(i));
}
// 存入向量库
vectorStore.add(chunks);
}
在实际应用中,Spring AI 的 VectorStore.add() 内部会帮你调 EmbeddingModel,你可以直接存原始文本,框架负责向量化。所以上面的代码可以简化为:
第三步:构建 RAG 问答链
最直接的方式是用 Advisor 实现一个检索增强切面,注入到 ChatClient 中:
@Component
public class RagAdvisor implements RequestResponseAdvisor {
private final VectorStore vectorStore;
public RagAdvisor(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
@Override
public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
// 1. 从用户输入中提取查询文本
String userQuery = request.userText();
// 2. 检索相关文档
List<Document> docs = vectorStore.similaritySearch(
SearchRequest.query(userQuery).withTopK(4));
// 3. 拼接为上下文字符串
String contextText = docs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n\n---\n\n"));
// 4. 增强 System Prompt
String augmentedSystem = String.format(
"基于以下参考资料回答用户问题。如果资料中没有答案,请明确告知。\n\n参考资料:\n%s",
contextText
);
// 5. 修改请求
return AdvisedRequest.from(request)
.withSystemText(augmentedSystem + "\n\n" + request.systemText())
.build();
}
@Override
public ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context) {
return response;
}
@Override
public int getOrder() { return 0; }
}
配置 ChatClient 使用该 Advisor:
@Bean
public ChatClient chatClient(ChatClient.Builder builder, RagAdvisor ragAdvisor) {
return builder.defaultAdvisors(ragAdvisor).build();
}
第四步:调用
@Autowired
private ChatClient chatClient;
public String ask(String question) {
return chatClient.prompt().user(question).call().content();
}
这样,所有通过 ChatClient 发出的请求都会自动携带检索到的知识,业务代码一行都不需要改。
进阶优化手段:
-
混合检索:除了向量相似度,还可以结合 BM25 关键词检索。
SearchRequest支持设置filterExpression,可以在向量库层面实现元数据过滤。 -
检索结果为空处理:在
RagAdvisor中判断docs是否为空,如果为空,可以把 System Prompt 改为“知识库中暂无相关信息,请基于你的常识谨慎回答”。 -
长文档压缩:检索到的文档如果太长,可以先过一个
DocumentCompressor(如 LLM 摘要),再注入 Prompt,避免撑爆上下文窗口。 -
多路召回与重排序:用
MultiQueryRetriever对用户 query 做多角度改写,分别检索后合并,再用一个重排序模型取最相关的 Top-K 片段。
整体效果:
Spring AI 通过 EmbeddingClient 和 VectorStore 这两个抽象,把 RAG 的核心——语义搜索和存储——变成了一种可自由切换的底层能力。而 Advisor 则让你在不侵入业务代码的前提下,将检索能力“织入”每一次 AI 调用。这种设计让 RAG 从“需要写一堆胶水代码”变成了“引入一个切面就生效”的即插即用模式。
🧩 8. Spring AI 的自动配置原理是什么?如何添加自定义模型?¶
Spring AI 的自动配置完全遵循 Spring Boot 的 AutoConfiguration 机制,核心思路是“条件装配”——类路径下存在对应的依赖时,框架就自动创建并注册 ChatModel、EmbeddingModel 等 Bean。

以 OpenAI 为例,OpenAiAutoConfiguration 会做这几件事:
-
检查类路径是否有
OpenAiApi.class(避免使用者没引包却启动了配置)。 -
把
spring.ai.openai前缀的配置绑定到OpenAiConnectionProperties。 -
创建
OpenAiApi(底层 HTTP 客户端),再创建OpenAiChatModel。 -
将
OpenAiChatModel暴露为ChatModel接口的 Bean。
添加自定义模型有两种方式:
方式一:直接实现 ChatModel 接口(适合完全自定义的模型)
@Component
public class MyCustomChatModel implements ChatModel {
@Override
public ChatResponse call(Prompt prompt) {
// 实现自定义推理逻辑,调用自研模型或第三方 API
String generatedText = customModelInference(prompt.getContents());
return new ChatResponse(List.of(
new Generation(new AssistantMessage(generatedText))));
}
@Override
public Flux<ChatResponse> stream(Prompt prompt) {
// 如需流式,可实现此方法
return ChatModel.super.stream(prompt);
}
}
之后就可以在应用中直接注入 ChatModel 使用,Spring 会把它和其他自动配置的模型一样对待。
方式二:复用现有基础设施,只换底层客户端
如果你的模型兼容 OpenAI API 格式,只需继承 OpenAiApi 并修改 Base URL 和鉴权头,然后手动构建 OpenAiChatModel:
@Bean
public ChatModel myCustomOpenAiCompatibleModel() {
OpenAiApi api = new OpenAiApi("https://my-model-service/v1", "custom-api-key");
return new OpenAiChatModel(api);
}
如果需要多个模型共存,可以用 @Qualifier 区分,或构造一个 RoutingChatModel 统一路由。
核心点:Spring AI 的自动配置就像一个可插拔的模型超市,引入不同的 Starter 就能获得不同的模型能力;而自定义模型只需交出符合 ChatModel 协议的 Bean,就能无缝融入整个生态。
🛡️ 9. 在 Spring AI 中如何保障生产环境的安全性,例如 API Key 管理和请求限流?¶
生产环境的安全围绕两个核心:凭证保护和流量控制。
① API Key 管理:绝不硬编码,与环境解耦
API Key 不能写在 application.yml 里提交到 Git。推荐分层管理:
- 本地开发:使用环境变量或
.env文件(不纳入版本控制)。
生产环境:注入 Kubernetes Secrets 或云平台的密钥管理服务(如 AWS Secrets Manager、Azure Key Vault)。
- 进一步加固:结合 Spring Cloud Config 或 Vault,对配置文件进行加密存储,运行时解密。
Spring AI 的 *Properties 类默认支持从环境变量、系统属性、配置文件等多种来源读取,所以你只需在配置文件中写 ${变量名} 即可。
② 请求限流:保护 API 额度与系统稳定性
调用大模型有成本和频率限制,必须防止恶意或失控调用。可以在多个层面实现限流:
方式一:基于 Resilience4j 的注解限流
@Bean
public RateLimiter rateLimiter() {
RateLimiterConfig config = RateLimiterConfig.custom()
.limitRefreshPeriod(Duration.ofSeconds(1))
.limitForPeriod(5) // 每秒最多5次调用
.timeoutDuration(Duration.ofMillis(500))
.build();
return RateLimiterRegistry.of(config).rateLimiter("ai-calls");
}
@Service
public class GuardedAiService {
@Autowired
private ChatModel chatModel;
@Autowired
private RateLimiter rateLimiter;
public String ask(String question) {
// 获取令牌,若失败会抛异常
RateLimiter.waitForPermission(rateLimiter);
return chatModel.call(new Prompt(question)).getResult().getOutput().getContent();
}
}
超过阈值时,waitForPermission 会阻塞或直接抛出 RequestNotPermitted,你可以返回友好提示“系统繁忙,请稍后重试”。
方式二:通过 Advisor 实现透明限流
定义一个 RateLimitAdvisor,在 adviseRequest 方法中检查令牌,若不通过则拒绝调用。
@Component
public class RateLimitAdvisor implements RequestResponseAdvisor {
private final RateLimiter rateLimiter;
@Override
public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
if (!rateLimiter.acquirePermission()) {
throw new RateLimitExceededException("AI 调用太频繁,请稍后再试");
}
return request;
}
// ...
}
将其注册到 ChatClient,所有调用自动受保护。
③ 内容安全:输入/输出过滤
利用 Advisor 还可以对用户输入做敏感词过滤,对模型输出做合规检查,这是安全的另一道防线。
一句话总结:API Key 用环境变量/密钥管理服务注入,决不明文写死;请求限流用 Resilience4j 或 Spring Cloud Gateway 形成保护壳;安全 Advisor 则像是给每次 AI 调用穿上的防弹衣。
⚙️ 10. 如何自定义 Spring AI 的 ChatClient,实现多轮对话和上下文管理?¶
ChatClient 是 Spring AI 提供的流式构建器,通过它可以轻松装配多轮对话记忆、Advisor、默认系统提示等。
实现多轮对话的关键:将对话历史注入上下文窗口。
Spring AI 默认是无状态的,要实现“记住之前说过的话”,需要把历史消息手动追加到 Prompt 中。我们可以把这个逻辑封装在一个 Advisor 里,实现透明化的上下文管理。
自定义 ConversationMemoryAdvisor:
@Component
public class ConversationMemoryAdvisor implements RequestResponseAdvisor {
// 使用线程安全的 Map 存储不同会话的历史,实际应用可改用 Redis
private final Map<String, List<Message>> conversationStore = new ConcurrentHashMap<>();
@Override
public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
// 1. 获取会话 ID,可从请求中携带,也可用用户标识
String conversationId = request.getAdviseParam("conversationId", String.class, "default");
// 2. 获取该会话的历史消息
List<Message> history = conversationStore.computeIfAbsent(conversationId, k -> new ArrayList<>());
// 3. 构建带历史的消息列表:系统提示 + 历史消息 + 当前用户消息
List<Message> fullMessages = new ArrayList<>();
fullMessages.add(new SystemMessage(request.systemText()));
fullMessages.addAll(history);
fullMessages.add(new UserMessage(request.userText()));
// 4. 替换原始的 Prompt,改由包含历史的消息列表
Prompt newPrompt = new Prompt(fullMessages, request.options());
return AdvisedRequest.from(request).withPrompt(newPrompt).build();
}
@Override
public ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context) {
// 5. 从请求上下文中取出当前用户消息
Message userMessage = new UserMessage(response.getMetadata().get("originalUserText", String.class));
// 6. 从响应中取出模型的回复
Message assistantMessage = response.getResult().getOutput();
// 7. 更新历史:将当前轮的一对消息追加进会话历史
String conversationId = context.get("conversationId", String.class, "default");
List<Message> history = conversationStore.get(conversationId);
if (history != null) {
history.add(userMessage);
history.add(assistantMessage);
// 控制历史长度,避免 token 超限,保留最近 20 条消息
if (history.size() > 20) {
history.subList(0, history.size() - 20).clear();
}
}
return response;
}
@Override
public int getOrder() { return 10; }
}
装配到 ChatClient 并使用:
@Bean
public ChatClient chatClient(ChatClient.Builder builder, ConversationMemoryAdvisor memoryAdvisor) {
return builder
.defaultSystem("你是一个智能助手,请用简洁的中文回答。")
.defaultAdvisors(memoryAdvisor)
.build();
}
// 业务调用
@Autowired
private ChatClient chatClient;
public String chat(String userInput, String conversationId) {
return chatClient.prompt()
.user(userInput)
.adviseParam("conversationId", conversationId) // 传入会话 ID
.call()
.content();
}
效果:同一个 conversationId 下的所有调用,都会自动携带之前所有的对话历史,模型能记住上下文。
进阶设计:
-
历史持久化:将
Map替换为 Redis,实现跨 JVM、跨重启的持久会话。 -
摘要压缩:当历史过长时,不直接删除旧消息,而是用 LLM 生成一份摘要,保留关键信息,极大节省 token。
-
多用户隔离:
conversationId可以与登录用户绑定,避免不同用户串话。
收束:
自定义 ChatClient 的精髓就在于通过 Advisor 把“上下文管理”这个横切关注点独立出来。业务代码只关心发什么消息,完全不感知记忆机制的存在。这种干净分离让你可以单独测试记忆策略、随时切换存储后端,甚至给不同用户配置不同的记忆长度——所有复杂性都藏在了 Advisor 内部。