不写一行 Python,用 Java 撸一个多智能体:AgentScope-Java 2.0 上手实测

0 阅读8分钟

不写一行 Python,用 Java 撸一个多智能体:AgentScope-Java 2.0 上手实测

AI Agent 的框架生态几乎是 Python 的天下——LangChain、AutoGen、CrewAI,想用 Java 搭 Agent,要么维护第二套语言栈,要么拿 Spring AI 一点点手搓。阿里开源的 AgentScope-Java 给了另一个选项:在 JVM 里把 ReAct 推理、工具调用、多智能体编排一次做齐。本文基于 2.0.1 版本,从零跑通第一个 Agent,并验证它最被低估的能力——跨进程会话恢复。

你能获得什么

  • AgentScope-Java 与 Spring AI Alibaba 的定位差异,选型不纠结
  • 10 分钟跑通第一个 HarnessAgent(含可复制代码)
  • 一次「杀掉进程再启动,Agent 还记得你」的完整实测
  • 三个真实踩坑与 Key 安全实践

一、为什么 Java 开发者值得关注这个框架

先交代背景:AgentScope 是阿里通义实验室开源的智能体框架家族,Python 版早已开源,AgentScope-Java 是其官方 Java 实现,2025 年 11 月发布 1.0,目前最新为 2.0.1,已在阿里集团内部 13+ 业务线生产使用。

对 Java 团队来说,它解决的是一个很具体的问题:不用引入 Python 栈,就能获得生产级的多智能体能力——ReAct 推理循环、工具调用、记忆管理、多 Agent 协作,全部以 Maven 依赖的形式进入你现有的 Spring Boot 工程。

社区里经常被拿来和 Spring AI Alibaba 对比,两者其实是同一团队不同小组的作品,定位互补:

AgentScope-JavaSpring AI Alibaba
出身阿里云自研跟随 Spring 生态
侧重多智能体通信、角色分工、状态同步单 Agent 复杂工作流编排(Graph)
抽象HarnessAgent 工程外壳Spring familiar 的 Bean/Starter 风格
适合多角色协作、长跑任务已有 Spring 体系的单 Agent 增强

一句话:单 Agent + 工作流选 Spring AI Alibaba;多智能体协作选 AgentScope-Java。 本文的主角是后者。

二、两个核心概念:ReActAgent 与 HarnessAgent

打开源码前,先建立最小心智模型。

【图1:HarnessAgent 架构图】 在这里插入图片描述

  • ReActAgent:框架的最小内核,实现 Think → Act → Observe 的推理循环。一个请求进来,模型自己决定要不要调工具、观察结果、继续思考,直到产出答案。适合单轮、无状态的轻量任务。
  • HarnessAgent:推荐的生产入口。它复用同一套 ReAct 循环,但在外面包了一层工程外壳——workspace(人格/记忆/技能)、会话持久化、上下文自动压缩、子智能体编排、沙箱隔离。

区别在于:ReActAgent 只回答「这一轮怎么推理」,HarnessAgent 回答「下一轮怎么办、明天怎么办、上下文爆了怎么办、进程挂了怎么办」。新手建议直接从 HarnessAgent 入手,这些工程能力早晚都要用上。

三、10 分钟跑通第一个 Agent

环境要求:JDK 17+(本文用 21)、Maven 3.9+。

3.1 引入依赖

两个依赖搞定,模型按需选择(本文用 OpenAI 兼容协议接 DeepSeek,成本低、国内直连):

<dependency>
    <groupId>io.agentscope</groupId>
    <artifactId>agentscope-harness</artifactId>
    <version>2.0.1</version>
</dependency>
<dependency>
    <groupId>io.agentscope</groupId>
    <artifactId>agentscope-extensions-model-openai</artifactId>
    <version>2.0.1</version>
</dependency>

如果只需要裸的 ReActAgent(不要 workspace、持久化、沙箱),单独引 agentscope-core 即可。模型侧还有 dashscope / anthropic / gemini / ollama 等扩展模块,换模型只换依赖和一行配置。

3.2 配置 API Key

重点立规矩:Key 一律走环境变量,绝不硬编码。硬编码的 Key 一旦提交仓库或误入截图,等于公开钱包密码:

export DEEPSEEK_API_KEY=sk-xxx

IDEA 用户在 Run Configuration → Environment variables 里配置:

代码里统一收敛到一个工厂类,缺失时给出友好提示:

package com.mk.agentscope.agent;

import io.agentscope.extensions.model.openai.OpenAIChatModel;
import io.agentscope.extensions.model.openai.formatter.OpenAIChatFormatter;

/**
 * 模型工厂:统一从环境变量读取 API Key,避免密钥硬编码进仓库。
 *
 * <p>使用前先设置环境变量(Git Bash / CMD / IDEA Run Configuration 均可):
 * <pre>
 *   export DEEPSEEK_API_KEY=sk-xxx
 * </pre>
 * 详见项目根目录 README.md 与 .env.example。
 */
public final class ModelFactory {

    private static final String API_KEY_ENV = "DEEPSEEK_API_KEY";
    private static final String BASE_URL = "https://api.deepseek.com";
    private static final String MODEL_NAME = "deepseek-chat";

    private ModelFactory() {
    }

    public static OpenAIChatModel build() {
        String apiKey = System.getenv(API_KEY_ENV);
        if (apiKey == null || apiKey.isBlank()) {
            throw new IllegalStateException("未检测到环境变量 " + API_KEY_ENV
                    + ",请先设置:export DEEPSEEK_API_KEY=sk-xxx(详见 README.md)");
        }
        return OpenAIChatModel.builder()
                .apiKey(apiKey)
                .modelName(MODEL_NAME)
                .baseUrl(BASE_URL)
                .stream(true)
                .formatter(new OpenAIChatFormatter())
                .build();
    }
}

3.3 十行代码构建 Agent

package com.mk.agentscope.agent;

import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.UserMessage;
import io.agentscope.harness.agent.HarnessAgent;
import io.agentscope.harness.agent.memory.compaction.CompactionConfig;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Scanner;

/**
 * 会话持久化演示。
 *
 * <p>演示 HarnessAgent 的跨进程会话恢复能力:
 * <ol>
 *   <li>第一次运行:告诉 Agent 你的名字和在做的事,然后 exit 退出;</li>
 *   <li>第二次运行:直接问「我叫什么、在做什么」,Agent 能答出上一次的内容。</li>
 * </ol>
 * 原理:AgentState 自动持久化到 {@code ~/.agentscope/state/<agentName>/<userId>/<sessionId>/},
 * 同一 sessionId 再次调用时自动恢复。运行后观察该目录与
 * {@code .agentscope/workspace/}(人格 / 记忆 / 会话日志)的变化,即可截图写文章。
 *
 * <p>运行前设置:export DEEPSEEK_API_KEY=sk-xxx
 */
public class SessionResumeDemo {

    private static final String SESSION_ID = "demo-session";
    private static final String USER_ID = "mk";

    public static void main(String[] args) {
        HarnessAgent agent = HarnessAgent.builder()
                .name("note-taker")
                .sysPrompt("你是一个帮助用户记录笔记的助手,需要记住用户提到的关键信息。")
                .model(ModelFactory.build())
                .workspace(Paths.get(".agentscope/workspace"))
                .compaction(CompactionConfig.builder()
                        .triggerMessages(30)
                        .keepMessages(10)
                        .build())
                .build();

        RuntimeContext ctx = RuntimeContext.builder()
                .sessionId(SESSION_ID)
                .userId(USER_ID)
                .build();

        // 状态目录(AgentState 自动落盘位置,注意是在「用户主目录」下,不是项目目录)
        Path stateDir = Paths.get(System.getProperty("user.home"),
                ".agentscope", "state", "note-taker", USER_ID, SESSION_ID);

        System.out.println("=== AgentScope-Java 会话持久化演示 ===");
        System.out.println("sessionId=" + SESSION_ID + "(两次运行请保持相同)");
        System.out.println("输入消息开始对话,输入 exit 退出。");
        System.out.println("建议:第一次运行说「我叫 MK,在写 AgentScope-Java 系列文章」,退出后重新运行本程序再问「我叫什么」。");
        System.out.println("状态落盘目录:" + stateDir);

        try (Scanner scanner = new Scanner(System.in)) {
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine().trim();
                if ("exit".equalsIgnoreCase(line)) {
                    break;
                }
                if (line.isEmpty()) {
                    continue;
                }
                Msg reply = agent.call(new UserMessage(line), ctx).block();
                System.out.println("[agent] " + (reply == null ? "(无回复)" : reply.getTextContent()));
            }
        }
        System.out.println("已退出。查看状态文件:" + stateDir.resolve("agent_state.json"));
    }
}

三个关键点:

  1. sysPrompt 定义人格,但 HarnessAgent 的人格不只在代码里——下一节你会看到它落在 workspace 的 AGENTS.md 文件上;
  2. RuntimeContext 是身份载体:sessionId 决定会话存储路径,userId 区分用户。同一套代码,靠这两个字段天然支持多租户;
  3. call(...) 是阻塞式返回,streamEvents(...) 是流式返回,按场景选。

3.4 见证时刻:跨进程会话恢复

现在做本文的核心实验。运行程序,输入:

我叫 MK,正在写 AgentScope-Java 的公众号系列文章。

Agent 正常回应后,输入 exit 退出,然后重新执行一遍启动命令——这是一个全新的 JVM 进程。这次只问一句:

我叫什么?在做什么?

【图4:两次运行的终端对比截图】

Agent 准确答出了「MK」和「在写 AgentScope-Java 系列文章」。第一轮对话发生在已经死掉的进程里,凭什么第二个进程知道?

答案在两个目录。

状态目录:会话状态在每次调用后自动持久化到用户主目录下(注意不是项目目录):

  • Windows:C:\Users\你的用户名\.agentscope\state\note-taker\mk\demo-session\agent_state.json
  • macOS / Linux:~/.agentscope/state/note-taker/mk/demo-session/agent_state.json

路径里的三段分别是 agent 名(note-taker)、用户 ID(mk)、会话 ID(demo-session),与代码里 name / userId / sessionId 一一对应。新进程用相同 sessionId 启动时自动加载这个文件恢复上下文。

【图5:state 目录与 agent_state.json 截图(打开文件确认无 Key 再截)】

workspace 目录 .agentscope/workspace/:这是 Agent 的「家」,里面躺着三样东西——

  • AGENTS.md:人格定义(框架自动生成,可手改,改完下次推理即生效)
  • memory/:分层长期记忆,对话超过阈值后自动蒸馏事实沉淀
  • sessions/:永不压缩的原始会话日志

【图6:workspace 目录树截图】

这就是 HarnessAgent 和「玩具 Demo」的分水岭:Agent 的状态不依赖进程内存,进程只是它的一个化身。 生产环境把本地文件后端换成 RedisAgentStateStore,多副本部署任意节点都能恢复用户上下文,代码不用改——这个留到 P4 展开。

四、进阶:流式输出

做 Web 界面时需要打字机效果,把 call() 换成 streamEvents(),拿到的是类型化事件流:

agent.streamEvents(new UserMessage("帮我把今天的内容关键点列三条"))
    .doOnNext(event -> {
        if (event.getType() == AgentEventType.TEXT_BLOCK_DELTA) {
            System.out.print(((TextBlockDeltaEvent) event).getDelta());
        } else if (event.getType() == AgentEventType.TOOL_CALL_START) {
            System.out.println("\n[tool] " + ((ToolCallStartEvent) event).getToolCallName());
        }
    }).blockLast();

TEXT_BLOCK_DELTA 是文本增量,TOOL_CALL_START 是工具调用开始——事件流直接映射到前端的 SSE/WebSocket 推送,天然适配实时渲染。

五、踩坑记录

坑 1:Key 硬编码。 我的第一版就把 Key 写在了 OpenAIChatModel.builder().apiKey("sk-...") 里,跑是能跑,但这是开源/交付前的必修项。统一收敛到 ModelFactory 读环境变量,并确认 .gitignore 排除 .env 和 .agentscope/。

坑 2:版本迭代快,锁版本。 2.0 系列 API 有过一轮重构(1.x 的 Msg 构造方式和 2.0 不同),文章和工程都明确锁定 2.0.1,升级时看 Release Notes 再动。

坑 3:workspace 用相对路径,跟着工作目录走。 Paths.get(".agentscope/workspace") 是相对 JVM 启动目录的,IDEA 里跑和命令行 mvn exec:java 跑生成的 workspace 位置一致的前提是工作目录相同;部署到服务器时建议改绝对路径。

六、总结与下期预告

AgentScope-Java 2.0 给出的承诺是:Java 团队不必迁就 Python 生态,也能搭建生产级多智能体系统。本文验证了它最基础也是最关键的一环——状态在进程之外,Agent 才是连续的。

下一篇 P2 进入实战核心:用 @Tool 注解把普通 Java 方法变成 Agent 的工具,让 Agent 真正「动手」查数据、写文件,并拆解框架自动生成 JSON Schema 的机制。

框架官方文档:java.agentscope.io 基于 agentscope-harness 2.0.1,DeepSeek API