- 人工智能
- AI Agent
- Agent 框架
- 工具调用
- Agent 工作流
- RAG
- MCP Clients
- 强化学习
【免费下载链接】agent-core-java
openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。
BaseMessageChunk 是 openJiuwen agent-core-java(com.openjiuwen.core.foundation.llm.schema包)中面向大模型流式输出场景设计的消息片段基类:它继承BaseMessage,定义消息片段的公共字段,并通过merge方法把连续到达的多个片段合并为一条完整消息。本文围绕 BaseMessageChunk.md 的类签名展开,结合仓库源码、合并工具类、模型客户端与 Agent 层的真实调用链及单元测试,讲解消息片段的数据模型、分片合并策略与在流式 Agent 推理中的落地方式,读完即可掌握 openJiuwen 中"流式分片 → 增量合并 → 完整消息"这条核心链路。
一、为什么需要消息片段(Message Chunk)
大模型推理普遍采用 SSE(Server-Sent Events)等协议逐 token 流式返回结果。一条完整的 assistant 回复会被拆分成几十上百个增量片段,每个片段只携带一小段文本、半个工具调用参数或一个finish_reason。如果调用方不处理片段,就无法拿到完整内容,也无法在流尚未结束时做增量处理(例如实时渲染文本、边生成边解析工具参数)。
openJiuwen 的设计思路与 Python 版openjiuwen/core/foundation/llm/schema/message_chunk.py保持一致:每个流式增量对应一个 Chunk 对象,Chunk 之间通过merge运算按顺序累加,最终还原为一条完整消息。BaseMessageChunk就是这套体系的地基——它定义了所有消息片段共有的字段与合并基础。
二、类定义与继承关系
BaseMessageChunk位于包com.openjiuwen.core.foundation.llm.schema,其类签名如下:
public class BaseMessageChunk extends BaseMessage源码位于 BaseMessageChunk.java,类声明同时使用了四个 Lombok 注解:
@Data @SuperBuilder @NoArgsConstructor @EqualsAndHashCode(callSuper = true) public class BaseMessageChunk extends BaseMessage { // ... }@SuperBuilder:允许子类(如AssistantMessageChunk、ToolMessageChunk)通过 Builder 链式构造,同时继承父类字段;@EqualsAndHashCode(callSuper = true):相等性判断包含父类BaseMessage的字段;@NoArgsConstructor:为无参构造提供支持,供序列化框架与合并工具使用。
继承的父类 BaseMessage.java 定义了四个公共字段:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
role | String | — | 消息角色(如system/user/assistant/tool) |
content | Object | ""(空字符串) | 消息内容,可为字符串、结构化列表或 Map |
name | String | null | 可选的消息发送方名称 |
metadata | Map<String, Object> | 空LinkedHashMap | 附加元数据 |
其中content在写入时会经过normalizeContent校验:内容必须可序列化(调用SerializationUtils.requireSerializable检查),以保证消息可以安全地跨进程传输、落盘或进入记忆/轨迹存储;metadata写入时同样会做一次拷贝(copyMetadata),避免外部 Map 被意外修改。
此外,BaseMessage提供两个实用的内容访问方法:
getContentAsString():当content是String时直接返回,否则返回toString();getContentAsList():当content是List时返回列表,否则返回null。
这两个方法在 Chunk 合并与下游消息构造中被广泛使用。
三、构造方法:从零创建一条消息片段
关联文档给出的构造签名如下:
public BaseMessageChunk(String role, Object content, String name)对应源码实现(BaseMessageChunk.java):
public BaseMessageChunk(String role, Object content, String name) { super(role, content, name, null); }它直接委托父类的(role, content, name, metadata)构造器,metadata传null(父类会将其拷贝为null而非默认空 Map,符合流式增量"不附加元数据"的语义)。注意BaseMessage的(role, content, name, metadata)构造器会对content执行normalizeContent(content, true),即允许content为null;而在实际流式链路中,OpenAIModelClient等实现会将缺失的content归一化为空字符串,避免下游空指针。
四、merge 方法:消息片段的合并基础
BaseMessageChunk的核心能力是merge,其公开签名与源码如下:
public BaseMessageChunk merge(BaseMessageChunk other) { ... }实际实现为merge(Object other)(BaseMessageChunk.java):
public BaseMessageChunk merge(Object other) { if (!(other instanceof BaseMessageChunk otherChunk)) { throw new IllegalArgumentException("Cannot merge BaseMessageChunk with " + other); } Object mergedContent = MessageChunkMerge.mergeParserContent(getContent(), otherChunk.getContent()); return new BaseMessageChunk( getRole(), mergedContent, getName() != null ? getName() : otherChunk.getName() ); }合并规则可以拆解为三点:
- 类型安全:入参必须是
BaseMessageChunk类型,否则抛出IllegalArgumentException。这与 Python 版__add__的类型校验行为一致; - 内容合并:
content交由MessageChunkMerge.mergeParserContent按类型智能合并(见下一节); - 名称保留:
name采用"左优先"策略——若当前片段已有名称则保留,否则取新片段的名称。这与"第一个片段携带 name,后续片段可能为空"的真实流式行为吻合。
合并后的结果是一个全新的BaseMessageChunk对象,原片段不被修改,符合不可变合并语义,可安全用于累加器。
五、合并策略的底层实现:MessageChunkMerge 与 MergeUtils
merge调用的核心工具是 MessageChunkMerge.java,其mergeParserContent(left, right)采用按运行时类型分派的合并策略:
| left 与 right 的类型 | 合并结果 |
|---|---|
任一为null | 返回非空一侧(两边都空则返回null) |
两侧均为String | 直接字符串拼接,如"left" + "right" = "leftright" |
两侧均为List | 先拷贝左列表,再追加右列表所有元素(concat) |
两侧均为Map | 递归执行mergeDicts做键级合并 |
| 两侧同类型且非基本标量 | 反射执行mergePydanticModels按字段合并 |
| 其他情况 | 返回右侧值("新值覆盖旧值") |
mergeDicts的键级规则同样是"字符串拼接、列表拼接、Map 递归合并,其余右值覆盖"。对同类型 POJO,mergePydanticModels借助 JavaBeans 内省(Introspector.getBeanInfo)遍历可读写属性,对每个字段递归调用mergeParserContent——这正是"工具调用参数分片拼接"的实现基础。
该工具类还额外提供两个流式字段合并能力:
concatTokenIds(left, right):把两段 token id 列表顺序拼接;mergeLogprobs(left, right):对 logprobs 的 Map 结构按 key 合并,key 下的列表执行拼接。
仓库中还提供了一份功能几乎等同、并额外抽象出Mergeable<T>接口(对应 Python 的__add__)的 MergeUtils.java,两者均明确标注"镜像 Python 版message_chunk.py",供上层按需选用。
5.1 合并工具的正确性由测试保障
MessageChunkTest.java 与 MessageChunkPythonParityTest.java 覆盖了上述全部策略,其中后者以动态测试工厂(@TestFactory)逐条对齐 Python 侧 30 个用例(如test_merge_parser_content_strings、test_merge_dicts_nested、test_merge_pydantic_models_tool_call)。测试断言包括:字符串拼接、列表拼接、嵌套 Map 递归合并、同 key 冲突时右侧覆盖等,为"Java 与 Python 行为一致"提供了可验证依据。
六、子类体系:AssistantMessageChunk 与 ToolMessageChunk
BaseMessageChunk是基类,实际流式链路中登场的是它的两个子类(见 schema.README.md 的类型清单):
6.1 AssistantMessageChunk:assistant 流式片段
AssistantMessageChunk.java 继承AssistantMessage(而AssistantMessage又继承BaseMessage),额外承载流式场景的字段:
toolCalls:工具调用增量(含id、type、name、arguments、index);finishReason:结束原因(默认字符串"null"表示"尚未结束");reasoningContent:思维链/推理内容增量;usageMetadata、promptTokenIds、completionTokenIds、logprobs、parserContent。
其merge方法对每个字段执行专门的合并策略:
content、parserContent:走mergeParserContent;reasoningContent:两侧字符串直接拼接(orEmpty处理 null);toolCalls:是最复杂的部分——按稳定键index > id > name > anon归桶(keyOf),同一调用 id 的增量片段合入同一条ToolCall并拼接arguments(appendFragment);同时处理三类流式容错场景:- 纯参数片段(无 id/name/index 仅有 arguments):合并进最近一次调用;
- 无 id 的流式增量:与最近一条 function 调用合并;
- 带不同
index的并行调用:必须保持独立,绝不误并入前一条(有专门测试assistantChunkMergeKeepsParallelCallsWithDistinctIndexesSeparate守护)。 此外还会过滤完全空的"虚片段"(isVacuousFragment);
completionTokenIds用concatTokenIds拼接、promptTokenIds采用"左优先"(preferLeft)、logprobs用mergeLogprobs合并;finishReason:只有当新片段的值为真实原因(非"null")时才覆盖旧值;usageMetadata:右非空取右,否则取左。
6.2 ToolMessageChunk:tool 流式片段
ToolMessageChunk.java 继承ToolMessage,其merge相对简单:content直接以字符串形式拼接(getContentAsString()),toolCallId采用"右非空取右、否则保留左"的策略。这与"工具执行结果可能分段返回、而调用 id 只在首段出现"的真实场景一致。
七、合并机制在模型客户端与 Agent 中的真实调用链
BaseMessageChunk的合并能力并非孤立存在,而是贯穿整个流式推理链路。
7.1 模型客户端:SSE 流解析为 Chunk
在 OpenAIModelClient.java 中,parseStreamChunk把每个 SSE 增量(choices[0].delta)解析为一个AssistantMessageChunk(缺失的content会被规范化为空字符串,finish_reason缺失时置为"null");随后mergeChunks用累加器把所有分片顺序合并为一条完整消息(OpenAIModelClient.java#L1602-L1608):
private AssistantMessageChunk mergeChunks(List<AssistantMessageChunk> chunks) { AssistantMessageChunk merged = null; for (AssistantMessageChunk chunk : chunks) { merged = merged == null ? chunk : (AssistantMessageChunk) merged.merge(chunk); } return merged; }7.2 Agent 层:边流边合并,还原完整消息
在 ReActAgent.java 中,流式推理循环同样维护一个accumulatedChunk累加器:每收到一个AssistantMessageChunk就调用accumulatedChunk.merge(chunk),待流结束后将合并结果转换为完整的AssistantMessage(携带合并后的content、toolCalls、reasoningContent、usageMetadata、token ids 与 logprobs),供后续工具调用、记忆写入或轨迹存储使用。这也说明:merge不仅是数据拼接,更是 Agent 流式行为正确性的关键一环——工具参数分片、推理内容增量、finish_reason 传递都依赖它。
八、小结
BaseMessageChunk是 openJiuwen 消息片段体系的基类,继承BaseMessage,通过merge(BaseMessageChunk other)提供片段合并基础;- 合并策略由
MessageChunkMerge/MergeUtils承载:字符串拼接、列表拼接、Map 递归合并、同类型 POJO 字段级合并,其余情况"新值覆盖"; - 子类
AssistantMessageChunk实现了包括工具调用归并、并行调用区分、推理内容拼接在内的复杂合并;ToolMessageChunk提供工具结果与toolCallId的保留式合并; - 合并机制在
OpenAIModelClient的 SSE 解析与ReActAgent的流式推理累加器中实际落地,并有MessageChunkTest与MessageChunkPythonParityTest两个测试类持续验证其与 Python 版行为的一致性。
如果需要进一步了解merge的调用前提与边界,可查阅 MessageChunkTest.java 中的合并失败用例(如非 Chunk 类型入参抛出IllegalArgumentException),以及 MessageChunkPythonParityTest.java 中与 Python 侧逐条对齐的动态测试清单。
- 人工智能
- AI Agent
- Agent 框架
- 工具调用
- Agent 工作流
- RAG
- MCP Clients
- 强化学习
【免费下载链接】agent-core-java
openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。
相关推荐
openJiuwen Agent Core Java:AssistantMessageChunk 流式消息片段与合并机制深度解析
openJiuwen Agent Core Java:AssistantMessageChunk 流式消息片段与合并机制深度解析 AssistantMessag
人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习openJiuwen Agent Core 消息基类 BaseMessage 完全指南:字段、构造、序列化与流式合并机制
openJiuwen Agent Core 消息基类 BaseMessage 完全指南:字段、构造、序列化与流式合并机制 com.openjiuwen.core
人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习openJiuwen Agent Core Java 消息模型详解:AssistantMessage 的字段、工具调用与序列化机制
openJiuwen Agent Core Java 消息模型详解:AssistantMessage 的字段、工具调用与序列化机制 本文围绕 openJiuwe
人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考