创建一个使用 Spring AI 配合 Function Calling 的实际场景案例:智能客服助手,它可以查询订单信息、检查库存和发送通知。
项目结构
首先,让我们创建一个完整的项目结构:
1. 依赖配置 (pom.xml)
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
2. 配置文件 (application.yml)
yaml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4
temperature: 0.7
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
3. 数据模型
java
// Order.java
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String orderNumber;
private String customerName;
private String customerEmail;
private String productName;
private Integer quantity;
private BigDecimal totalAmount;
private String status;
private LocalDateTime createTime;
// 构造函数、getter和setter
public Order() {}
public Order(String orderNumber, String customerName, String customerEmail,
String productName, Integer quantity, BigDecimal totalAmount, String status) {
this.orderNumber = orderNumber;
this.customerName = customerName;
this.customerEmail = customerEmail;
this.productName = productName;
this.quantity = quantity;
this.totalAmount = totalAmount;
this.status = status;
this.createTime = LocalDateTime.now();
}
// getters and setters...
}
// Product.java
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productCode;
private String productName;
private Integer stockQuantity;
private BigDecimal price;
// 构造函数、getter和setter
public Product() {}
public Product(String productCode, String productName, Integer stockQuantity, BigDecimal price) {
this.productCode = productCode;
this.productName = productName;
this.stockQuantity = stockQuantity;
this.price = price;
}
// getters and setters...
}
4. 数据访问层
java
// OrderRepository.java
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
Optional<Order> findByOrderNumber(String orderNumber);
List<Order> findByCustomerEmailOrderByCreateTimeDesc(String customerEmail);
}
// ProductRepository.java
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
Optional<Product> findByProductCode(String productCode);
Optional<Product> findByProductName(String productName);
}
5. Function Calling 实现
java
// CustomerServiceFunctions.java
@Component
public class CustomerServiceFunctions {
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
private final NotificationService notificationService;
public CustomerServiceFunctions(OrderRepository orderRepository,
ProductRepository productRepository,
NotificationService notificationService) {
this.orderRepository = orderRepository;
this.productRepository = productRepository;
this.notificationService = notificationService;
}
/**
* 查询订单信息
*/
@JsonClassDescription("根据订单号查询订单详细信息")
public record OrderQueryRequest(
@JsonProperty(required = true)
@JsonPropertyDescription("订单号")
String orderNumber
) {}
@JsonClassDescription("订单信息响应")
public record OrderQueryResponse(
String orderNumber,
String customerName,
String productName,
Integer quantity,
BigDecimal totalAmount,
String status,
String createTime
) {}
@Function("查询订单信息")
public OrderQueryResponse queryOrder(OrderQueryRequest request) {
Optional<Order> orderOpt = orderRepository.findByOrderNumber(request.orderNumber());
if (orderOpt.isEmpty()) {
return new OrderQueryResponse(request.orderNumber(),
"未找到", "未找到", 0, BigDecimal.ZERO, "订单不存在", "");
}
Order order = orderOpt.get();
return new OrderQueryResponse(
order.getOrderNumber(),
order.getCustomerName(),
order.getProductName(),
order.getQuantity(),
order.getTotalAmount(),
order.getStatus(),
order.getCreateTime().toString()
);
}
/**
* 检查库存
*/
@JsonClassDescription("检查产品库存信息")
public record StockCheckRequest(
@JsonProperty(required = true)
@JsonPropertyDescription("产品代码或产品名称")
String product
) {}
@JsonClassDescription("库存检查响应")
public record StockCheckResponse(
String productName,
String productCode,
Integer stockQuantity,
BigDecimal price,
String status
) {}
@Function("检查库存")
public StockCheckResponse checkStock(StockCheckRequest request) {
Optional<Product> productOpt = productRepository.findByProductCode(request.product());
if (productOpt.isEmpty()) {
productOpt = productRepository.findByProductName(request.product());
}
if (productOpt.isEmpty()) {
return new StockCheckResponse("未找到产品", "", 0, BigDecimal.ZERO, "产品不存在");
}
Product product = productOpt.get();
String status = product.getStockQuantity() > 0 ? "有库存" : "缺货";
return new StockCheckResponse(
product.getProductName(),
product.getProductCode(),
product.getStockQuantity(),
product.getPrice(),
status
);
}
/**
* 发送通知
*/
@JsonClassDescription("向客户发送通知")
public record NotificationRequest(
@JsonProperty(required = true)
@JsonPropertyDescription("客户邮箱")
String email,
@JsonProperty(required = true)
@JsonPropertyDescription("通知消息")
String message
) {}
@JsonClassDescription("通知发送响应")
public record NotificationResponse(
String email,
String message,
String status
) {}
@Function("发送通知")
public NotificationResponse sendNotification(NotificationRequest request) {
boolean success = notificationService.sendEmail(request.email(), request.message());
return new NotificationResponse(
request.email(),
request.message(),
success ? "发送成功" : "发送失败"
);
}
}
6. 通知服务
java
// NotificationService.java
@Service
public class NotificationService {
private static final Logger logger = LoggerFactory.getLogger(NotificationService.class);
public boolean sendEmail(String email, String message) {
// 模拟发送邮件
try {
logger.info("发送邮件到: {} 内容: {}", email, message);
Thread.sleep(1000); // 模拟网络延迟
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
7. 智能客服服务
java
// IntelligentCustomerService.java
@Service
public class IntelligentCustomerService {
private final ChatClient chatClient;
public IntelligentCustomerService(ChatClient.Builder chatClientBuilder,
CustomerServiceFunctions functions) {
this.chatClient = chatClientBuilder
.defaultFunctions("queryOrder", "checkStock", "sendNotification")
.build();
}
public String handleCustomerQuery(String query) {
String systemPrompt = """
你是一个专业的智能客服助手。你可以:
1. 查询订单信息 - 当客户询问订单状态、订单详情时使用
2. 检查库存信息 - 当客户询问产品库存、价格时使用
3. 发送通知 - 当需要向客户发送重要信息时使用
请根据客户的问题,选择合适的功能来帮助客户。
回答要友善、专业,并提供准确的信息。
""";
ChatResponse response = chatClient.prompt()
.system(systemPrompt)
.user(query)
.call()
.chatResponse();
return response.getResult().getOutput().getContent();
}
}
8. 控制器
java
// CustomerServiceController.java
@RestController
@RequestMapping("/api/customer-service")
public class CustomerServiceController {
private final IntelligentCustomerService customerService;
public CustomerServiceController(IntelligentCustomerService customerService) {
this.customerService = customerService;
}
@PostMapping("/chat")
public ResponseEntity<Map<String, String>> chat(@RequestBody Map<String, String> request) {
String query = request.get("query");
if (query == null || query.trim().isEmpty()) {
return ResponseEntity.badRequest()
.body(Map.of("error", "查询内容不能为空"));
}
try {
String response = customerService.handleCustomerQuery(query);
return ResponseEntity.ok(Map.of("response", response));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "处理请求时发生错误: " + e.getMessage()));
}
}
}
9. 数据初始化
java
// DataInitializer.java
@Component
public class DataInitializer {
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
public DataInitializer(OrderRepository orderRepository, ProductRepository productRepository) {
this.orderRepository = orderRepository;
this.productRepository = productRepository;
}
@EventListener(ApplicationReadyEvent.class)
public void initializeData() {
// 初始化产品数据
productRepository.save(new Product("P001", "iPhone 15 Pro", 50, new BigDecimal("999.00")));
productRepository.save(new Product("P002", "MacBook Pro", 20, new BigDecimal("1999.00")));
productRepository.save(new Product("P003", "AirPods Pro", 0, new BigDecimal("249.00"))); // 缺货
// 初始化订单数据
orderRepository.save(new Order("ORD001", "张三", "zhang@example.com",
"iPhone 15 Pro", 1, new BigDecimal("999.00"), "已发货"));
orderRepository.save(new Order("ORD002", "李四", "li@example.com",
"MacBook Pro", 1, new BigDecimal("1999.00"), "处理中"));
orderRepository.save(new Order("ORD003", "王五", "wang@example.com",
"AirPods Pro", 2, new BigDecimal("498.00"), "已取消"));
}
}
10. 使用示例
java
// 测试客户端请求示例
@RestController
@RequestMapping("/api/test")
public class TestController {
private final IntelligentCustomerService customerService;
public TestController(IntelligentCustomerService customerService) {
this.customerService = customerService;
}
@GetMapping("/demo")
public List<Map<String, String>> demo() {
List<String> testQueries = Arrays.asList(
"我想查询订单号 ORD001 的状态",
"请帮我查看 iPhone 15 Pro 的库存情况",
"MacBook Pro 现在有货吗?价格是多少?",
"请向 zhang@example.com 发送一条消息:您的订单已发货",
"我的订单 ORD999 在哪里?"
);
return testQueries.stream()
.map(query -> {
String response = customerService.handleCustomerQuery(query);
return Map.of("query", query, "response", response);
})
.collect(Collectors.toList());
}
}
使用方法
-
启动应用 :确保设置了
OPENAI_API_KEY
环境变量 -
发送请求
:
bashcurl -X POST http://localhost:8080/api/customer-service/chat \ -H "Content-Type: application/json" \ -d '{"query": "我想查询订单号 ORD001 的状态"}'
关键特性
- 自动函数选择:AI 根据用户查询自动选择合适的函数
- 类型安全:使用 Record 类型确保参数类型安全
- 错误处理:优雅处理各种异常情况
- 实际业务场景:模拟真实的客服系统功能
这个案例展示了 Spring AI Function Calling 在实际业务场景中的应用,AI 可以根据用户的自然语言查询自动调用相应的业务函数,提供智能化的客户服务体验。