不写一行 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-Java | Spring 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"));
}
}
三个关键点:
- sysPrompt 定义人格,但 HarnessAgent 的人格不只在代码里——下一节你会看到它落在 workspace 的
AGENTS.md文件上; - RuntimeContext 是身份载体:
sessionId决定会话存储路径,userId区分用户。同一套代码,靠这两个字段天然支持多租户; 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