如何基于spring alibaba AI 开发一个智能客服(包含:知识库、内部系统的dubbo调用)
把大模型的智能对话、本地知识库(RAG)的精准召回,和内部Dubbo服务的业务能力结合起来,可以构建出真正能解决实际问题的智能系统。
针对这个需求,最理想的模式是采用智能体(Agent)架构。让AI模型充当“大脑”,自主决定何时调用本地知识库查询资料,何时通过Dubbo接口执行业务操作。
基于Spring AI Alibaba,我为你梳理了一个完整的智能客服升级场景,并提供了可直接运行的代码。
典型业务场景:智能售后客服
-
目标:用户通过自然语言咨询售后问题,机器人需要既能从知识库中查找“退货流程”,又能调用内部Dubbo服务查询“具体订单状态”。
-
核心流程:
-
用户输入:“我想退货,帮我查一下订单号20240101BT的物流状态。”
-
智能体(Controller):分析用户意图,识别出需要两个工具:
-
RAG工具:从《售后服务手册》中检索“退货流程”。
-
Dubbo服务工具:调用内部订单系统的
OrderService,查询指定订单的物流状态。
-
-
并行调用与生成:智能体同时或依次调用这两个工具,获取结果后,由大模型整合成一段流畅、信息完整的回答。
-
下面,我将基于Spring AI Alibaba,通过“函数调用(Function Calling)” 机制,完整实现这个场景。我们会创建一个CustomerServiceAgent,它将具备两种工具:一个用于检索本地知识库,一个用于调用Dubbo服务。
完整代码实现
1. Maven依赖配置 (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-ai-agent-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<spring-ai-alibaba.version>1.0.0-M5.1</spring-ai-alibaba.version>
<dubbo.version>3.2.0</dubbo.version> <!-- 确保Dubbo版本与Spring Boot 3.x兼容 -->
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI Alibaba Starter (核心) -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<!-- 用于RAG的向量存储和文档处理 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-markdown-document-reader</artifactId>
<version>1.0.0-M6</version>
</dependency>
<!-- 使用内存向量库,方便演示 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-simple-vector-store</artifactId>
<version>1.0.0-M6</version>
</dependency>
<!-- Apache Dubbo (用于调用内部服务) -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!-- 如果Dubbo使用Zookeeper作为注册中心 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
2. 应用配置 (application.yml)
spring:
application:
name: spring-ai-agent
ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY:your-api-key}
chat:
options:
model: qwen-plus # 推荐使用支持函数调用的模型
vectorstore:
memory:
# 内存向量库配置,生产环境请替换为PGVector等持久化方案
initialize-schema: true
# Dubbo 配置(假设你有一个要调用的外部订单服务)
dubbo:
application:
name: spring-ai-agent-consumer
registry:
address: zookeeper://127.0.0.1:2181 # 替换为你的注册中心地址
protocol:
name: dubbo
port: -1
consumer:
check: false # 启动时不检查依赖的服务是否可用
# 日志
logging:
level:
com.example: debug
3. 定义Dubbo服务接口 (模拟外部系统)
首先,在公共模块或当前项目中定义要调用的Dubbo接口。
package com.example.api;
public interface OrderDubboService {
/**
* 根据订单号查询物流状态
* @param orderId 订单号
* @return 物流状态信息
*/
String getLogisticsInfo(String orderId);
}
4. 配置RAG知识库 (RagConfig.java)
和上一轮类似,配置一个从本地文档加载的向量存储。
java
package com.example.config;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class RagConfig {
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel,
ResourcePatternResolver resolver) throws IOException {
SimpleVectorStore vectorStore = new SimpleVectorStore(embeddingModel);
// 加载知识库文档 (放在 resources/docs/ 下)
Resource[] resources = resolver.getResources("classpath:docs/*.md");
List<Document> documents = new ArrayList<>();
for (Resource resource : resources) {
MarkdownDocumentReader reader = new MarkdownDocumentReader(resource);
documents.addAll(reader.read());
System.out.println("加载知识库文档: " + resource.getFilename());
}
if (!documents.isEmpty()) {
vectorStore.add(documents);
System.out.println("知识库向量化完成,共 " + documents.size() + " 个片段");
}
return vectorStore;
}
}
5. 定义智能体的“工具” (Tools)
这是最核心的部分。我们将创建一个ServiceTools类,其中定义了两个方法,分别标注为RAG检索和Dubbo调用的工具。
package com.example.service;
import com.example.api.OrderDubboService; // 引入Dubbo接口
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
import java.util.stream.Collectors;
@Slf4j
@Service
public class ServiceTools {
private final VectorStore vectorStore;
// 通过Dubbo远程注入外部订单服务
@DubboReference
private OrderDubboService orderDubboService;
public ServiceTools(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
/**
* 工具1: 从本地知识库(售后手册)中检索信息
* @param query 用户的查询问题
* @return 检索到的相关知识片段
*/
@Tool(name = "retrieveFromKnowledgeBase", description = "从售后服务知识库中检索相关信息,如退货流程、保修政策等")
public String retrieveFromKnowledgeBase(
@ToolParam(description = "用户关于售后政策的查询内容") String query) {
log.info("调用RAG工具,查询: {}", query);
// 从向量库中搜索最相关的3个文档片段
var results = vectorStore.similaritySearch(
SearchRequest.query(query).withTopK(3)
);
String context = results.stream()
.map(doc -> doc.getContent())
.collect(Collectors.joining("\n---\n"));
log.info("RAG检索到 {} 个片段", results.size());
return context.isEmpty() ? "未找到相关信息" : context;
}
/**
* 工具2: 调用内部Dubbo服务查询订单物流
* @param orderId 订单号
* @return 物流状态信息
*/
@Tool(name = "queryOrderLogistics", description = "通过内部订单系统查询指定订单号的物流状态")
public String queryOrderLogistics(
@ToolParam(description = "要查询的订单号,例如 OD20240101BT") String orderId) {
log.info("调用Dubbo工具,查询订单: {}", orderId);
try {
// 实际调用远程Dubbo服务
String result = orderDubboService.getLogisticsInfo(orderId);
log.info("Dubbo调用成功,返回: {}", result);
return result;
} catch (Exception e) {
log.error("Dubbo调用失败", e);
return "查询订单物流信息失败,请稍后重试或联系人工客服。错误:" + e.getMessage();
}
}
}
6. 创建智能体控制器 (AgentController.java)
这个控制器负责接收用户请求,并驱动智能体运行。我们将利用Spring AI Alibaba的ChatClient和工具机制。
package com.example.controller;
import com.example.service.ServiceTools;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@Slf4j
@RestController
@RequestMapping("/api/agent")
@RequiredArgsConstructor
public class AgentController {
private final ChatClient.Builder chatClientBuilder;
private final ServiceTools serviceTools; // 注入我们的工具
// 使用内存存储会话记忆
private final InMemoryChatMemory chatMemory = new InMemoryChatMemory();
@GetMapping(value = "/chat", produces = "text/event-stream;charset=UTF-8")
public Flux<String> agentChat(
@RequestParam String message,
@RequestParam(defaultValue = "default") String sessionId) {
log.info("智能体请求 - sessionId: {}, message: {}", sessionId, message);
// 构建一个专用于本次请求的ChatClient,并绑定工具
ChatClient chatClient = chatClientBuilder
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) // 开启记忆
.defaultTools(serviceTools) // 关键:将我们的ServiceTools注册为智能体可用的工具
.build();
return chatClient.prompt()
.user(message)
.advisors(a -> a.param(
MessageChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId
))
.stream()
.content()
.doOnComplete(() -> log.info("智能体响应完成"));
}
}
🧠 工作原理
-
用户输入:用户发送“我想退货,帮我查一下订单号OD20240101BT的物流状态”。
-
智能体分析:
ChatClient将用户问题和已注册的工具(ServiceTools中的两个方法)的描述一起发送给大模型。 -
模型决策:大模型理解到,回答这个问题需要“退货流程”和“订单物流”两类信息。它会决定调用两个工具,并解析出调用参数:
-
调用
retrieveFromKnowledgeBase,参数为“退货流程”。 -
调用
queryOrderLogistics,参数为“OD20240101BT”。
-
-
执行工具:Spring AI Alibaba框架根据模型的指令,在本地执行
ServiceTools中对应的Java方法。-
retrieveFromKnowledgeBase方法从向量库中检索出退货相关的文档片段。 -
queryOrderLogistics方法通过@DubboReference注入的代理,远程调用订单服务的Dubbo接口,获取物流信息。
-
-
整合与回复:两个工具的结果被返回给大模型。大模型根据这些信息,生成一段通顺、完整的回答,例如:“您好,关于退货,根据我们的售后手册,您需要... 另外,您查询的订单OD20240101BT当前物流状态为:已发货,预计明天送达。”
扩展思考
-
并行调用:上述流程中,模型可能会选择先调用一个工具,拿到结果后再决定是否调用下一个。通过提示词工程,可以引导模型同时调用多个工具,进一步提高效率。
-
异步与超时:对于耗时的Dubbo调用,需要考虑设置超时时间和异步处理模式,避免阻塞AI响应。
-
服务降级:当Dubbo服务不可用时,可以让模型告知用户“订单系统暂时繁忙”,并引导用户进行其他操作,结合Sentinel可以实现更精细的流量控制和降级。
这个架构将大模型的智能决策能力、知识库的静态知识、以及后端微服务的动态业务能力无缝地结合在了一起,构成了一个真正意义上的“智能体”。希望这份完整的代码和思路能对你的项目有所帮助。如果有任何具体问题,或者想深入了解某个环节,随时可以再问我。
大模型是如何来识别什么调用知识库和dubbo接口的呢?
这恰恰是智能体(Agent)最核心的机制——函数调用(Function Calling)。让我详细解释大模型是如何"知道"该调用哪个函数、以及怎么提取参数的。
🧠 核心机制:函数调用(Function Calling)
1. 幕后发生了什么?
当你注册工具后,Spring AI Alibaba会在幕后做三件事:
- 将你的Java方法转换成JSON Schema描述
- 把这个描述发给大模型
- 解析模型的返回,执行对应方法
让我们看看实际传输的数据:
发送给大模型的"工具描述"
{
"tools": [
{
"type": "function",
"function": {
"name": "queryOrderLogistics",
"description": "通过内部订单系统查询指定订单号的物流状态",
"parameters": {
"type": "object",
"properties": {
"orderId": {
"type": "string",
"description": "要查询的订单号,例如 OD20240101BT"
}
},
"required": ["orderId"]
}
}
},
{
"type": "function",
"function": {
"name": "retrieveFromKnowledgeBase",
"description": "从售后服务知识库中检索相关信息,如退货流程、保修政策等",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "用户关于售后政策的查询内容"
}
},
"required": ["query"]
}
}
}
]
}
2. 大模型如何"思考"和"决策"
当用户说:"我想退货,帮我查一下订单号OD20240101BT的物流状态",大模型内部的处理逻辑是:
用户输入:我想退货,帮我查一下订单号OD20240101BT的物流状态
模型分析步骤:
1. 理解意图:用户需要"退货流程"(知识库) + "订单物流"(业务系统)
2. 匹配工具:
- "退货流程" → 匹配到 retrieveFromKnowledgeBase(描述中包含"退货流程")
- "订单OD20240101BT" → 匹配到 queryOrderLogistics(描述中包含"订单号")
3. 提取参数:
- 从"订单号OD20240101BT"中提取订单号
4. 决定调用顺序(取决于模型策略)
模型返回的响应(不是最终答案,而是"工具调用请求"):
{
"tool_calls": [
{
"id": "call_1",
"type": "function",
"function": {
"name": "retrieveFromKnowledgeBase",
"arguments": "{\"query\":\"退货流程\"}"
}
},
{
"id": "call_2",
"type": "function",
"function": {
"name": "queryOrderLogistics",
"arguments": "{\"orderId\":\"OD20240101BT\"}"
}
}
]
}
3. 框架如何处理这个响应
// Spring AI Alibaba 内部的处理流程(伪代码)
public Flux<String> processWithTools(UserMessage userMessage, List<Tool> tools) {
// 1. 发送给模型的提示词包含:用户问题 + 工具描述
EnhancedPrompt prompt = buildPrompt(userMessage, tools);
// 2. 模型返回可能包含 tool_calls
ModelResponse response = model.generate(prompt);
if (response.hasToolCalls()) {
// 3. 遍历所有要调用的工具
for (ToolCall toolCall : response.getToolCalls()) {
// 4. 通过反射找到对应的Java方法
Method method = findMethod(toolCall.getName());
// 5. 解析参数(JSON转Java对象)
Object[] args = parseArguments(
toolCall.getArguments(),
method.getParameterTypes()
);
// 6. 执行方法(这里会调用你的Dubbo服务)
Object result = method.invoke(serviceTools, args);
// 7. 将结果返回给模型继续生成
return continueGeneration(userMessage, toolResults);
}
}
// 4. 如果没有工具调用,直接返回模型答案
return response.getContent();
}
4. 为什么模型能正确识别?
关键在于工具的描述质量。我在ServiceTools中写的注解起到了决定性作用:
@Tool(name = "queryOrderLogistics",
description = "通过内部订单系统查询指定订单号的物流状态")
// ↑ 这个描述非常关键!包含了"订单号"、"物流状态"等关键词
public String queryOrderLogistics(
@ToolParam(description = "要查询的订单号,例如 OD20240101BT")
// ↑ 参数的描述也很重要,包含了格式示例
String orderId) {
// ...
}
5. 实际案例对比
看看不同的描述会导致什么结果:
✅ 好的描述(能准确触发)
@Tool(description = "查询指定订单号的物流状态,参数orderId是13位订单号")
public String queryOrderLogistics(String orderId)
用户说"查一下OD123的物流" → ✅ 触发
❌ 差的描述(无法触发)
@Tool(description = "工具方法2")
public String func2(String id)
用户说"查物流" → ❌ 不触发,模型不知道这个工具是干嘛的
6. 调试技巧:查看实际交互
如果你想亲眼看到这个过程,可以开启debug日志:
logging:
level:
com.alibaba.cloud.ai: debug
org.springframework.ai: debug
然后在日志中你会看到类似这样的内容:
DEBUG --- Sending request to model with tools: [queryOrderLogistics, retrieveFromKnowledgeBase]
DEBUG --- Model response: {
"choices": [{
"message": {
"content": null,
"tool_calls": [{
"function": {
"name": "queryOrderLogistics",
"arguments": "{\"orderId\":\"OD20240101BT\"}"
}
}]
}
}]
}
DEBUG --- Executing tool: queryOrderLogistics with args: {orderId=OD20240101BT}
DEBUG --- Tool result: "已发货,预计明天送达"
7. 如何提高识别准确率
- 优化工具描述:
java
@Tool(description = "查询订单物流 - 当用户询问'物流'、'快递'、'发货'、'运单'时调用,需要从用户消息中提取13位订单号")
- 提供参数示例:
java
@ToolParam(description = "订单号,格式:OD+13位数字,例如'OD2024010100123'")
- 设置合适的模型:
spring.ai.dashscope.chat.options.model: qwen-max # 更强的函数调用能力
- 添加系统提示:
chatClient.prompt()
.system("你是一个智能客服助手,当用户询问订单相关问题时,务必调用queryOrderLogistics工具查询")
.user(message)
// ...
8. 复杂情况处理
多个相似工具时
如果用户说"查一下我的订单",但没提供订单号,模型可能会:
- 先调用另一个工具获取用户信息
- 或者要求用户提供订单号
参数提取失败时
@Tool(description = "查询订单")
public String queryOrder(String orderId, String userId) {
if (orderId == null || userId == null) {
return "需要订单号和用户ID才能查询";
}
// ...
}
这样,模型会理解有些参数缺失,可能会要求用户补充信息。
总结一下,大模型能识别调用哪个工具,本质上是一个意图识别 + 语义匹配 + 参数提取的过程。你提供的工具描述越精准,模型就越能准确地在合适的时机调用正确的工具。
更多推荐


所有评论(0)