Skip to content

[BUG][P2] 独立 ReAct 应将修复后的结构化输出消息用于最终交付 #278

Closed

Description

@Aias00

已确认问题

ReAct.finish 调用 StructuredOutputFinalizer 后,只读取 result.status 和 result.data,把结构化数据写入 outputs;没有采用 finalizer 返回的 result.message。一次修复成功后,AgentOutcome 虽为 COMPLETED,最终消息仍是未通过校验的原输出,导致消息与结构化数据互相矛盾。

代码:https://github.com/agentic-ai-java/argi/blob/b34835354b27f1f320ea8438edf2e9d170e69c84/argi-agent/argi-agent-core/src/main/java/io/github/agentic/ai/agent/react/ReActAgent.java#L548;https://github.com/agentic-ai-java/argi/blob/b34835354b27f1f320ea8438edf2e9d170e69c84/argi-agent/argi-agent-core/src/main/java/io/github/agentic/ai/agent/react/ReActAgent.java#L560。

最小复现与结果

  1. 假主模型返回终态文本 not-json。
  2. 配置要求包含字符串 answer 的对象 Schema,并启用 new StructuredOutputFinalizer(repairClient)。
  3. 假修复客户端返回 {"answer":"fixed"}。
  4. 调用独立 ReAct,检查最终状态、messages 最后一条和 outputs.structuredOutput。

实测:status=COMPLETED、最终消息 not-json、structuredOutput 为 {"answer":"fixed"}。两个模型均为离线 stub,修复已成功,问题位于结果交付而非 Schema 校验本身。

交付与验收

  • 修复成功时,以实际通过校验的终态消息作为最终交付,明确原始输出在诊断/历史中的保留方式,不能仅替换 data。
  • Finished.outcome.messages、结构化数据与流式消费者能看到一致的最终结果;若需要完整消息更新,复用已有事件契约并明确消息关联。
  • 不重复追加终态,不将原始失败输出误标为已经通过校验;无修复、修复失败和修复后仍不合法保持明确状态。
  • 覆盖原输出合法、成功修复、修复后仍失败、修复客户端失败及流式完成场景,断言最终消息内容与结构化数据一致。

关联与范围

关联 #142、#196、#236。修复请求漏计用量已有 #207,取消/deadline/预算已有 #206;本单仅处理 finalizer 已返回修复消息却未进入独立 ReAct 最终交付的问题,不重复上述记账和控制任务。

验证基线与变更约束

Core b34835354b27f1f320ea8438edf2e9d170e69c84;Extensions cb2635ab2fc338e8aade28a132a15f48b4e655fc。本轮 Core 19 模块 clean install 通过,2517 个测试中 173 个跳过;此处缺陷由额外的独立 Java 探针确认,说明已有测试尚未覆盖对应行为,不代表真实模型或生产环境验收。

优先修复现有契约的实现,不强制拆模块或重命名,不让独立 Agent/Workflow 反向依赖 Graph/Loop,不向 Core 引入供应商 SDK。若必须改变公开 API、事件、配置或持久格式,说明较小替代方案、消费者影响、收益、迁移成本与目标版本;不得通过删测试、吞错误或弱化取消保证获得通过。

配套缺陷

#280

Activity

  1. XianReallyHot-ZZH commented on Oct 28, 2026

    @XianReallyHot-ZZH
    Contributor

    你好,我想认领这个问题。计划先按 issue 中的最小复现确认交付缺陷(假主模型返回 not-json,修复客户端返回 {"answer":"fixed"},最终 status=COMPLETED 而最终消息仍为 not-json,与 structuredOutput 互相矛盾),然后在 ReActAgent.finish 采纳 finalizer 返回的 result.message() 作为最终交付:修复成功且修复消息与原终态不同时,将修复消息追加为最后一条交付消息(原始输出保留在其前一位供历史/诊断追溯,不误标为已通过校验),并复用现有 AgentEvent.Message 契约(model-repair)推送给流式消费者,保证 Finished.outcome.messages、structuredOutput 与事件流三方一致;无修复发生时零行为变化,不重复追加终态,无修复、修复失败、修复后仍不合法的状态保持不变。AgentResult / AgentEvent / AgentOutcome / StructuredOutputFinalizer 公共契约不动,仅改 ReActAgent.finish 内部实现;graph 侧旧 ReactAgent 不在本次范围,修复用量归集(#207)与流式消息 ID 一致性(#280)不纳入本单。测试为无模型依赖的行为测试,覆盖原输出合法、修复成功、修复后仍不合法、修复客户端失败、流式完成五个场景,断言最终消息内容与结构化数据一致。完成后提交 PR。

  2. added a commit that references this issue on Oct 30, 2026
    6333623
  3. konlue commented on Nov 2, 2026

    @konlue
    Contributor

    您好,我想认领 #278。

    现状(#333 合并后的 main):ReActAgent.finish 只消费 AgentResult 的 status 与 data,message 被丢弃——repair 成功后 structuredOutput 是修复后的数据,而最终交付(outcome.messages 与流式 Finished)仍是原始无效输出,两者矛盾;workflow 的对话历史(WorkflowAgent 的 addAll)也会吸收坏输出。

    方案(单 PR,范围一个点):

    • repair 成功(status == COMPLETED 且 result.message() 与 terminal 不同)时,用 result.message() 替换 messages 列表中的 terminal 消息——替换而非追加,避免双份交付;
    • 原始输出文本保留在 outputs 的专用 key(可诊断,满足 issue 对"原始输出如何保留"的要求);
    • VALIDATION_FAILED 维持现有 AgentExecutionException 路径(no-repair / repair 失败 / 仍无效三种状态由 finalizer 的 issue 列表区分,feat(agent): add AgentResult delivery contract with opt-in structured output finalizer (T39) #173 已落地);
    • 流式 MessageDelta 保持原始 delta(repair 是单轮 callBlocking 调用,无流可重放),终态以 Finished 为交付与验收边界——不发明 delta 重写协议;
    • core 无 checkpoint,替换无重放风险(已核实 outcome.messages 的唯一外部消费方是 WorkflowAgent.addAll)。

    测试(ScriptedClient + RepairStub 模式,全离线):有效输出直通(不替换)/ repair 成功(末条消息 = 修复文本 + structuredOutput = 解析数据 + 原始输出保留可查)/ repair 后仍失败(AgentExecutionException)/ repair client 失败(同上且 issue 标记 INVALID_JSON)/ 流式完成(Finished 的 messages 末条为 repaired)。

  4. konlue commented on Nov 2, 2026

    @konlue
    Contributor

    撤回上面的认领:刚确认 #294 已经交付了同样的修复(repaired message 进入最终交付 + 原始输出保留在历史),实现与我在认领里提的方案一致。本票无需再做,感谢 @XianReallyHot-ZZH 的交付 🌹 我会转到本卷其他未认领的票上。

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