Java开发者的大模型入门:AgentScope Java组件全攻略(一)

一、开篇:为什么Java开发者需要AgentScope Java

1.1 大模型时代,Java开发者的新机遇

过去两年,大语言模型(LLM)技术飞速发展,从ChatGPT到各种专业领域模型,AI能力正以前所未有的速度重塑软件开发。作为企业级后端的中流砥柱,Java开发者自然需要思考:如何将AI能力无缝集成到现有的Java系统中?

然而,传统的AI集成方式往往面临以下挑战:

  • 调用复杂:直接调用大模型API需要手动构建HTTP请求、处理JSON、管理密钥,代码冗长且易出错。
  • 缺乏智能体抽象:简单的"请求-响应"模式无法满足多步推理、工具调用、多智能体协作等复杂需求。
  • 与Java生态割裂:Python生态有丰富的AI框架(如LangChain),但Java开发者需要一套能融入Spring生态的解决方案。

AgentScope Java的出现,正是为了解决这些问题。

1.2 AgentScope Java是什么?

AgentScope Java是阿里巴巴开源的智能体(Agent)开发框架,专为Java生态打造。它提供了一套完整的工具和抽象,让Java开发者能够:

  • 用最熟悉的Java代码构建智能体应用
  • 轻松集成通义千问、OpenAI等大模型
  • 让智能体调用外部工具(如查询天气、操作数据库)
  • 编排多智能体协作完成复杂任务
  • 在生产环境可靠部署,具备可观测性和沙箱安全

简单来说,AgentScope Java = Java + 大模型 + 智能体 + 工具,让Java开发者像写普通业务代码一样开发AI应用。

1.3 原始调用方式 vs AgentScope Java:一个直观对比

让我们先看一段最原始的Java代码,它使用HttpClient调用大模型API:

java 复制代码
// 原始方式:直接调用大模型API
HttpClient client = HttpClient.newHttpClient();
String requestBody = """
    {
        "model": "qwen-plus",
        "messages": [{"role": "user", "content": "你好"}]
    }
    """;

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"))
    .header("Authorization", "Bearer " + apiKey)
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(requestBody))
    .build();

try {
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    // 手动解析JSON
    JSONObject json = new JSONObject(response.body());
    String answer = json.getJSONObject("output").getString("text");
    System.out.println(answer);
} catch (Exception e) {
    e.printStackTrace();
}

这段代码的问题显而易见:

  • 硬编码API地址和密钥
  • JSON构造和解析繁琐
  • 没有错误处理、重试机制
  • 难以扩展(如增加多轮对话、工具调用)

现在,看看AgentScope Java如何简化这一切:

java 复制代码
// 1. 配置LLM(以通义千问为例)
ChatClient llm = new DashScopeChatClient.Builder()
        .apiKey("your-api-key")
        .model("qwen-plus")
        .build();

// 2. 创建一个简单的智能体
BaseAgent agent = new BaseAgent("assistant", llm);

// 3. 运行智能体
String response = agent.run("你好");
System.out.println(response);

短短几行代码,实现了相同功能,且无需处理任何HTTP细节。这还只是开始------AgentScope Java的真正威力在于智能体的自主决策、工具调用和多智能体协作

1.4 AgentScope Java的核心优势

优势 说明
Java原生 完美融入Spring Boot、Maven/Gradle生态,用熟悉的Java语法开发AI应用。
统一模型抽象 支持通义千问、OpenAI、Ollama、Azure等,切换模型只需修改一行配置。
智能体即代码 通过@Tool注解将普通Java方法变为智能体的"手",让AI调用你的业务逻辑。
多智能体协作 内置消息总线(MessageHub),轻松编排多个智能体协同工作。
生产级特性 提供可观测性(Studio)、安全沙箱(Runtime)、记忆持久化,满足企业需求。
开放架构 可扩展自定义Agent、记忆存储、工具集,灵活适应各种场景。

下图展示了AgentScope Java的核心组件及其关系:

graph TD subgraph 应用层 A[Agent接口] --> B[BaseAgent] A --> C[ReActAgent] A --> D[自定义Agent] end subgraph 模型层 E[ChatClient] --> F[DashScope] E --> G[OpenAI] E --> H[Ollama] end subgraph 工具层 I[Tool注解] --> J[工具方法] J --> K[沙箱执行] end subgraph 通信层 L[MessageHub] --> M[点对点] L --> N[广播] end subgraph 基础设施 O[Memory] --> P[ConversationMemory] O --> Q[ReMe持久化] R[Studio] --> S[可视化调试] end A --> E A --> I A --> L A --> O R --> A style A fill:#f9f,stroke:#333,stroke-width:2px style E fill:#bbf,stroke:#333 style I fill:#bfb,stroke:#333 style L fill:#fbb,stroke:#333

1.5 本文目标:零基础全组件实战

如果你是一名Java开发者,从未接触过大模型开发,不用担心。本文将带你从零开始,一步一步亲手实践AgentScope Java的所有核心组件。你将学到:

  • 基础篇:搭建第一个智能体、配置模型、处理对话
  • 工具篇:让智能体调用你的Java方法(函数调用)
  • 多智能体篇:构建多个智能体协作完成复杂任务
  • 记忆篇:让智能体记住对话历史和用户偏好
  • ReAct篇:实现思考-行动-观察循环,应对复杂问题
  • 工作流篇:编排结构化多Agent流程
  • 部署篇:将智能体发布为服务,安全运行
  • 监控篇:使用Studio可视化调试和评估
  • 高级篇:多模态、长期记忆(ReMe)等前沿特性

二、环境准备:5分钟搭建第一个AgentScope Java应用

在开始动手之前,我们先确保你的开发环境就绪。本章将带领你完成从零到第一个可运行智能体应用的全过程,全程只需5分钟。

2.1 开发环境要求

  • JDK 17 或更高版本(AgentScope Java 基于 Spring Boot 3.x,要求 JDK 17+)
  • Maven (3.6+)或 Gradle(7.x+)------ 任选一种你熟悉的构建工具
  • IDE:IntelliJ IDEA、Eclipse 或 VS Code(Java插件)
  • 网络连接:能够访问阿里云DashScope API(国内网络一般没问题)

如果你还没有安装 JDK 或 Maven/Gradle,请先自行安装。这里不再赘述。

2.2 在项目中引入 AgentScope Java 依赖

AgentScope Java 提供了 Spring Boot Starter,可以轻松集成到你的 Spring Boot 应用中。我们将创建一个标准的 Spring Boot 项目,并添加必要的依赖。

2.2.1 使用 Spring Initializr 快速创建项目(推荐)

  1. 访问 start.spring.io/
  2. 选择以下选项:
    • Project:Maven 或 Gradle(以 Maven 为例)
    • Language:Java
    • Spring Boot:选择 3.2.x 或更高版本(3.1+ 也可以,但建议 3.2)
    • Groupcom.example
    • Artifactagentscope-demo
    • Dependencies :添加 Spring Web(因为我们后面可能需要提供 REST 接口)
  3. 点击 Generate 下载项目压缩包,解压后导入 IDE。

2.2.2 手动添加 AgentScope Java 依赖

由于 AgentScope Java 尚未发布到 Maven Central,需要添加阿里云仓库和官方仓库。在 pom.xml 中添加以下内容:

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>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>agentscope-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>17</java.version>
        <agentscope.version>0.1.0</agentscope.version> <!-- 使用最新版本,可从官网获取 -->
    </properties>

    <dependencies>
        <!-- Spring Boot Web Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- AgentScope Java Starter -->
        <dependency>
            <groupId>com.alibaba.agentscope</groupId>
            <artifactId>agentscope-spring-boot-starter</artifactId>
            <version>${agentscope.version}</version>
        </dependency>
    </dependencies>

    <!-- 添加仓库:阿里云仓库和 AgentScope 官方仓库 -->
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>agentscope</id>
            <name>AgentScope Repository</name>
            <url>https://agentscope.io/maven/releases</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.2.3 Gradle 配置(可选)

如果你使用 Gradle,在 build.gradle 中添加:

groovy 复制代码
plugins {
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'java'
}

group = 'com.example'
version = '1.0-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
    maven { url 'https://maven.aliyun.com/repository/public' }
    maven { url 'https://agentscope.io/maven/releases' }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'com.alibaba.agentscope:agentscope-spring-boot-starter:0.1.0'
}

2.3 获取大模型 API 密钥

AgentScope Java 需要调用大模型 API 来驱动智能体。本教程以阿里云通义千问(DashScope)为例,你也可以使用 OpenAI、Ollama 等,只需更换依赖和配置。

2.3.1 开通阿里云 DashScope 服务

  1. 访问 阿里云百炼平台(需登录阿里云账号)。
  2. 如果首次使用,点击"立即开通",同意服务协议。
  3. 开通后,在控制台左侧导航栏选择 API-KEY 管理

2.3.2 创建 API Key

  1. 点击"创建 API-KEY",输入名称(如"我的智能体应用")。
  2. 生成后,复制密钥字符串(格式如 sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)。
  3. 注意:请妥善保管 API Key,不要泄露。

2.3.3 配置 API Key

src/main/resources/application.yml 中配置:

yaml 复制代码
agentscope:
  llm:
    provider: dashscope          # 使用通义千问
    api-key: ${DASHSCOPE_API_KEY}  # 从环境变量读取,或直接写字符串(不推荐)
    model: qwen-plus             # 默认模型

为了安全,强烈建议使用环境变量

  • Linux/Mac :在 ~/.bashrc~/.zshrc 中添加 export DASHSCOPE_API_KEY=sk-xxx,然后执行 source ~/.bashrc
  • Windows :在系统环境变量中添加 DASHSCOPE_API_KEY=sk-xxx

如果你只是快速测试,可以临时在配置文件中写死,但切记不要提交到代码仓库。

2.3.4 可选:配置其他模型参数

yaml 复制代码
agentscope:
  llm:
    provider: dashscope
    api-key: ${DASHSCOPE_API_KEY}
    model: qwen-plus
    options:
      temperature: 0.8            # 温度,控制随机性
      max-tokens: 2048             # 最大生成 Token 数

2.4 编写第一个程序:Hello Agent!

现在我们来创建一个最简单的智能体,让它回答你的问题。

2.4.1 创建智能体类

com.example.demo 包下创建 HelloAgent.java

java 复制代码
package com.example.demo;

import com.alibaba.agentscope.agent.BaseAgent;
import com.alibaba.agentscope.llm.ChatClient;
import org.springframework.stereotype.Component;

@Component  // 将智能体声明为 Spring Bean,方便后续使用
public class HelloAgent extends BaseAgent {

    public HelloAgent(ChatClient chatClient) {
        // 调用父类构造器,传入智能体名称和 LLM 客户端
        super("hello_agent", chatClient);
    }

    // 可选:重写 getSystemPrompt 方法,为智能体设定角色
    @Override
    protected String getSystemPrompt() {
        return "你是一个友好的助手,用中文回答用户的问题。";
    }
}

代码解释

  • BaseAgent 是 AgentScope 提供的基类,我们通过继承它来创建自己的智能体。
  • 构造器接收 ChatClient(由自动配置创建),并传给父类。
  • getSystemPrompt() 方法可选,用于设定系统消息,告诉智能体它的角色。

2.4.2 创建 REST 接口调用智能体

创建 AgentController.java

java 复制代码
package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AgentController {

    private final HelloAgent helloAgent;

    public AgentController(HelloAgent helloAgent) {
        this.helloAgent = helloAgent;
    }

    @GetMapping("/chat")
    public String chat(@RequestParam(defaultValue = "你好") String message) {
        // 调用智能体的 run 方法,传入用户消息,获取回复
        return helloAgent.run(message);
    }
}

2.4.3 启动类

确保项目有标准的 Spring Boot 启动类(Initializr 会自动生成):

java 复制代码
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

2.4.4 运行测试

  1. 启动应用(运行 DemoApplicationmain 方法)。

  2. 打开浏览器或使用 curl 访问:http://localhost:8080/chat?message=你好

  3. 你将看到类似如下的响应:

    你好!我是你的智能助手,有什么可以帮你的吗?

完整流程示意图:

sequenceDiagram participant 浏览器 participant Controller participant HelloAgent participant ChatClient participant DashScope API 浏览器->>Controller: GET /chat?message=你好 Controller->>HelloAgent: run("你好") HelloAgent->>ChatClient: 调用大模型 ChatClient->>DashScope API: HTTP请求(包含消息) DashScope API-->>ChatClient: 返回AI生成的回复 ChatClient-->>HelloAgent: 返回文本 HelloAgent-->>Controller: 返回"你好,我是智能助手..." Controller-->>浏览器: 响应

2.4.5 可能遇到的问题及解决

问题 可能原因 解决方法
启动失败,ChatClient 未找到 依赖未正确引入,或自动配置未生效 检查 pom.xml 是否包含 agentscope-spring-boot-starter,并确保仓库配置正确
调用时报错 401 Unauthorized API Key 未配置或错误 检查 application.yml 中的 api-key 是否正确,或环境变量是否设置
响应超时或网络错误 无法访问 DashScope API 检查网络,或配置代理(如有需要)

2.5 本章小结

通过本章的学习,你成功搭建了第一个 AgentScope Java 应用,并实现了一个简单的智能体聊天接口。你学会了:

  • 使用 Spring Initializr 创建 Spring Boot 项目,并手动添加 AgentScope 依赖。
  • 在阿里云百炼平台获取 API Key,并配置到项目中。
  • 编写自定义智能体类,继承 BaseAgent 并设定系统消息。
  • 创建 REST 控制器,注入智能体并调用 run 方法。
  • 运行并测试第一个智能体接口。

三、核心概念:Agent、LLM与消息

在上一章中,我们创建了一个简单的智能体并让它回答了问题。但你可能会好奇:这个智能体内部是如何工作的?它如何理解我的问题?又如何与大模型交互?本章将深入AgentScope Java的三个核心抽象:Agent(智能体)LLM(大语言模型)Message(消息)。掌握了这些,你就能像搭积木一样构建各种复杂的智能体应用。

3.1 AgentScope Java的核心抽象

3.1.1 Agent接口:所有智能体的根接口

在AgentScope Java中,所有的智能体都实现了 Agent 接口。这个接口定义了智能体的基本行为:

java 复制代码
public interface Agent {
    /**
     * 运行智能体,处理输入并返回输出
     * @param input 输入字符串(通常是用户消息)
     * @return 智能体的回复
     */
    String run(String input);
    
    /**
     * 获取智能体的名称
     */
    String getName();
}

我们之前使用的 BaseAgent 就是这个接口的一个实现类,它封装了与大模型交互的通用逻辑。你可以通过继承 BaseAgent 快速创建自己的智能体,就像我们在第二章做的那样。

Agent的内部工作流程(简化版):

  1. 接收用户输入(run方法被调用)
  2. 将输入和系统提示词(如果有)组合成消息列表
  3. 调用大模型(LLM)获取回复
  4. 返回回复文本

3.1.2 LLM基座:ChatClient抽象

ChatClient 是AgentScope Java中所有大模型客户端的统一接口。它的作用是屏蔽不同模型提供商的API差异,让你可以用相同的方式调用通义千问、OpenAI、Ollama等模型。

java 复制代码
public interface ChatClient {
    /**
     * 发送消息列表给大模型,返回生成的回复
     * @param messages 消息列表(包含系统消息、用户消息、助手消息等)
     * @return 模型的回复文本
     */
    String chat(List<ChatMessage> messages);
    
    /**
     * 流式调用(可选)
     */
    // 其他方法...
}

AgentScope Java内置了多种实现:

  • DashScopeChatClient:调用阿里云通义千问
  • OpenAiChatClient:调用OpenAI(包括兼容接口的服务,如DeepSeek)
  • OllamaChatClient:调用本地Ollama模型
  • AzureOpenAiChatClient:调用Azure OpenAI

你可以通过配置文件或代码选择使用哪一个,而你的智能体代码完全不需要修改------这就是抽象的力量。

3.1.3 Message:智能体间通信的标准格式

在多智能体系统中,智能体之间需要交换信息。Message 类就是用来封装这些信息的标准格式。即使是单智能体,内部也是用消息列表与大模型交互的。

一个 Message 包含以下关键属性:

属性 类型 说明
role String 消息角色:system(系统)、user(用户)、assistant(助手)、tool(工具)
content String 消息内容
name String 可选,发送者的名称

例如,一个典型的对话历史可以表示为消息列表:

java 复制代码
List<ChatMessage> messages = Arrays.asList(
    new SystemMessage("你是一个友好的助手。"),
    new UserMessage("你好"),
    new AssistantMessage("你好,有什么可以帮你的?"),
    new UserMessage("今天天气怎么样?")
);

当智能体调用大模型时,它会将这个列表发送给 ChatClient,模型根据整个对话历史生成回复。

三种核心消息的类图:

classDiagram class ChatMessage { <<interface>> +String role +String content } class SystemMessage { +String content } class UserMessage { +String content } class AssistantMessage { +String content } ChatMessage <|-- SystemMessage ChatMessage <|-- UserMessage ChatMessage <|-- AssistantMessage

3.2 配置LLM客户端

在第二章中,我们通过 application.yml 配置了LLM客户端。AgentScope的自动配置会根据 agentscope.llm.provider 的值创建对应的 ChatClient Bean。让我们更详细地了解各种配置方式。

3.2.1 通过配置文件(推荐)

application.yml 中,你可以配置多种模型提供商:

通义千问(DashScope)

yaml 复制代码
agentscope:
  llm:
    provider: dashscope
    api-key: ${DASHSCOPE_API_KEY}
    model: qwen-plus
    options:
      temperature: 0.8
      max-tokens: 2048
      top-p: 0.9

OpenAI

yaml 复制代码
agentscope:
  llm:
    provider: openai
    api-key: ${OPENAI_API_KEY}
    model: gpt-3.5-turbo
    base-url: https://api.openai.com  # 可选,默认就是官方地址
    options:
      temperature: 0.7

Ollama(本地模型)

yaml 复制代码
agentscope:
  llm:
    provider: ollama
    base-url: http://localhost:11434
    model: llama3
    options:
      temperature: 0.8

3.2.2 在代码中手动创建LLM实例

如果你不想使用自动配置,也可以手动创建 ChatClient 实例。这在需要动态切换模型或进行单元测试时很有用。

java 复制代码
import com.alibaba.agentscope.llm.ChatClient;
import com.alibaba.agentscope.llm.dashscope.DashScopeChatClient;

// 创建通义千问客户端
ChatClient dashScopeClient = new DashScopeChatClient.Builder()
        .apiKey("your-api-key")
        .model("qwen-plus")
        .temperature(0.8)
        .maxTokens(2048)
        .build();

// 创建OpenAI客户端(需要引入对应依赖)
ChatClient openAiClient = new OpenAiChatClient.Builder()
        .apiKey("your-openai-key")
        .model("gpt-3.5-turbo")
        .build();

3.2.3 高级参数说明

参数 类型 说明 建议值
temperature Float 控制随机性,0-2之间,越低越确定 创意任务0.8-1.2,确定性任务0.2-0.5
maxTokens Integer 生成的最大Token数 根据场景调整,一般1024-2048
topP Float 核采样,0-1之间,通常与temperature协同使用 0.8-0.9
stop List 停止词,当模型生成到这些词时停止 可选

3.3 第一个自定义Agent:深入BaseAgent

在第二章中,我们简单地继承了 BaseAgent 并重写了 getSystemPrompt。现在,让我们更深入地了解 BaseAgent 的工作原理,并学习如何自定义它的行为。

3.3.1 BaseAgent的核心方法

BaseAgent 类提供了几个可以重写的方法,让你可以定制智能体的行为:

方法 作用 默认行为
getSystemPrompt() 返回系统提示词 返回空字符串
preprocess(String input) 预处理用户输入 直接返回输入
postprocess(String output) 后处理模型输出 直接返回输出
buildMessages(String input) 构建发送给模型的消息列表 组合系统提示和用户输入

最重要的方法是 run,它内部调用了上述方法:

java 复制代码
public String run(String input) {
    // 1. 预处理输入
    String processedInput = preprocess(input);
    
    // 2. 构建消息列表
    List<ChatMessage> messages = buildMessages(processedInput);
    
    // 3. 调用LLM
    String response = chatClient.chat(messages);
    
    // 4. 后处理输出
    return postprocess(response);
}

3.3.2 实现自定义Agent:记录对话历史

默认的 BaseAgent 是无状态的------它不记得之前的对话。下面我们创建一个能记住对话历史的智能体。这需要用到 Memory 的概念,我们将在第六章深入,这里先简单演示。

java 复制代码
package com.example.demo;

import com.alibaba.agentscope.agent.BaseAgent;
import com.alibaba.agentscope.llm.ChatClient;
import com.alibaba.agentscope.message.ChatMessage;
import com.alibaba.agentscope.message.UserMessage;
import com.alibaba.agentscope.message.AssistantMessage;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class MemoryAgent extends BaseAgent {

    private final List<ChatMessage> history = new ArrayList<>();

    public MemoryAgent(ChatClient chatClient) {
        super("memory_agent", chatClient);
    }

    @Override
    protected String getSystemPrompt() {
        return "你是一个有记忆的助手,会记住用户之前说过的话。";
    }

    @Override
    protected List<ChatMessage> buildMessages(String input) {
        // 将用户输入添加到历史记录
        history.add(new UserMessage(input));
        
        // 构建消息列表:系统消息 + 历史记录
        List<ChatMessage> messages = new ArrayList<>();
        messages.add(new SystemMessage(getSystemPrompt()));
        messages.addAll(history); // 包含之前的对话
        
        return messages;
    }

    @Override
    protected String postprocess(String output) {
        // 将AI回复也加入历史记录
        history.add(new AssistantMessage(output));
        return output;
    }
}

现在,当我们连续调用这个智能体时,它会记住之前的对话内容。

3.3.3 测试记忆Agent

创建一个新的控制器方法:

java 复制代码
@RestController
public class AgentController {
    
    @Autowired
    private MemoryAgent memoryAgent;
    
    @GetMapping("/memory-chat")
    public String memoryChat(@RequestParam String message) {
        return memoryAgent.run(message);
    }
}

测试两轮对话:

  1. 访问 /memory-chat?message=我叫小明
  2. 再访问 /memory-chat?message=我叫什么名字?
    • 如果智能体回答"你叫小明",说明记忆功能生效了。

3.4 动手实践:构建一个简单的问答助手

现在,让我们综合运用本章所学,构建一个更实用的问答助手。这个助手将:

  • 使用通义千问 qwen-plus 模型
  • 具备基本的角色设定(Java编程导师)
  • 能够记录对话历史(可选)

3.4.1 创建智能体类

java 复制代码
package com.example.demo;

import com.alibaba.agentscope.agent.BaseAgent;
import com.alibaba.agentscope.llm.ChatClient;
import org.springframework.stereotype.Component;

@Component
public class JavaTutorAgent extends BaseAgent {

    public JavaTutorAgent(ChatClient chatClient) {
        super("java_tutor", chatClient);
    }

    @Override
    protected String getSystemPrompt() {
        return "你是一位经验丰富的Java编程导师,用通俗易懂的语言解释Java概念,并给出代码示例。";
    }
}

3.4.2 创建控制器

java 复制代码
@RestController
@RequestMapping("/tutor")
public class TutorController {

    private final JavaTutorAgent tutorAgent;

    public TutorController(JavaTutorAgent tutorAgent) {
        this.tutorAgent = tutorAgent;
    }

    @GetMapping("/ask")
    public String ask(@RequestParam String question) {
        return tutorAgent.run(question);
    }
}

3.4.3 测试

启动应用,访问以下URL测试:

  • http://localhost:8080/tutor/ask?question=什么是多态?
  • http://localhost:8080/tutor/ask?question=请解释一下Java的泛型

你会看到智能体以导师的口吻回答,并附带代码示例。

3.4.4 完整调用流程图

sequenceDiagram participant 用户 participant TutorController participant JavaTutorAgent participant ChatClient participant DashScope API 用户->>TutorController: GET /ask?question=... TutorController->>JavaTutorAgent: run(question) JavaTutorAgent->>JavaTutorAgent: preprocess(question) JavaTutorAgent->>JavaTutorAgent: buildMessages(question) Note over JavaTutorAgent: 构建系统消息+用户消息 JavaTutorAgent->>ChatClient: chat(messages) ChatClient->>DashScope API: HTTP请求 DashScope API-->>ChatClient: 返回回复 ChatClient-->>JavaTutorAgent: 返回文本 JavaTutorAgent->>JavaTutorAgent: postprocess(回复) JavaTutorAgent-->>TutorController: 返回最终回复 TutorController-->>用户: 响应

3.5 本章小结

通过本章的学习,你掌握了AgentScope Java的三个核心抽象:

  • Agent :智能体的基本单元,通过继承 BaseAgent 可以快速创建自定义智能体。
  • LLM :通过 ChatClient 统一接口,轻松切换不同的模型提供商。
  • Message:标准化的消息格式,用于与LLM交互和多智能体通信。

你还学会了:

  • 通过配置文件或代码创建LLM客户端。
  • 重写 BaseAgent 的方法来自定义智能体行为。
  • 构建一个带简单记忆的智能体。
  • 实践了一个Java导师问答助手。

四、工具调用:让智能体拥有行动能力

在前面的章节中,我们的智能体只能"说话"------基于训练数据回答问题。但如果用户问"现在几点了?"、"帮我查一下订单状态"、"今天天气怎么样",纯文本模型是无法直接获取这些实时信息的------它没有时钟,也无法访问你的数据库。

工具调用(Tool Calling) 正是为了解决这个问题而生。它允许智能体在需要时调用你编写的 Java 方法,获取实时数据或执行操作,然后将结果整合到回答中。本章将带你掌握 AgentScope Java 的工具调用机制,让你的智能体真正"动手做事"。

4.1 什么是工具调用?智能体如何调用外部方法?

工具调用的核心思想是:你提供一组工具(Java 方法),并告诉智能体这些工具的存在、用途以及参数。当智能体认为需要某个工具来回答问题时,它会返回一个特殊的请求,要求执行该工具并提供参数。AgentScope 会自动执行对应方法,并将结果返回给智能体,智能体再根据结果生成最终回答。

工作流程示意图:

sequenceDiagram participant 用户 participant 智能体 participant 工具方法 用户->>智能体: 提问(例如"现在几点了?") 智能体->>智能体: 分析问题,决定需要调用工具 智能体->>工具方法: 执行 getCurrentTime() 工具方法-->>智能体: 返回 "14:30" 智能体->>智能体: 将结果整合到回答中 智能体-->>用户: 返回 "现在是下午2点30分。"

在整个流程中,除了定义工具方法外,你几乎不需要额外代码。AgentScope 会自动处理工具调用的握手过程。

4.2 在 AgentScope Java 中定义工具

AgentScope Java 通过 @Tool 注解来标记一个方法作为可被智能体调用的工具。你只需将工具类注册为 Spring Bean,框架会自动收集并提供给智能体。

4.2.1 引入依赖

工具调用功能已包含在 agentscope-spring-boot-starter 中,无需额外依赖。

4.2.2 定义第一个工具

创建一个 Spring 组件,其中的方法用 @Tool 注解标记。@Tool 注解需要指定工具的名称和描述,描述会被传递给模型,帮助模型理解何时调用该工具。

java 复制代码
import com.alibaba.agentscope.tool.annotation.Tool;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

@Component
public class TimeTools {

    @Tool(name = "getCurrentTime", description = "获取当前时间")
    public String getCurrentTime() {
        return LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
    }
}

说明

  • name:工具名称,建议使用驼峰命名,如 getCurrentTime
  • description:工具描述,非常重要!智能体会根据描述判断何时调用该工具。描述越清晰,调用准确率越高。

4.2.3 带参数的工具

很多工具需要参数。例如,查询天气需要城市名,查询订单需要订单号。工具方法可以定义参数,智能体在调用时会自动提取参数值。

java 复制代码
@Component
public class WeatherTools {

    @Tool(name = "getWeather", description = "查询指定城市的天气")
    public String getWeather(String city) {
        // 这里应该是真实的天气 API 调用,为了演示,我们返回模拟数据
        return switch (city) {
            case "北京" -> "晴,25℃";
            case "上海" -> "多云,28℃";
            case "广州" -> "雷阵雨,30℃";
            default -> city + "的天气数据暂未收录";
        };
    }
}

4.2.4 参数描述的重要性

为了让智能体更准确地填充参数,可以在 @Tool 注解中通过 params 属性提供更详细的参数描述。AgentScope Java 支持从方法参数名和 @Param 注解中提取说明。

java 复制代码
import com.alibaba.agentscope.tool.annotation.Param;

@Tool(name = "getWeather", description = "查询指定城市的天气")
public String getWeather(
        @Param(description = "城市名称,例如北京、上海") String city) {
    // ...
}

注意@Param 注解的 description 会被传递给模型,帮助模型理解参数的含义。

4.3 将工具绑定到智能体

在 AgentScope 中,有两种方式将工具绑定到智能体:自动绑定 (通过 Spring 容器)和手动绑定(在创建智能体时指定)。

4.3.1 自动绑定(推荐)

只要你的工具类是一个 Spring Bean(如 @Component@Service 等),并且你的智能体继承自 BaseAgent,AgentScope 的自动配置就会将这些工具收集起来,并在智能体需要时自动提供。你不需要在代码中显式绑定。

java 复制代码
@Component
public class MyAgent extends BaseAgent {

    public MyAgent(ChatClient chatClient) {
        super("my_agent", chatClient);
        // 不需要手动注册工具,框架会自动发现所有带 @Tool 的 Bean
    }

    @Override
    protected String getSystemPrompt() {
        return "你是一个助手,可以使用工具查询实时信息。";
    }
}

4.3.2 手动绑定

如果你希望某些工具只在特定智能体中使用,可以在创建智能体时通过 ToolManager 手动注册。这需要你自定义智能体的构造逻辑。

java 复制代码
@Component
public class SelectiveAgent extends BaseAgent {

    public SelectiveAgent(ChatClient chatClient, TimeTools timeTools) {
        super("selective_agent", chatClient);
        // 手动注册工具
        getToolManager().registerTool(timeTools);
    }
}

但通常自动绑定更简单,我们采用自动绑定即可。

4.4 实践:让智能体查询实时信息

现在让我们构建一个完整的示例,包含两个工具:获取时间和查询天气。我们将创建一个 REST 接口,用户输入问题,智能体自动决定是否调用工具。

4.4.1 定义工具类

java 复制代码
package com.example.demo.tool;

import com.alibaba.agentscope.tool.annotation.Param;
import com.alibaba.agentscope.tool.annotation.Tool;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;

@Component
public class TimeTool {
    @Tool(name = "getCurrentTime", description = "获取当前时间")
    public String getCurrentTime() {
        return LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
    }
}

@Component
public class WeatherTool {
    private static final Map<String, String> WEATHER_MAP = Map.of(
        "北京", "晴,25℃",
        "上海", "多云,28℃",
        "广州", "雷阵雨,30℃"
    );

    @Tool(name = "getWeather", description = "查询指定城市的天气")
    public String getWeather(
            @Param(description = "城市名称,如北京、上海") String city) {
        return WEATHER_MAP.getOrDefault(city, city + "的天气数据暂未收录");
    }
}

4.4.2 定义智能体

java 复制代码
package com.example.demo.agent;

import com.alibaba.agentscope.agent.BaseAgent;
import com.alibaba.agentscope.llm.ChatClient;
import org.springframework.stereotype.Component;

@Component
public class ToolAgent extends BaseAgent {

    public ToolAgent(ChatClient chatClient) {
        super("tool_agent", chatClient);
    }

    @Override
    protected String getSystemPrompt() {
        return "你是一个智能助手,可以使用工具查询实时信息。" +
               "当用户询问时间或天气时,请调用相应的工具。";
    }
}

4.4.3 创建 Controller

java 复制代码
package com.example.demo.controller;

import com.example.demo.agent.ToolAgent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ToolController {

    private final ToolAgent toolAgent;

    public ToolController(ToolAgent toolAgent) {
        this.toolAgent = toolAgent;
    }

    @GetMapping("/ask")
    public String ask(@RequestParam String question) {
        return toolAgent.run(question);
    }
}

4.4.4 测试

启动应用,用浏览器或 curl 测试:

bash 复制代码
# 时间查询
curl "http://localhost:8080/ask?question=现在几点了?"
# 预期返回类似:现在是下午2点30分45秒。

# 天气查询
curl "http://localhost:8080/ask?question=上海天气怎么样?"
# 预期返回:上海多云,28℃。

# 混合查询
curl "http://localhost:8080/ask?question=帮我查一下北京的天气,顺便告诉我现在几点了"
# 智能体会先后调用两个工具,然后综合回答。

# 无工具可用的问题
curl "http://localhost:8080/ask?question=计算123+456"  # 没有计算器工具,智能体会尝试自己计算或表示无法计算

4.4.5 观察日志

在控制台,你可能会看到类似以下的日志,表示智能体调用了工具:

css 复制代码
调用工具: getWeather, 参数: {"city":"上海"}
工具返回: 多云,28℃

4.5 工具调用的注意事项

4.5.1 工具方法应该是线程安全的

工具类通常是单例 Bean,因此方法需要是线程安全的。上面的例子中,getCurrentTime 是纯函数,安全;getWeather 也是只读操作,安全。如果工具修改了共享状态(如计数器),需要考虑同步。

4.5.2 工具方法的执行时间

工具方法执行时间不宜过长,因为整个调用是同步的(智能体在等待工具结果)。如果工具需要调用外部 API 或数据库,考虑设置超时,或采用异步方式(但 AgentScope 目前主要支持同步工具调用)。

4.5.3 错误处理

工具方法可能抛出异常。AgentScope 会捕获异常并将错误信息返回给智能体。智能体会根据错误信息决定如何回应(例如提示用户稍后重试)。你可以在工具方法内部处理异常,返回友好的错误消息。

java 复制代码
@Tool(name = "getWeather")
public String getWeather(String city) {
    try {
        // 调用外部 API
        return weatherApi.get(city);
    } catch (Exception e) {
        return "获取天气失败,请稍后重试。";
    }
}

4.5.4 工具数量

不要注册过多无关的工具,因为工具描述会消耗 Token,且可能让智能体混淆。只注册必要且描述清晰的工具。

4.5.5 参数类型

工具方法的参数支持基本类型、String、复杂对象等,但智能体需要能够生成对应的 JSON。建议使用简单类型,并配合清晰描述。对于复杂对象,需要确保模型能理解其结构。

4.5.6 通义千问的特殊性

通义千问模型对工具调用的支持与 OpenAI 类似,但细节可能略有不同。AgentScope Java 已经做了适配,开发者无需关心底层差异。

4.6 工具调用流程详解

为了更深入理解,我们来看一下工具调用在 AgentScope Java 中的详细流程:

  1. 用户请求 :用户发送问题 /ask?question=上海天气怎么样?
  2. 构建请求 :智能体将用户消息和可用工具列表(由 ToolManager 提供)一起封装成请求,发送给大模型。
  3. 模型决策 :模型判断需要调用 getWeather 工具,并生成参数 {"city": "上海"}
  4. 工具执行 :AgentScope 接收到模型返回的工具调用请求,查找对应的 Bean 方法,通过反射调用 WeatherTool.getWeather("上海")
  5. 结果返回:工具执行结果("多云,28℃")被封装成新的消息(工具消息)再次发送给模型。
  6. 生成最终回答:模型结合工具结果和原始问题,生成最终回答:"上海今天多云,28℃。"
  7. 响应客户端:最终回答返回给用户。

流程图:

sequenceDiagram participant 用户 participant 智能体 participant 工具管理器 participant 大模型 用户->>智能体: 提问(上海天气) 智能体->>大模型: 用户消息 + 工具列表 大模型-->>智能体: 工具调用请求(getWeather, 上海) 智能体->>工具管理器: 执行 getWeather(上海) 工具管理器->>工具方法: 反射调用 工具方法-->>工具管理器: 返回"多云,28℃" 工具管理器-->>智能体: 工具结果 智能体->>大模型: 工具结果 大模型-->>智能体: 最终回答 智能体-->>用户: 上海多云,28℃

4.7 动手实践:扩展工具集

现在,请你自己尝试添加一个新工具,比如一个简单的计算器,能够计算两个数字的和、差、积、商。

4.7.1 定义计算器工具

java 复制代码
@Component
public class CalculatorTool {

    @Tool(name = "calculate", description = "计算两个数字的运算结果,支持加、减、乘、除")
    public String calculate(
            @Param(description = "第一个数字") double a,
            @Param(description = "第二个数字") double b,
            @Param(description = "运算符,可以是 +、-、*、/") String operator) {
        return switch (operator) {
            case "+" -> String.valueOf(a + b);
            case "-" -> String.valueOf(a - b);
            case "*" -> String.valueOf(a * b);
            case "/" -> {
                if (b == 0) yield "除数不能为0";
                yield String.valueOf(a / b);
            }
            default -> "不支持的运算符";
        };
    }
}

4.7.2 测试

访问 http://localhost:8080/ask?question=计算123+456,智能体应该会调用计算器工具并返回结果。

4.8 本章小结

通过本章的学习,你掌握了:

  • 工具调用的概念:让智能体调用外部方法获取实时信息或执行操作。
  • 定义工具 :使用 @Tool 注解标记方法,并用 @Param 描述参数。
  • 工具注册 :AgentScope 自动收集所有带 @Tool 的 Spring Bean,无需手动配置。
  • 实践:构建了能查询时间和天气的智能助手,并扩展了计算器工具。
  • 注意事项:线程安全、错误处理、参数描述等。

五、多智能体协作:让智能体团队合作

在前面的章节中,我们一直和单个智能体打交道。单个智能体可以完成不少任务,但当面对更复杂、需要多种专业能力的场景时,单打独斗就显得力不从心了。比如,规划一次旅行,需要查天气、查酒店、规划路线、预订门票......如果让一个智能体包揽所有,它可能会手忙脚乱,而且代码会变得异常臃肿。

多智能体协作正是为了解决这类问题:将复杂任务拆解,由多个各有所长的智能体分工合作,像人类团队一样高效协同。AgentScope Java 提供了强大的多智能体通信机制,让你能轻松构建这样的"智能体团队"。

5.1 为什么需要多个智能体?

先来看一个现实场景:用户说"我想去北京旅游3天,帮我规划一下"。这个任务其实包含多个子任务:

  • 了解北京近期的天气(需要天气查询能力)
  • 查找合适的酒店(需要酒店查询能力)
  • 制定每日行程(需要景点推荐和路线规划能力)

如果用一个智能体完成所有事情,意味着它要同时具备天气查询、酒店查询、景点知识等多种能力,并且要自己拆解任务、按顺序执行。这不仅对模型要求高,而且代码耦合度极高,难以维护和扩展。

使用多智能体系统,我们可以这样分工:

graph TD User[用户] --> Supervisor[主管智能体] Supervisor --> Weather[天气智能体] Supervisor --> Hotel[酒店智能体] Supervisor --> Itinerary[行程智能体] Weather --> Supervisor Hotel --> Supervisor Itinerary --> Supervisor Supervisor --> User
  • 主管智能体:接收用户请求,拆解任务,协调其他智能体工作,汇总结果。
  • 天气智能体:专门负责查询天气。
  • 酒店智能体:专门负责查询酒店信息。
  • 行程智能体:专门负责规划行程。

每个智能体各司其职,代码清晰,易于扩展(比如增加机票查询智能体),而且可以复用(天气智能体也能用于其他场景)。

5.2 多智能体通信机制

要让智能体们协同工作,它们之间必须能"交流"。AgentScope Java 提供了 **MessageHub(消息总线)**作为智能体间通信的中枢。

5.2.1 MessageHub:全局消息总线

MessageHub 是一个全局的消息中心,所有智能体都可以通过它发送和接收消息。你可以把它想象成一个聊天群组:智能体可以向群组广播消息,也可以私聊某个特定智能体。

核心概念:

  • 消息(Message):智能体间传递的信息单元,包含发送者、接收者、内容等。
  • 发送 :智能体调用 MessageHub.send(message) 将消息放入总线。
  • 接收:智能体可以通过轮询或监听的方式从总线获取发给自己的消息。

为了简化,我们可以在智能体内部维护一个收件箱,每次处理完一条消息后,从 MessageHub 拉取下一条消息。

5.2.2 消息类型:广播与点对点

AgentScope 的消息支持两种路由方式:

  • 广播(Broadcast) :消息的接收者为 null 或特殊标识,表示所有智能体都能收到。适合发布公告、任务通知等。
  • 点对点(P2P):指定接收者的名称,只有该智能体能收到。适合任务分配、结果返回。

5.2.3 在智能体中使用 MessageHub

要让智能体具备通信能力,我们只需在智能体类中注入 MessageHub 的 Bean,然后调用它的方法。下面是一个简单的例子:

java 复制代码
import com.alibaba.agentscope.message.Message;
import com.alibaba.agentscope.message.MessageHub;
import org.springframework.beans.factory.annotation.Autowired;

public class MyAgent extends BaseAgent {
    
    @Autowired
    private MessageHub messageHub;  // 注入消息总线
    
    public MyAgent(ChatClient chatClient) {
        super("my_agent", chatClient);
    }
    
    // 发送消息给指定智能体
    protected void sendTo(String receiver, String content) {
        Message msg = new Message(getName(), receiver, content);
        messageHub.send(msg);
    }
    
    // 接收所有发给自己的消息
    protected List<Message> receiveMessages() {
        return messageHub.receive(getName());  // 获取所有发给自己的消息
    }
}

实际使用时,我们通常会让智能体在一个循环中不断处理消息,类似于:

java 复制代码
public void run() {
    while (true) {
        List<Message> messages = receiveMessages();
        for (Message msg : messages) {
            // 处理每条消息
            String reply = processMessage(msg);
            if (reply != null) {
                sendTo(msg.getSender(), reply);  // 回复发送者
            }
        }
        // 短暂休眠,避免空转
        Thread.sleep(100);
    }
}

但为了与现有的 BaseAgent.run() 模型兼容,我们可以设计一种任务驱动 的方式:当用户请求到达时,主管智能体开始协调,而工人智能体则被动响应。这样我们不需要独立的消息循环,而是让 run 方法在需要时触发消息处理。

5.3 构建第一个多智能体系统:主管+工人模式

让我们通过一个简单的示例来感受多智能体协作:用户问"上海的天气怎么样?",主管智能体将问题转给天气智能体,获取结果后返回。

5.3.1 定义消息类

为了区分不同类型的消息,我们可以定义一个简单的 TaskMessage 类,包含任务类型和内容。但为了保持简单,我们直接使用 AgentScope 的 Message 类,并在内容中放入任务信息。

5.3.2 定义天气智能体

天气智能体专门处理天气查询。它从消息中提取城市,调用天气工具(上一章已定义),然后回复结果。

java 复制代码
package com.example.demo.agent;

import com.alibaba.agentscope.agent.BaseAgent;
import com.alibaba.agentscope.llm.ChatClient;
import com.alibaba.agentscope.message.Message;
import com.alibaba.agentscope.message.MessageHub;
import com.example.demo.tool.WeatherTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class WeatherAgent extends BaseAgent {

    @Autowired
    private MessageHub messageHub;

    @Autowired
    private WeatherTool weatherTool;

    public WeatherAgent(ChatClient chatClient) {
        super("weather_agent", chatClient);
    }

    @Override
    public String run(String input) {
        // 此方法不会被直接调用,我们通过消息驱动
        return null;
    }

    // 处理消息的方法,由主管智能体触发
    public void handleMessage(Message msg) {
        String content = msg.getContent();
        // 解析任务,假设格式为 "天气:城市"
        if (content.startsWith("天气:")) {
            String city = content.substring(3).trim();
            String weather = weatherTool.getWeather(city);
            // 回复给发送者
            Message reply = new Message(getName(), msg.getSender(), weather);
            messageHub.send(reply);
        }
    }
}

5.3.3 定义主管智能体

主管智能体接收用户输入,然后根据需要向天气智能体发送消息,并等待回复。

java 复制代码
package com.example.demo.agent;

import com.alibaba.agentscope.agent.BaseAgent;
import com.alibaba.agentscope.llm.ChatClient;
import com.alibaba.agentscope.message.Message;
import com.alibaba.agentscope.message.MessageHub;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Component
public class SupervisorAgent extends BaseAgent {

    @Autowired
    private MessageHub messageHub;

    @Autowired
    private WeatherAgent weatherAgent;  // 注入工人智能体

    public SupervisorAgent(ChatClient chatClient) {
        super("supervisor", chatClient);
    }

    @Override
    public String run(String input) {
        // 用户输入:例如 "上海的天气"
        if (input.contains("天气")) {
            // 1. 向天气智能体发送消息
            String city = extractCity(input);  // 简单提取城市,可用LLM或规则
            Message taskMsg = new Message(getName(), "weather_agent", "天气:" + city);
            messageHub.send(taskMsg);

            // 2. 等待回复(这里用简单轮询,实际可用CompletableFuture)
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start < 10000) { // 最多等10秒
                List<Message> replies = messageHub.receive(getName());
                for (Message reply : replies) {
                    if (reply.getSender().equals("weather_agent")) {
                        return "天气查询结果:" + reply.getContent();
                    }
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            return "天气查询超时";
        }
        // 其他情况由自己回答
        return super.run(input);
    }

    private String extractCity(String input) {
        // 简单规则:如果包含"上海"返回上海,否则默认北京
        if (input.contains("上海")) return "上海";
        if (input.contains("北京")) return "北京";
        return "北京";
    }
}

5.3.4 注册智能体为 Bean

确保所有智能体都被 Spring 管理,加上 @Component 注解。

5.3.5 创建控制器

修改之前的 ToolController,注入主管智能体:

java 复制代码
@RestController
public class MultiAgentController {

    @Autowired
    private SupervisorAgent supervisorAgent;

    @GetMapping("/multi-ask")
    public String ask(@RequestParam String question) {
        return supervisorAgent.run(question);
    }
}

5.3.6 测试

启动应用,访问 http://localhost:8080/multi-ask?question=上海的天气怎么样?,应该能正确返回天气信息。

多智能体协作流程图:

sequenceDiagram participant 用户 participant 主管智能体 participant 消息总线 participant 天气智能体 用户->>主管智能体: 上海的天气怎么样? 主管智能体->>消息总线: 发送任务(天气:上海) 消息总线-->>天气智能体: 传递任务 天气智能体->>天气智能体: 调用天气工具 天气智能体->>消息总线: 回复结果(多云,28℃) 消息总线-->>主管智能体: 传递结果 主管智能体-->>用户: 天气查询结果:多云,28℃

5.4 实践:旅游规划多智能体系统

现在,我们来构建一个更实用的系统:旅游规划助手。它将包含三个专业智能体:天气智能体酒店智能体行程智能体 ,由一个主管智能体协调。

5.4.1 定义工具类

我们需要酒店查询工具和景点推荐工具(用于行程规划)。为简化,我们使用模拟数据。

java 复制代码
// 酒店工具
@Component
public class HotelTool {
    private static final Map<String, List<String>> HOTEL_MAP = Map.of(
        "北京", List.of("北京饭店", "王府井酒店", "三里屯宾馆"),
        "上海", List.of("和平饭店", "浦东香格里拉", "外滩W酒店"),
        "广州", List.of("白天鹅宾馆", "花园酒店", "长隆酒店")
    );

    @Tool(name = "getHotels", description = "查询指定城市的酒店推荐")
    public String getHotels(@Param(description = "城市名称") String city) {
        List<String> hotels = HOTEL_MAP.get(city);
        if (hotels == null) {
            return city + "暂无酒店推荐";
        }
        return String.join("、", hotels);
    }
}

// 景点推荐工具(用于行程)
@Component
public class AttractionTool {
    private static final Map<String, List<String>> ATTRACTION_MAP = Map.of(
        "北京", List.of("故宫", "长城", "天坛", "颐和园"),
        "上海", List.of("外滩", "东方明珠", "迪士尼乐园", "豫园"),
        "广州", List.of("广州塔", "长隆野生动物园", "沙面", "白云山")
    );

    @Tool(name = "getAttractions", description = "查询指定城市的景点推荐")
    public String getAttractions(@Param(description = "城市名称") String city) {
        List<String> attractions = ATTRACTION_MAP.get(city);
        if (attractions == null) {
            return city + "暂无景点推荐";
        }
        return String.join("、", attractions);
    }
}

5.4.2 定义三个工人智能体

天气智能体 (复用之前的 WeatherAgent,但改为通过消息处理)。为了代码整洁,我们创建一个基类 WorkerAgent 封装消息处理逻辑。

java 复制代码
public abstract class WorkerAgent extends BaseAgent {

    @Autowired
    protected MessageHub messageHub;

    public WorkerAgent(String name, ChatClient chatClient) {
        super(name, chatClient);
    }

    // 处理消息的方法,由子类实现
    protected abstract String handleTask(String task);

    // 启动消息处理循环(可以在单独的线程中运行,这里简化为由主管触发)
    public void processMessages() {
        List<Message> messages = messageHub.receive(getName());
        for (Message msg : messages) {
            String result = handleTask(msg.getContent());
            Message reply = new Message(getName(), msg.getSender(), result);
            messageHub.send(reply);
        }
    }
}

然后实现三个工人智能体:

java 复制代码
@Component
public class WeatherWorker extends WorkerAgent {

    @Autowired
    private WeatherTool weatherTool;

    public WeatherWorker(ChatClient chatClient) {
        super("weather_worker", chatClient);
    }

    @Override
    protected String handleTask(String task) {
        if (task.startsWith("天气:")) {
            String city = task.substring(3).trim();
            return weatherTool.getWeather(city);
        }
        return "未知任务";
    }
}

@Component
public class HotelWorker extends WorkerAgent {

    @Autowired
    private HotelTool hotelTool;

    public HotelWorker(ChatClient chatClient) {
        super("hotel_worker", chatClient);
    }

    @Override
    protected String handleTask(String task) {
        if (task.startsWith("酒店:")) {
            String city = task.substring(3).trim();
            return hotelTool.getHotels(city);
        }
        return "未知任务";
    }
}

@Component
public class ItineraryWorker extends WorkerAgent {

    @Autowired
    private AttractionTool attractionTool;

    public ItineraryWorker(ChatClient chatClient) {
        super("itinerary_worker", chatClient);
    }

    @Override
    protected String handleTask(String task) {
        if (task.startsWith("行程:")) {
            String city = task.substring(3).trim();
            String attractions = attractionTool.getAttractions(city);
            // 简单行程:假设三天,每天两个景点
            return String.format("为您规划%s三日游行程:\n第一天:%s\n第二天:%s\n第三天:%s",
                city, 
                attractions.split("、")[0] + "、" + attractions.split("、")[1],
                attractions.split("、")[2] + "、" + attractions.split("、")[3],
                "自由活动");
        }
        return "未知任务";
    }
}

5.4.3 定义主管智能体

主管智能体接收用户请求,解析城市和任务类型,然后并发地向各工人智能体发送任务,收集结果后汇总。

java 复制代码
@Component
public class TravelPlannerSupervisor extends BaseAgent {

    @Autowired
    private MessageHub messageHub;

    public TravelPlannerSupervisor(ChatClient chatClient) {
        super("travel_supervisor", chatClient);
    }

    @Override
    public String run(String input) {
        // 解析城市(简单规则:如果输入包含"北京"/"上海"/"广州",否则用默认)
        String city = extractCity(input);
        
        // 定义需要执行的任务列表
        List<String> tasks = List.of(
            "天气:" + city,
            "酒店:" + city,
            "行程:" + city
        );

        // 发送任务给对应工人(通过任务前缀匹配工人名称)
        for (String task : tasks) {
            String workerName = task.split(":")[0] + "_worker"; // 如 "weather_worker"
            Message msg = new Message(getName(), workerName, task);
            messageHub.send(msg);
        }

        // 等待所有工人回复(最多10秒)
        Map<String, String> results = new HashMap<>();
        long start = System.currentTimeMillis();
        while (results.size() < tasks.size() && System.currentTimeMillis() - start < 10000) {
            List<Message> replies = messageHub.receive(getName());
            for (Message reply : replies) {
                String sender = reply.getSender();
                if (!results.containsKey(sender)) {
                    results.put(sender, reply.getContent());
                }
            }
            try { Thread.sleep(200); } catch (InterruptedException e) {}
        }

        // 汇总结果
        String weather = results.getOrDefault("weather_worker", "天气信息获取失败");
        String hotel = results.getOrDefault("hotel_worker", "酒店信息获取失败");
        String itinerary = results.getOrDefault("itinerary_worker", "行程信息获取失败");

        return String.format("【%s旅游规划】\n天气:%s\n酒店推荐:%s\n%s", 
                city, weather, hotel, itinerary);
    }

    private String extractCity(String input) {
        if (input.contains("北京")) return "北京";
        if (input.contains("上海")) return "上海";
        if (input.contains("广州")) return "广州";
        return "北京"; // 默认
    }
}

5.4.4 触发工人处理消息

为了让工人智能体能及时处理消息,我们需要在适当的时候调用它们的 processMessages() 方法。简单的方式是在每个工人智能体中添加一个 @Scheduled 定时任务,定期处理消息。或者,我们可以在主管发送消息后,手动调用工人的处理方法(但这样耦合度高)。这里采用定时任务方式,简单有效。

在 Spring Boot 主类上启用定时任务:

java 复制代码
@SpringBootApplication
@EnableScheduling
public class DemoApplication {
    // ...
}

然后在每个工人智能体中添加定时方法:

java 复制代码
@Component
public class WeatherWorker extends WorkerAgent {
    // ...
    
    @Scheduled(fixedDelay = 500)  // 每500毫秒处理一次消息
    public void scheduledProcess() {
        processMessages();
    }
}

同样为 HotelWorker 和 ItineraryWorker 添加定时方法。

5.4.5 创建控制器

java 复制代码
@RestController
public class TravelController {

    @Autowired
    private TravelPlannerSupervisor supervisor;

    @GetMapping("/plan-trip")
    public String planTrip(@RequestParam String destination) {
        return supervisor.run(destination);
    }
}

5.4.6 测试

启动应用,访问:http://localhost:8080/plan-trip?destination=我想去上海旅游3天,会返回类似:

复制代码
【上海旅游规划】
天气:多云,28℃
酒店推荐:和平饭店、浦东香格里拉、外滩W酒店
为您规划上海三日游行程:
第一天:外滩、东方明珠
第二天:迪士尼乐园、豫园
第三天:自由活动

5.4.7 多智能体协作流程图(完整)

graph TD User[用户] -->|输入目的地| Supervisor[主管智能体] Supervisor -->|发送天气任务| WeatherWorker Supervisor -->|发送酒店任务| HotelWorker Supervisor -->|发送行程任务| ItineraryWorker WeatherWorker -->|返回天气| Supervisor HotelWorker -->|返回酒店| Supervisor ItineraryWorker -->|返回行程| Supervisor Supervisor -->|汇总结果| User subgraph MessageHub[消息总线] direction LR W1[天气工人] -- 接收/发送 --> M W2[酒店工人] -- 接收/发送 --> M W3[行程工人] -- 接收/发送 --> M S[主管] -- 接收/发送 --> M end

5.5 本章小结

通过本章的学习,你掌握了多智能体协作的核心技术:

  • 多智能体的价值:将复杂任务拆解,由专业智能体分工合作,提高系统可维护性和扩展性。
  • MessageHub 通信机制:利用全局消息总线实现智能体间的松耦合通信。
  • 广播与点对点:了解消息的两种路由方式。
  • 实践:构建了旅游规划多智能体系统,包含天气、酒店、行程三个专业智能体,由主管智能体统一协调。

六、记忆管理:让智能体记住对话历史

在前面的章节中,我们构建的智能体虽然能够回答问题,但它们都是无状态 的------每次对话都是全新的开始,完全不记得之前说过什么。这就像和一个每次都忘记你是谁的朋友聊天,体验可想而知。要让智能体真正智能、个性化,它必须拥有记忆:记住用户的偏好、之前的对话内容,甚至从过往交互中学习。

本章将深入探讨 AgentScope Java 的记忆管理机制,让你能够为智能体添加短期和长期记忆,让它们成为用户的"老朋友"。

6.1 为什么需要记忆?

6.1.1 多轮对话中的上下文保持

想象一下这样的对话:

复制代码
用户:我叫小明。
智能体:你好小明!
用户:我喜欢打篮球。
智能体:篮球是一项很棒的运动!
用户:你觉得我最喜欢的运动是什么?

如果没有记忆,智能体在最后一轮就无法知道用户之前提到过喜欢篮球。有了记忆,智能体可以回顾历史,给出正确答案:"你之前说过喜欢打篮球。"

6.1.2 用户偏好和个性化

记忆可以让智能体记住用户的个性化信息,如:

  • 语言偏好("我喜欢用英文回答")
  • 常用地点("我住在上海")
  • 特殊要求("回答要简洁")

这样,智能体可以逐渐适应每个用户的独特风格,提供更贴心的服务。

6.1.3 长期学习和经验积累

更高级的记忆可以让智能体从过去的交互中学习。例如,用户经常询问 Java 编程问题,智能体可以自动调整回答风格,提供更多代码示例。这种学习通常需要结合长期记忆存储。

6.2 AgentScope Java 的 Memory 接口

AgentScope Java 提供了一个统一的 Memory 接口,用于抽象各种记忆存储实现。该接口定义了记忆的基本操作:

java 复制代码
public interface Memory {
    /**
     * 向记忆中存入一条消息
     */
    void put(ChatMessage message);

    /**
     * 获取所有记忆消息
     */
    List<ChatMessage> get();

    /**
     * 清除记忆
     */
    void clear();

    /**
     * 判断记忆是否为空
     */
    boolean isEmpty();

    /**
     * 获取记忆中的消息数量
     */
    int size();
}

任何记忆实现都需要实现这些方法。ChatMessage 是我们在第三章介绍过的消息类,包含角色和内容。

6.3 内置记忆实现详解

AgentScope Java 提供了几种内置的记忆实现,适用于不同场景。

6.3.1 ConversationMemory:简单的对话历史记忆

ConversationMemory 是最简单的实现,它将所有消息原封不动地存储在内存列表中。它无限增长,适合对话轮次不多的场景。

java 复制代码
import com.alibaba.agentscope.memory.ConversationMemory;

Memory memory = new ConversationMemory();
memory.put(new UserMessage("你好"));
memory.put(new AssistantMessage("你好,有什么可以帮你的?"));
List<ChatMessage> history = memory.get(); // 返回两条消息

优点 :简单直接,信息完整。
缺点:无限增长,当对话很长时,可能超出模型上下文窗口。

6.3.2 TokenWindowMemory:基于 Token 窗口的记忆

TokenWindowMemory 解决无限增长的问题。它维护一个固定大小的 Token 窗口,当总 Token 数超过限制时,自动丢弃最早的消息,确保上下文长度可控。它需要配合一个 Tokenizer 来估算 Token 数。

java 复制代码
import com.alibaba.agentscope.memory.TokenWindowMemory;
import com.alibaba.agentscope.tokenizer.SimpleTokenizer; // 假设有一个简单的 Tokenizer

Tokenizer tokenizer = new SimpleTokenizer();
Memory memory = new TokenWindowMemory(tokenizer, 2000); // 窗口大小 2000 tokens

// 不断添加消息,当总 token 超过 2000 时,最早的消息被移除

优点 :有效控制上下文长度,避免超出模型限制。
缺点:可能丢失早期重要信息。

6.3.3 SummaryMemory:定期总结压缩记忆

SummaryMemory 是一种更智能的记忆策略。它会定期(例如每 N 轮对话)调用大模型对之前的对话进行总结,将总结作为新的记忆存储,并丢弃原始对话细节。这样既保留了核心信息,又极大压缩了 Token 占用。

java 复制代码
import com.alibaba.agentscope.memory.SummaryMemory;
import com.alibaba.agentscope.llm.ChatClient;

ChatClient llm = ...; // 大模型客户端
Memory memory = new SummaryMemory(llm, 5); // 每5轮总结一次

优点 :高度压缩,保留关键信息,适合长期对话。
缺点:需要额外调用模型,可能增加成本和延迟。

6.4 为智能体添加记忆

要让智能体拥有记忆,我们需要在智能体内部持有一个 Memory 实例,并在构建消息列表时加入历史消息。AgentScope 的 BaseAgent 已经预留了记忆接口,我们可以通过构造器传入或重写相关方法。

6.4.1 在创建智能体时指定 Memory

java 复制代码
@Component
public class MemoryAgent extends BaseAgent {

    private final Memory memory;

    public MemoryAgent(ChatClient chatClient, Memory memory) {
        super("memory_agent", chatClient);
        this.memory = memory;
    }

    @Override
    protected List<ChatMessage> buildMessages(String input) {
        // 1. 将用户输入存入记忆
        UserMessage userMsg = new UserMessage(input);
        memory.put(userMsg);

        // 2. 获取所有历史消息(包括刚存入的用户消息)
        List<ChatMessage> history = memory.get();

        // 3. 构建消息列表:系统消息 + 历史消息
        List<ChatMessage> messages = new ArrayList<>();
        messages.add(new SystemMessage(getSystemPrompt()));
        messages.addAll(history);
        return messages;
    }

    @Override
    protected String postprocess(String output) {
        // 将助手回复存入记忆
        memory.put(new AssistantMessage(output));
        return output;
    }
}

6.4.2 在配置中定义 Memory Bean

为了灵活切换记忆策略,我们可以将 Memory 定义为 Spring Bean:

java 复制代码
@Configuration
public class MemoryConfig {

    @Bean
    public Memory conversationMemory() {
        return new ConversationMemory();
    }

    // 也可以选择其他实现,例如:
    // @Bean
    // public Memory tokenWindowMemory(Tokenizer tokenizer) {
    //     return new TokenWindowMemory(tokenizer, 2000);
    // }
}

然后在智能体中通过 @Autowired 注入即可。

6.5 实践:构建带记忆的聊天助手

现在让我们创建一个使用 ConversationMemory 的智能体,并测试它的记忆能力。

6.5.1 创建智能体类

java 复制代码
package com.example.demo.agent;

import com.alibaba.agentscope.agent.BaseAgent;
import com.alibaba.agentscope.llm.ChatClient;
import com.alibaba.agentscope.memory.Memory;
import com.alibaba.agentscope.message.AssistantMessage;
import com.alibaba.agentscope.message.ChatMessage;
import com.alibaba.agentscope.message.SystemMessage;
import com.alibaba.agentscope.message.UserMessage;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class RememberingAgent extends BaseAgent {

    private final Memory memory;

    public RememberingAgent(ChatClient chatClient, Memory memory) {
        super("remembering_agent", chatClient);
        this.memory = memory;
    }

    @Override
    protected String getSystemPrompt() {
        return "你是一个有记忆的助手,会记住用户之前说过的话。";
    }

    @Override
    protected List<ChatMessage> buildMessages(String input) {
        // 存入用户消息
        memory.put(new UserMessage(input));

        // 构建消息列表:系统消息 + 历史消息
        List<ChatMessage> messages = new ArrayList<>();
        messages.add(new SystemMessage(getSystemPrompt()));
        messages.addAll(memory.get());
        return messages;
    }

    @Override
    protected String postprocess(String output) {
        // 存入助手消息
        memory.put(new AssistantMessage(output));
        return output;
    }
}

6.5.2 创建控制器

java 复制代码
@RestController
public class MemoryController {

    @Autowired
    private RememberingAgent rememberingAgent;

    @GetMapping("/memory-chat")
    public String chat(@RequestParam String message) {
        return rememberingAgent.run(message);
    }
}

6.5.3 测试

  1. 启动应用。
  2. 第一轮:http://localhost:8080/memory-chat?message=我叫小明
  3. 第二轮:http://localhost:8080/memory-chat?message=我叫什么名字?

你会看到智能体在第二轮正确回答"你叫小明"。这说明智能体记住了之前的对话。

6.5.4 不同记忆策略的对比

你可以将配置中的 Memory Bean 替换为 TokenWindowMemorySummaryMemory,观察不同策略对长对话的影响。

6.6 记忆持久化

目前的所有记忆实现都是内存存储,这意味着一旦应用重启,记忆就会丢失。在生产环境中,我们通常需要将记忆持久化到磁盘或数据库中,以便长期保存。

6.6.1 为什么需要持久化?

  • 用户长期体验:用户希望智能体在多次访问后仍然记得自己的偏好。
  • 跨会话记忆:比如电商客服,需要记住用户的历史订单和咨询记录。
  • 系统恢复:应用重启后,记忆不丢失,无缝继续对话。

6.6.2 使用 Redis 实现持久化记忆

AgentScope Java 支持通过自定义 Memory 实现来对接 Redis。下面是一个简单的基于 Redis 的 Memory 实现(使用 Spring Data Redis):

首先,添加 Redis 依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

然后,实现 Memory 接口:

java 复制代码
import com.alibaba.agentscope.memory.Memory;
import com.alibaba.agentscope.message.ChatMessage;
import com.alibaba.agentscope.serializer.JsonSerializer;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.List;
import java.util.concurrent.TimeUnit;

public class RedisMemory implements Memory {

    private final RedisTemplate<String, String> redisTemplate;
    private final String memoryKey;
    private final JsonSerializer serializer = new JsonSerializer(); // 用于序列化消息

    public RedisMemory(RedisTemplate<String, String> redisTemplate, String sessionId) {
        this.redisTemplate = redisTemplate;
        this.memoryKey = "agent:memory:" + sessionId;
    }

    @Override
    public void put(ChatMessage message) {
        // 将消息序列化为 JSON 字符串
        String json = serializer.serialize(message);
        // 存入 Redis List,右推
        redisTemplate.opsForList().rightPush(memoryKey, json);
        // 可选:设置过期时间,比如 7 天
        redisTemplate.expire(memoryKey, 7, TimeUnit.DAYS);
    }

    @Override
    public List<ChatMessage> get() {
        // 获取整个 List,从 0 到 -1 表示所有元素
        List<String> jsonList = redisTemplate.opsForList().range(memoryKey, 0, -1);
        if (jsonList == null) return List.of();
        return jsonList.stream()
                .map(json -> serializer.deserialize(json, ChatMessage.class))
                .collect(Collectors.toList());
    }

    @Override
    public void clear() {
        redisTemplate.delete(memoryKey);
    }

    @Override
    public boolean isEmpty() {
        Long size = redisTemplate.opsForList().size(memoryKey);
        return size == null || size == 0;
    }

    @Override
    public int size() {
        Long size = redisTemplate.opsForList().size(memoryKey);
        return size == null ? 0 : size.intValue();
    }
}

注意:JsonSerializer 需要自己实现,可以使用 Jackson 或 Gson 将 ChatMessage 与 JSON 互转。

6.6.3 在智能体中使用持久化记忆

由于每个用户应该有独立的记忆,我们需要在智能体中根据用户标识(如 sessionId)来创建不同的 RedisMemory 实例。这可以通过在 run 方法中接收用户 ID 来实现,但 BaseAgent.run 只接受一个字符串。一个常见的设计是:智能体内部持有一个 MemoryProvider,根据上下文动态提供记忆实例。

简化起见,我们可以在控制器层为每个用户创建独立的智能体实例,但这会消耗大量资源。更好的方式是在智能体内部维护一个 Map<userId, Memory>,并在每次调用时根据 userId 获取对应的记忆。这需要修改智能体接口,让它接收两个参数:用户 ID 和消息。

我们将在后续章节中探讨这种设计。目前,理解持久化的概念即可。

6.7 本章小结

通过本章的学习,你掌握了:

  • 记忆的重要性:上下文保持、个性化、长期学习。
  • Memory 接口:AgentScope Java 统一的记忆抽象。
  • 内置记忆实现ConversationMemory(完整历史)、TokenWindowMemory(滑动窗口)、SummaryMemory(总结压缩)。
  • 为智能体添加记忆 :在 BaseAgent 中集成 Memory,重写 buildMessagespostprocess
  • 实践:构建了带记忆的聊天助手,测试多轮对话效果。
  • 记忆持久化:了解为何需要持久化,以及如何用 Redis 实现。

七、ReAct 模式:智能体的思考与行动循环

在前面的章节中,我们已经学会了让智能体调用工具(第四章)和使用记忆(第六章)。但是,这些工具调用是单次的:智能体一次决策调用一个工具,得到结果后就直接回答。这种模式对于简单查询(如天气、时间)足够了,但对于需要多步推理、根据中间结果调整下一步行动的复杂任务,就显得力不从心。

ReAct 模式正是为了解决这类问题而生的。它让智能体进入一个"思考→行动→观察→再思考"的循环,像人类一样逐步推理,直到完成复杂任务。

7.1 什么是 ReAct 模式?

ReAct 是 Reasoning + Acting 的缩写,由研究人员提出的一种让大模型通过交替进行"推理"和"行动"来解决复杂任务的范式。其核心思想是:智能体在执行任务时,不仅要生成行动(如调用工具),还要生成推理过程(思考下一步该做什么),并根据行动的结果(观察)调整后续推理。

一个典型例子:用户要求"写一段 Python 代码计算斐波那契数列,并运行它"。

  • 思考1:我需要先写代码。
  • 行动1:调用代码编写工具(或直接用模型生成)写代码。
  • 观察1:代码已生成。
  • 思考2:现在需要运行这段代码。
  • 行动2:调用代码执行工具运行代码。
  • 观察2:代码运行出错,提示"变量未定义"。
  • 思考3:错误原因是变量名写错了,我需要修改代码。
  • 行动3:调用代码修改工具修正代码。
  • 观察3:代码运行成功,输出结果。
  • 最终回答:向用户报告结果。

这个循环可以持续多轮,直到任务完成或达到最大尝试次数。

ReAct 循环示意图:

graph TD Start((开始)) --> Thought[思考:推理当前状态和下一步] Thought --> Action[行动:调用工具或生成内容] Action --> Observation[观察:获取行动结果] Observation --> Condition{任务完成?} Condition -->|否| Thought Condition -->|是| Final[生成最终回答] Final --> End((结束))

与普通工具调用相比,ReAct 的优势在于:

  • 多步决策:可以根据中间结果调整计划。
  • 自我纠错:遇到错误可以修正,而不是直接放弃。
  • 透明可解释:可以看到智能体的思考过程,便于调试。

7.2 AgentScope 中的 ReActAgent

AgentScope Java 内置了 ReActAgent 类,它继承自 BaseAgent,并封装了 ReAct 循环的逻辑。你只需要做两件事:

  1. 提供工具集(通过 @Tool 注解,与第四章相同)。
  2. 设置系统提示词,告诉智能体它的任务和可用工具。

7.2.1 ReActAgent 的核心方法

ReActAgent 主要增加了以下关键配置:

  • 最大迭代次数(maxIterations):防止无限循环,默认通常是 10。
  • 工具管理器(ToolManager) :与第四章相同,自动收集所有 @Tool Bean。

在构造 ReActAgent 时,你需要传入 ChatClient 和最大迭代次数。

7.2.2 在 Spring 中定义 ReActAgent

由于 ReActAgent 也是 Spring Bean,我们可以通过 @Component@Bean 来创建它。这里我们使用 @Bean 方式,以便在创建时指定最大迭代次数。

首先,在配置类中定义 ReActAgent Bean:

java 复制代码
@Configuration
public class AgentConfig {

    @Bean
    public ReActAgent codeAssistant(ChatClient chatClient) {
        return ReActAgent.builder()
                .name("code_assistant")
                .chatClient(chatClient)
                .maxIterations(5)          // 最多循环5次
                .build();
    }
}

注意 :工具会自动被发现,不需要手动传入。因为 ReActAgent 内部会使用 ToolManager 从 Spring 容器中收集所有带 @Tool 的 Bean。

7.3 实践:构建一个能自我纠错的代码助手

现在,我们来实现一个能写代码、运行代码、并根据错误修正代码的智能体。为了简化,我们模拟一个代码执行工具,它可以"运行"代码并返回结果或错误信息。

7.3.1 定义代码执行工具

创建一个工具类,包含一个方法模拟运行代码。这里我们不真的执行代码,而是模拟一些常见错误。

java 复制代码
package com.example.demo.tool;

import com.alibaba.agentscope.tool.annotation.Param;
import com.alibaba.agentscope.tool.annotation.Tool;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

@Component
public class CodeExecutorTool {

    // 模拟代码执行结果
    private final Map<String, String> codeResults = new HashMap<>();

    @Tool(name = "executeCode", description = "执行一段代码并返回结果,如果代码有错误会返回错误信息")
    public String executeCode(
            @Param(description = "要执行的代码字符串") String code) {
        // 这里我们模拟一些常见的代码错误
        if (code.contains("斐波那契")) {
            // 假设第一次运行总是有错误
            if (!codeResults.containsKey(code)) {
                codeResults.put(code, "错误:变量 'n' 未定义");
                return "错误:变量 'n' 未定义";
            } else {
                // 第二次运行成功
                return "执行成功,斐波那契数列的第10项是 55";
            }
        } else if (code.contains("print")) {
            // 模拟成功执行
            return "执行成功,输出:" + code.substring(code.indexOf("print") + 6);
        } else {
            return "执行成功,无输出";
        }
    }
}

为了更真实,我们也可以加入简单的语法检测。这里只是演示目的。

7.3.2 创建 ReAct 智能体

我们已经在 AgentConfig 中定义了 codeAssistant Bean。现在需要为它设置合适的系统提示词,指导它如何进行 ReAct 循环。系统提示词非常关键,它告诉智能体应该遵循的格式:思考、行动、观察。

通常,系统提示词会包含以下内容:

  • 智能体的角色(代码助手)。
  • 可用的工具列表(尽管框架会自动提供,但提示词中最好也说明)。
  • ReAct 循环的格式示例,例如每次输出必须包含 Thought:Action:Final Answer:

由于 ReActAgent 内部已经内置了默认的 ReAct 提示词模板,我们只需要简单设置系统消息即可。但为了定制,我们可以重写 getSystemPrompt 方法。不过 ReActAgent 本身不直接提供这个方法,我们需要通过 ReActAgent.builder()systemPrompt 方法设置。

修改 AgentConfig

java 复制代码
@Bean
public ReActAgent codeAssistant(ChatClient chatClient) {
    String systemPrompt = """
            你是一个代码助手,可以编写代码并执行它。
            你的任务是根据用户需求编写代码,然后使用 executeCode 工具运行代码。
            如果代码运行出错,根据错误信息修改代码,再次运行,直到成功。
            每次输出必须包含:
            Thought: 你的推理过程
            Action: 工具名称和参数,格式为:工具名(参数)
            当任务完成时,输出:
            Final Answer: 最终结果
            """;
    return ReActAgent.builder()
            .name("code_assistant")
            .chatClient(chatClient)
            .systemPrompt(systemPrompt)
            .maxIterations(5)
            .build();
}

7.3.3 创建控制器

java 复制代码
package com.example.demo.controller;

import com.alibaba.agentscope.agent.ReActAgent;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CodeController {

    private final ReActAgent codeAssistant;

    public CodeController(@Qualifier("codeAssistant") ReActAgent codeAssistant) {
        this.codeAssistant = codeAssistant;
    }

    @GetMapping("/code")
    public String runCode(@RequestParam String task) {
        return codeAssistant.run(task);
    }
}

7.3.4 测试

启动应用,访问:http://localhost:8080/code?task=请写一个Python函数计算斐波那契数列的第10项,并运行它

观察控制台日志,你会看到智能体的思考过程,以及多轮调用 executeCode 工具的尝试。最终返回的结果应该是成功后的回答。

预期输出(简化):

复制代码
经过尝试,代码最终成功运行,斐波那契数列的第10项是 55。

7.3.5 ReAct 循环详细流程

让我们用序列图展示一次典型的执行过程:

sequenceDiagram participant 用户 participant 智能体(ReAct) participant 工具(executeCode) 用户->>智能体: 任务:写代码并运行 智能体->>智能体: 思考1:需要写代码 智能体->>智能体: 行动1:生成代码 Note over 智能体: 生成代码串 智能体->>工具: 调用 executeCode(代码) 工具-->>智能体: 返回错误信息 智能体->>智能体: 观察:代码出错,错误是... 智能体->>智能体: 思考2:需要修改代码 智能体->>智能体: 行动2:修改代码 智能体->>工具: 调用 executeCode(新代码) 工具-->>智能体: 返回成功结果 智能体->>智能体: 观察:运行成功 智能体->>智能体: 思考3:任务完成 智能体-->>用户: 最终答案:成功结果

7.4 ReAct 模式的进阶应用

7.4.1 多工具协作

在 ReAct 循环中,智能体可以使用多个不同的工具,根据任务需要动态选择。例如,一个数据分析智能体可能先后使用"查询数据"、"生成图表"、"解释结果"等多个工具。

7.4.2 处理复杂错误

你可以让智能体在遇到错误时,不仅修正代码,还能根据错误类型调整策略。比如,如果是语法错误,可能只需要简单修改;如果是逻辑错误,可能需要重新设计算法。

7.4.3 人机交互(Human-in-the-loop)

在某些关键步骤,可以让智能体暂停并向用户请求确认。虽然 ReActAgent 目前不直接支持,但可以通过自定义工具实现:工具可以返回一个需要用户输入的标记,智能体看到这个标记后输出问题,等待用户响应(可以通过外部机制实现)。

7.5 注意事项

  1. 迭代次数限制 :务必设置合理的 maxIterations,防止智能体陷入无限循环(例如工具总是返回错误,导致不断重试)。生产环境中可设置为 10 或 20。

  2. 提示词设计 :ReAct 模式高度依赖提示词的质量。系统提示词必须清晰地说明格式要求,并给出示例。如果智能体不按照格式输出(例如缺少 Thought:),框架可能无法解析。

  3. 工具返回格式:工具返回的结果应该清晰易懂,便于智能体理解。例如,返回 JSON 格式或带有明确错误码的文本。

  4. 性能考虑:每轮循环都会调用一次大模型,多次循环会增加延迟和 Token 消耗。对于实时性要求高的场景,应尽量减少不必要的循环。

7.6 本章小结

通过本章的学习,你掌握了 ReAct 模式的精髓:

  • ReAct 模式:让智能体在思考、行动、观察之间循环,解决多步复杂任务。
  • ReActAgent 使用 :通过 ReActAgent.builder() 创建,设置系统提示词和最大迭代次数。
  • 实践:构建了一个能自我纠错的代码助手,它可以根据运行错误修改代码,直到成功。
  • 流程可视化:用序列图展示了 ReAct 循环的详细步骤。

ReAct 模式是构建强大智能体的关键。现在,你的智能体不仅会调用工具,还能自主地多步推理和纠错,应对更开放、更复杂的任务。

八、工作流编排:多智能体流程设计

在前几章中,我们学习了多智能体协作(第五章),让智能体们通过消息总线自由交流。这种模式灵活,但有时我们需要更确定的流程------比如客服系统中的"客户问题→分类→分派→处理→回复",每一步都必须按顺序执行,不允许智能体随意跳转。这种预定义、结构化 的执行路径,就是工作流编排

AgentScope Java 提供了强大的 Pipeline 机制,让你能以声明式的方式组合多个处理单元(可以是智能体、函数,甚至是另一个工作流),实现顺序、并行、条件分支等控制流。

8.1 什么是工作流编排?

工作流编排,简单说就是定义好一个流程,告诉系统先做什么、后做什么、哪些可以同时做、根据什么条件选择做什么。它与多智能体协作的区别在于:

对比维度 多智能体协作 (MessageHub) 工作流编排 (Pipeline)
通信方式 通过消息总线异步通信,智能体自主决定行为 流程引擎控制执行顺序,节点被动调用
流程确定性 低,智能体可能不按预期顺序执行 高,执行顺序完全由流程定义
适用场景 开放式任务、需要动态调整 业务流程固定、需要审计和可靠性的场景
实现复杂度 需要处理消息、协调状态 只需定义流程,框架负责执行

两者并非互斥,可以结合使用:外层用工作流定义宏观流程,内层用多智能体协作处理复杂子任务。

8.2 AgentScope 中的 Pipeline 机制

AgentScope Java 的 pipeline 包提供了以下核心接口和类:

  • Pipeline :所有工作流的顶级接口,定义了 run(input) 方法。
  • SequentialPipeline:顺序执行多个节点,前一个的输出作为下一个的输入。
  • ParallelPipeline:并行执行多个节点,等待所有节点完成后,将所有结果合并返回。
  • ConditionalPipeline:根据条件选择执行某个分支节点。

每个节点可以是:

  • Agent :智能体,执行 run 方法。
  • Pipeline:嵌套子工作流,实现复用。
  • 普通函数 :通过 FunctionNode 包装,执行任意 Java 代码。

8.3 顺序执行:SequentialPipeline

顺序执行是最简单的流程。例如:先调用天气智能体,再将结果传给穿衣建议智能体。

8.3.1 定义节点

假设我们已经有两个智能体(或函数):

  • weatherAgent:输入城市,返回天气。
  • clothingAdviceAgent:输入天气描述,返回穿衣建议。

我们让它们顺序执行。

8.3.2 构建 SequentialPipeline

java 复制代码
import com.alibaba.agentscope.pipeline.SequentialPipeline;
import com.alibaba.agentscope.pipeline.Node;

@Configuration
public class WorkflowConfig {

    @Bean
    public Pipeline weatherAdviceWorkflow(WeatherAgent weatherAgent, ClothingAdviceAgent clothingAgent) {
        // 将智能体包装为 Node
        Node weatherNode = Node.from(weatherAgent);
        Node clothingNode = Node.from(clothingAgent);

        // 构建顺序工作流
        return SequentialPipeline.builder()
                .name("weather_advice_workflow")
                .nodes(List.of(weatherNode, clothingNode))
                .build();
    }
}

8.3.3 运行工作流

java 复制代码
@RestController
public class WorkflowController {

    @Autowired
    @Qualifier("weatherAdviceWorkflow")
    private Pipeline weatherAdviceWorkflow;

    @GetMapping("/advice")
    public String getAdvice(@RequestParam String city) {
        return weatherAdviceWorkflow.run(city);
    }
}

访问 /advice?city=上海,工作流会:

  1. weatherAgent.run("上海") → 返回 "多云,28℃"
  2. clothingAgent.run("多云,28℃") → 返回 "建议穿短袖,带一件薄外套"

8.4 并行执行:ParallelPipeline

并行执行可以同时执行多个节点,提升效率。例如,在旅游规划中,同时查询天气、酒店、景点,然后汇总。

8.4.1 构建 ParallelPipeline

java 复制代码
@Bean
public Pipeline parallelTravelPipeline(WeatherAgent weatherAgent, 
                                        HotelAgent hotelAgent, 
                                        ItineraryAgent itineraryAgent) {
    List<Node> nodes = List.of(
        Node.from(weatherAgent),
        Node.from(hotelAgent),
        Node.from(itineraryAgent)
    );

    return ParallelPipeline.builder()
            .name("parallel_travel")
            .nodes(nodes)
            .build();
}

注意:并行节点的输入相同,都会收到原始输入(城市名)。它们的输出会被收集成一个列表,顺序与节点顺序一致。

8.4.2 处理并行结果

由于并行工作流的输出是一个 List<String>,我们可以再通过一个 FunctionNode 来汇总结果。

java 复制代码
Node summarizerNode = Node.from((List<String> results) -> {
    String weather = results.get(0);
    String hotel = results.get(1);
    String itinerary = results.get(2);
    return String.format("天气:%s\n酒店:%s\n行程:%s", weather, hotel, itinerary);
});

Pipeline parallelPipeline = ParallelPipeline.builder()
        .name("parallel_travel")
        .nodes(List.of(weatherNode, hotelNode, itineraryNode))
        .build();

// 将并行结果与汇总节点组合成顺序工作流
Pipeline fullWorkflow = SequentialPipeline.builder()
        .name("travel_workflow")
        .nodes(List.of(
            Node.from(parallelPipeline),  // 先并行执行
            summarizerNode                 // 再汇总
        ))
        .build();

8.5 条件分支:ConditionalPipeline

条件分支根据输入或中间结果,决定执行哪个节点。例如:如果用户问题包含"天气",走天气查询路径;否则走默认路径。

8.5.1 构建条件分支

需要定义一个决策函数,返回要执行的节点名称。

java 复制代码
import com.alibaba.agentscope.pipeline.ConditionalPipeline;
import com.alibaba.agentscope.pipeline.DecisionFunction;

// 决策函数
DecisionFunction decision = (input) -> {
    if (input.contains("天气")) {
        return "weather_agent";
    } else {
        return "default_agent";
    }
};

ConditionalPipeline conditionalPipeline = ConditionalPipeline.builder()
        .name("routing_workflow")
        .decisionFunction(decision)
        .branch("weather_agent", Node.from(weatherAgent))  // 分支节点
        .branch("default_agent", Node.from(defaultAgent))
        .build();

8.6 实践:构建一个智能客服工作流

现在,我们综合运用上述知识,构建一个智能客服系统。流程如下:

  1. 分类节点:根据用户问题,判断是"订单查询"、"产品咨询"还是"其他"。
  2. 分支路由
    • 订单查询 → 调用订单智能体
    • 产品咨询 → 调用产品智能体
    • 其他 → 调用通用智能体
  3. 汇总节点:将结果统一格式,返回给用户。

8.6.1 定义分类节点(用智能体实现分类)

创建一个 ClassifierAgent,专门用于分类。

java 复制代码
@Component
public class ClassifierAgent extends BaseAgent {

    public ClassifierAgent(ChatClient chatClient) {
        super("classifier", chatClient);
    }

    @Override
    protected String getSystemPrompt() {
        return "你是一个问题分类器,将用户问题分类为:order(订单)、product(产品)、other(其他)。只输出分类名称,不要其他内容。";
    }

    @Override
    protected String postprocess(String output) {
        // 确保输出是纯分类名
        return output.trim().toLowerCase();
    }
}

8.6.2 定义专业智能体

java 复制代码
@Component
public class OrderAgent extends BaseAgent {
    public OrderAgent(ChatClient chatClient) {
        super("order_agent", chatClient);
    }
    @Override
    protected String getSystemPrompt() {
        return "你是一个订单客服助手,回答关于订单的问题,如物流、退换货等。";
    }
}

@Component
public class ProductAgent extends BaseAgent {
    public ProductAgent(ChatClient chatClient) {
        super("product_agent", chatClient);
    }
    @Override
    protected String getSystemPrompt() {
        return "你是一个产品专家,回答关于产品功能、规格、价格的问题。";
    }
}

@Component
public class DefaultAgent extends BaseAgent {
    public DefaultAgent(ChatClient chatClient) {
        super("default_agent", chatClient);
    }
    @Override
    protected String getSystemPrompt() {
        return "你是一个通用客服助手,回答一般性问题。";
    }
}

8.6.3 构建条件工作流

首先,分类节点的输出作为决策依据。我们创建一个决策函数,根据分类结果选择不同的专业智能体。

java 复制代码
@Configuration
public class CustomerServiceWorkflowConfig {

    @Bean
    public Pipeline customerServiceWorkflow(ClassifierAgent classifier,
                                            OrderAgent orderAgent,
                                            ProductAgent productAgent,
                                            DefaultAgent defaultAgent) {
        // 分类节点
        Node classifierNode = Node.from(classifier);

        // 决策函数:根据分类结果选择分支
        DecisionFunction routeDecision = (classification) -> {
            return switch (classification.trim()) {
                case "order" -> "order";
                case "product" -> "product";
                default -> "other";
            };
        };

        // 条件分支
        ConditionalPipeline routingPipeline = ConditionalPipeline.builder()
                .name("routing")
                .decisionFunction(routeDecision)
                .branch("order", Node.from(orderAgent))
                .branch("product", Node.from(productAgent))
                .branch("other", Node.from(defaultAgent))
                .build();

        // 最终汇总节点(这里简单直接返回结果,可以添加包装)
        // 但为了输出整洁,我们可以加一个汇总节点

        // 构建顺序工作流:先分类,再路由
        return SequentialPipeline.builder()
                .name("customer_service_workflow")
                .nodes(List.of(
                    classifierNode,
                    Node.from(routingPipeline)  // 嵌套条件工作流
                ))
                .build();
    }
}

8.6.4 控制器

java 复制代码
@RestController
public class CustomerServiceController {

    @Autowired
    @Qualifier("customerServiceWorkflow")
    private Pipeline customerServiceWorkflow;

    @GetMapping("/customer-service")
    public String handleQuery(@RequestParam String question) {
        return customerServiceWorkflow.run(question);
    }
}

8.6.5 测试

  • http://localhost:8080/customer-service?question=我的订单什么时候发货? → 触发订单智能体。
  • http://localhost:8080/customer-service?question=这款手机有什么颜色? → 触发产品智能体。
  • http://localhost:8080/customer-service?question=你好 → 触发通用智能体。

8.6.6 工作流流程图

graph TD Start((开始)) --> Classifier[分类节点] Classifier --> Decision{分类结果} Decision -- order --> OrderAgent[订单智能体] Decision -- product --> ProductAgent[产品智能体] Decision -- other --> DefaultAgent[通用智能体] OrderAgent --> End((结束)) ProductAgent --> End DefaultAgent --> End

8.7 工作流与 Agent 的嵌套

AgentScope 的 Pipeline 支持任意层级的嵌套,这意味着你可以构建非常复杂的流程。例如,在客服工作流中,订单智能体内部又可以是一个子工作流(先查物流、再查退换货政策)。这为实现模块化、复用提供了极大便利。

8.8 本章小结

通过本章的学习,你掌握了:

  • 工作流编排的概念及其与多智能体协作的区别。
  • SequentialPipeline:顺序执行多个节点。
  • ParallelPipeline:并行执行多个节点,提升效率。
  • ConditionalPipeline:根据条件动态路由。
  • 实践:构建了一个智能客服工作流,综合运用了顺序、条件和嵌套。

现在,你已经能够用工作流将多个智能体组装成可靠的业务流程,满足企业级应用的确定性要求。

相关推荐
AlienZHOU40 分钟前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
狗头大军之江苏分军3 小时前
《潮水漫过十七岁》开学了
后端
苏三说技术4 小时前
如何看待GPT-6在UP主众测中碾压夺冠?它是现在最强大模型吗?
后端
mldong4 小时前
一份 JSON,一条能跑的审批流:把报销流程送上工作流引擎
后端·架构
wno7044 小时前
Spring Boot WebFlux增删改查
java·spring boot·后端
Captaincc4 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
aramae5 小时前
模拟实现strlen()函数 (C语言)
c语言·开发语言·后端
计算机魔术师5 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen6 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒6 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端