Skip to content

[BUG][OPT-16] 将结构化输出修复请求纳入用量归集 #207

Open

Description

@Aias00

总览:#180

代码依据

ChatMessage repaired = attemptRepair(terminal, outputSchema, first.issues());

问题、范围与验收

修复得到 ChatResponse 后仅提取 ChatMessage;最终 validateAgainst 仍使用原 usage,修复响应的 usage 未进入返回结果。当前有节点级用量合并工具,不应称为全框架没有用量统计;问题是额外修复调用未进入交付统计。

验收

  • 明确 AgentResult.usage 是整次交付总用量还是分段用量,并提供足以区分主调用与修复的归集方式。
  • 成功修复、修复后仍不合法,以及已收到模型响应但无法提取有效消息时,按已发生调用统计其用量。
  • 未提供 usage、部分字段未知时保留未知语义,不把 null 当零或返回伪精确总量;仅能估算时明确来源。
  • callResult/streamResult 对相同调用序列统计一致,原响应和修复响应不重复计数。
  • 覆盖已知/未知字段、无修复、修复一次和失败路径测试;不能凭异常猜测供应商实际消耗。
  • 若调整公共结果字段或统计语义,说明消费者影响与版本;本单不实现价格模型或成本结算。

与 OPT-15 协调;复用 #142 交付及现有 NodeUsage 的合理语义。

验证与变更约束

代码基线 Core f45b2cf。本单来自静态审查,尚未运行针对性复现或实现。先补行为验证,复用已有能力;破坏性调整逐项说明收益、兼容替代、消费者影响、迁移成本和版本。局部修复不等待 Agent/Workflow/Loop 全局重构,新增通用契约不反向依赖 Loop。

关联任务

Activity

  1. Aias00 commented on Oct 23, 2026

    @Aias00
    ContributorAuthor

    当前版本复验:修复调用 usage 仍未归集

    Core 76d44429fdaa6a69320e21a1cb77119d0889d792 已 clean install;使用实际编译的 StructuredOutputFinalizer 验证:

    • 原输出非法,传入 usage 为 input=1/output=2/total=3。
    • 修复 ChatClient 返回合法 {"status":"ok"},usage 为 input=10/output=20/total=30。
    • 修复成功后的结果 usage.totalTokens() 仍为 3,修复请求的已知消耗未计入。

    本次复现将此前静态判断升级为实际调用证据。预期按原单确定的总量/分段契约归集,并保留未知字段语义;若采用总量语义,该案例应为 33,不能重复计算或将缺失 usage 当作零。

  2. XianReallyHot-ZZH commented on Nov 11, 2026

    @XianReallyHot-ZZH
    Contributor

    你好,我想认领这个问题 @Aias00

    复现确认(代码走读,基线 main 4077074;实现时先写红的行为测试钉死)

    您的可执行复现在当前 main 仍成立:StructuredOutputFinalizer.attemptRepair 拿到修复 ChatResponse 后只提取 assistant 消息,response.usage() 被丢弃;修复后的所有 AgentResult(成功修复 / REPAIRED_STILL_INVALID / 响应已到但提取不到 assistant 消息的 REPAIR_CALL_FAILED)都只携带修复前 usage。另有一处连带缺口:独立 ReAct 的 finish() 用修复前的累计 usage 发 Finished 事件,修复消耗不会进入流式消费者的最终用量,与 AgentResult.usage 自相矛盾。

    修复方案

    1. 合并点放在 finalizer 内部(两条交付路径共享的唯一交付边界,Graph callResult/streamResult 零改动自动获益):attemptRepair 同时返回修复消息与该次响应的 usage,按既有 null 传播语义(已知+已知=求和,任一未知该字段置 null,与 UsageAccumulator/NodeUsage 一致)合并进修复后全部 AgentResult 的 usage。
    2. 归集语义:AgentResult.usage 明确为整次交付总用量(主调用累计 + 修复调用);新增组件 repairUsage 区分主调用与修复——null=未发生修复调用,非 null 且字段可空=发生了修复但用量未知,非空字段即修复响应实际报告的用量。
    3. null 传播合并收敛为 Usage 契约上的 plus(Usage) 实例方法(项目内已有两处私有实现同一语义,本次第三处,收敛到中立契约,纯增量兼容)。
    4. 独立 ReAct 的 finish() 以合并后的 result.usage 回填 Finished 事件,发生修复时补发一次累计 UsageUpdate(对齐 [BUG][P2] 独立 ReAct 的 UsageUpdate 应发送调用级累计用量而非单轮用量 #279/fix(agent): emit cumulative ReAct usage updates #341 的累计语义)。

    兼容边界

    • finalizeOutput 两个重载签名不变;AgentResult 是 opt-in 严格交付专用类型,加组件的同时保留旧 5 参形态构造器保证源兼容。
    • 修复客户端在收到任何响应前失败(无 usage 可知)不计入,不凭异常猜测消耗;收到响应但提取不到 assistant 消息时按已发生调用计入。
    • Finished 事件/UsageUpdate 携带修复后总量属于本单行为修正本身,PR 中说明消费者影响。

    测试范围

    模型无关行为测试:已知/未知字段组合、无修复、成功修复一次、修复后仍不合法、响应已到无 assistant 消息、修复客户端异常;独立 ReAct 的 UsageUpdate/Finished 一致性;Graph callResult/streamResult 相同调用序列统计一致的回归。完成后提交 PR。

  3. XianReallyHot-ZZH commented on Nov 11, 2026

    @XianReallyHot-ZZH
    Contributor

    已在最新 main(407707442)实现:#366。

    关键语义决定:

    1. AgentResult.usage = 整次交付总用量(主调用累计 + 修复调用,逐字段 null 传播合并:已知+已知=求和,任一未知该字段保持未知,不把 null 当零;主 usage 为 null 同样安全);新增 repairUsage 组件区分主调用与修复——null=未发生修复调用,全未知=发生了修复但用量未上报,非空字段即修复响应实际报告的值。保留旧 5 参构造器,源兼容。
    2. 三种已发生调用均计入且仅计一次:成功修复、修复后仍不合法、响应已到但无 assistant 消息(REPAIR_CALL_FAILED 分支也合并该响应 usage);修复客户端在收到任何响应前失败只保留已知用量,不凭异常猜测消耗。
    3. 合并点在 finalizer(两条交付路径共享的边界),Graph callResult/streamResult 零产线改动自动一致;独立 ReAct 的 finish() 以合并后总量回填 Finished 事件,并在发生修复时补发一次累计 UsageUpdate(对齐 [BUG][P2] 独立 ReAct 的 UsageUpdate 应发送调用级累计用量而非单轮用量 #279/fix(agent): emit cumulative ReAct usage updates #341 的累计语义),事件消费者与结果消费者看到相同总量。
    4. null 传播合并收敛为 Usage.plus(中立契约上的纯增量方法,与既有 UsageAccumulator/NodeUsage 语义一致)。

    测试:finalizer 层 9 个新用例(成功/仍不合法/无消息/响应前失败/未知字段/无修复/null 主 usage)+ 独立 ReAct 事件层 4 个(流式/非流式 × 成功/失败)+ Graph callResult/streamResult 一致性回归,agent-core 248/248、agent-framework 相关套件全绿。

  4. added 2 commits that reference this issue on Nov 12, 2026
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