Spring AI 集成 Qwen3.7-Max 实战:从 Chat 到 Function Calling 的完整指南

在线体验各类最新模型,更有模型 免费Token 额度领取!
立即体验
简介: Qwen3.7-Max 的 API 调用很多人已经熟悉,但如何用 Spring AI 的抽象层优雅地集成它,充分发挥其推理、函数调用和结构化输出能力?本文从 Spring AI 的项目初始化开始,覆盖 Chat/Streaming/Structured Output/Function Calling/Chat Memory 五大核心模式,并给出生产级的配置建议和踩坑实录。

适用人群: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

009-architecture-comparison.png

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-maxqwen3_7b_max。配置错了会返回 404。

009-spring-ai-patterns.png

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小时内会有客服联系您跟进。

给您带来的不便非常抱歉!如果还有其他问题,随时问我 🙏

009-function-calling-flow.png

💡 关键体验:整个多步推理 + 工具调用的编排,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 → 测试 → 部署)。敬请期待。

相关文章
|
11天前
|
人工智能 JSON 自然语言处理
让教学更智慧:用阿里云百炼工作流,自动生成中小学教材内容#小有可为#有温度的AI
通过可视化工作流编排,将大模型推理能力转化为标准化的教学内容生成引擎。教师只需输入教材标题和适用学段,即可自动获得结构完整、符合课程标准的章节内容,大幅降低备课门槛,助力教育资源均衡化。
489 126
|
20天前
|
Linux 程序员 数据格式
【2026最新】Notepad++下载、安装和使用一篇搞定(附中文版安装包)
Notepad++ 是一款免费开源、轻量高效的 Windows 文本编辑器,支持 C/Python/HTML 等 80+ 语言语法高亮、代码折叠、正则替换、编码转换及插件扩展,专为程序员与文本处理用户打造,完美替代系统记事本。(239字)
|
5天前
|
人工智能 缓存 安全
Claude Code 封号真实原因曝光,这次彻底不装了,直接针对国内开发者的账号下手?
Claude Code 封号潮背后:逆向扒出客户端隐写区域标记,Anthropic 政策收紧叠加 DeepSeek 7 月涨价,国产替代更紧迫。
|
7天前
|
人工智能 安全 Cloud Native
Higress 新发布:AI Gateway 能力增强,Gateway API 及其推理扩展持续打磨
增强 AI 网关能力,持续打磨 Gateway API 及其推理扩展。
352 124
|
6天前
|
人工智能 安全 程序员
终于,Claude Code 封号的原因被曝光了!竟然针对中国用户,植入隐形代码?!
通俗易懂地揭秘 Claude Code 封号的手段,分享一些自己对 AI 编程困境的思考,Codex、Cursor、DeepSeek、智谱 GLM、甚至是豆包,都有所行动了
372 1
|
15天前
|
机器学习/深度学习 人工智能 调度
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
HappyHorse 1.1 是新一代视频生成大模型,全面升级动态表现力、角色一致性、指令遵循、视觉质感与音画协同能力。支持I2V/T2V/R2V三类生成,适配短剧、电商广告、品牌营销等场景,提供高质、流畅、可控的AI视频生产力。
865 5
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
|
12天前
|
人工智能 定位技术 SEO
我学 GEO 第 15 天:终于知道AI GEO该如何做?
我是暴走的莉莉酱,边旅行边研究AI GEO的数字游民。专注普通人如何提升“AI可见度”——让AI在回答用户问题时准确识别、理解并推荐你。不讲玄学,只做可测、可调、可持续的GEO实践。
472 127