## 解决什么问题 Web 中,模型运行期间通过 Pi assistant message 返回的失败会丢失错误原因。重试耗尽或 Provider 返回错误后,界面从“运行中”恢复为普通输入状态,可能只剩用户消息,用户无法判断任务为何停止、是否需要重试或调整配置。 这与缺少模型、凭据等启动前检查错误不同:后者可以走 `prompt_failed`;这里的问题是 Pi 已启动模型调用,并将错误记录为 `stopReason: "error"` 的正常消息结果。 ## 价值是什么 让外部用户能区分成功完成、模型失败、取消和结果不确定。限流、Provider 服务异常等失败必须留下可回看的原因,已有部分回答也不能被默认为完整结果。 ## 已验证的复现 验证日期:2026-09-08。源码固定为 [`ed9dbc1018f890fd54375f5371990ddfee8af5df`](https://github.com/openpi-dev/openpi/commit/ed9dbc1018f890fd54375f5371990ddfee8af5df)。使用干净的相同源码 checkout、独立临时 Pi agent 目录,`pi list` 中唯一 OpenPI 来源指向该 checkout。 1. 启动真实 Pi 和 Web Host,将一个合成测试模型连接到本机 HTTP Provider fixture;不使用真实 Provider 凭据或付费模型。 2. 在真实浏览器输入合成消息 `AUDIT_ERROR`。 3. 本地 fixture 返回 HTTP 400,错误标记为 `AUDIT_PROVIDER_ERROR`。 4. 等待本轮结束,检查 Pi Session JSONL、Web snapshot 和浏览器。 观察到: - Pi JSONL 保存了 assistant 消息及其 `stopReason: "error"`、`errorMessage`。 - Web snapshot 中相应 assistant 的 `content` 为空,错误原因未投影,runtime 已回到 `idle`。 - 真实浏览器最终只显示用户的 `AUDIT_ERROR` 和普通空输入框,没有错误、失败提示或原因。 另有直接导入当前源码的最小事件探针:输入 `{ role: "assistant", content: [], stopReason: "error", errorMessage: "synthetic provider error" }`,最终 Store 为 `notice: null`、`livePhase: "idle"`、`turnTerminalStatus: "failed"`;真实 React 组件渲染普通发送提示。 这证明了真实 Pi → Web 协议 → 浏览器的错误呈现缺陷;本地 Provider 是协议 fixture,不代表任何线上 Provider 的可用性测试。全部输入和错误均为合成内容,没有公开凭据或私人 Session。 ## 方法与根因 - [`WebLiveMessage` / `projectMessage`](https://github.com/openpi-dev/openpi/blob/ed9dbc1018f890fd54375f5371990ddfee8af5df/web/protocol/types.ts#L85-L101) 未保留 assistant 的停止原因和错误;[返回值](https://github.com/openpi-dev/openpi/blob/ed9dbc1018f890fd54375f5371990ddfee8af5df/web/protocol/types.ts#L397-L420) 只保留内容等字段。 - [`projectEvent`](https://github.com/openpi-dev/openpi/blob/ed9dbc1018f890fd54375f5371990ddfee8af5df/web/runtime/pi-runtime.ts#L1054-L1083) 能识别 `stopReason: "error"` 并记下 `failed`,但发送给 Web 的消息仍使用上述投影;`auto_retry_end` 的最终错误也没有对应的 Web 事件分支。 - [Store 保存终态](https://github.com/openpi-dev/openpi/blob/ed9dbc1018f890fd54375f5371990ddfee8af5df/web/ui/src/store/web-store.ts#L406-L421),但 [Composer 仅针对 `cancelled` 提示](https://github.com/openpi-dev/openpi/blob/ed9dbc1018f890fd54375f5371990ddfee8af5df/web/ui/src/features/composer/Composer.tsx#L120-L132);[Transcript 不渲染空 assistant 内容](https://github.com/openpi-dev/openpi/blob/ed9dbc1018f890fd54375f5371990ddfee8af5df/web/ui/src/features/transcript/Transcript.tsx#L724-L743)。 建议保留有界、可安全显示的规范错误信息及停止原因,由同一事实来源驱动实时状态和持久记录。不要只添加临时 toast,也不要将显示标签作为执行事实。 ## 最小验收 - [ ] Provider 返回错误时,Web 明确显示本轮失败及有界错误原因。 - [ ] 流式输出部分内容后失败,已有内容保留,同时明确标记回答未正常完成。 - [ ] 自动重试耗尽后显示最终失败;重试期间与最终状态不互相覆盖。 - [ ] 刷新或重新进入 Session 后仍可看到失败事实和原因。 - [ ] 成功、取消、失败、未知保持独立状态;启动前拒绝路径继续工作。 - [ ] 用本地协议 fixture 覆盖真实 Pi → snapshot/SSE → 浏览器的链路;相关专项测试、`bun run check`、`bun run test` 通过。
Activity
testikun commentedon Sep 8, 2026
我来认领 Web Provider 运行失败证据丢失切片。
技术方案:在 Pi assistant stopReason=error 的 authoritative runtime boundary 生成稳定的 failure receipt,保留 sanitized provider error、request/turn identity、已产生的部分输出和 retry/limit 信息;snapshot、SSE replay 与 Transcript 显式渲染失败态,不把它降级成普通 idle 或 no-output。启动前 prompt_failed、用户取消和未知终态继续走各自合同;补充 fixture Provider、重试耗尽、部分响应、刷新/重连测试。