Skip to content

[BUG][OPT-22] A2A 传输与解析失败应传播结构化错误而非正常文本完成 #213

Open

Description

@Aias00

总览:#180

代码依据

问题与验收

部分 SSE 解析异常被 catch(Exception ignore) 忽略;非 SSE 解析和 HTTP 异常被包装成普通 StreamingOutput("Error: ...");finally 仍提交 done(buildFinalResult(...))。上层可能把失败路径视为正常输出,且难以区分远程正文与协议错误。

范围与验收

本单侧重当前 Core A2A 客户端的失败语义,不重写全部远程协议。

验证与兼容约束

Core 基线 f45b2cf。当前为静态代码分析,尚未运行本单针对性复现。先补有意义的行为测试,优先局部修复;公共 API/输出/调度语义变化需说明收益、影响、兼容替代、迁移成本及目标版本。修复不等待 Agent/Workflow/Loop 全局重构。

关联任务

Activity

  1. XianReallyHot-ZZH commented on Nov 14, 2026

    @XianReallyHot-ZZH
    Contributor

    你好,我想认领这个缺陷 @Aias00

    根因核对(基于当前 main,非 issue 基线 f45b2cf):issue 引用的旧 AsyncGeneratorQueue/LinkedBlockingQueue 实现已被重写为 Flux.create + FluxSink(#336 等),但本单的全部缺陷在当前代码仍完整存在:HTTP 非 200/空实体 → 发 "HTTP request failed..." 文本块后 finally 照常 done();SSE 帧解析失败 catch (Exception ignore) 静默丢弃;JSON-RPC error 帧({"error":{...}})因 result 为 null 被静默跳过;传输异常包装为 "Error: ..." 文本 + done();远端 failed/canceled 在 extractResponseText 返回空串照常完成;流结束无终态(无 [DONE]、无 completed)不报错;非流式路径的远端 failed/canceled 同样伪装成空文本正常完成。

    方案

    1. 复用现有结构化契约,零新机制:错误路径一律 sink.error(A2a 异常)(流式)或抛出(非流式),经 NodeExecutor → GraphResponse.error → CompiledGraph onError 上抛([BUG][OPT-14] AgentResult 不应以回答正文的 Exception 前缀判断模型失败 #205/OPT-14 已为模型路径建立同型先例);错误路径最多一个终态,不再发 done();已发出的部分文本块身份保留(错误终态跟在其后,不撤回)。
    2. 中立异常类型可区分:a2a 包内新增 A2aTransportException(HTTP 非 200/空实体/连接中断/URL 缺失)、A2aProtocolException(损坏帧/JSON-RPC error/缺失终态/非 SSE 体解析失败)、A2aRemoteTaskException(远端 failed/canceled,携带状态),基类继承 IllegalStateException 保持现有 catch 兼容。
    3. SSE 帧处理明确化(按 WHATWG SSE 规范):注释行(:)与未知字段(event:/id:/retry:)跳过;多 data 行按规范以 \n 连接后解析;空行分发事件;有效 JSON 但未知事件视为可忽略扩展;非法 JSON 视为损坏帧必须报错;[DONE] 哨兵与 status-update completed/failed/canceled 均为终态判定,流关闭而无任何终态 → 缺失终态错误。
    4. 正文不作为控制信号:错误判定完全不嗅探正文文本,远端正文以 "Error:"/"Exception:" 开头照常作为普通内容投递([BUG][OPT-14] AgentResult 不应以回答正文的 Exception 前缀判断模型失败 #205 的教训应用到 A2A)。
    5. 非流式路径一并修复:远端 failed/canceled 与解析失败同样抛结构化异常(同一状态分类助手,验收"上层可观察真实结果、不误报完成"不区分调用方式)。
    6. 范围约束:不改 GraphResponse/StreamingOutput/AgentEvent 公开契约与 A2A 请求格式;不动 OPT-21([BUG][OPT-21] 为 A2A 流队列满载提供明确背压与可靠终态 #212)的背压/缓冲策略与 Extensions [BUG] 流式节点未正常完成时,下一轮对话消息序列结构错乱(导致LLM 拒绝请求) #25/[BUG] AgentTool模式,子agent的skill如果关联了mcp工具,mcp工具无法调用 #26;保留消息关联与既有顺序。

    测试:沿用本地 HttpServer + SSE 帧模式新增行为测试,覆盖验收全部场景:HTTP 非成功、损坏帧、部分文本后断开、远端 failed/canceled、正常含错误字样正文、空/缺失终态、SSE 注释/空行/多 data 行/未知事件、非流式对称场景;更新两个断言旧"错误文本+done"契约的既有测试(streamingReportsHttpFailureAndStillCompletes、streamingWithoutAgentUrlCompletesInsteadOfWaitingForever,其有界终止意图保留,机制改为错误终态)。取消路径(#277 回归)不回退:取消后不发错误不发 done。

  2. XianReallyHot-ZZH commented on Nov 15, 2026

    @XianReallyHot-ZZH
    Contributor

    已基于最新 main 实现:#371。

    检视后补充的两点:

    1. Task 对象形状:单 body 响应(message/send)返回的是 Task 对象(kind:"task",status.state),不是 status-update——失败检测按形状(有 status.state 即判)同时覆盖两种载体,避免真实服务器形状漏检后被误分类为协议错误。
    2. 行为变更说明(issue 允许但需论证的语义变化):事件流结束而无任何终态(无 [DONE]、无 completed 状态帧)现在报结构化协议错误——此前照常 done()。这正是验收第 4 条"空/缺失终态响应"的要求;若维护者认为该严格化影响面过大,可退回"静默完成"。

    测试 23 个新行为用例 + 既有 A2A 套件 68/68 通过;上层可观察性用编译图流断言错误上抛(复用 NodeExecutor → GraphResponse.error → Flux onError 通道,与 #205 先例一致)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions