系列第 3 篇 · 启航篇
上两篇我们做到了:知道 AgentScope Java 是什么、还亲手跑通了第一个 HarnessAgent。但说实话------它到底是怎么"想"的?消息在内部怎么流转?模型又是怎么被调用的?
如果你对这三个问题还是模糊的,那后面讲工具调用、记忆、RAG、多 Agent 时,你只能"照着抄代码",一旦报错就无从下手。
所以这一篇,我们把镜头彻底拉远,讲清楚支撑一切的四个核心概念:Agent、Message、Model、ReAct 循环,外加 2.0 一个很 Java 工程师胃口的理念------透明开发。理解了它们,后面所有能力你都会"知其所以然"。
一、先建立一张全景图
AgentScope 的世界里,有四类核心抽象,它们的关系一句话就能说清:
Agent 在 ReAct 循环里,不断用 Message 与 Model 对话,直到产出最终答案。
sql
graph TD
A[Agent 智能体] -->|运行于| R[ReAct 循环]
R -->|用 Message 与| M[Model 大脑]
R -->|产出/消费| MSG[Message 消息]
A -->|两种形态| RA[ReActAgent推理循环]
A -->|两种形态| HA[HarnessAgent工程封装]
Agent是"执行者",负责把任务跑完;
ReAct是它内部的"运转机制",决定每一步是继续推理还是调用工具;
Message是 Agent 与模型、Agent 与 Agent 之间流通的"通用语言";
Model是 Agent 的"大脑",负责真正的推理与生成。
记住这张图,后面每篇都是在给其中一块"添砖加瓦"。
二、Agent:智能体的两种形态
在 2.0 里,"Agent"不是只有一个类,而是分两层,这也是 2.0 最重要的设计取舍:
1.ReActAgent------ 推理核心
它实现了 ReAct 算法(Reasoning + Acting 循环),负责"这一次推理怎么跑":纯推理、工具调用、记忆读写等原子能力都长在它上面。如果你只是想理解原理、或写个一次性脚本,用它就够了。
2.HarnessAgent------ 工程外壳(2.0 推荐)
它在 ReActAgent 之上,负责"长期运行的 Agent 怎么稳定、安全、可扩展":工作区、会话持久化、长期记忆、上下文压缩、沙箱隔离、权限控制......这些都是通过 Middleware / Toolkit叠加上去的,关键点在于它不改写推理循环。
2.0 一句话定位:推理归 ReAct,运行归 Harness。两者职责分离,所以你既能享受稳定工程能力,又不必担心底层推理被改坏。
cs
// 2.0 推荐:直接 HarnessAgent 起步
HarnessAgent agent = HarnessAgent.builder()
.name("assistant")
.sysPrompt("你是一个有用的 AI 助手。")
.model("dashscope:qwen-plus") // ModelRegistry 解析
.workspace(Paths.get(".agentscope/workspace"))
.build();
(如果你只想要"裸推理"做实验,把 HarnessAgent 换成 ReActAgent、workspace(...) 去掉即可,其余写法一致。)
三、Message:Agent 之间的"通用语言"
这是 2.0 变化最大、也最值得理解的一块。很多 1.x 教程里把消息当"一句话",2.0 把它升级成了结构化的对话轮次。
3.1 一个关键认知
Msg(包 io.agentscope.core.message)不是"一句话",而是完整的一轮对话------可以是用户的一句输入、Agent 的一次回复、或一条系统指令。特别重要:一条 assistant 消息对应一次完整的call周期(内含多轮"推理 + 行动",直到最终回复)。
3.2 内容由"内容块"组成
2.0 里消息内容是有序的、带类型的 ContentBlock 列表,而不是一坨字符串。常见块类型:
| 块类型 | 说明 | 出现在哪些角色 | | --- | --- | --- | | TextBlock | 纯文本 | USER / ASSISTANT / SYSTEM | | DataBlock | 多模态:图片/音频/视频(base64 或 URL) | USER / ASSISTANT | | ThinkingBlock | 模型的思维链(推理过程) | ASSISTANT | | ToolUseBlock | 一次工具调用(id / name / input / state) | ASSISTANT | | ToolResultBlock | 工具执行结果 | ASSISTANT | | HintBlock | 以"用户上下文"形式注入循环的指令 | ASSISTANT |
角色约束在构造时就强制生效:USER 只允许文本/数据/媒体块,SYSTEM 只允许 TextBlock,ASSISTANT 则全部允许。
3.3 角色固定子类(2.0 推荐写法)
2.0 推荐直接用按角色固定的子类,而不是通用的 Msg.builder():
java
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.message.SystemMessage;
import io.agentscope.core.message.AssistantMessage;
// 便捷构造:普通字符串会自动包成 TextBlock
UserMessage userMsg = new UserMessage("你好,请介绍一下自己");
SystemMessage sysMsg = new SystemMessage("system", "你是一个乐于助人的助手。");
AssistantMessage asstMsg = new AssistantMessage("agent", "好的,我是......");
// 读取内容
String text = userMsg.getTextContent(); // 所有 TextBlock 用 \n 拼接
多模态也不复杂,直接往里塞 DataBlock 即可:
bash
UserMessage multi = new UserMessage(
"user",
TextBlock.builder().text("描述这张图:").build(),
DataBlock.builder()
.source(Base64Source.builder()
.data("...base64...")
.mediaType("image/png")
.build())
.build());
小贴士:2.0 用
DataBlock统一替代了旧的ImageBlock/AudioBlock/VideoBlock,新代码建议直接用DataBlock。
四、Model:Agent 的"大脑"
Model 负责真正的推理与生成。AgentScope 用 Model 接口(各厂商聊天模型继承 ChatModelBase 抽象类)把各家大模型 API 的差异屏蔽掉,让你写业务时不用关心"这是通义千问还是 OpenAI"。
4.1 2.0 新写法:字符串模型
2.0 引入了最省事的写法------字符串模型,由 ModelRegistry 解析并自动读取对应的 API Key(如 DASHSCOPE_API_KEY):
php
.model("dashscope:qwen-plus") // qwen-max / qwen-turbo 同理
4.2 需要精细控制时:传 Model 实例
字符串只能跑默认配置。如果你的场景需要指定温度、超时、代理等,2.0 仍然支持直接传 Model 实例(例如 DashScopeChatModel.builder()...build())------.model()既收字符串、也收Model实例,这点不要被"字符串写法"误导。
4.3 Formatter:你不用关心的适配层
不同厂商的 API 格式天差地别,AgentScope 用 Formatter 在内部自动转换消息格式(DashScopeChatFormatter、OpenAIChatFormatter、AnthropicChatFormatter、GeminiChatFormatter......)。通常你不用手动配,框架按 Model 类型自动选。
一句话:2.0 里"换模型" ≈ 改一个字符串前缀(
dashscope:/deepseek:/openai:),前提是装了对应的agentscope-extensions-model-*扩展包。
五、ReAct 循环:Agent 到底怎么"思考"
这是整个框架的发动机,也是理解一切能力的前提。
ReAct = Reasoning(推理)+ Acting(行动)。它不像传统"写死的流程图"那样一步步走,而是让模型动态决策:这一步是该直接回答,还是该先调个工具拿数据再说。
一次 call 的典型循环:
sql
flowchart TD
Start([用户消息]) --> Think[Reasoning:模型推理]
Think --> Decide{需要调用工具?}
Decide -->|否| Answer([最终回复 / MODEL_STOP])
Decide -->|是| Action[Acting:执行 Tool]
Action --> Observe[Observation:拿到工具结果]
Observe --> Think
用"查天气"举个具体例子:
Reasoning(思考):用户问"北京今天天气?"→ 模型判断:我得先查天气,不能直接编。
Acting(行动):调用 get_weather(city="北京")。
Observation(观察):工具返回"北京:晴,25℃"。
Reasoning 再思考:信息齐了,可以作答。
最终回复:"北京今天晴,25℃,注意防晒。"
如果信息还不够,模型会带着新观察再走一轮循环------这就是"自主规划、动态决策",也是它比刚性工作流强的地方。
每轮 call 结束时,框架会给出一个 GenerateReason(终止原因),常见的有:MODEL_STOP(正常答完)、TOOL_SUSPENDED(工具执行被挂起,常见于需要人工确认)、MAX_ITERATIONS(到达最大循环次数)、INTERRUPTED 等。生产代码一定要处理这些状态,别默认"每次都能拿到正常回答"。
六、透明开发:用 Middleware 窥探 ReAct 全过程
企业落地的老大难是:Agent "黑盒决策",出了错查不到为什么。AgentScope 的解法是Middleware(中间件)体系------在 Agent 生命周期的 5 个关键执行点挂上你自己的逻辑(日志、监控、改消息、改系统提示词),把推理全过程"摊开"给你看。
2.0 的 MiddlewareBase 接口暴露了 5 个钩子("洋葱模型",before/after 包裹执行):
| 钩子 | 拦截点 | 常用场景 | | --- | --- | --- | | onAgent | 整个 Agent 调用 | 入口日志、鉴权 | | onReasoning | 推理 / 模型调用阶段 | 打印推理文本、耗时 | | onActing | 单次工具调用执行 | 打印工具入参 / 结果 | | onModelCall | 原始模型 API 调用 | 改请求、归一化输出 | | onSystemPrompt | 系统提示词(管道式) | 动态注入上下文 |
旧版
Hook接口自 2.0.0 起已标记@Deprecated(forRemoval = true),官方推荐统一改用Middleware。
来看一个"调试中间件",把推理流式文本和工具调用实时打印出来------这就是"透明开发"最直观的体现:
typescript
import io.agentscope.core.middleware.MiddlewareBase;
import io.agentscope.core.middleware.ReasoningInput;
import io.agentscope.core.middleware.ActingInput;
import io.agentscope.core.agent.Agent;
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.event.AgentEvent;
import io.agentscope.core.event.TextBlockDeltaEvent;
import io.agentscope.core.event.ToolResultEndEvent;
import java.util.function.Function;
import reactor.core.publisher.Flux;
MiddlewareBase debugMiddleware = new MiddlewareBase() {
@Override
public Flux onReasoning(
Agent agent, RuntimeContext ctx, ReasoningInput input,
Function> next) {
System.out.println("\n[思考] Agent 开始推理 (session=" + ctx.getSessionId() + ")");
return next.apply(input)
.doOnNext(event -> {
if (event instanceof TextBlockDeltaEvent e) {
System.out.print(e.getDelta()); // 推理流式文本增量
}
})
.doOnComplete(() -> System.out.println("\n[思考] 推理结束"));
}
@Override
public Flux onActing(
Agent agent, RuntimeContext ctx, ActingInput input,
Function> next) {
input.toolCalls().forEach(tc ->
System.out.println("[行动] 执行工具 → " + tc.getName()));
return next.apply(input)
.doOnNext(event -> {
if (event instanceof ToolResultEndEvent e) {
System.out.println("[行动] 工具 " + e.getToolCallName()
+ " 结果状态=" + e.getState());
}
});
}
};
// 挂到 Agent 上(HarnessAgent / ReActAgent 通用)
HarnessAgent agent = HarnessAgent.builder()
.name("assistant")
.sysPrompt("你是一个有用的 AI 助手。")
.model("dashscope:qwen-plus")
.workspace(Paths.get(".agentscope/workspace"))
.middleware(debugMiddleware) // 2.0 推荐:用 Middleware 而非已弃用的 Hook
.build();
中间件的返回值走 Project Reactor(
Flux<AgentEvent>/Mono<String>),生产里你完全可以把这些事件推到 OpenTelemetry 或 AgentScope Studio 做可视化调试------更完整的可观测能力我们放到"生产篇"再展开。
七、新手最常误解的 5 个点
把Msg当成"一句话"------ 它是完整的"一轮对话",一条 assistant 消息可能内含多轮推理+工具调用。
还在用Msg.builder()通用写法------ 2.0 推荐角色固定子类(UserMessage/AssistantMessage/SystemMessage/ToolResultMessage),约束更清晰、不易写错。
以为.model()只认字符串------ 它也能收 ChatModel 实例,需要精细配置温度/超时/代理时用后者。
非 DashScope 模型却没装扩展包------ dashscope: 适配器也要单独引 agentscope-extensions-model-dashscope,并非 harness 内置;换 DeepSeek / OpenAI 等同样要补对应的 agentscope-extensions-model-*。
忽视GenerateReason------ 别假设每次都 MODEL_STOP;TOOL_SUSPENDED、MAX_ITERATIONS 等中断状态要在业务里妥善处理。
小结
这一篇你建立起了 AgentScope Java 2.0 的心智模型:
Agent分两层:ReActAgent(推理核心)与 HarnessAgent(工程外壳,2.0 推荐);
Message是结构化对话轮次,由带类型的 ContentBlock 组成,2.0 推荐角色固定子类;
Model是大脑,ChatModel 屏蔽厂商差异,2.0 用字符串 + ModelRegistry 极简接入;
ReAct 循环= 推理→行动→观察 的不断迭代,支持动态决策与自主规划;
透明开发靠 Middleware 把推理全过程"摊开"给你看,这是企业落地可控性的关键。
理解这四块,后面所有能力------工具、记忆、RAG、多 Agent------本质上都是"给 Agent 接更多 Message 来源 / 更多 Model 能力 / 更丰富的循环分支"。
下一篇,我们就从最实用的一块切入:让 Agent 真正"动手"------Tool Calling 工具调用实战。你会看到 ReAct 循环里那个 "Acting" 步骤,是怎么被你自己的 Java 方法填满的。
下篇预告:《让 Agent 会"用工具":Tool Calling 实战》
本篇基于 AgentScope Java 2.0。API 与概念以官方文档(java.agentscope.io)最新 2.0.x 为准,建议结合对应版本动手实验。