本教程演示如何在 AgentScope Runtime Java 框架中构建一个简单的智能体应用并将其部署为服务。
添加 AgentScope Runtime Java 针对 AgentScope 框架的适配器依赖和应用启动依赖,基础依赖已通过适配器依赖传递依赖完成:
<dependency>
<groupId>io.agentscope</groupId>
<artifactId>agentscope-runtime-agentscope</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>io.agentscope</groupId>
<artifactId>agentscope-runtime-web</artifactId>
<version>1.0.0</version>
</dependency>您需要为所选的大语言模型提供商提供API密钥。本示例使用阿里云的Qwen模型,服务提供方是DashScope,所以需要使用其API_KEY,您可以按如下方式将key作为环境变量:
export DASHSCOPE_API_KEY="your_api_key_here"首先导入所有必要的依赖:
import java.util.List;
import java.util.Map;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.agent.EventType;
import io.agentscope.core.agent.StreamOptions;
import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter;
import io.agentscope.core.memory.LongTermMemoryMode;
import io.agentscope.core.message.Msg;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.tool.Toolkit;
import io.agentscope.runtime.adapters.agentscope.AgentScopeAgentHandler;
import io.agentscope.runtime.adapters.agentscope.memory.LongTermMemoryAdapter;
import io.agentscope.runtime.adapters.agentscope.memory.MemoryAdapter;
import io.agentscope.runtime.engine.agents.agentscope.tools.ToolkitInit;
import io.agentscope.runtime.engine.schemas.AgentRequest;
import io.agentscope.runtime.sandbox.box.BaseSandbox;
import io.agentscope.runtime.sandbox.box.Sandbox;
import io.agentscope.runtime.sandbox.manager.SandboxService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;下面的四个方法分别定义了整个应用的属性和执行逻辑,isHealthy方法用于返回当前应用的健康状况,getName方法用于返回当前应用的名称,getDescription方法用于返回当前应用的描述,**streamQuery**方法则是整个AgentScopeAgentHandler的逻辑执行核心,用于用户设置记忆、构建 Agent 以及自定义 Agent 执行逻辑
public class MyAgentScopeAgentHandler extends AgentScopeAgentHandler {
@Override
public boolean isHealthy() {
return false;
}
@Override
public String getName() {
return "";
}
@Override
public String getDescription() {
return "";
}
@Override
public Flux<?> streamQuery(AgentRequest request, Object messages) {
return null;
}
}String sessionId = request.getSessionId();
String userId = request.getUserId();Map<String, Object> state = null;
if (stateService != null) {
try {
state = stateService.exportState(userId, sessionId, null).join();
}
catch (Exception e) {
logger.warn("Failed to export state: {}", e.getMessage());
}
}- 目的:恢复该用户在该会话中的上一轮 Agent 状态(例如:内部变量、对话阶段、任务进度等)。
roundId = null表示取最新一轮的状态。- 使用
.join()阻塞等待(因为后续构建 Agent 需要同步状态)。 - 失败时仅警告,不影响主流程(Agent 可从空状态开始)。
Toolkit toolkit = new Toolkit();
if (sandboxService != null) {
Sandbox sandbox = sandboxService.connect(userId, sessionId, BaseSandbox.class);
toolkit.registerTool(ToolkitInit.RunPythonCodeTool(sandbox));
}- Toolkit:Agent 可调用的工具集合。
- Sandbox:安全沙箱环境,具体包含基础沙箱、文件系统沙箱、浏览器沙箱等,每个
(userId, sessionId)独立实例。 - 注册沙箱工具。
- 如果沙箱创建失败,跳过工具注册,Agent 仍可运行,但无法调用此工具。
MemoryAdapter memory = null;
if (sessionHistoryService != null) {
memory = new MemoryAdapter(sessionHistoryService, userId, sessionId);
}- 作用:提供当前会话的历史消息记录(如用户和 Agent 的对话历史)。
sessionHistoryService是底层存储服务。- 适配器将通用服务接口转换为 AgentScope 框架所需的
Memory接口。
LongTermMemoryAdapter longTermMemory = null;
if (memoryService != null) {
longTermMemory = new LongTermMemoryAdapter(memoryService, userId, sessionId);
}- 作用:访问用户的跨会话长期记忆(如个人偏好、知识库摘要等)。
- 通常基于向量数据库或结构化存储。
- 后续会配置 Agent 在生成回复时同时参考短期和长期记忆。
ReActAgent.Builder agentBuilder = ReActAgent.builder()
.name("Friday")
.sysPrompt("You're a helpful assistant named Friday.")
.toolkit(toolkit)
.model(
DashScopeChatModel.builder()
.apiKey(apiKey)
.modelName("qwen-max")
.stream(true)
.formatter(new DashScopeChatFormatter())
.build());- 使用 Builder 模式组装 Agent:
- 名称、系统提示词(system prompt)
- 绑定工具集(
toolkit) - 配置大模型(这里用通义千问
qwen-max,通过 DashScope API) - 启用流式输出(
.stream(true))
- 注意:此时 Agent 尚未加载状态或记忆。
if (longTermMemory != null) {
agentBuilder.longTermMemory(longTermMemory)
.longTermMemoryMode(LongTermMemoryMode.BOTH);
}
if (memory != null) {
agentBuilder.memory(memory);
}memory→ 短期记忆(当前会话历史)longTermMemory→ 长期记忆(跨会话知识)LongTermMemoryMode.BOTH:表示在思考和生成阶段都使用长期记忆。
if (state != null && !state.isEmpty()) {
agent.loadStateDict(state);
}- 将 Step 2 获取的状态字典反序列化到 Agent 内部。
- 使 Agent 能“接续”上一次的对话状态(如继续未完成的任务)。
List<Msg> agentMessages;
if (messages instanceof List) {
@SuppressWarnings("unchecked")
List<Msg> msgList = (List<Msg>) messages;
agentMessages = msgList;
}
else if (messages instanceof Msg) {
agentMessages = List.of((Msg) messages);
}
else {
logger.warn("Unexpected messages type: {}, using empty list",
messages != null ? messages.getClass().getName() : "null");
agentMessages = List.of();
}
Msg queryMessage;
if (agentMessages.size() > 1) {
// 将前 N-1 条消息加入 memory
for (int i = 0; i < agentMessages.size() - 1; i++) {
agent.getMemory().addMessage(agentMessages.get(i));
}
queryMessage = agentMessages.get(agentMessages.size() - 1); // 最后一条作为当前查询
} else {
queryMessage = agentMessages.get(0) or empty Msg;
}StreamOptions streamOptions = StreamOptions.builder()
.eventTypes(EventType.REASONING, EventType.TOOL_RESULT)
.incremental(true)
.build();
Flux<Event> agentScopeEvents = agent.stream(queryMessage, streamOptions);stream():启动 ReAct 循环(Thought → Action → Observation → ... → Final Answer)StreamOptions:eventTypes:只返回推理步骤和工具结果(过滤掉内部日志等)incremental = true:启用增量流式输出(如逐字生成)
- 返回的是 AgentScope 原生 Event 流(不是 Runtime Event),由外层
StreamAdapter转换。
return agentScopeEvents
.doOnNext(event -> {
logger.info("Agent event: {}", event);
})
.doFinally(signalType -> {
if (stateService != null) {
try {
Map<String, Object> finalState = agent.stateDict();
if (finalState != null && !finalState.isEmpty()) {
stateService.saveState(userId, finalState, sessionId, null)
.exceptionally(e -> {
logger.error("Failed to save state: {}", e.getMessage(), e);
return null;
});
}
}
catch (Exception e) {
logger.error("Error saving state: {}", e.getMessage(), e);
}
}
})
.doOnError(error -> {
logger.error("Error in agent stream: {}", error.getMessage(), error);
});- 无论成功/失败/取消,在流结束时保存 Agent 的最终状态。
roundId = null→ 自动分配新轮次 ID(见InMemoryStateService实现)。- 使用
exceptionally处理保存异常,避免影响主流程。
🔁 实现了“状态持久化闭环”:加载 → 执行 → 保存。
import io.agentscope.runtime.app.AgentApp;
import io.agentscope.runtime.engine.services.agent_state.InMemoryStateService;
import io.agentscope.runtime.engine.services.memory.persistence.memory.service.InMemoryMemoryService;
import io.agentscope.runtime.engine.services.memory.persistence.session.InMemorySessionHistoryService;
import io.agentscope.runtime.sandbox.manager.ManagerConfig;
import io.agentscope.runtime.sandbox.manager.SandboxService;
import io.agentscope.runtime.sandbox.manager.client.container.BaseClientStarter;
import io.agentscope.runtime.sandbox.manager.client.container.docker.DockerClientStarter;
import org.jetbrains.annotations.NotNull;@NotNull
private static SandboxService buildSandboxService() {
BaseClientStarter clientConfig = DockerClientStarter.builder().build();
ManagerConfig managerConfig = ManagerConfig.builder()
.clientStarter(clientConfig)
.build();
SandboxService sandboxService = new SandboxService(
managerConfig
);
sandboxService.start();
return sandboxService;
}- 沙箱运行环境支持 Docker、**K8s 、 AgentRun 以及 FC,未配置
clientStarter默认使用本地 Docker **作为运行环境 - 使用
managerConfig构建 SandboxService
MyAgentScopeAgentHandler agentHandler = new MyAgentScopeAgentHandler();
agentHandler.setStateService(new InMemoryStateService());
agentHandler.setSessionHistoryService(new InMemorySessionHistoryService());
agentHandler.setMemoryService(new InMemoryMemoryService());
agentHandler.setSandboxService(buidSandboxService());实例化刚刚编写的 AgentScopeAgentHandler 类,并注册服务
AgentApp agentApp = new AgentApp(agentHandler);
agentApp.cors(registry -> registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true));
agentApp.run(10001);使用实例化的 AgentScopeAgentHandler 类初始化 AgentApp,并在10001端口上启动
curl --location --request POST 'http://localhost:10001/a2a/' \
--header 'Content-Type: application/json' \
--header 'Accept: */*' \
--header 'Host: localhost:10001' \
--header 'Connection: keep-alive' \
--data-raw '{
"method": "message/stream",
"id": "2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc",
"jsonrpc": "2.0",
"params": {
"configuration": {
"blocking": false
},
"message": {
"role": "user",
"kind": "message",
"metadata": {
"userId": "me",
"sessionId": "test1"
},
"parts": [
{
"text": "你好,给我用python计算一下第10个斐波那契数",
"kind": "text"
}
],
"messageId": "c4911b64c8404b7a8bf7200dd225b152"
}
}
}'你将会看到以**Server-Sent Events(SSE)**格式流式输出的响应:
id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc
event:jsonrpc
data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"id":"92ccdc36-006f-4d66-a47c-18d0cb171506","contextId":"fd5bccd1-770f-4872-8c9a-f086c094f90a","status":{"state":"submitted","timestamp":"2025-12-09T10:53:47.612001Z"},"artifacts":[],"history":[{"role":"user","parts":[{"text":"你好,给我用python计算一下第10个斐波那契数","kind":"text"}],"messageId":"c4911b64c8404b7a8bf7200dd225b152","contextId":"fd5bccd1-770f-4872-8c9a-f086c094f90a","taskId":"92ccdc36-006f-4d66-a47c-18d0cb171506","metadata":{"userId":"me","sessionId":"test1"},"kind":"message"}],"kind":"task"}}
id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc
event:jsonrpc
data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"taskId":"92ccdc36-006f-4d66-a47c-18d0cb171506","status":{"state":"working","timestamp":"2025-12-09T10:53:47.614736Z"},"contextId":"fd5bccd1-770f-4872-8c9a-f086c094f90a","final":false,"kind":"status-update"}}
......
id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc
event:jsonrpc
data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"taskId":"92ccdc36-006f-4d66-a47c-18d0cb171506","artifact":{"artifactId":"293bb1b0-1442-4ca2-997f-575b798dfad1","name":"agent-response","parts":[{"text":"是55。","kind":"text"}],"metadata":{"type":"chunk"}},"contextId":"fd5bccd1-770f-4872-8c9a-f086c094f90a","append":true,"lastChunk":false,"kind":"artifact-update"}}
id:2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc
event:jsonrpc
data:{"jsonrpc":"2.0","id":"2d2b4dc8-8ea2-437b-888d-3aaf3a8239dc","result":{"taskId":"92ccdc36-006f-4d66-a47c-18d0cb171506","status":{"state":"completed","message":{"role":"agent","parts":[{"text":"run_ipython_cellrun_ipython_cell第10个斐波那契数是55。","kind":"text"}],"messageId":"7b878071-d63e-4710-81e0-91d50a57c373","contextId":"fd5bccd1-770f-4872-8c9a-f086c094f90a","taskId":"92ccdc36-006f-4d66-a47c-18d0cb171506","metadata":{"type":"final_response"},"kind":"message"},"timestamp":"2025-12-09T10:53:51.538933Z"},"contextId":"fd5bccd1-770f-4872-8c9a-f086c094f90a","final":true,"kind":"status-update"}}
后续的章节包括如下几个部分