第 8 章 · AgentMessage 与消息转换
本章目标:理解
AgentMessage灵活类型与 LLM 原生消息的差异,掌握convertToLlm桥接与自定义消息类型的声明合并。
8.1 两种消息体系
Agent 内部流转的消息 ≠ 发给 LLM 的消息:
text
AgentMessage(应用层,灵活)
│ 可含 user / assistant / toolResult
│ 还可含自定义类型:notification / progress / card …
▼
transformContext() (第 9 章)
▼
convertToLlm() ← 本主角:过滤+转换
▼
Message[](LLM 层,严格)
仅认 user / assistant / toolResult 三种角色为什么这样设计?UI 提示、审批卡片、系统通知这类信息人要看、机器不用看——让它们留在 Agent 历史,又在每次调 LLM 前被过滤掉。
8.2 convertToLlm:桥接函数
convertToLlm 在每次 LLM 调用前执行。最简单的实现是只保留三种标准角色:
typescript
const agent = new Agent({
streamFn: models.streamSimple.bind(models),
// 过滤掉非 LLM 消息;无自定义类型时也推荐显式声明以防泄漏
convertToLlm: (messages) =>
messages.filter((m) =>
["user", "assistant", "toolResult"].includes(m.role)
),
initialState: { systemPrompt: "...", model },
});8.3 自定义消息类型:声明合并
通过 TypeScript 的 module augmentation 给 AgentMessage 联合类型添加新成员:
typescript
// 扩展 pi-agent-core 的 CustomAgentMessages 接口
declare module "@earendil-works/pi-agent-core" {
interface CustomAgentMessages {
notification: {
role: "notification";
text: string;
timestamp: number;
};
}
}
// 之后即可合法构造这种消息并放入历史
const msg: AgentMessage = {
role: "notification",
text: "用户已上传附件 report.pdf",
timestamp: Date.now(),
};
agent.state.messages.push(msg);然后在 convertToLlm 里决定它的命运——过滤或降级:
typescript
convertToLlm: (messages) =>
messages.flatMap((m) => {
if (m.role === "notification") return []; // 选择 A:对模型隐藏
// return [{ role: "user", content: `[系统通知] ${m.text}`,
// timestamp: m.timestamp }]; // 选择 B:转成 user 消息
return [m];
}),8.4 实战模式:审批卡片
一个典型的生产场景:工具调用前需要人工审批。
typescript
declare module "@earendil-works/pi-agent-core" {
interface CustomAgentMessages {
approval_request: {
role: "approval_request";
action: string;
status: "pending" | "approved" | "rejected";
timestamp: number;
};
}
}
const agent = new Agent({
streamFn: models.streamSimple.bind(models),
initialState: { systemPrompt: "...", model },
// 审批记录对模型不可见,但完整保留在会话历史供审计
convertToLlm: (msgs) => msgs.filter((m) => m.role !== "approval_request"),
});8.5 与低层 API 的关系
使用低层 agentLoop() 时没有默认转换,必须显式提供:
typescript
import { agentLoop } from "@earendil-works/pi-agent-core";
const config = {
model,
// 低层必须自己给 convertToLlm
convertToLlm: (msgs) => msgs.filter((m) =>
["user", "assistant", "toolResult"].includes(m.role)),
};
for await (const event of agentLoop([userMsg], context, config)) {
console.log(event.type);
}8.6 本章小结
AgentMessage面向应用可扩展,LLM 只认 user/assistant/toolResult;convertToLlm是两者之间的桥:在每次 LLM 调用前过滤与转换;- 用
declare module + CustomAgentMessages声明合并添加自定义消息类型; - 典型模式:通知/审批类消息留档不进模型上下文;
- 低层
agentLoop()不带默认转换,需自行提供。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. LLM 能理解哪些角色的消息?
2. convertToLlm 的执行时机是?
3. 如何给 AgentMessage 添加自定义的 notification 类型?
4. 使用低层 agentLoop() 时若不提供 convertToLlm 会怎样?
🛠️ 动手实践
- 实现「进度日志」自定义消息类型 progress,在 UI 显示但对模型完全透明。
- 把 notification 消息改为「转成 user 文本」策略,观察模型行为差异并记录适用场景。
- 写一个单元测试:历史里混入 3 种自定义消息后,convertToLlm 输出只含标准三角色。
下一章第 9 章:在 convertToLlm 之前还有一层 transformContext——长对话的救命稻草。