Spring Boot 3.x 集成 DeepSeek 实现 Function Calling(工具调用)

Spring Boot 3.x 集成 DeepSeek 实现 Function Calling(工具调用)

技术栈:Spring Boot 3.5.16 + Spring AI 1.0.1 + DeepSeek + Java 21


ps:该文档也是ai根据demo总结的。

1. 概述

1.1 什么是 Function Calling?

Function Calling(工具调用)是指大模型在对话过程中,能够自动识别用户的意图,判断是否需要调用某个外部函数来获取数据,然后根据函数返回的结果组织回答。

举个例子,当用户问「帮我查一下用户ID是10001的订单」时:

  • DeepSeek 识别到需要调用 get_user_order 方法
  • 自动提取参数 userId=10001
  • 调用方法获取订单数据
  • 将结果整理成自然语言返回给用户

整个过程用户无感知,但背后模型实际做了「意图识别 → 参数提取 → 函数调用 → 结果封装」四步。

1.2 Spring AI 的角色

Spring AI 封装了各大模型提供商的 Function Calling 细节,提供了统一的 @Tool 注解定义工具方法,通过 ChatClient 流式编排调用。我们只需关注业务方法怎么写,不用管底层 API 差异。


2. 引入依赖

pom.xml 中添加以下内容:

2.1 Spring AI BOM(版本管理)

xml 复制代码
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

说明:Spring AI BOM 统一管理所有 Spring AI 组件的版本,避免版本冲突。

2.2 DeepSeek Starter

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>

说明spring-ai-starter-model-deepseek 已内置了 ChatClientChatModel 等核心组件,无需额外引入 spring-ai-openai-spring-boot-starter,因为 DeepSeek API 与 OpenAI 接口兼容,Spring AI 官方已适配。

2.3 完整依赖参考

xml 复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.16</version>
</parent>

<properties>
    <java.version>21</java.version>
</properties>

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- DeepSeek AI -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-deepseek</artifactId>
    </dependency>

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>

    <!-- 测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3. 配置文件

3.1 application.yml(主配置)

yaml 复制代码
server:
  port: 8080

spring:
  profiles:
    active: dev
  application:
    name: demo
  servlet:
    context-path: /

3.2 application-dev.yml(DeepSeek 配置)

yaml 复制代码
spring:
  ai:
    deepseek:
      api-key: "sk-your-deepseek-api-key"   # 替换为自己的 API Key
      chat:
        options:
          model: deepseek-chat               # 可选:deepseek-reasoner / deepseek-v4-pro
          temperature: 0.8                   # 控制回复随机性,0-1 之间

模型选择说明

模型 用途
deepseek-chat 通用对话,支持 Function Calling
deepseek-reasoner 推理增强,适合复杂逻辑
deepseek-v4-pro 最新旗舰模型

安全提示:API Key 是敏感信息,生产环境建议通过环境变量注入:

yaml 复制代码
api-key: ${DEEPSEEK_API_KEY}

4. 实现代码

4.1 启动类

java 复制代码
package com.zhh.web_demo_ai;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebDemoAiApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebDemoAiApplication.class, args);
    }
}

4.2 定义工具方法(核心)

使用 @Tool@ToolParam 注解将业务方法注册为 AI 可调用的工具:

java 复制代码
package com.zhh.web_demo_ai.service;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;

@Service
public class OrderAssistantService {

    /**
     * 查询用户订单
     * @param userId 用户ID
     */
    @Tool(name = "get_user_order",
          description = "根据用户ID查询该用户的订单列表,返回订单号、金额和状态")
    public String getOrder(
            @ToolParam(description = "用户id") String userId) {
        // 实际项目中,这里调用数据库或微服务
        System.out.println(">>> 执行 getOrder 方法,参数: userId=" + userId);
        return "用户 " + userId + " 的订单:\n" +
                "- 订单号:ORD2026001,金额:299元,状态:已发货\n" +
                "- 订单号:ORD2026002,金额:89元,状态:待支付";
    }

    /**
     * 按类型查询商品
     * @param type 商品类型
     */
    @Tool(name = "query_products_by_type",
          description = "根据商品类型查询商品列表,返回商品名称和价格")
    public String getProduct(
            @ToolParam(description = "商品类型,如:手机、电脑、书籍") String type) {
        System.out.println(">>> 执行 getProduct 方法,参数: type=" + type);
        return "商品类型:" + type + "\n" +
                "- 商品A:华为Mate60,价格:6999元\n" +
                "- 商品B:小米14 Pro,价格:4999元";
    }
}

关键注解说明

注解 说明
@Tool(name, description) 声明该方法为 AI 可调用的工具。name 是唯一标识,description 告诉 AI 这个方法是干什么的------写清楚能让 AI 更准确判断何时调用
@ToolParam(description) 描述参数的含义和格式,AI 会据此从用户输入中提取参数

⚠️ 注意description 写得越清晰,AI 调用准确率越高。

4.3 ChatClient 配置

java 复制代码
package com.zhh.web_demo_ai.config;

import com.zhh.web_demo_ai.service.OrderAssistantService;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RagConfig {

    /**
     * 创建 ChatClient Bean
     * 采用按需注册工具的方式,比全局注册更灵活
     */
    @Bean
    public ChatClient chatClient(
            ChatClient.Builder builder,
            OrderAssistantService orderAssistantService) {
        return builder
                // 不在全局注册工具,而是在每次调用时按需 .tools(...)
                // .defaultTools(orderAssistantService)
                .build();
    }
}

设计思路ChatClient.Builderspring-ai-starter-model-deepseek 自动配置,我们只需注入即可。工具注册有两种方式:

  • 全局注册defaultTools):所有对话都带上这些工具,简单但不灵活
  • 按需注册.tools() 在调用时指定):每次对话自主决定启用哪些工具,推荐使用

4.4 测试用例

java 复制代码
package com.zhh.web_demo_ai.ai;

import com.zhh.web_demo_ai.service.OrderAssistantService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@Slf4j
@SpringBootTest
public class FunctionCallingTest {

    @Autowired
    private ChatClient chatClient;

    @Autowired
    private OrderAssistantService orderAssistantService;

    /**
     * 简单问答 --- 不需要 Function Calling
     */
    @Test
    public void show() {
        String msg = chatClient.prompt()
                .user("你好")
                .call()
                .content();
        System.out.println(msg);
    }

    /**
     * Function Calling 全场景测试
     */
    @Test
    public void testAssistant() {
        // 测试1:查询订单 --- AI 自动识别需要调用 get_user_order
        System.out.println("=== 测试1:查询订单 ===");
        String answer = chatClient.prompt()
                .tools(orderAssistantService)               // 按需注册工具
                .user("帮我查一下用户ID是10001的订单")
                .call()
                .content();
        System.out.println(answer);

        // 测试2:查询商品 --- AI 自动识别需要调用 query_products_by_type
        System.out.println("\n=== 测试2:查询商品 ===");
        String answer2 = chatClient.prompt()
                .tools(orderAssistantService)
                .user("有什么手机可以买?")
                .call()
                .content();
        System.out.println(answer2);

        // 测试3:普通闲聊 --- AI 判断不需要调用任何工具
        System.out.println("\n=== 测试3:不需要调函数的普通问题 ===");
        String answer3 = chatClient.prompt()
                .user("你好,你是谁?")
                .call()
                .content();
        System.out.println(answer3);
    }
}

5. 运行流程解析

以「帮我查一下用户ID是10001的订单」为例,整个 Function Calling 流程如下:

复制代码
用户输入: "帮我查一下用户ID是10001的订单"
          │
          ▼
┌─────────────────────────┐
│ 1. 构造请求              │
│    chatClient.prompt()  │
│    .tools(orderAssistant│
│     Service)            │
│    .user("...")         │
│    .call()              │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│ 2. 发送给 DeepSeek       │
│    - 用户消息            │
│    - 可用工具列表         │
│      (get_user_order,   │
│       query_products...) │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│ 3. DeepSeek 返回         │
│    tool_calls: [{        │
│      name: "get_user    │
│             _order",    │
│      arguments: {        │
│        userId: "10001"   │
│      }                   │
│    }]                    │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│ 4. Spring AI 执行        │
│    orderAssistantService │
│    .getOrder("10001")    │
│    → 返回订单数据         │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│ 5. 二次请求 DeepSeek     │
│    将函数返回结果         │
│    再次发送给模型          │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│ 6. DeepSeek 整理回复     │
│    "用户10001的订单如下:  │
│     订单号ORD2026001,    │
│     金额299元,已发货..."  │
│    → .content() 返回     │
└─────────────────────────┘

6. 注意事项与最佳实践

6.1 description 是灵魂

AI 不会读你的代码,它只看 description。务必写出清晰、具体的描述:

java 复制代码
// ❌ 不好:描述太模糊
@Tool(description = "获取数据")

// ✅ 好:清楚说明功能和参数
@Tool(name = "get_user_order",
      description = "根据用户ID查询该用户的订单列表,返回订单号、金额和状态")

6.2 按需注册 > 全局注册

java 复制代码
// ✅ 推荐:调用时按需指定
chatClient.prompt()
    .tools(orderAssistantService)  // 仅本次对话启用
    .user("...")
    .call();

// ⚠️ 全局注册:所有对话都带上,增加 token 消耗
// builder.defaultTools(orderAssistantService)

6.3 Token 消耗

每次调用 .tools(...) 都会将工具定义(名称、描述、参数 Schema)发送给 DeepSeek,会消耗 token。工具越多,每次都越贵,所以只注册本次对话可能需要的工具。

6.4 安全考虑

  • API Key 使用环境变量,不硬编码到配置文件
  • 工具方法中做好参数校验,防止 AI 幻觉传入非法参数
  • 敏感操作(如转账、删除)在方法内部做二次确认,不能完全信任 AI 的判断

6.5 幂等性

查询类工具天然幂等,但如果你定义了写操作工具(如下单、扣款),需要确保:

java 复制代码
@Tool(name = "create_order")
public String createOrder(@ToolParam(description = "商品SKU") String sku,
                          @ToolParam(description = "数量") int quantity) {
    // 生成唯一订单号,防止重复创建
    String orderId = generateUniqueOrderId();
    // ...
}

7. 扩展阅读


完整项目结构

复制代码
src/main/java/com/zhh/web_demo_ai/
├── WebDemoAiApplication.java          # 启动类
├── config/
│   └── RagConfig.java                 # ChatClient 配置
└── service/
    └── OrderAssistantService.java     # 工具方法定义(@Tool)

src/test/java/com/zhh/web_demo_ai/ai/
└── FunctionCallingTest.java           # 测试用例

src/main/resources/
├── application.yml                    # 主配置
└── application-dev.yml                # DeepSeek 配置
相关推荐
@航空母舰1 小时前
SpringBoot通过Map实现天然的策略模式
java·spring boot·后端
犀利豆1 小时前
Claude Code Tools 研究系列-前置篇(tool 机制)
人工智能
阿里云大数据AI技术1 小时前
AI Native, Now|阿里云 Milvus AI Function,从能力集成走向产品化落地
人工智能
SamChan902 小时前
在Web应用中集成PDF多语言翻译功能:PDFTranslator API实战指南
前端·python·ai·pdf·yapi·机器翻译
阿三08122 小时前
跨境电商售后自动化分级标准:哪些能全自动、哪些半自动、哪些禁止自动化
大数据·人工智能·自动化
IT_陈寒2 小时前
JavaScript的this又双叒叕让我怀疑人生了
前端·人工智能·后端
颜酱2 小时前
09 | 重构项目结构
人工智能·python·langchain
陈随易2 小时前
MCP协议第5次更新,从打电话到微信聊天的巨大变革
前端·后端·程序员
65岁退休Coder2 小时前
LangChain v1.3.4 笔记 - 06 RAG 检索增强生成
后端