消息(Message)与事件(Event)是 AgentScope 中两种基础数据结构。
- 消息 — 智能体间通信与上下文持久化的基本单元。
- 事件 — 前后端交互与流式传输的基本单元,支持人工介入(Human-in-the-loop)场景。
AgentScope 中Msg类的一个实例容纳一次完整的对话信息——一次用户输入或一次完整的智能体回复,信息以不同类型的内容块(Block)进行组织。
- 智能体运行一次
reply产生一个完整的Msg实例,包含多轮的思考,工具调用,运行结果等所有信息。
- 前端渲染时,一个
Msg实例即对应渲染成一个完整的消息气泡。
Msg 类的核心字段如下:
| 字段 | 类型 | 说明 |
|---|
id | str | 唯一消息标识符 |
name | str | 发送方名称 |
role | "user" | "assistant" | "system" | 发送方角色 |
content | list[ContentBlock] | 有序内容块列表 |
metadata | dict | 任意键值元数据 |
created_at | str | 创建时间(ISO 8601) |
finished_at | str | None | 消息完成时间(ISO 8601) |
usage | Usage | 次元用量统计(仅 assistant 消息) |
内容块
消息内容由类型化的块组成,每种块代表一类独立信息:
| 块类型 | 说明 |
|---|
TextBlock | 纯文本内容 |
DataBlock | 二进制数据(图片、音频、视频等),可以是 base64 或 URL |
ThinkingBlock | 模型推理过程(思维链) |
ToolCallBlock | 工具调用,包含名称、输入和状态 |
ToolResultBlock | 工具执行结果 |
HintBlock | 提示信息(例如调度任务触发、团队消息、后台工具结果),支持多模态数据,使用source标识提示来源。 |
角色约束在构造时强制执行:
msg.role=="user"的消息只能包含TextBlock和DataBlock;
msg.role=="system"的消息只能包含TextBlock;
msg.role=="assistant"的消息可包含所有块类型。
这些数据块承载不同的数据信息,其详细字段说明如下:
| 字段 | 类型 | 说明 |
|---|
type | str | 固定为 "text"。 |
text | str | 文本的具体内容。 |
id | str | 该内容块的唯一标识符(默认自动生成 UUID)。 |
此内容块允许透传模型厂商自定义的元数据(如 Anthropic 模型的 signature 等)| 字段 | 类型 | 说明 |
|---|
type | str | 固定为 "thinking"。 |
thinking | str | 模型的思维或推理文本。 |
id | str | 该内容块的唯一标识符(默认自动生成 UUID)。 |
DataBlock
多模态数据(如图片、音频、视频等)
| 字段 | 类型 | 说明 |
|---|
type | str | 固定为 "data"。 |
id | str | 该内容块的唯一标识符(默认自动生成 UUID)。 |
source | Base64Source | URLSource | 标识数据源。支持 Base64 输入或 URL 输入。 |
name | str | None | 可选字段,表示该内容资产的名称。 |
数据源配置说明:
Base64Source:
type: 固定为 "base64"。
data: 经 Base64 编码的二进制数据。
media_type: 媒体类型(如 "image/png", "audio/mpeg"、"video/mp4" 等)。
URLSource:
type: 固定为 "url"。
url: 满足 RFC 3986 标准的有效 URI/URL 字符串。
media_type: 媒体类型(如 "image/png"、"audio/wav" 等)。
在最终传递给 LLM API 时,HintBlock也会被转换为标准的用户消息(User message)。为避免和用户输入混淆,推荐在提示中使用 XML 标签标记提示内容(如<system-reminder>...</system-reminder>)。| 字段 | 类型 | 说明 |
|---|
type | str | 固定为 "hint"。 |
hint | str | list[TextBlock | DataBlock] | 提示信息——支持纯文本或复合多模态块列表。 |
id | str | 该内容块的唯一标识符(默认自动生成 UUID)。 |
source | str | None | 发送方/提示源标签(可以是 JSON 字符串用于前端解析和渲染)。 |
ToolResultBlock
工具执行结果的数据和状态
创建消息
AgentScope 提供三个快捷方法来构建Msg对象,以避免重复的设置role参数,并支持从字符串构建TextBlock:
| 工厂函数 | 角色 |
|---|
UserMsg(name, content) | user |
AssistantMsg(name, content) | assistant |
SystemMsg(name, content) | system |
当content参数为字符串时,会自动包装为TextBlock。
from agentscope.message import UserMsg, SystemMsg, AssistantMsg
# 用户消息
user_msg = UserMsg(
name="user",
content="这张图片里有什么?"
)
# 系统消息,仅用于系统提示(System prompt)
system_msg = SystemMsg(
name="system",
content="你是一个名为 Friday 的 AI 助手。"
)
# 助手消息
assistant_msg = AssistantMsg(
name="Friday",
content="你好,有什么我可以帮你的吗?"
)
访问内容
Msg 提供了一组辅助方法用于提取特定块类型:
| 方法 | 返回值 |
|---|
get_text_content(separator="\n") | 返回所有 TextBlock 的拼接文本,或 None |
get_content_blocks(block_type) | 按类型过滤后的块列表 |
has_content_blocks(block_type) | 若存在指定类型的块则返回 True |
# 获取所有文本内容
text = msg.get_text_content()
# 获取所有工具调用
tool_calls = msg.get_content_blocks("tool_call")
# 检查消息是否包含工具结果
if msg.has_content_blocks("tool_result"):
...
事件是消息的流式传输单元,即一个序列的事件可以组成一个完整的消息。
智能体类Agent在运行reply_stream的过程中,会产生一系列AgentEvent对象,表示增量的思考、文本回复、工具调用和工具调用结果。前端可通过订阅事件流来实现消息的实时渲染。
事件生命周期
事件的reply_id标识它所属的消息,block_id或tool_call_id标识它所属的内容块。
事件的产生遵循 start → delta → end 模式:
同一次reply_stream的调用中所有事件共享相同的 reply_id。在回复内部,用 block_id 关联文本/思考/数据块事件,用 tool_call_id 关联工具调用和工具结果事件。
事件类型
所有事件继承自 EventBase,提供以下公共字段:
| 字段 | 类型 | 说明 |
|---|
id | str | 唯一事件标识符 |
created_at | str | ISO 8601 时间戳 |
事件按类别分组如下。除特别说明外,每个事件还携带 reply_id 字段,关联到正在构建的消息。
ReplyStartEvent — 智能体开始新的回复。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
session_id | str | 会话 ID |
name | str | 智能体名称 |
role | str | 智能体角色(默认 "assistant") |
ReplyEndEvent — 智能体完成回复。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
session_id | str | 会话 ID |
ExceedMaxItersEvent — 智能体达到最大推理-执行迭代次数。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
name | str | 智能体名称 |
TextBlockStartEvent — 新的文本块开始。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
block_id | str | 文本块唯一标识符 |
TextBlockDeltaEvent — 增量文本内容到达。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
block_id | str | 文本块唯一标识符 |
delta | str | 增量文本内容 |
TextBlockEndEvent — 文本块完成。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
block_id | str | 文本块唯一标识符 |
ThinkingBlockStartEvent — 新的思考块开始。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
block_id | str | 思考块唯一标识符 |
ThinkingBlockDeltaEvent — 增量思考内容到达。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
block_id | str | 思考块唯一标识符 |
delta | str | 增量思考文本 |
ThinkingBlockEndEvent — 思考块完成。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
block_id | str | 思考块唯一标识符 |
ToolResultStartEvent — 工具开始执行。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
tool_call_id | str | 对应工具调用的 ID |
tool_call_name | str | 工具名称 |
ToolResultTextDeltaEvent — 工具的增量文本输出到达。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
tool_call_id | str | 对应工具调用的 ID |
delta | str | 增量文本内容 |
ToolResultDataDeltaEvent — 工具的二进制数据输出到达。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
tool_call_id | str | 对应工具调用的 ID |
block_id | str | 数据块唯一标识符 |
media_type | str | 内容的 MIME 类型 |
data | str | None | base64 编码数据(与 url 互斥) |
url | str | None | 指向内容的 URL(与 data 互斥) |
ToolResultEndEvent — 工具执行完成。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
tool_call_id | str | 对应工具调用的 ID |
state | ToolResultState | 最终状态:SUCCESS、ERROR、INTERRUPTED、DENIED 或 RUNNING |
与文本 / 思考 / 数据 / 工具块不同,这些事件不遵循 start → delta → end 模式。完整载荷在单个事件中到达,因为它在事前就已知,无需流式传输。HintBlockEvent — 一个 HintBlock 被注入到智能体上下文中(例如调度任务触发、团队消息、卸载后台工具返回的结果)。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
block_id | str | 提示块唯一标识符 |
hint | str | list[TextBlock | DataBlock] | 提示载荷 —— 纯文本或多模态块列表 |
source | str | None | 可选的发送方 / 来源标签(通常是一个小的 JSON 对象,描述前端应如何标记此提示) |
CustomEvent — 通用可扩展事件,由服务层 middleware 用来通知订阅者状态变更(任务进度、团队成员、权限更新……),无需污染核心 agent 事件枚举。| 字段 | 类型 | 说明 |
|---|
reply_id | str | 回复消息 ID |
name | str | 信号名称(例如 "tasks_context"、"team_updated") |
value | dict | 该信号任意可 JSON 序列化的载荷 |
从事件流重建消息
事件与消息并非相互独立,而是同一数据的两种视图。reply_stream 产出的每个事件都可以通过 append_event() 追加数据到 Msg 上,从而增量地重建完整消息。这保证了最终消息状态可以仅凭事件流完整还原。
from agentscope.message import Msg, AssistantMsg
msg = None
async for event in agent.reply_stream(user_msg):
if isinstance(event, ReplyStartEvent):
# 回复开始时创建新消息
msg = AssistantMsg(name=event.name, content=[], id=event.reply_id)
else:
# 其他事件追加到消息,逐步还原状态
msg.append_event(event)
append_event 方法处理所有事件类型:
| 事件类型 | 对消息的影响 |
|---|
ReplyEndEvent | 设置 finished_at 时间戳 |
TextBlockStartEvent | 追加新的空 TextBlock |
TextBlockDeltaEvent | 将 delta 拼接到对应块的文本 |
DataBlockStartEvent | 追加新的空 DataBlock |
DataBlockDeltaEvent | 将 data 拼接到对应块的 base64 内容 |
ThinkingBlockStartEvent | 追加新的空 ThinkingBlock |
ThinkingBlockDeltaEvent | 将 delta 拼接到对应块的思考文本 |
ToolCallStartEvent | 追加新的空参数 ToolCallBlock |
ToolCallDeltaEvent | 将 delta 拼接到工具调用的参数 |
ToolResultStartEvent | 追加新的空输出 ToolResultBlock |
ToolResultTextDeltaEvent | 将文本追加到工具结果的输出 |
ToolResultDataDeltaEvent | 将二进制数据块追加到工具结果的输出 |
ToolResultEndEvent | 设置工具结果的最终 state |
HintBlockEvent | 将 HintBlock 追加到 content(携带事件的 hint 与 source),从而持久化并可重放该提示 |
RequireUserConfirmEvent | 将对应工具调用的状态更新为 ASKING |
ExternalExecutionResultEvent | 将 ToolResultBlock 追加到消息内容 |
这种设计让部署更加灵活:后端可以通过 SSE 将事件流推送到前端,前端重建消息并进行渲染。即使连接中断,从任意检查点重放事件序列也能精确恢复消息状态。
TypeScript 支持
AgentScope 提供 TypeScript 版本的消息和事件元语,前端可以使用相同的 appendEvent API 从事件流重建消息。
安装 TS 版本的 AgentScope:
pnpm install @agentscope-ai/agentscope
前端接收消息并重建消息的示例:
import { Msg, AssistantMsg, EventType } from "@agentscope-ai/agentscope/message";
let msg: Msg | null = null;
for await (const event of stream) {
if (event.type === EventType.REPLY_START) {
msg = new AssistantMsg({
name: event.name,
content: [],
id: event.reply_id
});
} else {
msg?.appendEvent(event);
}
}
示例:流式界面
以终端打印为例,展示如何在前端接收事件流并实时渲染:
from agentscope.message import AssistantMsg, UserMsg
from agentscope.event import (
ReplyStartEvent,
TextBlockDeltaEvent,
ToolCallStartEvent,
ToolResultEndEvent,
ReplyEndEvent,
)
msg = None
async for event in agent.reply_stream(UserMsg("user", "帮我修复这个 bug")):
if isinstance(event, ReplyStartEvent):
msg = AssistantMsg(name=event.name, content=[], id=event.reply_id)
elif isinstance(event, TextBlockDeltaEvent):
print(event.delta, end="", flush=True)
elif isinstance(event, ToolCallStartEvent):
print(f"\n[正在调用 {event.tool_call_name}...]")
elif isinstance(event, ToolResultEndEvent):
print(f"[工具执行完成:{event.state}]")
elif isinstance(event, ReplyEndEvent):
print("\n[完成]")
# 始终将事件追加到消息中
if msg is not None:
msg.append_event(event)
# msg 现在包含完整的回复内容
延伸阅读
智能体
智能体如何在 ReAct 循环中产出事件和消息