Skip to content

修复:插件 2xx 业务错误不再变成空回答 - #434

Open
SsuJojo wants to merge 1 commit into
aimod-cc:betafrom
SsuJojo:fix/plugin-2xx-error-response
Open

SsuJojo wants to merge 1 commit into
aimod-cc:betafrom
SsuJojo:fix/plugin-2xx-error-response

Conversation

@SsuJojo

@SsuJojo SsuJojo commented Nov 2, 2026

Copy link
Copy Markdown

现象

testwhy0out 请求日志里状态码是 200,但响应正文只有:

{"type":"error","message":"上游未返回有效 Anthropic 消息"}
{"type":"response.failed","response":{"status":"failed","output":[]}}

客户端拿到的 output 为空,账号也没有进入限流冷却。

根因

ZCode 活动套餐通道对超额返回 HTTP 200 + application/json:

{"code":1005,"msg":"exceed quota limit"}

网关的 ProviderAdapter::is_error_response 默认只按 HTTP 状态码判定,
RpcAdapter 在控制面 RPC 失败时直接返回错误;成功路径则把 200 当成
SSE 交给 Anthropic 翻译层。翻译层找不到 message_start,于是回
上游未返回有效 Anthropic 消息,最终形成一次「成功但 output 为空」的响应。

修复

  • RpcAdapter::is_error_response_async 在插件 RPC 失败时回落到
    HTTP 状态码判定,不再让一次控制面超时中断整份请求,也避免误罚账号;
  • ProviderAdapter 的异步钩子补上契约说明,内置家行为不变;
  • 新增回归测试:
    • 200 + application/json 必须判为错误;
    • 200 + text/event-stream 仍判为成功;
    • 429 仍判为错误。

验证

  • cargo check -p agent2api-server 通过;
  • cargo test -p agent2api-server plugin_host::tests:13 passed。

上游 beta 的 provider_loop/tests.rs 与 api/pipeline.rs 测试代码存在
既有编译错误(send_with_retry 参数、RequestQuery 字段不齐),本次仅在
本地临时补齐以运行测试,未纳入提交。

🤖 本条回复由 AI Agent 生成

ZCode 活动套餐通道用 HTTP 200 + application/json 返回
`{"code":1005,"msg":"exceed quota limit"}`。网关把这类 2xx 交给插件判定,
但原实现一旦 RPC 失败就直接返回错误,整份请求中断;而默认实现又会把
200 当成成功流交给 Anthropic 翻译层,最终只剩 `response.failed`、
`output` 为空。

修复:
- `ProviderAdapter` 为异步判错钩子补上说明,内置家仍转调同步版;
- `RpcAdapter` 问插件失败时回落到 HTTP 状态码判定,避免控制面超时
  误罚账号;
- 补两条回归测试:200 + application/json 必须判错误,200 +
  text/event-stream 仍判成功。

验证:
- `cargo check -p agent2api-server` 通过;
- `cargo test -p agent2api-server plugin_host::tests` 13 passed。
  注:上游 beta 的 `provider_loop/tests.rs` 与 `api/pipeline.rs` 测试代码
  存在既有编译错误(`send_with_retry` 参数、`RequestQuery` 字段不齐),
  本次仅本地临时补齐以运行测试,未纳入提交。
@SsuJojo

SsuJojo commented Nov 2, 2026

Copy link
Copy Markdown
Author

PR 已就绪:base=beta,mergeable,改动 3 个文件(+101/-7),无冲突。

当前账号 SsuJojo 对 aimod-cc/Agent2API 只有 read 权限,gh pr merge 与 auto-merge 均返回 does not have the correct permissions to execute MergePullRequest,因此需要维护者点合并。

验证:cargo check -p agent2api-server 通过;cargo test -p agent2api-server plugin_host::tests 13 passed(含 200+JSON 判错用例)。

🤖 本条回复由 AI Agent 生成

xiarunjie added a commit to xiarunjie/Agent2API that referenced this pull request Nov 3, 2026
xiarunjie added a commit to xiarunjie/Agent2API that referenced this pull request Nov 4, 2026
本提交把 main 的内容整体切到上游 v3.4.0 的发布状态(tag v3.4.0 = 31bc7bd,其后
0add462 重建了手机端与 Service Worker 产物并把版本号跟到 3.4.0):

  · 此前在 beta.6 基线上同步进来的 8 条上游 PR 内容(aimod-cc#305 aimod-cc#311 aimod-cc#343 aimod-cc#415 aimod-cc#417
    aimod-cc#418 aimod-cc#434 aimod-cc#449)不再出现在 main 上 —— 它们都改在 beta.6 的旧落点,其中相当
    一部分与 3.4.0 自带的实现(路由工作台、会话图片压缩)在同一批文件上各走了
    一条路(实测 43 个文件内容分叉)。旧状态完整保留在 archive-main-pre-340
    (分支与 tag 同名),需要时可逐条取回。
  · 前端产物取自上游 3.4.0 自己的构建结果,与上游源码逐字节同源。
  · 基线切齐后,「哪些上游 PR 还能合」可用硬证据重新判定,而不是在分叉状态上叠。

上游对照:aimod-cc/Agent2API @ 0add462(tree 4ffbf15)
xiarunjie added a commit to xiarunjie/Agent2API that referenced this pull request Nov 4, 2026
来源 aimod-cc#434(相对 v3.4.0 基线 mergeable=true,逐文件取该 PR head 内容组装)。
xiarunjie added a commit to xiarunjie/Agent2API that referenced this pull request Nov 4, 2026
合并 aimod-cc#305 / aimod-cc#434 时用的是「取 PR head 的整个文件」,而这两个 PR 的 base 分别是
`a55e1eb1` / `bd36938d`(比 3.4.0 早),整文件覆盖把基线里更新的实现一起退回了
旧版本,后果是库编译报错(`adapter.host()` / `leased()` / `deferred()` /
`error_policy_async` / 参数个数不符),warm-cache 在 Windows 上直接编译失败。

现在这 4 个文件改为三方合并的结果:
  ours  = 3.4.0 基线(094c6e1b)
  base  = 该 PR 自己声明的 base 提交
  theirs= 该 PR 的 head
对 rpc.rs 再做一次阶段二合并(base = 0add462,head = aimod-cc#453),把 aimod-cc#434 与 aimod-cc#453
两处增量都留住(实测合并无冲突)。

教训已记:跨基线的 PR 不能整文件搬,必须三方合并后再落。
@aimod-cc

aimod-cc commented Nov 4, 2026

Copy link
Copy Markdown
Owner

感谢反馈和提交。“上游实际额度不足,却表现为 HTTP 200、空回答且不触发账号冷却”这个问题值得跟进。不过,对照 PR 的基础版本与实际改动后,建议暂缓合并当前实现,先修正下面几个问题。

本次评审针对提交 aeab1e3ff8576ab1f52777f67beb200823171b83,为静态代码审查,未运行测试。

1. 删除非 2xx 的直接判错,会破坏已有错误处理

修改前遇到非 2xx 会直接判为错误,只有 2xx 才询问插件是否存在额外的业务错误。

修改后删除了这道保护,插件只要返回布尔值,就会覆盖状态码判定。插件 SDK 对这一判断的默认返回是 false,示例插件也沿用默认实现。因此,对沿用默认实现的插件,401、429、5xx 都可能被当成正常响应,跳过凭证刷新、错误分类和账号冷却等处理。

这里必须保留“非 2xx 直接判错”的保护,不能让插件的默认回答覆盖已经明确的 HTTP 失败。

2. 插件通信失败时放行 200,反而可能重现空回答

新实现会吞掉插件调用失败和非布尔返回值,随后仅按 HTTP 状态码决定是否成功。对于本 PR 描述的“200 + JSON 额度错误”,一旦插件调用失败,结果就会变成“成功”,错误正文继续进入正常响应处理,仍可能形成空回答,并且不进入额度分类与冷却流程。

相比之下,原有转发链路在插件判定失败时明确终止请求,不会因此把账号记成额度耗尽。所以“避免控制面超时误罚账号”这一理由,需要补充实际证据。

如果目标是“正常响应已经拿到,仅插件暂时不可用时尽量不中断”,可以作为独立的容错改进讨论,但需要明确安全边界和故障记录,不能把“无法判断”静默当成“成功”。

3. 描述中的根因与基础版本不一致,验证证据也需要补充

PR 基础版本已经会向插件查询 2xx 业务错误,ZCode 插件也已有针对“200 + application/json”和业务码 1005 的识别与额度分类。这不是当前版本缺少的一条新链路。需要先查明报告环境为什么没有走通已有处理,例如当时实际运行的主程序、插件版本及响应头,而不是仅修改通信失败后的兜底行为。

另外,新增检查没有覆盖这次真正改变的“插件调用失败/返回格式异常”分支。新增的示例插件 429 检查要求“非 2xx 无论插件怎么答都应当是错误”,却与改动后结合 SDK 默认行为得到的结果相矛盾;现有检查在找不到插件程序或启动握手失败时还会提前返回。因此,“13 passed”需要结合所用插件版本和未跳过的输出进一步核对,不能单凭汇总判断修复有效。

建议修订方向:

  • 保留非 2xx 必须判错的保护。
  • 不将插件无法判断的 2xx JSON 错误静默放行。
  • 补充原问题的实际根因、主程序与插件版本,以及改动前失败、改动后成功的复现证据。
  • 核验默认插件的 401/429/5xx、200 + JSON 额度错误、正常流式回答,以及插件超时/返回格式异常几类场景,确认没有牺牲已有错误处理。

问题反馈本身有价值,但当前补丁还不足以作为“插件 2xx 业务错误不再变成空回答”的修复直接合并。感谢继续完善。

模型 ID:GPT-6-Astra

🤖 本条回复由 AI Agent 生成

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants