AgentScope Java新手教程:首个Agent基础对话详解
第二章 HarnessAgent 与流式对话:基于 DeepSeek 打造首个具备推理能力的 Agent
2.1 极简启动示例
下面提供一个可直接运行的极简对话 Agent 示例,完整代码已附上:
package com.example;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.formatter.openai.OpenAIChatFormatter;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.model.GenerateOptions;
import io.agentscope.core.model.OpenAIChatModel;
import io.agentscope.core.tool.Toolkit;
import io.agentscope.harness.HarnessAgent;
import java.nio.file.Path;
public class BasicChatExample {
public static void main(String[] args) {
String apiKey = System.getenv("DEEPSEEK_API_KEY");
// 创建 Model
OpenAIChatModel model = OpenAIChatModel.builder()
.apiKey(apiKey)
.modelName("deepseek-chat")
.baseUrl("https://api.deepseek.com")
.stream(true)
.enableThinking(true)
.formatter(new OpenAIChatFormatter())
.defaultOptions(GenerateOptions.builder().thinkingBudget(1024).build())
.build();
// 方式 A:纯 ReActAgent(最轻量,仅一个推理循环)
ReActAgent plain = ReActAgent.builder()
.name("Assistant")
.sysPrompt("你是一个乐于助人的AI助手,请友好简洁地回答问题。")
.model(model)
.toolkit(new Toolkit())
.build();
// 方式 B:HarnessAgent(推荐——开箱即用:工作区、Session、记忆、子 agent、压缩…)
HarnessAgent agent = HarnessAgent.builder()
.name("Assistant")
.sysPrompt("你是一个乐于助人的AI助手,请友好简洁地回答问题。")
.model(model)
.workspace(Path.of("./workspace"))
.build();
// 构造用户消息 —— 2.0 推荐用具体子类型
UserMessage userMsg = new UserMessage("你好,请介绍一下自己");
// 调用 Agent 并获取回复
//- streamEvents() 是 2.0 推荐的流式 API(返回 Flux)
//- call() 是简化的同步入口(返回 Mono)
String reply = agent.call(userMsg, RuntimeContext.empty()).block().getTextContent();
System.out.println(reply);
}
}
2.2 关键代码拆解
下面逐段分析代码实现,首先聚焦 Model 的构建过程。
创建模型实例
OpenAIChatModel.builder()
.apiKey(apiKey) // API Key
.modelName("deepseek-chat") // 模型名称
.baseUrl("https://api.deepseek.com")
.stream(true) // 启用流式输出
.enableThinking(true) // 启用思考模式(类似 DeepSeek 的思考链)
.formatter(new OpenAIChatFormatter()) // 格式化器,负责将消息转换为 API 格式
.defaultOptions(GenerateOptions.builder().thinkingBudget(1024) // 思考 token 预算
.build())
.build()
OpenAIChatModel 通过 Builder 模式构建。核心配置项解析:
apiKey:LLM 服务的 API Key(对接 DeepSeek 使用DEEPSEEK_API_KEY,OpenAI 则用OPENAI_API_KEY)modelName:模型标识符,DeepSeek 可选deepseek-chat或deepseek-reasoner,OpenAI 对应gpt-4o等stream:是否启用流式输出,实时获取模型增量响应enableThinking:开启思考模式后,Agent 会先执行内部推理链再生成最终回答formatter:消息格式化器,不同模型提供商需匹配对应的实现
Agent 的两种构建方式 —— ReActAgent 与 HarnessAgent 对比
AgentScope 2.0 提供两种 Agent 创建路径,根据场景灵活选择:
// 纯 ReActAgent:一个推理循环,无任何工程能力
ReActAgent.builder()
.name("Assistant")
.sysPrompt("你是一个乐于助人的AI助手,请友好简洁地回答问题。")
.model(model)
.toolkit(new Toolkit())
.build();
// HarnessAgent(推荐):在 ReActAgent 之上叠加了工作区、Session、记忆、子 agent、压缩…
// 不开任何额外能力时行为等价于裸 ReActAgent;按需打开:
HarnessAgent.builder()
.name("Assistant")
.sysPrompt("你是一个乐于助人的AI助手,请友好简洁地回答问题。")
.model(model)
.workspace(Path.of("./workspace")) // 必填:Harness 需要工作区根目录
// .stateStore(...) // 可选:分布式 AgentStateStore 后端(Redis/MySQL…)
// .compaction(...) // 可选:上下文压缩
// .subagent(...) // 可选:声明子 agent
// .skillRepository(...) // 可选:技能仓库
// .enablePlanMode() // 可选:Plan Mode(HITL 退出)
.build();
核心差异速览:
| 维度 | ReActAgent | HarnessAgent |
|---|---|---|
| 推理循环 | ✅ | ✅(继承父类) |
| 工具调用 | ✅ | ✅ |
| 会话持久化 | ❌(1.x 依赖 Memory,2.0 需自行对接 Session) | ✅ 默认 WorkspaceSession,可切换 Redis/MySQL 后端 |
| 工作区 / 长期记忆 | ❌ | ✅ |
| 上下文压缩 | ❌ | ✅ 按需开启 |
| 子 agent 编排 | ❌ | ✅ |
| 沙箱隔离 | ❌ | ✅ |
| 适用场景 | 理解 ReAct 原理、编写一次性脚本 | 生产级 / 长期运行的 Agent 应用 |
构造消息对象
2.0 推荐使用具体子类型而非通用 Msg.builder():
import io.agentscope.core.message.UserMessage;
UserMessage userMsg = new UserMessage("你好,请介绍一下自己");
四种角色均有对应子类型:UserMessage / AssistantMessage / SystemMessage / ToolResultMessage。每个子类型提供便捷构造函数及 Builder,支持附加多模态内容块(TextBlock / DataBlock / ToolUseBlock / ToolResultBlock)。
若需更精细控制(如多内容块、指名发送方),仍可回退至 Msg.builder():
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.TextBlock;
Msg userMsg = Msg.builder()
.name("alice")
.role(MsgRole.USER)
.textContent("你好") // 单文本快捷方式
.build();
// 等价于:
Msg userMsg2 = Msg.builder()
.name("alice")
.role(MsgRole.USER)
.content(TextBlock.builder().text("你好").build())
.build();
调用 Agent 并获取返回值
import io.agentscope.core.agent.RuntimeContext;
String reply = agent.call(new UserMessage("你好"), RuntimeContext.empty()).block().getTextContent();
agent.call(messages, ctx)返回Mono(Project Reactor 响应式类型),.block()将异步操作转为同步等待。RuntimeContext.empty()指不带身份信息的空上下文。生产环境应至少填充sessionId与userId,详情见第五章。- 从 2.0 起,多次
call()调用只要使用相同sessionId,历史记录自动恢复,无需手动调用memory.add(msg)。
2.3 多轮对话实战
会话历史由 Session 组件自动管理。每次调用 call() 时,只要 RuntimeContext 中的 sessionId 保持不变,系统就会自动拼接上轮上下文。这一设计极大降低上手门槛,开发者无需手动维护历史记录。
package com.example;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.formatter.openai.OpenAIChatFormatter;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.model.OpenAIChatModel;
import io.agentscope.core.tool.Toolkit;
import io.agentscope.harness.HarnessAgent;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.file.Path;
public class MultiTurnChat {
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("DEEPSEEK_API_KEY");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
OpenAIChatModel model = OpenAIChatModel.builder()
.apiKey(apiKey)
.modelName("deepseek-chat")
.baseUrl("https://api.deepseek.com")
.stream(true)
.formatter(new OpenAIChatFormatter())
.build();
HarnessAgent agent = HarnessAgent.builder()
.name("Assistant")
.sysPrompt("你是一个乐于助人的AI助手,请友好简洁地回答问题。")
.model(model)
.workspace(Path.of("./workspace"))
.build();
System.out.println("=== Chat Started ===");
System.out.println("Type 'exit' to quit");
while (true) {
System.out.print("You> ");
String input = reader.readLine();
if (input == null || "exit".equalsIgnoreCase(input.trim())) {
System.out.println("Goodbye!");
break;
}
if (input.trim().isEmpty()) {
continue;
}
// 同一 sessionId 走同一份历史;这里是 demo 用固定值
RuntimeContext ctx = RuntimeContext.builder()
.sessionId("demo-001")
.userId("alice")
.build();
String reply = agent.call(new UserMessage(input), ctx).block().getTextContent();
System.out.println("Agent> " + reply + "");
}
}
}
启动后即可与 Agent 进行多轮对话,其会记住全部历史内容。sessionId 是恢复上下文的“钥匙”——无论在哪一个节点调用,只要 sessionId 一致,都会自动合并为同一段历史。
2.4 模型切换指南
框架内置多种模型支持,切换仅需更换 Model 实现即可。以下为常见用法:
接入 OpenAI
import io.agentscope.core.model.OpenAIChatModel;
OpenAIChatModel model = OpenAIChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.stream(true)
.build();
接入 Anthropic Claude
import io.agentscope.core.model.AnthropicChatModel;
OpenAIChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName("claude-sonnet-4-20250514")
.build();
接入 Google Gemini
import io.agentscope.core.model.GeminiChatModel;
OpenAIChatModel model = GeminiChatModel.builder()
.apiKey(System.getenv("GEMINI_API_KEY"))
.modelName("gemini-2.0-flash")
.build();
接入本地模型 Ollama
// 2.0 不再有独立的 OllamaChatModel;用 OpenAIChatModel + Ollama 的 OpenAI 兼容端点
OpenAIChatModel model = OpenAIChatModel.builder()
.apiKey("ollama") // Ollama 不校验 key
.modelName("llama3")
.baseUrl("http://localhost:11434")
.build();
Ollama 无需 API Key,但需确保本地已运行 Ollama 服务。
使用 OpenAI 兼容接口
众多国产模型均提供 OpenAI 兼容接口,仅需修改 baseUrl 配置即可:
OpenAIChatModel.builder()
.apiKey("your-api-key")
.baseUrl("https://your-model-endpoint.com/v1/")
.modelName("your-model-name")
.build()
2.5 GenerateOptions 生成参数详解
GenerateOptions 用于控制 LLM 的生成行为,类似于“输出微调旋钮”:
import io.agentscope.core.model.GenerateOptions;
GenerateOptions options = GenerateOptions.builder()
.temperature(0.7) // 温度,控制随机性,范围 0-2
.topP(0.9) // 核采样参数
.maxTokens(2048) // 最大输出 token 数
.frequencyPenalty(0.0) // 频率惩罚
.presencePenalty(0.0) // 存在惩罚
.build();
// 作为 Model 的默认参数
OpenAIChatModel.builder()
.apiKey(apiKey)
.modelName("deepseek-chat")
.baseUrl("https://api.deepseek.com")
.defaultOptions(options)
.build();
调用时也可临时覆盖:
agent.call(userMsg, ctx, options).block();
2.6 流式输出机制
2.0 提供两套流式 API,新项目应直接采用推荐方案:
| API | 返回类型 | 状态 | 适用场景 |
|---|---|---|---|
agent.streamEvents(messages, ctx) | Flux | 推荐 | 新代码;仅关注父 Agent 自身事件(文本增量、工具调用、生命周期) |
agent.stream(messages, opts, ctx) | Flux | @Deprecated(forRemoval = true) | 当前唯一能实时获取子 Agent 事件的接口;待 AgentEvent 子来源通道落地后也将迁移 |
AgentEvent 体系下几种关键事件类型(均位于 io.agentscope.core.event 包):
| 事件 | 含义 |
|---|---|
AgentStartEvent / AgentEndEvent | 一次 call() 的开始/结束标记 |
TextBlockDeltaEvent | 文本增量(单个 token 或 chunk) |
ReasoningEvent | 思考过程(开启 enableThinking 后触发) |
ToolCallStartEvent / ToolResultStartEvent | 工具调用开始/工具结果开始 |
RequireUserConfirmEvent | 需要用户确认(人机协作场景) |
2.6.1 streamEvents() —— 2.0 首选方案
import io.agentscope.core.event.AgentEvent;
import io.agentscope.core.event.AgentEventType;
import io.agentscope.core.event.TextBlockDeltaEvent;
import io.agentscope.core.event.ToolCallStartEvent;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.agent.RuntimeContext;
agent.streamEvents(new UserMessage("写一首关于秋天的诗"), RuntimeContext.empty())
.doOnNext(event -> {
if (event.getType() == AgentEventType.TEXT_BLOCK_DELTA) {
System.out.print(((TextBlockDeltaEvent) event).getDelta());
} else if (event.getType() == AgentEventType.TOOL_CALL_START) {
ToolCallStartEvent start = (ToolCallStartEvent) event;
System.out.println("[tool] " + start.getToolName());
}
})
.blockLast();
streamEvents() 不会转发子 Agent 事件——子 Agent 在后台静默运行,其结果以 TOOL_RESULT 块形式回传给父 Agent。具体细节参见第七章。
2.6.2 stream() —— 已弃用但仍是获取子 Agent 事件的唯一入口
import io.agentscope.core.agent.Event;
import io.agentscope.core.agent.EventType;
import io.agentscope.core.agent.StreamOptions;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.agent.RuntimeContext;
import reactor.core.publisher.Flux;
StreamOptions streamOptions = StreamOptions.builder()
.eventTypes(EventType.REASONING, EventType.TOOL_RESULT, EventType.AGENT_RESULT)
.incremental(true) // 增量 vs 累积
.build();
Flux events = agent.stream(
List.of(new UserMessage("写一首关于秋天的诗")),
streamOptions,
RuntimeContext.empty());
events.doOnNext(event -> {
String text = event.getMessage() != null ? event.getMessage().getTextContent() : null;
if (text != null && !text.isEmpty()) {
System.out.print(text);
}
})
.blockLast();
StreamOptions 的主要配置项:
eventTypes:订阅的事件类型(REASONING思考过程、TOOL_RESULT工具结果、AGENT_RESULT最终结果)incremental:true表示增量输出(仅输出新内容),false表示累积输出(从开头到当前全部内容)
2.7 Msg 的高级用法
2.7.1 快捷创建(具体子类型)
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.message.SystemMessage;
import io.agentscope.core.message.AssistantMessage;
import io.agentscope.core.message.TextBlock;
UserMessage userMsg = new UserMessage("你好!"); // 用户消息
SystemMessage sysMsg = new SystemMessage("你是一个乐于助人的助手。"); // 系统消息
AssistantMessage asstMsg = new AssistantMessage("你好!有什么可以帮你的吗?"); // Agent 回复
2.7.2 通用 Msg.builder()
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
// 最简写法(默认 USER 角色)
Msg msg = Msg.builder().textContent("你好").build();
// 指定角色
Msg systemMsg = Msg.builder().role(MsgRole.SYSTEM).textContent("你是一个乐于助人的助手。").build();
2.7.3 读取消息内容
import io.agentscope.core.message.ContentBlock;
import java.util.List;
Msg response = agent.call(userMsg, ctx).block();
// 获取文本内容
String text = response.getTextContent();
// 获取角色
MsgRole role = response.getRole();
// 获取发送者名称
String name = response.getName();
// 获取所有内容块
List blocks = response.getContent();
2.7.4 MsgRole 枚举
public enum MsgRole {
USER, // 用户消息
ASSISTANT, // Agent 回复
SYSTEM, // 系统消息(如系统提示词)
TOOL // 工具调用结果
}
