适用人群:Java/Spring 开发者,已有 Spring Boot 基础
1. 为什么是 Spring AI + Qwen3.7-Max
1.1 场景回顾
上周我们团队在做一个智能客服升级项目。需求听起来不复杂:
- 用户问"我的订单怎么还没到?"
- 系统需要先查订单状态 → 判断是否超时 → 调用物流查询 → 生成人性化的回复
如果用裸 DashScope API 写,代码会变成这样:
// 裸调 API 的典型困境
String userMessage = "我的订单怎么还没到?";
// 1. 拼接 system prompt(硬编码)
// 2. 手动维护对话历史(List<Map>)
// 3. 手动解析工具调用请求(JSON 字符串解析)
// 4. 手动调用外部服务
// 5. 拼接工具返回结果
// 6. 再次调用模型
// ... 循环
代码迅速膨胀到难以维护。而 Spring AI 提供了统一抽象层——不管底层是 Qwen、GPT 还是 DeepSeek,你写的业务代码都是一套 API。
1.2 Spring AI 的核心价值
| 维度 | 裸调 DashScope API | Spring AI |
|---|---|---|
| 模型切换 | 改代码 + 改 endpoint | 改一行 model: 配置 |
| 对话历史 | 手动维护 Message 列表 | ChatMemory 自动管理 |
| 函数调用 | JSON 解析 + 路由分发 | @Tool 注解 + 自动调度 |
| 流式输出 | SSE 手动解析 | Flux<String> 响应式 |
| 结构化输出 | 手写 JSON Schema | OutputParser<T> 类型安全 |
| 重试/熔断 | 自己写 | Spring Retry + Resilience4j |

2. 环境准备与项目初始化
2.1 开通 DashScope 服务
访问 百炼控制台 → 模型广场 → 找到 Qwen3.7-Max → 申请 API Key。
💡 注意:Qwen3.7-Max 有两个接入方式:DashScope API(标准 OpenAI 兼容)和百炼 Agent 接入。本文使用 DashScope API,因为 Spring AI 的
spring-ai-alibaba-starter原生支持。
2.2 创建 Spring Boot 项目
<!-- pom.xml 核心依赖 -->
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0</spring-ai.version>
</properties>
<dependencies>
<!-- Spring AI 核心 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
</dependency>
<!-- 阿里云 DashScope 适配(Spring AI 官方出品) -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Web 支持(流式输出需要) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- WebFlux(流式响应式支持) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.3 配置
# application.yml
spring:
ai:
dashscope:
api-key: ${
DASHSCOPE_API_KEY} # 从环境变量读取,绝不硬编码
chat:
options:
model: qwen3-7b-max # Qwen3.7-Max
temperature: 0.7 # 创意型任务 0.7-0.9,精确型任务 0.1-0.3
top-p: 0.9
max-tokens: 4096 # 根据输出长度需求调整
⚠️ 踩坑:Qwen3.7-Max 的模型名称在 DashScope 中是
qwen3-7b-max(注意连字符),不是qwen3.7-max或qwen3_7b_max。配置错了会返回 404。

3. 模式一:基础 Chat 与 Streaming
3.1 简单对话
@RestController
@RequestMapping("/api/chat")
public class ChatController {
@Autowired
private ChatClient chatClient;
@GetMapping("/simple")
public String simpleChat(@RequestParam String message) {
return chatClient.call(message);
}
}
就这么简单。ChatClient 是 Spring AI 的核心门面,自动使用 yml 中配置的模型。
3.2 带 System Prompt 的对话
@GetMapping("/with-system")
public String withSystem(@RequestParam String message) {
return chatClient.prompt()
.system("你是一个专业的 Java 开发助手,回答要简洁、准确,优先给出代码示例。")
.user(message)
.call()
.content();
}
3.3 Streaming 流式输出
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.stream()
.content(); // 返回 Flux<String>,前端用 EventSource 接收
}
前端配合:
// 前端 SSE 接收
const eventSource = new EventSource(`/api/chat/stream?message=解释一下Spring的IoC`);
eventSource.onmessage = (e) => {
document.getElementById('output').textContent += e.data;
};
3.4 自定义 Chat Options
如果需要在运行时动态调整参数(而不是全部写死在 yml):
@GetMapping("/custom")
public String custom(@RequestParam String message) {
return chatClient.prompt()
.system("用幽默的风格回答技术问题")
.user(message)
.options(ChatOptionsBuilder.builder()
.withModel("qwen3-7b-max")
.withTemperature(0.9) // 幽默需要高温度
.withMaxTokens(2048)
.build())
.call()
.content();
}
💡 最佳实践:yml 中配置默认值,
options()中覆盖特例。不要在每个请求中都写一遍 model 名。
4. 模式二:Structured Output(结构化输出)
这是 Spring AI 对比裸调 API 的最大优势——类型安全的输出。
4.1 BeanOutputConverter
假设我们需要模型返回结构化的订单分析结果:
// 1. 定义 POJO
public record OrderAnalysis(
String orderId,
String status,
String estimatedDelivery,
String delayReason, // 如果延迟的话
String suggestedAction // 建议的下一步操作
) {
}
// 2. 使用 BeanOutputConverter
@GetMapping("/analyze-order")
public OrderAnalysis analyzeOrder(@RequestParam String orderId) {
var outputConverter = new BeanOutputConverter<>(OrderAnalysis.class);
return chatClient.prompt()
.system("你是一个订单分析助手。根据用户描述分析订单状态,返回结构化数据。")
.user("帮我查一下订单 OD20260707001 的状态,用户说已经等了 5 天了还没到。")
.options(ChatOptionsBuilder.builder()
.withModel("qwen3-7b-max")
.build())
.call()
.entity(OrderAnalysis.class); // ← 一行搞定:自动 JSON 解析 + 类型转换
}
底层原理:Spring AI 自动生成 JSON Schema 注入到 System Prompt,Qwen3.7-Max 返回 JSON,框架帮你反序列化为 POJO。
4.2 更复杂:嵌套结构 + 枚举
public record RiskAssessment(
String customerId,
RiskLevel riskLevel,
List<RiskFactor> factors,
double confidenceScore
) {
public enum RiskLevel {
LOW, MEDIUM, HIGH, CRITICAL }
public record RiskFactor(String name, String description, double weight) {
}
}
@GetMapping("/assess-risk")
public RiskAssessment assessRisk(@RequestParam String customerId) {
return chatClient.prompt()
.system("你是一个风控分析专家。评估客户风险,返回结构化风险报告。")
.user("客户 " + customerId + " 最近 30 天内有 3 次密码重置、2 次异地登录、1 次大额转账。")
.call()
.entity(RiskAssessment.class);
}
Qwen3.7-Max 的 JSON 模式遵循能力在国产模型中处于领先水平,即使嵌套枚举也能稳定输出。
⚠️ 踩坑:如果结构化输出不稳定(偶尔解析失败),可以尝试降低 temperature 到 0.1-0.3 并增加
"response_format": { "type": "json_object" }的提示约束。
5. 模式三:Function Calling(工具调用)
这才是 Spring AI + Qwen3.7-Max 的重头戏。Qwen3.7-Max 在 MCP-Mark 评测中拿到国产第一(60.8 分),工具调用能力是它的核心卖点。
5.1 定义工具
用 @Tool 注解,Spring AI 自动生成工具描述和参数 Schema:
@Service
public class OrderService {
@Tool("根据订单号查询订单当前状态")
public String getOrderStatus(String orderId) {
// 实际调用后端订单服务
return String.format("""
{
"orderId": "%s",
"status": "SHIPPED",
"createTime": "2026-07-01 10:30:00",
"shippedTime": "2026-07-03 14:00:00",
"carrier": "顺丰速运",
"trackingNo": "SF1234567890",
"estimatedDays": 3
}""", orderId);
}
@Tool("查询物流轨迹,需要运单号")
public String queryLogistics(String trackingNo) {
// 调用物流 API
if ("SF1234567890".equals(trackingNo)) {
return """
{
"trackingNo": "SF1234567890",
"carrier": "顺丰速运",
"events": [
{
"time": "2026-07-03 14:00", "location": "深圳分拣中心", "status": "已揽收"},
{
"time": "2026-07-03 22:00", "location": "广州中转站", "status": "运输中"},
{
"time": "2026-07-05 08:30", "location": "北京分拣中心", "status": "到达派件站"},
{
"time": "2026-07-07 09:00", "location": "北京朝阳区", "status": "派送中"}
]
}""";
}
return "{}";
}
@Tool("给指定已超时的订单发起客服催单,返回催单结果")
public String escalateOrder(String orderId, String reason) {
// 调用客服工单系统
return String.format(
"订单 %s 的催单已发起,原因:%s。预计 2 小时内客服回电。工单号:CS-%s",
orderId, reason, System.currentTimeMillis() % 1000000
);
}
}
5.2 组装调用
@RestController
@RequestMapping("/api/agent")
public class AgentController {
@Autowired
private ChatClient chatClient;
@Autowired
private OrderService orderService;
@GetMapping("/order-helper")
public String orderHelper(@RequestParam String question) {
return chatClient.prompt()
.system("你是一个智能订单助手。根据用户问题:\n"
+ "1. 先查询订单状态\n"
+ "2. 如果已发货则查物流\n"
+ "3. 如果超时则发起催单\n"
+ "4. 用友好的语气回复用户")
.user(question)
.tools(orderService) // ← 注入工具
.call()
.content();
}
}
5.3 执行效果
请求:
GET /api/agent/order-helper?question=我的订单OD20260707001已经5天了还没到,帮我看看怎么回事
Qwen3.7-Max 内部执行链路:
1. 调用 getOrderStatus("OD20260707001")
→ 返回:已发货,7月3日发出,预计3天到
2. 模型判断:已发5天 > 预计3天 → 超时
3. 调用 queryLogistics("SF1234567890")
→ 返回:7月7日正在派送中
4. 模型判断:今天在派送中,但确实超时了
5. 调用 escalateOrder("OD20260707001", "物流超时")
→ 返回:催单已发起
6. 综合所有信息,生成给用户的友好回复
最终回复:
您好!我查到了您的订单 OD20260707001 的情况:
📦 订单状态:已发货(7月3日从深圳发出)
📍 最新物流:今天(7月7日)正在北京朝阳区派送中
虽然快递今天已经在派送了,但确实比预计的3天晚了2天。我已经帮您发起了催单(工单号:CS-889012),预计2小时内会有客服联系您跟进。
给您带来的不便非常抱歉!如果还有其他问题,随时问我 🙏

💡 关键体验:整个多步推理 + 工具调用的编排,Spring AI + Qwen3.7-Max 自动完成。你只需要定义工具(
@Tool)和系统提示词,模型自己决定调用顺序和调用条件。
6. 模式四:Chat Memory(对话记忆)
没有记忆的 AI 客服每次都是"初次见面",体验很差。Spring AI 提供了 ChatMemory 抽象:
6.1 配置
@Configuration
public class ChatConfig {
@Bean
public ChatMemory chatMemory() {
// 生产环境建议用 Redis 替代内存存储
return new InMemoryChatMemory();
}
@Bean
public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
return builder
.defaultSystem("你是一个友好的客服助手。回答要简短亲切。")
.build();
}
}
6.2 带记忆的对话
@Service
public class ConversationService {
@Autowired
private ChatClient chatClient;
@Autowired
private ChatMemory chatMemory;
// 每个会话一个 ID,前端传入 sessionId
public String chat(String sessionId, String userMessage) {
// 1. 从 ChatMemory 获取历史
List<Message> history = chatMemory.get(sessionId, 10);
// 2. 构建 prompt(历史 + 新消息)
var prompt = new Prompt(
history,
new UserMessage(userMessage)
);
// 3. 调用模型
var response = chatClient.prompt(prompt).call().content();
// 4. 保存到历史
chatMemory.add(sessionId, new UserMessage(userMessage));
chatMemory.add(sessionId, new AssistantMessage(response));
return response;
}
}
6.3 简洁写法
Spring AI 还提供 Advisor 机制,一行搞定记忆管理:
public String chatWithAdvisor(String sessionId, String message) {
return chatClient.prompt()
.user(message)
.advisors(a -> a.param("chat_memory_conversation_id", sessionId)
.param("chat_memory_response_size", 10))
.call()
.content();
// 自动管理:读取历史 → 拼接 → 调用 → 保存历史
}
⚠️ 踩坑:
InMemoryChatMemory重启后丢失。生产环境务必替换为 Redis 实现:@Bean public ChatMemory chatMemory(RedisTemplate<String, Object> redisTemplate) { return new RedisChatMemory(redisTemplate); }
7. 生产环境配置与最佳实践
7.1 完整的生产配置
spring:
ai:
dashscope:
api-key: ${
DASHSCOPE_API_KEY}
# 连接池配置
connect-timeout: 30s
read-timeout: 60s
chat:
options:
model: qwen3-7b-max
temperature: 0.7
top-p: 0.9
max-tokens: 4096
# 重试配置
retry:
max-attempts: 3
backoff:
initial-interval: 1000
multiplier: 2
max-interval: 10000
7.2 限流与熔断
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("你是专业的技术支持助手。")
// 请求级限流:每秒最多 10 个请求
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
// 结合 Resilience4j 做熔断
@Service
public class ResilientChatService {
@Autowired
private ChatClient chatClient;
@CircuitBreaker(name = "aiService", fallbackMethod = "fallbackReply")
@Retry(name = "aiService")
@RateLimiter(name = "aiService")
public String chat(String message) {
return chatClient.call(message);
}
public String fallbackReply(String message, Throwable t) {
return "抱歉,AI 服务暂时不可用,请稍后再试。";
}
}
7.3 Qwen3.7-Max 专属参数调优
| 参数 | 推荐值 | 说明 |
|---|---|---|
temperature |
0.1-0.3(精确) / 0.7-0.9(创意) | Qwen3.7-Max 对温度敏感度比 Qwen-Plus 更高 |
top_p |
0.8-0.95 | 与 temperature 配合,一般固定 0.9 |
max_tokens |
2048-8192 | 长文本生成(报告/代码)设大,对话设小 |
stop |
可自定义 | 如 ["\n\n", "Human:"] 控制生成终止 |
enable_search |
true/false | 是否启用百炼联网搜索(需额外配置) |
7.4 成本优化策略
// 策略:简单问题用 Qwen-Plus,复杂问题用 Qwen3.7-Max
@Service
public class CostOptimizedService {
@Autowired
private ChatClient.Builder builder;
public String chat(String message) {
// 判断问题复杂度
boolean isComplex = message.length() > 50
|| message.contains("原因") || message.contains("分析");
var chatClient = isComplex
? builder.defaultOptions(o -> o.withModel("qwen3-7b-max")).build()
: builder.defaultOptions(o -> o.withModel("qwen-plus")).build();
return chatClient.call(message);
}
}
💡 成本参考:Qwen3.7-Max 价格约为 Qwen-Plus 的 5-8 倍。用上述策略,大约 70% 的流量走 Plus,30% 走 Max,综合成本仅比全 Plus 方案高 2-3 倍,但核心体验提升显著。
8. 完整示例:智能订单助手
把前面的所有模式组合起来,实现一个完整的订单助手 API:
@RestController
@RequestMapping("/api/order-assistant")
public class OrderAssistantController {
@Autowired
private ChatClient chatClient;
@Autowired
private OrderService orderService;
@GetMapping("/chat")
public String chat(
@RequestParam String sessionId,
@RequestParam String message) {
return chatClient.prompt()
.system("""
你是一个智能订单助手,帮助用户查询订单、物流和发起催单。
规则:
1. 先确认用户想做什么
2. 用工具查数据后再回复
3. 涉及超时的主动建议催单
4. 语言友好、带 emoji
""")
.user(message)
.tools(orderService)
.advisors(a -> a.param("chat_memory_conversation_id", sessionId))
.call()
.content();
}
}
启动项目后,用 curl 测试:
# 测试流式输出
curl -N "http://localhost:8080/api/chat/stream?message=用三句话解释什么是依赖注入"
# 测试结构化输出
curl "http://localhost:8080/api/chat/analyze-order?orderId=OD001"
# 测试函数调用
curl "http://localhost:8080/api/order-assistant/chat?sessionId=user01&message=我的订单OD20260707001还没到"
# 第二次对话(验证记忆)
curl "http://localhost:8080/api/order-assistant/chat?sessionId=user01&message=查物流的那个呢?"
9. 踩坑实录
坑 1:模型名称写错
现象:Caused by: com.alibaba.cloud.ai.dashscope.common.DashScopeException: Model not found
原因:DashScope 中 Qwen3.7-Max 的模型名为 qwen3-7b-max,不是 qwen3.7-max 也不是 qwen-3.7b-max。
解决:在百炼控制台「模型广场」确认准确的模型名称,有疑问时先裸调 API 验证再接入 Spring AI。
坑 2:Function Calling 返回 "Unknown tool"
现象:模型识别了工具但 Spring AI 报 Unknown tool: xxx
原因:@Tool 注解的方法必须是 public,并且所在 Bean 必须被 Spring 管理。
解决:检查 Service 类是否有 @Service 注解,方法是否是 public。
坑 3:流式输出在 Nginx 后被缓冲
现象:本地测试 streaming 正常,部署后前端收到的是整段文本而非逐字输出。
原因:Nginx 默认缓冲了 SSE 响应。
解决:
location /api/chat/ {
proxy_pass http://backend;
proxy_buffering off; # 关闭缓冲
proxy_cache off; # 关闭缓存
chunked_transfer_encoding on;
proxy_set_header Connection '';
proxy_http_version 1.1;
}
坑 4:Chat Memory 导致 Token 消耗暴增
现象:对话到第 5 轮后,响应越来越慢,Token 消耗暴增。
原因:ChatMemory 默认保留全部历史,未做截断。
解决:限制历史轮数,并用滑动窗口策略:
// 只保留最近 10 条消息(5 轮对话)
List<Message> history = chatMemory.get(sessionId, 10);
// 或者用 Token 数量截断
var messageWindow = chatMemory.get(sessionId, Integer.MAX_VALUE);
int totalTokens = countTokens(messageWindow);
while (totalTokens > 4000 && messageWindow.size() > 2) {
messageWindow.remove(0); // 移除最早的消息
totalTokens = countTokens(messageWindow);
}
总结
Spring AI + Qwen3.7-Max 的组合,让 Java 团队能以最少的代码量接入大模型能力:
| 场景 | 代码量 | 核心代码 |
|---|---|---|
| 简单对话 | ~3 行 | chatClient.call(message) |
| 流式输出 | ~3 行 | chatClient.prompt().stream().content() |
| 结构化输出 | ~5 行 | chatClient.call().entity(MyRecord.class) |
| 函数调用 | ~10 行 | @Tool + .tools(service) |
| 对话记忆 | ~5 行 | .advisors() + ChatMemory |
核心价值在于:你把 LLM 当成 Spring 生态中的一个普通 Bean 来用——注入、配置、调用,和用 RestTemplate 一样自然。模型的细节(API 地址、鉴权、重试、流式解析)被框架封装,业务代码只关心输入和输出。
💡 下一步:本文的下一部分将深入 Qwen3.7-Max 的 Multi-Agent 模式,用 Spring AI 编排多个 Agent 协作完成复杂任务(代码生成 → Review → 测试 → 部署)。敬请期待。