创建一个使用 Spring AI 配合 Function Calling 的实际场景案例:智能客服助手

创建一个使用 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());
    }
}

使用方法

  1. 启动应用 :确保设置了 OPENAI_API_KEY 环境变量

  2. 发送请求

    bash 复制代码
    curl -X POST http://localhost:8080/api/customer-service/chat \  -H "Content-Type: application/json" \  -d '{"query": "我想查询订单号 ORD001 的状态"}'

关键特性

  1. 自动函数选择:AI 根据用户查询自动选择合适的函数
  2. 类型安全:使用 Record 类型确保参数类型安全
  3. 错误处理:优雅处理各种异常情况
  4. 实际业务场景:模拟真实的客服系统功能

这个案例展示了 Spring AI Function Calling 在实际业务场景中的应用,AI 可以根据用户的自然语言查询自动调用相应的业务函数,提供智能化的客户服务体验。

相关推荐
数据智能老司机44 分钟前
Spring AI 实战——提交用于生成的提示词
spring·llm·ai编程
程序定小飞1 小时前
基于springboot的作业管理系统设计与实现
java·开发语言·spring boot·后端·spring
数据智能老司机1 小时前
Spring AI 实战——评估生成结果
spring·llm·ai编程
量子位2 小时前
机器人“会用手”了!银河通用首破手掌任意朝向旋转难题,拧螺丝、砸钉子样样精通
人工智能·aigc
该用户已不存在2 小时前
免费的 Vibe Coding 助手?你想要的Gemini CLI 都有
人工智能·后端·ai编程
qq_12498707533 小时前
基于springboot+vue的物流管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·毕业设计
程序员X小鹿3 小时前
2025最火的4个国产AI音乐工具全面评测,最后两个完全免费!(建议收藏)
aigc
后端小肥肠4 小时前
Coze+n8n实战:公众号文章从仿写到草稿箱,2分钟全搞定,你只需提交链接!
aigc·agent·coze
刘一说4 小时前
深入理解 Spring Boot Actuator:构建可观测性与运维友好的应用
运维·spring boot·后端
郝开4 小时前
最终 2.x 系列版本)2 - 框架搭建:pom配置;多环境配置文件配置;多环境数据源配置;测试 / 生产多环境数据源配置
java·spring boot·后端