把大模型的智能对话、本地知识库(RAG)的精准召回,和内部Dubbo服务的业务能力结合起来,可以构建出真正能解决实际问题的智能系统。

针对这个需求,最理想的模式是采用智能体(Agent)架构。让AI模型充当“大脑”,自主决定何时调用本地知识库查询资料,何时通过Dubbo接口执行业务操作。

基于Spring AI Alibaba,我为你梳理了一个完整的智能客服升级场景,并提供了可直接运行的代码。

典型业务场景:智能售后客服

  • 目标:用户通过自然语言咨询售后问题,机器人需要既能从知识库中查找“退货流程”,又能调用内部Dubbo服务查询“具体订单状态”。

  • 核心流程

    1. 用户输入:“我想退货,帮我查一下订单号20240101BT的物流状态。”

    2. 智能体(Controller):分析用户意图,识别出需要两个工具:

      • RAG工具:从《售后服务手册》中检索“退货流程”。

      • Dubbo服务工具:调用内部订单系统的OrderService,查询指定订单的物流状态。

    3. 并行调用与生成:智能体同时或依次调用这两个工具,获取结果后,由大模型整合成一段流畅、信息完整的回答。

下面,我将基于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("智能体响应完成"));
    }
}

🧠 工作原理

  1. 用户输入:用户发送“我想退货,帮我查一下订单号OD20240101BT的物流状态”。

  2. 智能体分析ChatClient将用户问题和已注册的工具(ServiceTools中的两个方法)的描述一起发送给大模型。

  3. 模型决策:大模型理解到,回答这个问题需要“退货流程”和“订单物流”两类信息。它会决定调用两个工具,并解析出调用参数:

    • 调用 retrieveFromKnowledgeBase,参数为“退货流程”。

    • 调用 queryOrderLogistics,参数为“OD20240101BT”。

  4. 执行工具:Spring AI Alibaba框架根据模型的指令,在本地执行ServiceTools中对应的Java方法。

    • retrieveFromKnowledgeBase 方法从向量库中检索出退货相关的文档片段。

    • queryOrderLogistics 方法通过@DubboReference注入的代理,远程调用订单服务的Dubbo接口,获取物流信息。

  5. 整合与回复:两个工具的结果被返回给大模型。大模型根据这些信息,生成一段通顺、完整的回答,例如:“您好,关于退货,根据我们的售后手册,您需要... 另外,您查询的订单OD20240101BT当前物流状态为:已发货,预计明天送达。”

扩展思考

  • 并行调用:上述流程中,模型可能会选择先调用一个工具,拿到结果后再决定是否调用下一个。通过提示词工程,可以引导模型同时调用多个工具,进一步提高效率。

  • 异步与超时:对于耗时的Dubbo调用,需要考虑设置超时时间和异步处理模式,避免阻塞AI响应。

  • 服务降级:当Dubbo服务不可用时,可以让模型告知用户“订单系统暂时繁忙”,并引导用户进行其他操作,结合Sentinel可以实现更精细的流量控制和降级。

这个架构将大模型的智能决策能力、知识库的静态知识、以及后端微服务的动态业务能力无缝地结合在了一起,构成了一个真正意义上的“智能体”。希望这份完整的代码和思路能对你的项目有所帮助。如果有任何具体问题,或者想深入了解某个环节,随时可以再问我。

大模型是如何来识别什么调用知识库和dubbo接口的呢?

这恰恰是智能体(Agent)最核心的机制——函数调用(Function Calling)。让我详细解释大模型是如何"知道"该调用哪个函数、以及怎么提取参数的。

🧠 核心机制:函数调用(Function Calling)

1. 幕后发生了什么?

当你注册工具后,Spring AI Alibaba会在幕后做三件事:

  1. 将你的Java方法转换成JSON Schema描述
  2. 把这个描述发给大模型
  3. 解析模型的返回,执行对应方法

让我们看看实际传输的数据:

发送给大模型的"工具描述"
{
  "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. 如何提高识别准确率

  1. 优化工具描述

java

@Tool(description = "查询订单物流 - 当用户询问'物流'、'快递'、'发货'、'运单'时调用,需要从用户消息中提取13位订单号")
  1. 提供参数示例

java

@ToolParam(description = "订单号,格式:OD+13位数字,例如'OD2024010100123'")
  1. 设置合适的模型

spring.ai.dashscope.chat.options.model: qwen-max # 更强的函数调用能力

  1. 添加系统提示
chatClient.prompt()
.system("你是一个智能客服助手,当用户询问订单相关问题时,务必调用queryOrderLogistics工具查询")
.user(message)
// ...

8. 复杂情况处理

多个相似工具时

如果用户说"查一下我的订单",但没提供订单号,模型可能会:

  1. 先调用另一个工具获取用户信息
  2. 或者要求用户提供订单号
参数提取失败时
@Tool(description = "查询订单")
public String queryOrder(String orderId, String userId) {
  if (orderId == null || userId == null) {
  return "需要订单号和用户ID才能查询";
}
// ...
}

这样,模型会理解有些参数缺失,可能会要求用户补充信息。

总结一下,大模型能识别调用哪个工具,本质上是一个意图识别 + 语义匹配 + 参数提取的过程。你提供的工具描述越精准,模型就越能准确地在合适的时机调用正确的工具。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐