Skip to content

对话历史结果约定结构

User

举例一个场景(稍微复杂点,调用了后端工具),给我模拟一份对话历史接口查询出来的数据

AI

好的 ✅ 我给你构造一个稍微复杂的场景,涵盖 A2A 的多 Agent 协作 + 调用后端工具(通过 MCP 或自定义工具)+ 会话历史接口查询 的完整示例。


场景:

用户想要分析一个 PDF 报告 → AI 助理代理(Assistant Agent)调用一个 PDF 解析工具代理(PDF Agent) → 再调用一个 数据分析代理(Analytics Agent),最后给出总结。


对话历史接口返回的 JSON(示例)

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这份财报显示,公司营收仍在增长,但盈利能力有所下降,主要原因是原材料成本上涨。"
        }
      ]
    }
  ]
}

说明:

  1. role 字段:

    • "user" → 用户输入
    • "assistant" → 主对话代理(AI 助理)
    • "agent" → 被调用的工具代理(pdf-parser-agent、analytics-agent)
  2. parts 字段:

    • "text" → 纯文本
    • "file" → 文件输入
    • "data" → 结构化数据输出(如 JSON URI)
  3. taskId & referencedTaskIds

    • 体现了 任务的生命周期依赖关系
    • 例如 task-parse-pdf 完成后,结果被 task-analyze 使用。
  4. status

    • 工具 agent 的执行结果状态(completed / working / failed)。
  5. conversationId & messageId

    • 方便历史对话查询、溯源、重放。

这个结构基本符合 A2A 规范里的 Message + Task 模型,同时又带有多代理协作和工具调用的特点。

要不要我再帮你整理一份 基于 SSE 的实时流式返回版本(比如 analytics-agent 分多次返回 delta 数据,assistant 最后拼接总结)?


User

看起来没有artifact,如果模拟的再完美点是什么样子的?

AI

👌好问题! 确实,在 A2A 规范里,任务的最终产出应该通过 Artifact 表达,而不是仅仅靠 message 里的 text。

如果要“模拟得更完美”,需要把 对话消息(Message)任务产出(Artifact) 区分开:

  • Message 负责对话交流(User / Assistant / Agent 的对话内容)。
  • Task 管理执行过程(状态流转:submittedworkingcompleted)。
  • Artifact 才是任务的最终交付结果,可以是 文本、文件、数据表 等。

改进后的对话历史查询结果(更贴近规范)

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 ..." }
      ],
      "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"
      ]
    }
  ]
}

改进点:

  1. 引入了 artifacts 字段

    • 每个 task 的最终产出以 Artifact 表示,而不是直接放在 message 里。
    • Artifact 可多模态(JSON、PPT、图片、音频都可以)。
  2. assistant 在总结时引用 referencedArtifacts

    • 表示对话和任务产出挂钩,方便查询和追溯。
  3. 任务状态流转

    • workingcompleted → 产出 Artifact。
    • 历史记录能清晰还原任务生命周期。
  4. 更企业化

    • 结果不仅仅是文本,还可能有 JSON 分析结果、PPT 报告等可交付成果。

这样一来,对话历史接口查出来的数据,就既能体现 对话过程,也能体现 任务与产出(Artifacts),基本符合 A2A 的理想实践。

要不要我再帮你模拟一个 SSE 流式版本(例如 analytics-agent 在分析过程中不断输出 delta 消息,最后封装为 Artifact)?


User

站在用户或者前端的角度,遍历消息应该是一问一答,但是答复怎么这么多?是不是前端拿到数据还得格式化呢?

AI

你问得特别到点子 ✅。 A2A(包括类似 MCP、LangGraph、CopilotKit 这些协议)设计的时候考虑的是 Agent 协作 + 工具调用的完整溯源,而用户/前端侧的需求通常只是「一问一答式对话」。所以会出现你说的这种「看起来答复很多」的情况。


为什么消息这么多?

在我前面给的例子里:

  1. user 发起问题(1 条消息)
  2. assistant 表示要调用 PDF 工具(1 条消息)
  3. pdf-parser-agent 返回解析结果(1 条消息)
  4. assistant 再转交给分析代理(1 条消息)
  5. analytics-agent 返回分析结果(1 条消息)
  6. assistant 最终总结(1 条消息)

👉 这就导致 一次用户提问 = 6 条 message

协议设计是为了完整性,保留所有链路(方便调试、追溯、做 explainable AI)。


前端该怎么处理?

前端遍历 messages 时,可以根据不同场景做「压缩/格式化」:

方案 1:只显示关键的 user / assistant

js
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:支持展开调试信息」,前端展示层面一般会做一次 格式化,把「一问一答」归并成对,然后在答复里再挂上「折叠的调试信息」。


🔨 处理思路

原始数据(协议里的对话历史)

json
[
  { "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,不是理想的「一问一答」。


目标格式(归并后)

json
[
  {
    "user": "帮我分析这份财报",
    "assistant": {
      "final": "总结如下:公司营收增长 ...",
      "debug": [
        "我先解析一下 PDF",
        { "role": "agent", "tool": "pdf_parser", "input": "...", "output": "文本结果" },
        "我得到的关键数据是 ..."
      ]
    }
  }
]

前端展示效果

  • 用户提问:正常展示
  • AI 回复:只展示 final
  • 调试信息debug 部分默认折叠,点击展开能看到 agent 调用链 & 中间回复

⚖️ 两种实现方式

  1. 归并成「两项数组」(你说的)

    • 数据层面就是 [{user, assistant}]
    • UI 遍历时天然一问一答,折叠部分直接渲染在答复里
    • 好处:展示逻辑清晰,开发简单
  2. 保留原始数组,前端遍历时做归并

    • 逻辑:遇到 user → 开始一组,后续 assistant/agent → 收集到「debug」,最后一个 assistant → 当作「final」
    • 好处:数据更忠实于协议
    • 缺点:UI 每次渲染都要做处理

✅ 建议

既然你是要放在 VitePress 文章里展示,推荐走 方案 1(预格式化成两项)

  • 写文章时更直观
  • 遍历更容易
  • 折叠逻辑天然挂在 assistant.debug

要不要我帮你写一段 归并函数(TS/JS 版本),把原始协议消息 → 转成 [ {user, assistant} ] 这种结构?这样你在 VitePress 项目里直接用。


User

AI

Released under the MIT License.