对话历史结果约定结构
User
举例一个场景(稍微复杂点,调用了后端工具),给我模拟一份对话历史接口查询出来的数据
AI
好的 ✅ 我给你构造一个稍微复杂的场景,涵盖 A2A 的多 Agent 协作 + 调用后端工具(通过 MCP 或自定义工具)+ 会话历史接口查询 的完整示例。
场景:
用户想要分析一个 PDF 报告 → AI 助理代理(Assistant Agent)调用一个 PDF 解析工具代理(PDF Agent) → 再调用一个 数据分析代理(Analytics Agent),最后给出总结。
对话历史接口返回的 JSON(示例)
{
"conversationId": "conv-001",
"messages": [
{
"messageId": "msg-001",
"role": "user",
"createTime": "2025-08-29T08:00:01Z",
"parts": [
{
"type": "text",
"text": "帮我分析一下这份财报(report.pdf),重点看盈利情况。"
},
{
"type": "file",
"mimeType": "application/pdf",
"fileUri": "https://example.com/files/report.pdf"
}
]
},
{
"messageId": "msg-002",
"role": "assistant",
"createTime": "2025-08-29T08:00:03Z",
"parts": [
{
"type": "text",
"text": "好的,我先调用 PDF 解析工具来提取内容。"
}
],
"referencedTaskIds": ["task-parse-pdf"]
},
{
"messageId": "msg-003",
"role": "agent",
"agent": "pdf-parser-agent",
"createTime": "2025-08-29T08:00:05Z",
"parts": [
{
"type": "text",
"text": "PDF 文本已提取,共 42 页,已转换为 JSON 结构。"
},
{
"type": "data",
"mimeType": "application/json",
"dataUri": "https://example.com/data/report-structured.json"
}
],
"taskId": "task-parse-pdf",
"status": "completed"
},
{
"messageId": "msg-004",
"role": "assistant",
"createTime": "2025-08-29T08:00:06Z",
"parts": [
{
"type": "text",
"text": "解析完成,接下来我会把数据交给分析代理。"
}
],
"referencedTaskIds": ["task-analyze"]
},
{
"messageId": "msg-005",
"role": "agent",
"agent": "analytics-agent",
"createTime": "2025-08-29T08:00:09Z",
"parts": [
{
"type": "text",
"text": "分析结果:\n- 营收同比增长 18%\n- 净利润同比下降 5%\n- 成本结构显示原材料上涨导致毛利率下降"
}
],
"taskId": "task-analyze",
"status": "completed"
},
{
"messageId": "msg-006",
"role": "assistant",
"createTime": "2025-08-29T08:00:11Z",
"parts": [
{
"type": "text",
"text": "总结:\n这份财报显示,公司营收仍在增长,但盈利能力有所下降,主要原因是原材料成本上涨。"
}
]
}
]
}说明:
role字段:"user"→ 用户输入"assistant"→ 主对话代理(AI 助理)"agent"→ 被调用的工具代理(pdf-parser-agent、analytics-agent)
parts字段:"text"→ 纯文本"file"→ 文件输入"data"→ 结构化数据输出(如 JSON URI)
taskId&referencedTaskIds:- 体现了 任务的生命周期 与 依赖关系。
- 例如
task-parse-pdf完成后,结果被task-analyze使用。
status:- 工具 agent 的执行结果状态(
completed/working/failed)。
- 工具 agent 的执行结果状态(
conversationId&messageId:- 方便历史对话查询、溯源、重放。
这个结构基本符合 A2A 规范里的 Message + Task 模型,同时又带有多代理协作和工具调用的特点。
要不要我再帮你整理一份 基于 SSE 的实时流式返回版本(比如 analytics-agent 分多次返回 delta 数据,assistant 最后拼接总结)?
User
看起来没有artifact,如果模拟的再完美点是什么样子的?
AI
👌好问题! 确实,在 A2A 规范里,任务的最终产出应该通过 Artifact 表达,而不是仅仅靠 message 里的 text。
如果要“模拟得更完美”,需要把 对话消息(Message) 和 任务产出(Artifact) 区分开:
Message负责对话交流(User / Assistant / Agent 的对话内容)。Task管理执行过程(状态流转:submitted→working→completed)。Artifact才是任务的最终交付结果,可以是 文本、文件、数据表 等。
改进后的对话历史查询结果(更贴近规范)
{
"conversationId": "conv-001",
"messages": [
{
"messageId": "msg-001",
"role": "user",
"createTime": "2025-08-29T08:00:01Z",
"parts": [
{
"type": "text",
"text": "帮我分析一下这份财报(report.pdf),重点看盈利情况。"
},
{
"type": "file",
"mimeType": "application/pdf",
"fileUri": "https://example.com/files/report.pdf"
}
]
},
{
"messageId": "msg-002",
"role": "assistant",
"createTime": "2025-08-29T08:00:03Z",
"parts": [
{ "type": "text", "text": "好的,我会先调用 PDF 解析代理。" }
],
"referencedTaskIds": ["task-parse-pdf"]
},
{
"messageId": "msg-003",
"role": "agent",
"agent": "pdf-parser-agent",
"createTime": "2025-08-29T08:00:05Z",
"parts": [
{ "type": "text", "text": "正在解析 PDF ..." }
],
"taskId": "task-parse-pdf",
"status": "working"
},
{
"messageId": "msg-004",
"role": "agent",
"agent": "pdf-parser-agent",
"createTime": "2025-08-29T08:00:08Z",
"parts": [
{ "type": "text", "text": "PDF 解析完成,生成结构化数据 Artifact。" }
],
"taskId": "task-parse-pdf",
"status": "completed",
"artifacts": [
{
"artifactId": "artifact-pdf-json",
"type": "data",
"mimeType": "application/json",
"uri": "https://example.com/data/report-structured.json",
"metadata": { "pages": 42 }
}
]
},
{
"messageId": "msg-005",
"role": "assistant",
"createTime": "2025-08-29T08:00:09Z",
"parts": [
{ "type": "text", "text": "我已拿到结构化数据,现在交给分析代理处理。" }
],
"referencedTaskIds": ["task-analyze"]
},
{
"messageId": "msg-006",
"role": "agent",
"agent": "analytics-agent",
"createTime": "2025-08-29T08:00:12Z",
"parts": [
{ "type": "text", "text": "正在分析数据 ..." }
],
"taskId": "task-analyze",
"status": "working"
},
{
"messageId": "msg-007",
"role": "agent",
"agent": "analytics-agent",
"createTime": "2025-08-29T08:00:15Z",
"parts": [
{ "type": "text", "text": "分析完成,结果已生成 Artifact。" }
],
"taskId": "task-analyze",
"status": "completed",
"artifacts": [
{
"artifactId": "artifact-analysis-report",
"type": "data",
"mimeType": "application/json",
"uri": "https://example.com/artifacts/analysis.json",
"metadata": {
"summary": "营收增长 18%,净利润下降 5%,毛利率下降。"
}
},
{
"artifactId": "artifact-analysis-ppt",
"type": "file",
"mimeType": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"uri": "https://example.com/artifacts/analysis.pptx"
}
]
},
{
"messageId": "msg-008",
"role": "assistant",
"createTime": "2025-08-29T08:00:17Z",
"parts": [
{
"type": "text",
"text": "总结:财报显示营收上涨但盈利能力下降,主要原因是原材料成本上升。我已为你生成了一份详细的 JSON 数据和 PPT 文件。"
}
],
"referencedArtifacts": [
"artifact-analysis-report",
"artifact-analysis-ppt"
]
}
]
}改进点:
引入了
artifacts字段- 每个
task的最终产出以Artifact表示,而不是直接放在 message 里。 Artifact可多模态(JSON、PPT、图片、音频都可以)。
- 每个
assistant 在总结时引用
referencedArtifacts- 表示对话和任务产出挂钩,方便查询和追溯。
任务状态流转
working→completed→ 产出 Artifact。- 历史记录能清晰还原任务生命周期。
更企业化
- 结果不仅仅是文本,还可能有 JSON 分析结果、PPT 报告等可交付成果。
这样一来,对话历史接口查出来的数据,就既能体现 对话过程,也能体现 任务与产出(Artifacts),基本符合 A2A 的理想实践。
要不要我再帮你模拟一个 SSE 流式版本(例如 analytics-agent 在分析过程中不断输出 delta 消息,最后封装为 Artifact)?
User
站在用户或者前端的角度,遍历消息应该是一问一答,但是答复怎么这么多?是不是前端拿到数据还得格式化呢?
AI
你问得特别到点子 ✅。 A2A(包括类似 MCP、LangGraph、CopilotKit 这些协议)设计的时候考虑的是 Agent 协作 + 工具调用的完整溯源,而用户/前端侧的需求通常只是「一问一答式对话」。所以会出现你说的这种「看起来答复很多」的情况。
为什么消息这么多?
在我前面给的例子里:
user发起问题(1 条消息)assistant表示要调用 PDF 工具(1 条消息)pdf-parser-agent返回解析结果(1 条消息)assistant再转交给分析代理(1 条消息)analytics-agent返回分析结果(1 条消息)assistant最终总结(1 条消息)
👉 这就导致 一次用户提问 = 6 条 message。
协议设计是为了完整性,保留所有链路(方便调试、追溯、做 explainable AI)。
前端该怎么处理?
前端遍历 messages 时,可以根据不同场景做「压缩/格式化」:
方案 1:只显示关键的 user / assistant
displayMessages = allMessages.filter(msg =>
msg.role === "user" || msg.role === "assistant"
)这样用户看到的就是传统「一问一答」:
👤 用户: 帮我分析这份财报
🤖 AI: 总结:公司营收仍在增长,但盈利能力有所下降...方案 2:支持「展开调试信息」
保留 agent 和中间 assistant 的消息,但默认折叠。 用户点击「展开」时才显示工具调用详情,比如:
👤 用户: 帮我分析这份财报
🤖 AI: 我先解析 PDF ...
↳ PDF Agent: 文本已提取,共 42 页
🤖 AI: 交给分析代理处理 ...
↳ Analytics Agent: 净利润同比下降 5%
🤖 AI: 总结:营收增长但盈利下降方案 3:Artifact 式展示
把 agent 产出的 data(如 JSON、图表、文件链接)渲染成「附件卡片 / 组件」,而不是像消息一样直接铺开。 比如财报分析结果就显示为一张表格卡片,而不是纯文本。
总结
- 协议层:保留所有消息,便于追溯、调试、解释模型行为
- 前端层:通常需要「过滤/折叠/格式化」成「一问一答」对话,增强可读性
- 高级 UI:可以通过 artifact/卡片/折叠面板来保留细节
要不要我给你做一份 「前端遍历消息 → 格式化为对话流」的示例代码(Vue3 + VitePress 里能直接用的)?这样你文章里既能展示原始数据,又能展示格式化后的一问一答。
User
方案 2:支持「展开调试信息」
保留 agent 和中间 assistant 的消息,但默认折叠。 假如就这种方案也得把数据格式化成:[{用户提问的},{助理回复的内容,折叠内容}],要不数组格式化成两项再去遍历数组吧?
AI
对,你理解得很对 👍
像你说的 「方案 2:支持展开调试信息」,前端展示层面一般会做一次 格式化,把「一问一答」归并成对,然后在答复里再挂上「折叠的调试信息」。
🔨 处理思路
原始数据(协议里的对话历史)
[
{ "id": "m1", "role": "user", "content": "帮我分析这份财报" },
{ "id": "m2", "role": "assistant", "content": "我先解析一下 PDF" },
{ "id": "m3", "role": "agent", "tool": "pdf_parser", "input": "...", "output": "文本结果" },
{ "id": "m4", "role": "assistant", "content": "我得到的关键数据是 ..." },
{ "id": "m5", "role": "assistant", "content": "总结如下:公司营收增长 ..." }
]这里有很多条 message,不是理想的「一问一答」。
目标格式(归并后)
[
{
"user": "帮我分析这份财报",
"assistant": {
"final": "总结如下:公司营收增长 ...",
"debug": [
"我先解析一下 PDF",
{ "role": "agent", "tool": "pdf_parser", "input": "...", "output": "文本结果" },
"我得到的关键数据是 ..."
]
}
}
]前端展示效果
- 用户提问:正常展示
- AI 回复:只展示
final - 调试信息:
debug部分默认折叠,点击展开能看到 agent 调用链 & 中间回复
⚖️ 两种实现方式
归并成「两项数组」(你说的)
- 数据层面就是
[{user, assistant}] - UI 遍历时天然一问一答,折叠部分直接渲染在答复里
- 好处:展示逻辑清晰,开发简单
- 数据层面就是
保留原始数组,前端遍历时做归并
- 逻辑:遇到 user → 开始一组,后续 assistant/agent → 收集到「debug」,最后一个 assistant → 当作「final」
- 好处:数据更忠实于协议
- 缺点:UI 每次渲染都要做处理
✅ 建议
既然你是要放在 VitePress 文章里展示,推荐走 方案 1(预格式化成两项):
- 写文章时更直观
- 遍历更容易
- 折叠逻辑天然挂在
assistant.debug
要不要我帮你写一段 归并函数(TS/JS 版本),把原始协议消息 → 转成 [ {user, assistant} ] 这种结构?这样你在 VitePress 项目里直接用。
User
AI
