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已内置了ChatClient、ChatModel等核心组件,无需额外引入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 是敏感信息,生产环境建议通过环境变量注入:
yamlapi-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.Builder由spring-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 配置