Spring AI 框架实战:Java 后端集成大模型的架构设计与工程落地
Spring AI 框架实战:Java 后端集成大模型的架构设计与工程落地
一、API 调用之外的工程挑战:Java 应用集成 LLM 的真实痛点
当团队决定在 Java 后端系统中集成大语言模型时,最常见的起点是直接用 RestTemplate 或 WebClient 调用 OpenAI 的 HTTP API。这种做法在 Demo 阶段看似可行,但一旦进入生产环境,问题便接踵而至:不同模型供应商的 API 格式各异(OpenAI、Azure OpenAI、Anthropic、国内模型),切换供应商需要重写调用逻辑;流式响应(SSE)的处理缺乏统一抽象;Prompt 模板散落在业务代码中,难以版本管理和 A/B 测试;调用失败时的重试与降级策略缺失,导致级联故障。
Spring AI 正是为了解决这些工程痛点而诞生的框架。它提供了一套统一的模型调用抽象、Prompt 管理机制、向量存储接口和 RAG(检索增强生成)管道,让 Java 开发者能够以 Spring 风格的方式集成大模型,而非在业务代码中堆砌 HTTP 调用。
本文将从架构视角出发,完整演示 Spring AI 在生产环境中的集成方案,覆盖模型调用、Prompt 管理、RAG 管道和可观测性四个核心环节。
二、从 ChatModel 到 VectorStore:Spring AI 的核心抽象与协作机制
Spring AI 的架构设计遵循 Spring 的一贯哲学:通过接口抽象屏蔽实现差异,通过自动装配降低集成成本。
flowchart TB
subgraph 应用层
A[业务服务] --> B[ChatClient]
A --> C[PromptTemplate]
A --> D[RAG 管道]
end
subgraph Spring AI 核心抽象
B --> E[ChatModel 接口]
C --> F[Prompt 管理]
D --> G[DocumentReader]
D --> H[DocumentTransformer]
D --> I[VectorStore 接口]
end
subgraph 模型实现
E --> J[OpenAI ChatModel]
E --> K[Azure OpenAI]
E --> L[Ollama ChatModel]
E --> M[其他供应商]
end
subgraph 向量存储实现
I --> N[PgVector]
I --> O[Chroma]
I --> P[Milvus]
I --> Q[Redis]
end
subgraph 可观测性
B --> R[Micrometer 指标]
E --> S[Token 用量统计]
B --> T[OpenTelemetry Tracing]
end
ChatModel 接口是 Spring AI 的核心抽象,定义了与 LLM 交互的统一契约。无论是 OpenAI、Azure OpenAI 还是本地部署的 Ollama,都通过实现 ChatModel 接口来适配。应用代码只依赖接口,切换模型供应商只需修改配置,无需改动业务逻辑。
PromptTemplate 提供了参数化的 Prompt 管理能力。Prompt 不再硬编码在 Java 代码中,而是以模板文件的形式外部化管理,支持变量替换和条件逻辑。这使得 Prompt 的版本管理、A/B 测试和跨团队协作成为可能。
VectorStore 接口统一了向量存储的访问方式。无论是 PgVector、Chroma 还是 Milvus,应用代码都通过 VectorStore 接口进行向量写入和相似度检索,底层实现可随时替换。
三、生产级 Spring AI 集成:从模型调用到 RAG 管道
下面给出一个完整的生产级 Spring AI 集成方案,包含模型调用、Prompt 管理、RAG 管道和异常处理。
Maven 依赖配置:
<dependencies>
<!-- Spring AI BOM 统一版本管理 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- OpenAI 模型适配器 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<!-- PgVector 向量存储适配器 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
</dependencies>
应用配置:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: ${OPENAI_BASE_URL:https://api.openai.com}
chat:
options:
model: gpt-4o
temperature: 0.7
max-tokens: 2048
vectorstore:
pgvector:
index-type: HNSW
distance-type: COSINE
dimensions: 1536
Prompt 模板管理(外部化文件):
<!-- src/main/resources/prompts/customer-support.st -->
你是一个专业的客户支持助手。请根据以下知识库内容回答用户问题。
知识库内容:
{{knowledge}}
用户问题:{{question}}
回答要求:
1. 仅基于知识库内容回答,不要编造信息
2. 如果知识库中没有相关信息,明确告知用户
3. 回答需简洁、准确、专业
RAG 管道服务实现:
/**
* RAG 增强的客户支持服务
* 将用户问题与知识库向量检索结合,生成有依据的回答
*/
@Service
@Slf4j
public class CustomerSupportRagService {
private final ChatClient chatClient;
private final VectorStore vectorStore;
private final PromptTemplate promptTemplate;
public CustomerSupportRagService(ChatClient.Builder chatClientBuilder,
VectorStore vectorStore) {
this.chatClient = chatClientBuilder.build();
this.vectorStore = vectorStore;
// 加载外部化 Prompt 模板
var promptResource = new ClassPathResource("prompts/customer-support.st");
this.promptTemplate = new PromptTemplate(promptResource);
}
/**
* RAG 查询主流程:检索 -> 构建 Prompt -> 调用模型
*
* @param question 用户问题
* @return 基于知识库的回答
*/
public String query(String question) {
// 1. 向量检索:从知识库中找到与问题最相关的文档片段
List<Document> relevantDocs = retrieveRelevantDocuments(question);
// 2. 拼接知识库上下文
String knowledge = relevantDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n\n"));
// 3. 构建 Prompt
Prompt prompt = promptTemplate.create(Map.of(
"knowledge", knowledge,
"question", question
));
// 4. 调用模型,带重试与降级
try {
ChatResponse response = chatClient.prompt(prompt)
.call()
.chatResponse();
// 记录 Token 用量,用于成本监控
logTokenUsage(response);
return response.getResult().getOutput().getContent();
} catch (ApiException e) {
log.error("LLM API 调用失败,状态码: {}, 原因: {}",
e.getStatusCode(), e.getMessage());
return generateFallbackAnswer(question, relevantDocs);
}
}
/**
* 向量检索:基于问题语义查找最相关的知识库片段
* 限制 Top-K 和相似度阈值,避免无关内容干扰
*/
private List<Document> retrieveRelevantDocuments(String question) {
SearchRequest searchRequest = SearchRequest.builder()
.query(question)
.topK(5) // 返回最相关的 5 个片段
.similarityThreshold(0.7) // 相似度阈值,过滤低质量结果
.build();
return vectorStore.similaritySearch(searchRequest);
}
/**
* 降级策略:当 LLM 调用失败时,返回检索到的原始文档片段
* 保证服务可用性,避免因模型不可用导致完全无响应
*/
private String generateFallbackAnswer(String question,
List<Document> relevantDocs) {
if (relevantDocs.isEmpty()) {
return "抱歉,当前无法获取回答,请稍后重试。";
}
StringBuilder sb = new StringBuilder("以下是与您问题相关的知识库内容:\n\n");
for (int i = 0; i < relevantDocs.size(); i++) {
sb.append("[").append(i + 1).append("] ")
.append(relevantDocs.get(i).getContent()).append("\n\n");
}
sb.append("(系统暂时无法生成总结,以上为原始检索结果)");
return sb.toString();
}
/**
* 记录 Token 用量,用于成本监控与预算控制
*/
private void logTokenUsage(ChatResponse response) {
Usage usage = response.getMetadata().getUsage();
log.info("Token 用量 - Prompt: {}, Completion: {}, Total: {}",
usage.getPromptTokens(),
usage.getGenerationTokens(),
usage.getTotalTokens());
}
}
知识库文档导入服务:
/**
* 知识库文档导入服务
* 将文档分块、生成向量嵌入并写入 VectorStore
*/
@Service
public class KnowledgeBaseIngestionService {
private final VectorStore vectorStore;
public KnowledgeBaseIngestionService(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
/**
* 导入文本文档到知识库
* 分块策略:按段落分割,每块不超过 500 Token,重叠 50 Token
*/
public void ingestDocument(String documentId, String content) {
// 文档分块:控制块大小以平衡检索精度与上下文完整性
TextSplitter splitter = new TokenTextSplitter(
500, // 每块最大 Token 数
50, // 块间重叠 Token 数
5, // 最小块大小
10000, // 最大块大小
true // 保留分隔符
);
Document originalDoc = new Document(documentId, content,
Map.of("source", documentId, "ingestedAt",
Instant.now().toString()));
List<Document> chunks = splitter.split(originalDoc);
// 写入向量存储,Spring AI 自动调用 Embedding 模型生成向量
vectorStore.add(chunks);
}
}
四、Token 成本与延迟抖动:Spring AI 集成的架构权衡
Spring AI 显著降低了 Java 应用集成 LLM 的工程复杂度,但引入大模型调用链路后,系统面临的新约束必须被正视。
第一,Token 成本的不可预测性。 LLM 的计费基于 Token 数量,而用户输入的长度和模型的输出长度在请求前无法精确预估。一个看似简单的 RAG 查询,可能因为检索到大量知识库片段而导致 Prompt Token 数飙升。在 Spring AI 的配置中,max-tokens 参数可以限制输出长度,但无法限制输入长度。必须在业务层实现 Token 预算控制:在调用模型前估算 Prompt Token 数,超出预算时截断知识库片段或拒绝请求。
第二,模型调用的延迟不确定性。 LLM 的推理延迟从数百毫秒到数十秒不等,取决于输入长度、输出长度和模型负载。Spring AI 默认使用同步调用模式,一个慢请求会阻塞业务线程。对于延迟敏感的接口,必须使用 stream() 方法进行流式调用,或设置合理的超时时间。Spring AI 的 ChatClient 支持通过 RestClient.Builder 自定义超时配置。
第三,向量检索的精度与召回率权衡。 RAG 管道的质量高度依赖向量检索的效果。相似度阈值设置过高会导致召回不足(知识库中有答案但未被检索到),设置过低则引入噪声(检索到不相关的内容,干扰模型回答)。similarityThreshold 参数需要根据业务场景反复调优,没有通用的最优值。
适用边界: Spring AI 适合需要集成多种模型供应商、管理复杂 Prompt、构建 RAG 管道的企业级 Java 应用。对于只需要调用单一模型 API 的简单场景,直接使用 HTTP 客户端可能更轻量。Spring AI 的抽象层引入了一定的学习成本和框架约束,团队需要评估其收益是否大于引入成本。
五、总结
Spring AI 为 Java 后端集成大模型提供了一套完整的工程化方案:ChatModel 接口统一了模型调用抽象,PromptTemplate 实现了 Prompt 的外部化管理,VectorStore 接口屏蔽了向量存储的实现差异,RAG 管道将检索与生成串联为端到端流程。降级策略和 Token 用量监控则是生产环境不可或缺的保障机制。
然而,Token 成本的不可预测性、推理延迟的不确定性、向量检索的精度与召回率权衡,都是架构师在设计 LLM 集成方案时必须纳入考量的约束条件。Spring AI 提供的是工具和抽象,而非银弹。
落地路线建议:第一步,以单一模型供应商(如 OpenAI)为起点,验证 Spring AI 的基本调用链路;第二步,将硬编码的 Prompt 迁移为外部化模板,建立 Prompt 版本管理流程;第三步,引入 VectorStore 和 RAG 管道,构建知识库增强的问答能力;第四步,实现 Token 预算控制和延迟监控,将 LLM 调用纳入系统的可观测性体系。
更多推荐



所有评论(0)