news 2026/10/13 2:42:36

openJiuwen Agent Core 的 ToolMessage 类深入解析:tool 角色消息的字段、构造、序列化与在工具调用链路中的实际应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
openJiuwen Agent Core 的 ToolMessage 类深入解析:tool 角色消息的字段、构造、序列化与在工具调用链路中的实际应用
  • 人工智能
  • AI Agent
  • Agent 框架
  • 工具调用
  • Agent 工作流
  • RAG
  • MCP Clients
  • 强化学习

【免费下载链接】agent-core-java

openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。

项目地址:https://gitcode.com/openJiuwen/agent-core-java
点击查看免费下载

ToolMessage是 openJiuwen agent-core-java 中用于承载工具(tool)执行结果的统一消息载体,属于com.openjiuwen.core.foundation.llm.schema消息体系的一员。它继承自BaseMessage,以tool角色将工具返回内容回灌到对话上下文中,供大模型在下一轮继续推理。阅读本文后,你将掌握ToolMessage的字段语义、全部构造方法与序列化行为,并了解它在工具超时、执行失败、中断恢复、消息流式合并等真实场景中的调用方式,能够直接在 Agent 开发中正确构造与消费 tool 消息。

类概览:tool 角色消息在消息体系中的定位

ToolMessage定义于 core/src/main/java/com/openjiuwen/core/foundation/llm/schema/ToolMessage.java,源码注释明确说明它镜像了 Python 版本 openJiuwen 中openjiuwen/core/foundation/llm/schema/message.py的同名类,保证了 Java 端与 Python 端在消息协议上的一致性。

public class ToolMessage extends BaseMessage

在llm.schema包中,消息类族呈如下继承关系:

类角色说明
BaseMessage—消息基类,定义role、content、name、metadata公共字段
SystemMessagesystem系统提示消息
UserMessageuser用户输入消息
AssistantMessageassistant模型回复消息,可携带toolCalls工具调用请求
ToolMessagetool工具执行结果消息,通过toolCallId与 assistant 发出的调用请求一一对应

BaseMessage(见 core/src/main/java/com/openjiuwen/core/foundation/llm/schema/BaseMessage.java)提供了以下公共字段与能力:

  • role:消息角色(system/user/assistant/tool);
  • content:消息内容,默认值为空字符串"",经normalizeContent做可序列化校验;
  • name:可选的参与者名称(如工具名);
  • metadata:扩展元数据,默认LinkedHashMap空集合;
  • modelDump()/model_dump():将消息导出为Map<String, Object>,供模型接口请求或协议转换使用;
  • getContentAsString()/getContentAsList():按内容类型取字符串或列表形态。

ToolMessage正是在此基础上添加了toolCallId字段,用于把工具结果与模型发出的调用请求关联起来。

字段详解:toolCallId 与继承自 BaseMessage 的公共字段

ToolMessage自身仅声明一个字段:

字段JSON 属性名说明
private String toolCallIdtool_call_id保存toolCallId标识,用于关联 assistant 消息中对应的ToolCall.id

注意,该字段使用了 Jackson 的@JsonProperty("tool_call_id")注解(源码第 29-30 行),因此在 JSON 反序列化与modelDump()导出时均使用下划线命名的tool_call_id键,这与 OpenAI Chat 兼容协议一致。

其余字段全部继承自BaseMessage:

  • role:ToolMessage 的 role 固定为tool;
  • content:工具返回的执行结果文本(或结构化的可序列化对象);
  • name:工具名称,便于在多工具场景中标识结果来源;
  • metadata:可选的附加元数据。

此外,类上标注了 Lombok 注解@Data、@SuperBuilder、@NoArgsConstructor、@AllArgsConstructor、@EqualsAndHashCode(callSuper = true)与@JsonInclude(JsonInclude.Include.NON_NULL),意味着:

  • 支持链式 Builder 构造与无参/全参构造;
  • equals/hashCode会连同父类字段一并比较;
  • 序列化时自动忽略null字段,保证content之外的字段缺省时不写入 JSON。

构造方法:两种便捷入参方式

签名说明
public ToolMessage(String content, String toolCallId)以内容与调用 ID 构造实例,内部调用super("tool", content)将 role 固定为tool
public ToolMessage(String content, String toolCallId, String name)在上一构造基础上额外设置name(工具名)

两处构造方法均为源码级事实(ToolMessage.java):

public ToolMessage(String content, String toolCallId) { super("tool", content); this.toolCallId = toolCallId; } public ToolMessage(String content, String toolCallId, String name) { this(content, toolCallId); setName(name); }

实际项目中三种构造方式都会被使用:

  • 只有内容与调用 ID(如中断/失败占位消息,见下文 LlmEventHandler 示例);
  • 内容、调用 ID 加工具名(工具正常执行后的完整结果,name携带工具名便于模型区分来源);
  • 无参构造 +setToolCallId/setContent/setName(用于从 JSON 或协议 Map 反序列化恢复,如 Serializer.java 中的restoreToolMessage)。

方法详解:getRole 的兜底语义与 modelDump 导出

getRole():默认值兜底

@Override public String getRole() { String value = super.getRole(); return value != null ? value : "tool"; }

ToolMessage重写了getRole():当父类role字段为null(例如通过无参构造创建、尚未显式 setRole 时),返回默认值"tool"。这一兜底逻辑保证了工具消息无论以何种方式构造,角色语义始终正确,与AssistantMessage.getRole()对"assistant"的处理方式完全对称。

modelDump():导出为协议友好的 Map

@Override public Map<String, Object> modelDump() { Map<String, Object> result = super.modelDump(); result.put("role", getRole()); if (toolCallId != null) { result.put("tool_call_id", toolCallId); } return result; }

modelDump()在父类导出结果(role、content、可选的name、metadata)基础上,强制覆盖role为"tool",并在toolCallId非空时写入tool_call_id键;同时提供 Python 风格别名model_dump()。该方法是消息进入 LLM 请求体前的标准转换入口。

由测试 MessageTest.java 可以验证上述行为:

ToolMessage toolMessage = new ToolMessage("done", "call-9"); assertEquals("tool", toolMessage.getRole()); assertEquals("call-9", toolMessage.modelDump().get("tool_call_id"));

实战链路:ToolMessage 在工具调用生命周期中的典型用法

ToolMessage并非孤立的数据类,而是贯穿工具调用链路的实际载体。以下用法均可从仓库源码中找到依据。

正常执行:将工具结果回灌上下文

在单智能体能力管理器中,工具执行完成后会构造ToolMessage携带结果(见 core/src/main/java/com/openjiuwen/core/singleagent/AbilityManager.java):

toolMessage = new ToolMessage(String.valueOf(toolResult), toolCall.getId(), toolCall.getName());

content为工具返回值文本、toolCallId取自模型发出的ToolCall.id、name为工具名,三者构成了完整可追溯的工具消息。随后它被写入上下文(AgentContext),模型在下一轮即可看到该工具的输出。通用工具消息写入入口封装在 MessageUtils.addToolMessage 中,直接对接上下文引擎的addMessages。

超时与失败:以 ToolMessage 承载错误占位

工具执行可能超时或抛错,这两种情况同样通过ToolMessage表达:

  • 超时:AbilityManager.safeRailedExecuteOne在工具超时时构造超时提示消息(AbilityManager.java):
String timeoutMsg = "Tool execution timed out after " + timeoutMillis + "ms for tool: " + toolCall.getName(); ToolMessage toolMsg = new ToolMessage(timeoutMsg, toolCall.getId(), toolCall.getName());
  • 失败:AbilityExecutionError.java 中AbilityExecutionError.of(toolCall, message)会把错误文本封装为ToolMessage并随异常抛出,供上层统一处理。

控制器场景:中断占位与失败标记

在 core/src/main/java/com/openjiuwen/core/application/llm/LlmEventHandler.java 中,ToolMessage还被用作流程占位消息:

  • 任务中断时写入new ToolMessage("[INTERRUPTED - Waiting for user input]", task.getTaskId())(第 497-500 行),表示等待用户输入;
  • 任务失败时写入new ToolMessage("[FAILED - " + errorMsg + "]", task.getTaskId())(第 548 行);
  • 工具无返回结果时写入new ToolMessage("", task.getTaskId(), toolName)(第 461 行),保证对话上下文始终有完整的 tool 消息。

由此可见,ToolMessage是工具调用流程中“正常结果、超时、失败、中断、空结果”等所有分支的统一返回形态。

流式场景:ToolMessageChunk 的合并语义

与消息类族配套,ToolMessage还有流式片段版本ToolMessageChunk(core/src/main/java/com/openjiuwen/core/foundation/llm/schema/ToolMessageChunk.java),它继承ToolMessage并实现merge方法:

public ToolMessageChunk merge(Object other) { if (!(other instanceof ToolMessageChunk otherChunk)) { throw new IllegalArgumentException("Cannot merge ToolMessageChunk with " + other); } return ToolMessageChunk.builder() .role("tool") .content(getContentAsString() + otherChunk.getContentAsString()) .toolCallId((otherChunk.getToolCallId() != null && !otherChunk.getToolCallId().isEmpty()) ? otherChunk.getToolCallId() : getToolCallId()) .build(); }

合并规则为:content顺序拼接;toolCallId优先取后一个片段非空的值,否则沿用前一片段的值。仓库中的 MessageChunkPythonParityTest.java 与 MessageChunkTest.java 专门用于校验该类与 Python 端片段合并行为的一致性。

序列化与持久化:JSON 字段与协议恢复

由于toolCallId标注了@JsonProperty("tool_call_id"),ToolMessage与 OpenAI 兼容 JSON 之间可以无损互转。在 core/src/main/java/com/openjiuwen/core/graph/store/Serializer.java 的restoreToolMessage中,反序列化要求:

  • 允许的字段集合为{type, role, content, name, toolCallId};
  • role必须为"tool";
  • toolCallId为必需字段(requireStringField),content通过fromProtocolValue还原为协议值,name可缺省。

这意味着一份合法的 tool 消息协议表示至少包含role=tool、content与tool_call_id三个要素。同样的恢复逻辑还出现在上下文落盘场景中,例如 SessionModelContext.java 与 OffloadMessageBuffer.java,它们均从持久化 Map 中读取tool_call_id键重建ToolMessage。

小结

维度要点
定位llm.schema包中表示 tool 角色完整消息的类,继承BaseMessage
核心字段toolCallId(JSON 键tool_call_id),关联 assistant 的ToolCall.id
构造方式(content, toolCallId)、(content, toolCallId, name)、无参 + setter、@SuperBuilder
角色兜底getRole()在父类 role 为 null 时返回"tool"
导出协议modelDump()输出含role、content、tool_call_id的 Map
典型用途工具正常结果、超时/失败/中断占位消息回灌上下文
流式扩展ToolMessageChunk.merge拼接 content、择优保留 toolCallId

在 openJiuwen Agent Core 的工具调用闭环中,ToolMessage是连接“模型发出 ToolCall”与“工具执行结果回到上下文”的关键一环。正确理解并构造ToolMessage,是开发 ReAct、Workflow 等各类 Agent 时保证多轮工具对话上下文完整、可追溯的基础。更多消息类族信息可参考 llm/schema 包说明 及同目录下的AssistantMessage、ToolCall等文档。

  • 人工智能
  • AI Agent
  • Agent 框架
  • 工具调用
  • Agent 工作流
  • RAG
  • MCP Clients
  • 强化学习

【免费下载链接】agent-core-java

openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。

项目地址:https://gitcode.com/openJiuwen/agent-core-java
点击查看免费下载

相关推荐

上一篇:重新定义远程管理:MobaXterm中文版如何彻底解决工具碎片化难题
下一篇:洛雪音乐终极配置指南:5分钟搭建你的免费无损音乐库

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 2:40:10

Windows下部署Copaw:飞书AI机器人接入全流程指南

部署 AI 机器人到办公 IM 这件事&#xff0c;我前后折腾过不少次&#xff0c;Copaw 算是其中让我觉得"安装到飞书集成"这条链路走得比较顺的一个。Copaw 本质上是一个把大模型能力接进飞书的机器人网关服务&#xff0c;你把它部署在 Windows 机器上&#xff0c;配置好…

作者头像 李华
网站建设 2026/10/13 2:36:11

线段树模拟费用流:高效解决产品销售利润最大化问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 2:35:19

Zynq7020 Linux开发实战:环境搭建、SD卡启动与设备树配置

1. 为什么一块Zynq7020开发板值得折腾Linux实验手里这块Zynq7020开发板已经放了小半年&#xff0c;最近终于抽出时间把Linux相关的实验从头到尾跑了一遍。说实话&#xff0c;如果你之前只玩过单片机裸机开发&#xff0c;第一次接触Zynq系列的芯片&#xff0c;大概率会有点懵——…

作者头像 李华
网站建设 2026/10/13 2:34:40

Linux内核加固:从__user指针校验到LKRG运行时防护

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华