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 { <> +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:根据条件动态路由。
  • 实践:构建了一个智能客服工作流,综合运用了顺序、条件和嵌套。

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

相关推荐
LawrenceLan2 小时前
36.Flutter 零基础入门(三十六):StatefulWidget 与 setState 进阶 —— 动态页面必学
开发语言·前端·flutter·dart
ego.iblacat2 小时前
Web 技术与 Nginx 网站环境部署
运维·前端·nginx
ricky_fan2 小时前
(已解决)安装openclaw龙虾[特殊字符]npm权限问题EACCES
前端·npm·node.js
专业流量卡2 小时前
让小龙虾给我写文章
前端
技术人生黄勇2 小时前
微信接入|企业微信官方插件支持 OpenClaw 3步快速接入(实操版)
java·前端·人工智能·微信·企业微信
倔强的石头_2 小时前
MySQL 兼容性深度解析:从内核级优化到“零修改”迁移工程实践
前端·数据库
小小小米粒2 小时前
k8s流程创建清单
服务器·前端·etcd
Beginner x_u2 小时前
Vue scoped 样式不生效的一个坑:CSS 选择器与 class 合并机制
前端·css·vue.js
脸大是真的好~2 小时前
黑马AI+前端教程 02-视频和音频-超链接-布局标签-表格-文本密码-单选复选框-单个多个文件上传-多行文本-按键-辅助标签
前端