对话与 Tool 模块
约 1053 字大约 4 分钟
布欧-Lewyon
2026-05-15
首页 › Spring AI › 实战项目:AI 智能客服系统
对话模块是项目的核心,整合 RAG、Tool Calling 和多轮对话。
对话配置
@Configuration
public class AIConfig {
@Bean
public ChatClient csChatClient(
ChatClient.Builder builder,
ChatMemory chatMemory,
VectorStore vectorStore,
OrderQueryTool orderTool,
ProductQueryTool productTool,
UserAuthTool authTool) {
return builder
.defaultSystem("""
You are a professional customer service assistant for an e-commerce platform.
Core Capabilities:
1. Answer product questions based on knowledge base
2. Query order status and logistics information
3. Provide user account support
4. Handle common complaints and returns
Guidelines:
- Be polite and professional
- If you don't know the answer, admit it honestly
- When providing order info, include order number and timestamp
- For complex issues, offer to escalate to human agent
Always maintain context from previous messages in this conversation.
""")
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory, "sessionId", 20),
new QuestionAnswerAdvisor(
vectorStore,
SearchRequest.builder()
.topK(3)
.similarityThreshold(0.5)
.build()
)
)
.tools(orderTool, productTool, authTool)
.defaultOptions(ChatOptionsBuilder.builder()
.withTemperature(0.3)
.withMaxTokens(1000)
.build())
.build();
}
@Bean
public ChatMemory chatMemory() {
return new PersistentChatMemory();
}
}Tool:订单查询
@Component
public class OrderQueryTool {
private final JdbcTemplate jdbcTemplate;
@Tool(name = "query_order", description = "Query order status by order number or customer phone")
public String queryOrder(
@ToolParam(description = "Order number (ORD-xxx) or customer phone") String keyword) {
String sql = """
SELECT o.order_no, o.status, o.total_amount, o.created_at,
o.tracking_no, o.logistics_company,
o.estimated_delivery, o.consignee, o.consignee_phone,
o.shipping_address
FROM orders o
WHERE o.order_no = ? OR o.consignee_phone LIKE ?
LIMIT 1
""";
try {
Map<String, Object> order = jdbcTemplate.queryForMap(sql,
keyword, "%" + keyword + "%");
return """
订单号:%s
状态:%s
金额:¥%.2f
下单时间:%s
收件人:%s
收货地址:%s
物流公司:%s
物流单号:%s
预计送达:%s
""".formatted(
order.get("order_no"),
getStatusText((String) order.get("status")),
order.get("total_amount"),
order.get("created_at"),
order.get("consignee"),
order.get("shipping_address"),
order.get("logistics_company"),
order.get("tracking_no"),
order.get("estimated_delivery"));
} catch (EmptyResultDataAccessException e) {
return "未找到订单信息,请检查订单号或联系电话是否正确。";
}
}
private String getStatusText(String status) {
return switch (status) {
case "PENDING" -> "待付款";
case "PAID" -> "已付款";
case "SHIPPED" -> "已发货";
case "DELIVERED" -> "已签收";
case "RETURNING" -> "退货中";
case "COMPLETED" -> "已完成";
default -> status;
};
}
}Tool:产品查询
@Component
public class ProductQueryTool {
private final JdbcTemplate jdbcTemplate;
@Tool(name = "query_product", description = "Query product detail by product name or SKU")
public String queryProduct(
@ToolParam(description = "Product name or SKU code") String keyword) {
String sql = """
SELECT p.name, p.sku, p.price, p.stock, p.description,
p.category, p.brand, p.specifications
FROM products p
WHERE p.name LIKE ? OR p.sku = ?
LIMIT 3
""";
List<Map<String, Object>> products = jdbcTemplate.queryForList(sql,
"%" + keyword + "%", keyword);
if (products.isEmpty()) {
return "未找到相关商品";
}
StringBuilder sb = new StringBuilder();
for (Map<String, Object> p : products) {
sb.append("""
商品:%s
SKU:%s
价格:¥%.2f
库存:%d
分类:%s
简介:%s
-------------------
""".formatted(
p.get("name"), p.get("sku"),
p.get("price"), p.get("stock"),
p.get("category"), p.get("description")));
}
return sb.toString();
}
}Tool:用户身份验证
@Component
public class UserAuthTool {
@Tool(name = "verify_user", description = "Verify user identity by name and phone")
public String verifyUser(
@ToolParam(description = "User's full name") String name,
@ToolParam(description = "User's registered phone number") String phone) {
// 实际项目中查询用户表
if ("张三".equals(name) && "13800138000".equals(phone)) {
return """
验证成功!
用户:张三
会员等级:金牌会员
注册时间:2024-01-15
累计消费:¥12,580.00
""";
}
return "验证失败,请核对姓名和手机号。如果您是新用户,请先注册。";
}
}对话服务
@Service
public class ChatService {
private final ChatClient chatClient;
private final ChatMessageRepository messageRepository;
@GetMapping("/api/chat/send")
public ChatResponse sendMessage(
@RequestParam String sessionId,
@RequestParam String message) {
// 1. 保存用户消息
saveMessage(sessionId, "user", message);
// 2. 调用 AI(Advisor 自动处理记忆 + RAG + Tool)
String aiResponse = chatClient.call(
new Prompt(new UserMessage(message),
ChatOptionsBuilder.builder()
.withTemperature(0.3)
.build())
);
// 3. 保存 AI 回复
saveMessage(sessionId, "assistant", aiResponse);
return new ChatResponse(sessionId, aiResponse);
}
// 流式版本
@GetMapping(value = "/api/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamMessage(
@RequestParam String sessionId,
@RequestParam String message) {
SseEmitter emitter = new SseEmitter(300_000L);
saveMessage(sessionId, "user", message);
StringBuilder fullResponse = new StringBuilder();
// 流式输出
// 需要获取 StreamingChatClient
streamingChatClient.stream(new Prompt(new UserMessage(message)))
.subscribe(
content -> {
fullResponse.append(content);
try {
emitter.send(SseEmitter.event()
.data(content)
.name("message"));
} catch (IOException e) {
emitter.completeWithError(e);
}
},
emitter::completeWithError,
() -> {
saveMessage(sessionId, "assistant", fullResponse.toString());
emitter.complete();
}
);
return emitter;
}
// 历史记录
@GetMapping("/api/chat/history")
public List<ChatMessage> getHistory(@RequestParam String sessionId) {
return messageRepository.findBySessionIdOrderByCreatedAtAsc(sessionId);
}
private void saveMessage(String sessionId, String role, String content) {
ChatMessage msg = new ChatMessage();
msg.setSessionId(sessionId);
msg.setRole(role);
msg.setContent(content);
msg.setCreatedAt(LocalDateTime.now());
messageRepository.save(msg);
}
}
public record ChatResponse(String sessionId, String content) {}对话消息实体
@Entity
@Table(name = "chat_messages")
public class ChatMessage {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String sessionId;
@Column(nullable = false)
private String role; // user / assistant / system
@Column(columnDefinition = "TEXT", nullable = false)
private String content;
@Column(nullable = false)
private LocalDateTime createdAt;
}管理后台 API
@RestController
@RequestMapping("/api/admin")
public class AdminController {
private final ChatMessageRepository messageRepository;
// Token 统计
@GetMapping("/stats/tokens")
public Map<String, Object> tokenStats(
@RequestParam(defaultValue = "7") int days) {
LocalDateTime since = LocalDateTime.now().minusDays(days);
return Map.of(
"totalMessages", messageRepository.countByCreatedAtAfter(since),
"totalSessions", messageRepository.countDistinctSessions(since)
);
}
// 对话记录
@GetMapping("/sessions")
public List<String> getSessions() {
return messageRepository.findDistinctSessions();
}
}小结
- ChatClient 集成 MessageChatMemoryAdvisor(多轮对话)、QuestionAnswerAdvisor(RAG)、tools(Tool Calling)。
- 订单查询 Tool 从 MySQL 查数据并格式化返回。
- 对话记录持久化到 MySQL,支持历史查询。
- 流式输出 + 消息保存,纯文本和流式两种 API。
- 管理后台 API 提供 Token 统计和对话记录查看。
