Spring AI + OpenAI 协议:Java Agent 与大模型交互核心指南
作者:白晨ovis 首发平台:掘金 项目源码:gitee.com/abao123/bac… 标签:Java / Spring Boot / Spring AI / AI Agent / 大模型
一、前言
在 AI Agent 项目中,如何优雅地与大模型交互是核心技术能力。本文基于真实生产项目(Java 后端专家 Agent),详细讲解:
- Spring AI 的核心交互模式
- 多模型路由与降级策略
- Tool Calling(函数调用)的注册与触发
- RAG + ReAct 联合调用流程
- 工程化落地的避坑经验
二、技术架构总览
用户提问
↓
┌─────────────────────────────────────┐
│ AgentService │
│ ┌───────────┐ ┌───────────────┐ │
│ │ RAG 检索 │ │ 问题分类路由 │ │
│ └─────┬─────┘ └──────┬────────┘ │
│ ↓ ↓ │
│ 组装 System Prompt 获取 ChatModel│
│ └────────┬───────────────────┘
│ ↓
│ ┌───────────────────────────────┐ │
│ │ ChatClient 调用 │ │
│ │ - System Message │ │
│ │ - User Message + History │ │
│ │ - ToolCallbacks[] │ │
│ └───────────────┬───────────────┘ │
└──────────────────┼──────────────────┘
↓
┌─────────────────┐
│ 大模型 (LLM) │
│ OpenAI / 百炼 │
└─────────────────┘
三、核心交互代码
3.1 模型配置与初始化
使用 OpenAI 兼容协议,支持 OpenAI 和阿里百炼:
@Service
public class ModelRouterService {
/** DashScope API Key,从 application.yml 注入 */
@Value("${spring.ai.openai.api-key}")
private String apiKey;
/** API Base URL,默认阿里百炼 OpenAI 兼容接口 */
@Value("${spring.ai.openai.base-url:https://dashscope.aliyuncs.com/compatible-mode/v1}")
private String apiBase;
/** 模型缓存:key=模型名, value=ChatModel 实例 */
private final ConcurrentHashMap<String, ChatModel> modelCache = new ConcurrentHashMap<>();
/**
* 懒加载 + 缓存 ChatModel 实例
*/
private ChatModel getOrCreateModel(String modelName) {
return modelCache.computeIfAbsent(modelName, name -> {
log.info("创建 ChatModel 实例: {}", name);
// 构建 OpenAI API 客户端
OpenAiApi api = OpenAiApi.builder()
.apiKey(apiKey)
.baseUrl(apiBase)
.build();
// 构建 ChatModel(支持 Tool Calling)
OpenAiChatOptions options = OpenAiChatOptions.builder()
.model(name)
.build();
return OpenAiChatModel.builder()
.openAiApi(api)
.defaultOptions(options)
.toolCallingManager(DefaultToolCallingManager.builder().build())
.retryTemplate(RetryTemplate.defaultInstance())
.observationRegistry(ObservationRegistry.NOOP)
.build();
});
}
}
配置 application.yml:
spring:
ai:
openai:
api-key: ${DASHSCOPE_API_KEY} # 阿里百炼 API Key
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
chat:
options:
model: qwen-max # 默认模型
3.2 多模型路由策略
根据问题复杂度动态选择模型:
public enum QuestionType {
SIMPLE, // 简单事实类 → 用快速便宜的模型
COMPLEX, // 复杂推理类 → 用能力强的模型
NORMAL // 普通问题 → 用默认模型
}
@Service
public class ModelRouterService {
/**
* 获取 LLM 实例(根据问题类型路由)
*/
public ChatModel getChatModel(QuestionType questionType) {
AgentProperties.ModelProperties modelProps = properties.getModel();
if (!modelProps.isSmartRouting()) {
return getOrCreateModel(modelProps.getPrimary());
}
// 智能路由:根据问题类型选择不同模型
String modelName = switch (questionType) {
case SIMPLE -> modelProps.getFast(); // 如 qwen-turbo
case COMPLEX -> modelProps.getStrong(); // 如 qwen-max / gpt-4o
default -> modelProps.getPrimary();
};
return getOrCreateModel(modelName);
}
/**
* 问题分类器
*/
public QuestionType classifyQuestion(String userInput) {
// 实现问题复杂度判断逻辑
// 可以用关键词匹配 or 小模型分类
}
}
路由策略优势:
| 问题类型 | 示例 | 推荐模型 | 成本 |
|---|---|---|---|
| 简单事实 | "HashMap 初始容量是?" | qwen-turbo | ¥0.001/千token |
| 复杂推理 | "设计一个分布式 ID 生成器" | qwen-max | ¥0.02/千token |
| 工具调用 | "帮我生成 SQL" | qwen-max | ¥0.02/千token |
3.3 Tool Calling 注册与触发
Spring AI 中 Tool Calling(函数调用)是 Agent 能力的核心。需要特别注意 API 差异:
@Component
public class SqlGeneratorTool {
/**
* SQL 生成工具
* @Tool 注解声明工具能力,描述要清晰
*/
@Tool(description = "根据自然语言描述生成 MySQL InnoDB 的 DDL/DML 语句。" +
"适用于需要给出表结构设计、复杂查询语句、数据迁移 SQL 等场景。")
public String generateSql(
@ToolParam(description = "用自然语言描述的 SQL 需求") String requirement,
@ToolParam(description = "SQL 类型:DDL/DML/ALL", required = false) String sqlType) {
// 工具逻辑:生成符合规范的 SQL prompt 框架
return "## SQL 生成请求\n\n" +
"**需求**: " + requirement + "\n\n" +
"请生成符合以下规范的 SQL:\n" +
"- 引擎: InnoDB\n" +
"- 字符集: utf8mb4\n" +
"- 主键: 自增 BIGINT id\n" +
"- 时间字段: created_at/updated_at\n" +
"- 逻辑删除: is_deleted\n" +
"- 必须附带: 索引设计说明";
}
}
注册到 ChatClient(关键!):
@Service
public class AgentService {
private final ToolRegistryService toolRegistry;
public AgentResponse invoke(String userInput) {
// ── Step 1: 获取 ToolCallback[] ─────────────────────────
ToolCallback[] toolArray = toolRegistry.getToolCallbacks();
// ── Step 2: 构建 ChatClient(重点!)─────────────────────
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem(systemPrompt) // 系统提示词
.defaultToolCallbacks(toolArray) // ✅ 正确:注册工具回调
// ❌ 错误:.defaultTools() 不会触发回调
.build();
// ── Step 3: 调用大模型 ──────────────────────────────────
List<Message> messages = new ArrayList<>(chatHistory);
messages.add(new UserMessage(userInput));
ChatResponse response = chatClient.prompt()
.messages(messages)
.call()
.chatResponse();
// ── Step 4: 解析响应 ────────────────────────────────────
if (response != null && response.getResult() != null) {
String content = response.getResult().getOutput().getText();
// Spring AI 会自动处理工具调用
// 工具执行结果会追加到 messages 中重新调用 LLM
}
}
}
3.4 完整调用流程(AgentService)
@Service
public class AgentService {
public AgentResponse invoke(String userInput) {
long startTime = System.currentTimeMillis();
// ── Step 1: RAG 检索 ──────────────────────────────────────
RetrievalResult retrieval = knowledgeBase.retrieve(userInput);
String ragContext = retrieval.getContext();
// ── Step 2: 组装 System Prompt ────────────────────────────
String systemPrompt = promptBuilder.build(ragContext);
// ── Step 3: 智能路由 → 获取 ChatModel ─────────────────────
QuestionType questionType = modelRouter.classifyQuestion(userInput);
ChatModel chatModel = modelRouter.getChatModel(questionType);
String modelName = getModelName(questionType);
// ── Step 4: 构建 ChatClient ──────────────────────────────
ToolCallback[] toolArray = toolRegistry.getToolCallbacks();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem(systemPrompt)
.defaultToolCallbacks(toolArray)
.build();
// ── Step 5: 构造消息列表 ─────────────────────────────────
List<Message> messages = new ArrayList<>(chatHistory);
messages.add(new UserMessage(userInput));
chatHistory.add(new UserMessage(userInput)); // 追加到历史
// ── Step 6: 调用 LLM ──────────────────────────────────────
String finalContent = "";
try {
ChatResponse response = chatClient.prompt()
.messages(messages)
.call()
.chatResponse();
if (response != null && response.getResult() != null) {
finalContent = response.getResult().getOutput().getText();
}
} catch (Exception e) {
log.error("[Agent] 调用失败", e);
// 主模型失败时自动降级
try {
ChatModel fallbackModel = modelRouter.getFallbackModel();
ChatClient fallbackClient = ChatClient.builder(fallbackModel)
.defaultSystem(systemPrompt)
.defaultToolCallbacks(toolArray)
.build();
ChatResponse fallbackResponse = fallbackClient.prompt()
.messages(messages)
.call()
.chatResponse();
if (fallbackResponse != null) {
finalContent = fallbackResponse.getResult().getOutput().getText();
modelName = properties.getModel().getFallback();
}
} catch (Exception fallbackEx) {
log.error("[Agent] 降级模型也调用失败", fallbackEx);
}
}
// ── Step 7: 更新对话历史 ─────────────────────────────────
if (finalContent != null && !finalContent.isEmpty()) {
chatHistory.add(new AssistantMessage(finalContent));
}
// ── Step 8: 持久化日志 ────────────────────────────────────
conversationLog.log(userInput, finalContent,
toolCallRecords, retrievalCount, modelName, durationMs);
// ── Step 9: 返回结果 ──────────────────────────────────────
return new AgentResponse(finalContent, toolCallRecords,
retrievalCount, modelName, durationMs);
}
}
四、工程化关键设计
4.1 对话历史管理
/** 对话历史(按 session 隔离) */
private final List<Message> chatHistory = new ArrayList<>();
public AgentResponse invoke(String userInput) {
// 发送前追加用户消息
chatHistory.add(new UserMessage(userInput));
// 调用 LLM...
// 发送后追加助手回复
chatHistory.add(new AssistantMessage(finalContent));
// 限制历史长度,防止上下文膨胀
int maxHistory = properties.getChat().getMaxHistory();
while (chatHistory.size() > maxHistory) {
chatHistory.removeFirst(); // 移除最老的消息
}
}
4.2 模型降级策略
try {
// 主模型调用
ChatResponse response = chatClient.prompt().messages(messages).call().chatResponse();
} catch (Exception e) {
// 自动降级到备用模型
ChatModel fallbackModel = modelRouter.getFallbackModel();
// 备用模型通常选择免费额度充足 or 更稳定的模型
}
4.3 ToolRegistry 统一管理
@Service
public class ToolRegistryService {
@Autowired
private List<Object> toolBeans; // 自动注入所有 @Component 的工具类
public ToolCallback[] getToolCallbacks() {
List<ToolCallback> callbacks = new ArrayList<>();
for (Object tool : toolBeans) {
// 将工具 Bean 转换为 ToolCallback
ToolCallback[] callbacksForBean =
ToolCallback.from(tool); // Spring AI 1.0 API
callbacks.addAll(Arrays.asList(callbacksForBean));
}
return callbacks.toArray(new ToolCallback[0]);
}
}
五、避坑指南
❌ 坑 1:defaultTools() vs defaultToolCallbacks()
// ❌ 错误:只会注册工具,不会触发工具回调
ChatClient.builder(chatModel)
.defaultTools(toolArray)
.build();
// ✅ 正确:注册工具并启用回调
ChatClient.builder(chatModel)
.defaultToolCallbacks(toolArray)
.build();
原因:Spring AI 1.0 的 breaking change。.defaultTools() 只是把对象注册为工具,不会自动调用;必须用 .defaultToolCallbacks() 才能真正触发。
❌ 坑 2:对话历史重复添加消息
// ❌ 错误:用户消息在 messages 和 chatHistory 中重复添加
List<Message> messages = new ArrayList<>(chatHistory);
messages.add(new UserMessage(userInput));
chatHistory.add(new UserMessage(userInput)); // 导致重复!
// ✅ 正确:先追加到历史,再构建 messages
chatHistory.add(new UserMessage(userInput));
List<Message> messages = new ArrayList<>(chatHistory);
❌ 坑 3:API Key 暴露
// ❌ 错误:硬编码 API Key
.apiKey("sk-xxxxxxx")
// ✅ 正确:从环境变量或配置中心读取
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
六、性能优化建议
| 优化项 | 方案 | 效果 |
|---|---|---|
| 模型缓存 | ConcurrentHashMap 懒加载缓存 ChatModel | 避免重复创建连接 |
| 历史截断 | 限制 chatHistory.size() | 控制 Token 消耗 |
| 异步调用 | 使用 ChatClient.prompt().async().call() | 提升吞吐量 |
| 流式响应 | 使用 ChatClient.prompt().prompt().stream() | 提升用户体验 |
| 模型路由 | 简单问题用便宜模型 | 节省 60%+ 成本 |
七、项目地址
Java 后端专家 Agent 完整源码:
技术栈
| 分类 | 技术选型 |
|---|---|
| 核心框架 | Spring Boot 3.4.5 + Spring AI 1.0 |
| Agent 架构 | LangGraph 状态机 |
| 向量数据库 | ChromaDB |
| 模型 | OpenAI GPT-4o / 阿里百炼 qwen-max |
| 数据库 | PostgreSQL 18.3 |
核心模块
AgentService— 核心调度器ModelRouterService— 多模型路由KnowledgeBaseService— RAG 检索ToolRegistryService— 工具注册SqlGeneratorTool— SQL 生成工具
八、总结
本文详细讲解了 Spring AI 与大模型的交互核心:
- OpenAI 兼容协议:一套代码支持 OpenAI / 阿里百炼 / 其他兼容 API
- 多模型路由:按复杂度动态选择模型,平衡成本与效果
- Tool Calling:通过
@Tool注解声明工具能力,通过defaultToolCallbacks()注册 - 降级策略:主模型失败时自动切换备用模型
- 工程化设计:对话历史管理、工具统一注册、性能优化
掌握这些核心能力,你也可以构建出生产级别的 AI Agent!
如果对你有帮助,欢迎点赞 + 收藏!