创建一个使用 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 可以根据用户的自然语言查询自动调用相应的业务函数,提供智能化的客户服务体验。

相关推荐
江湖十年2 小时前
Google ADK + DeepSeek 快速构建 Agent
llm·agent·ai编程
bug菌3 小时前
还在为编程效率发愁?字节跳动Trae如何让你秒变“代码大师“!
后端·ai编程·trae
算家计算3 小时前
阿里开源首个图像生成基础模型——Qwen-Image本地部署教程,超强中文渲染能力刷新SOTA!
人工智能·开源·aigc
2501_909686703 小时前
基于SpringBoot的旅游网站系统
vue.js·spring boot·后端
量子位3 小时前
26岁陶中恺教授,加盟法国数学象牙塔
aigc
量子位3 小时前
华为诺亚首席研究员,也具身智能创业了
ai编程·harmonyos
老崔AI编程3 小时前
牛马变高铁,用ai编程做100个app第一期:飞机大战
ai编程
POLOAPI4 小时前
震撼!GPT-5已来,我用3种方法将API成本降低90%(附完整代码)
人工智能·chatgpt·ai编程
sorryhc4 小时前
CSR秒开有可能么?(附AI驱动学习实践推理过程)
前端·javascript·ai编程