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

相关推荐
Kagol2 分钟前
TinyVue 支持 Skills 啦!现在你可以让 AI 使用 TinyVue 组件搭建项目
前端·agent·ai编程
柳杉4 分钟前
从零打造 AI 全球趋势监测大屏
前端·javascript·aigc
李广坤1 小时前
使用 Skills 的技巧与规范
ai编程
哈基咪怎么可能是AI1 小时前
OpenClaw 插件系统:如何打造全能私人助理 --OpenClaw源码系列第2期
开源·ai编程
饼干哥哥1 小时前
用Openclaw+Obsidian搭建内容工厂,写100W+阅读爆文,单篇6000元
aigc
本末倒置1831 小时前
我研究了OpenClaw一周,发现它不是另一个ChatGPT,而是数字员工的起点
openai·ai编程·claude
DigitalOcean2 小时前
GPU对比:MI350X、MI325X、MI300X、H200、H100
llm·aigc
兔子零10243 小时前
Star-Office-UI-Node 实战:从 0 到 1 接入 OpenClaw 的多 Agent 看板
前端·ai编程
用户8307196840823 小时前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq