1. 写在前面
为了后续的学习,我们先来看两个概念,ReActAgent和HarnessAgent。
Agent(接口位于io.agentscope.core.agent.Agent,默认实现是ReActAgent)是 AgentScope 的核心抽象——一个推理-行动循环引擎,将模型、工具、权限系统、人机交互、上下文管理、中间件、状态管理和事件系统整合到一个统一接口中。
HarnessAgent是ReActAgent的一层薄包装,把长期运行 agent 必备的工程能力打包进单一 builder:工作区驱动的人格、长期记忆、子 agent 编排、沙箱隔离、技能装配、计划模式、Channel 路由。裸的ReActAgent只解决”一次请求 → 推理 → 工具 → 回复”。Harness 要回答的是另一组问题:下一轮怎么接着上一轮、上下文如何保持有界、多用户如何隔离、危险操作如何先 review 再执行、可复用能力如何沉淀。
有点类似于Deep Agents和LangChain的关系,HarnessAgent是针对ReActAgent的进一步包装。
整体的学习路线,我们将快速的过一遍ReActAgent的知识点,然后核心放到HarnessAgent。
2. ReActAgent配置介绍
上一篇搭建了一个简单的demo。那么这一篇我们一起看看ReActAgent具体有哪些参数和配置。
参数说明
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
name | String | 必填 | 智能体标识符,用于消息和日志 |
sysPrompt | String | 必填 | 智能体的基础系统提示词 |
model | Model | 必填 | 用于推理的大语言模型(继承自ChatModelBase) |
toolkit | Toolkit | new Toolkit() | 管理工具、MCP 客户端、技能和工具组 |
middlewares | List<? extends MiddlewareBase> | List.of() | 应用于 agent / reasoning / acting / model call / system prompt 钩子 |
stateStore | AgentStateStore | null(不持久化) | 配置后 agent 在每次call后自动加载/保存AgentState,按该次调用RuntimeContext的(userId, sessionId)寻址 |
defaultSessionId | String | agentname | 当某次调用的RuntimeContext没带sessionId时的兜底值 |
permissionContext | PermissionContextState | 默认DEFAULT模式 | 工具执行的细粒度规则 |
modelConfig | ModelConfig | 默认值 | 模型重试次数和备用模型 |
reactConfig | ReactConfig | 默认值 | 最大迭代次数和拒绝处理方式 |
maxIters | int | 10 | ReAct 主循环最大迭代次数(也可放在reactConfig中) |
有一些基础的参数我们就不展开赘述了。这里先大体一览,后面我们会逐渐介绍。
本篇所有的代码,下面会用到,不再一一贴了:
packagecom.maple.agent.demo;importcom.maple.agent.MapleAgentApplication;importcom.maple.agent.config.OpenAiModelConfig;importio.agentscope.core.ReActAgent;importio.agentscope.core.event.*;importio.agentscope.core.message.*;importio.agentscope.extensions.model.openai.OpenAIChatModel;importorg.springframework.boot.SpringApplication;importorg.springframework.context.ConfigurableApplicationContext;publicclassReActAgent_02{publicstaticvoidmain(String[]args){// 为了方便提及代码,不暴露隐私信息,密钥放到application-pro.yml中ConfigurableApplicationContextctx=SpringApplication.run(MapleAgentApplication.class,args);OpenAiModelConfigprops=ctx.getBean(OpenAiModelConfig.class);OpenAIChatModelmodel=OpenAIChatModel.builder().baseUrl(props.getBaseUrl()).apiKey(props.getApiKey()).modelName(props.getModelName()).stream(true).build();ReActAgentagent=ReActAgent.builder().name("笑小枫的学习助手").model(model).sysPrompt("你是笑小枫的学习助手,可以帮助用户快速学习使用AgentScope开发智能体。").build();// call(agent);streamEvents(agent);}// 多模态消息,直接返回结果privatestaticvoidcall(ReActAgentagent){UserMessageuserMessage=newUserMessage("user",TextBlock.builder().text("这张图片是什么?").build(),DataBlock.builder().source(URLSource.builder().url("https://image.xiaoxiaofeng.site/blog/image/image-20260719154443011.png?xiaoxiaofeng").build()).build());Msgreply=agent.call(userMessage).block();System.out.println(reply.getTextContent());}// 流式返回结果privatestaticvoidstreamEvents(ReActAgentagent){agent.streamEvents(newUserMessage("user","帮我制定学习计划")).doOnNext(event->{if(eventinstanceofAgentStartEventstart){System.out.println("[start replyId="+start.getReplyId()+"]");}elseif(eventinstanceofTextBlockDeltaEventdelta){System.out.print(delta.getDelta());}elseif(eventinstanceofAgentEndEventend){System.out.println("\n[完成]");}}).blockLast();}// 演示执行事件,流式返回结果privatestaticvoidstreamEvents2(ReActAgentagent){StringBuilderaccumulated=newStringBuilder();agent.streamEvents(newUserMessage("user","你是谁?")).doOnNext(event->{if(eventinstanceofAgentStartEventstart){System.out.println("[start replyId="+start.getReplyId()+"]");}elseif(eventinstanceofThinkingBlockStartEventthinkingBlockStartEvent){System.out.println("[思考开始 replyId="+thinkingBlockStartEvent.getReplyId()+"]");}elseif(eventinstanceofThinkingBlockDeltaEventthinkingBlockDeltaEvent){System.out.println("[思考内容 ="+thinkingBlockDeltaEvent.getDelta()+"]");}elseif(eventinstanceofThinkingBlockEndEventthinkingBlockEndEvent){System.out.println("[思考完成 replyId="+thinkingBlockEndEvent.getReplyId()+"]");}elseif(eventinstanceofTextBlockDeltaEventdelta){accumulated.append(delta.getDelta());}elseif(eventinstanceofToolCallStartEventtc){System.out.println("[tool] "+tc.getToolCallName());}elseif(eventinstanceofToolResultEndEventend){System.out.println("[tool result state="+end.getState()+"]");}elseif(eventinstanceofAgentEndEventend){System.out.println("\n[end] full text:\n"+accumulated);}}).blockLast();}}3. 消息类型
Msg(位于io.agentscope.core.message)代表对话中的一个轮次——用户输入、智能体回复或系统指令,内容以有序的类型化块(ContentBlock)列表表示。
Msg类的核心字段(getter)如下:
| 方法 | 类型 | 说明 |
|---|---|---|
getId() | String | 唯一消息标识符 |
getName() | String | 发送方名称(可空) |
getRole() | MsgRole | USER/ASSISTANT/SYSTEM/TOOL |
getContent() | List<ContentBlock> | 有序内容块列表(不可变) |
getMetadata() | Map<String, Object> | 任意键值元数据 |
getTimestamp() | String | 创建时间(yyyy-MM-dd HH:mm:ss.SSS) |
getUsage() | ChatUsage | Token 用量(仅 assistant 消息) |
getGenerateReason() | GenerateReason | 退出原因:MODEL_STOP/TOOL_SUSPENDED/REASONING_STOP_REQUESTED/ACTING_STOP_REQUESTED/ALL_TOOLS_DENIED/INTERRUPTED/MAX_ITERATIONS |
通过断点图,可以比较直观的看到上面的内容。
内容块
消息内容由类型化的块组成,每种块代表一类独立信息。块类位于io.agentscope.core.message:
| 块类型 | 说明 | 允许出现在 |
|---|---|---|
TextBlock | 纯文本内容 | USER、ASSISTANT、SYSTEM |
DataBlock | 二进制数据(图片、音频、视频),通过 base64 或 URL;统一替代旧的 ImageBlock/AudioBlock/VideoBlock | USER、ASSISTANT |
ThinkingBlock | 模型推理过程(思维链) | ASSISTANT |
ToolUseBlock | 工具调用,包含id/name/input/state(ToolCallState) | ASSISTANT |
ToolResultBlock | 工具执行结果,包含state(ToolResultState) | ASSISTANT |
HintBlock | 以用户上下文形式注入循环的指令 | ASSISTANT |
我们用到的比较多的也就是前面两个,看一下怎么使用:
privatestaticvoidcall(ReActAgentagent){UserMessageuserMessage=newUserMessage("user",TextBlock.builder().text("这张图片是什么?").build(),DataBlock.builder().source(URLSource.builder().url("https://image.xiaoxiaofeng.site/blog/image/image-20260719154443011.png?xiaoxiaofeng").build()).build());Msgreply=agent.call(userMessage).block();System.out.println(reply.getTextContent());}返回的结果如下:
关于ThinkingBlock和Tool相关(后面讲到工具再说)的消息,只有在streamEvents方式中能更好的体现。
4. 输出方式
我们上一篇演示的demo,直接返回了结果,如果我们想要打字机的那种展示怎么做呢,这里我们详细的讲一讲
- call
call在内部消费所有事件,当智能体完成或因外部交互暂停时返回最终Msg。
streamEvents
构建流式输出。
streamEvents逐一产出AgentEvent对象,让你实时将文本输出、工具调用进度和生命周期事件流式传输给用户。
5. 执行事件
在上面我们看到了各种执行过程中的事件,事件是消息的流式对应物。每种类型的事件,都遵循start → delta → end模式。
其中delta可能产生多次,属于增量内容片段。
关于事件的执行顺序和生命周期,这里就不展开了,可以前往AgentScope官网查看。
https://java.agentscope.io/v2/zh/docs/building-blocks/message-and-event
6. 本章小结
本篇围绕ReActAgent展开,主要讲了三件事:
1. 概念区分:ReActAgent是 AgentScope 的核心抽象,负责“一次请求 → 推理 → 工具 → 回复”的循环;HarnessAgent是它的薄包装,额外打包了工作区、长期记忆、子 agent、沙箱等工程能力。
2. 消息体系:Msg代表对话中的一个轮次,由有序的ContentBlock组成。常用的有TextBlock(文本)和DataBlock(图片/音频/视频等多模态数据),此外还有ThinkingBlock(思维链)、ToolUseBlock/ToolResultBlock(工具调用与结果)、HintBlock(注入指令)。
3. 输出方式和执行事件:call会内部消费所有事件,等智能体完成或暂停后返回最终Msg,适合一次性拿结果;streamEvents则逐一产出AgentEvent,遵循start → delta → end模式,适合打字机式的实时展示。
本篇就到这里了,下一篇会深入介绍下ReActAgent的Middleware的扩展能力。
本系列源码https://gitee.com/hack-feng/maple-agent-demo