agentscope-ai / agentscope-ai/QwenPaw
[Bug]: harnesses/codex:后端不流式下发 agentMessage/delta 时(如火山方舟 agentplan 网关 + codex 0.144.x),第三方智能体每轮回复"空响应",usage 全 0
- 主要語言
- Python
- 星號
- 34.9k
- 分支
- 3.1k
- 平均合併
- 1 天 15 小時
- 30 天內合併 PR
- 225
描述
## 环境
- QwenPaw:2.2.0b3(pip 安装,Linux x86_64)
- codex-cli:0.144.4(`codex_cli_bin`,app-server 模式)
- 接入方式:第三方智能体,`agent.json` 中 `backend: "codex"`
- 模型提供商:某厂商 Responses 协议网关(下称"该网关";应公开提交要求对提供商做匿名化,细节可私信提供),公开模型 glm-5-3-flash
## 现象
console 频道与该 codex 智能体对话,每轮都得到"助手返回了空响应。",无任何错误日志。日志签名:
```
Usage for session ...: {'provider_id': '', 'model_name': '', 'prompt_tokens': 2, 'completion_tokens': 0, 'total_tokens': 2, 'estimated': True, ...}
console stream done: event_count=3 has_response=True
```
而以下两条路径均正常,说明问题在 QwenPaw 适配层而非上游:
1. 直接 `codex exec`(同目录、同 provider)回复正常;
2. 用与 QwenPaw 相同的 JSON-RPC 序列直接驱动 app-server(initialize → thread/start → turn/start),turn 正常完成并返回完整 `agentMessage`。
## 根因分析
三重证据指向同一结论:**当后端不流式下发 `item/agentMessage/delta` 时,`harnesses/codex/adapter.py` 的 `_convert_notification` 会把最终文本整体丢弃。**
1. **实拍事件流(0.144.4 + 该网关)**:全程没有任何 `item/agentMessage/delta` 事件;完整文本直接出现在 `item/started` / `item/completed` 的 `item.text` 中。`turn/completed` 为瘦负载:`{"items": [], "itemsView": "notLoaded"}`。codex 自身日志同时刷出 `OutputTextDelta without active item` / `ReasoningSummaryDelta without active item`(该网关的流式分片顺序触发了 codex 0.144 的流式状态机问题,文本最终以非流式 item 兜底送达)。
2. **适配层代码**:`_convert_notification` 中 `item/completed` 分支只处理 `_TOOL_ITEM_TYPES`(commandExecution 等);`agentMessage` 类型的 item/completed 掉出分支返回 `None`。文本进入事件流的**唯一**通道是 `item/agentMessage/delta` → `TEXT_DELTA`。零 delta ⇒ 零文本 ⇒ "空响应"。usage 全 0/estimated 同因(`thread/tokenUsage/updated` 未被消费)。
3. **同类问题已有官方确认**:姊妹项目 sipeed/picoclaw #2674("Codex OAuth: empty assistant response when ChatGPT backend streams items via response.output_item.done",签名同样是 token 有计数、content_chars=0),修复方案 PR #2581 即"把流式 item 事件累积的文本嫁接回终结事件"。
## 复现步骤
1. 将某 Responses 协议网关配置为 codex 的 provider(触发该问题的网关行为特征:不下发 `item/agentMessage/delta`、终结事件为瘦负载);
2. 创建 `backend: "codex"` 的第三方智能体,模型选 glm-5-3-flash;
3. console 发送任意消息 → 每轮"助手返回了空响应。",usage 全 0。
(本地验过的旁证:deepseek 等正常流式 Responses 后端因 delta 正常下发,不触发此问题——这也解释了"换 provider 后突然全空"的现象。)
## 修复建议(已在本地验证)
在 `_convert_notification` 中按 picoclaw #2581 的模式补上"整段文本嫁接 + 按已流式量去重",不影响正常流式后端:
- `__init__` 增加实例状态 `self._delta_chars: dict[str, int]`(itemId → 已流式字符数);
- `item/agentMessage/delta` 分支累加 `self._delta_chars[itemId]`;
- `item/completed` 且 `item_type == "agentMessage"` 时,设 `delivered = self._delta_chars.pop(itemId, 0)`,若 `len(item.text) > delivered` 则补发 `TEXT_DELTA(text=item.text[delivered:])`,否则返回 `None`(正常流式后端 delta 已发全量,天然去重不重复);
- `turn/completed` 分支 `self._delta_chars.clear()`;
- 由于需要访问实例状态,`_convert_notification` 从 `@staticmethod` 改为实例方法(现有调用点均为 `self._convert_notification(...)`,无破坏)。
本地单元测试 5/5 通过:纯 completed 嫁接(该网关场景)、delta 全量去重、部分 delta 差额嫁接、turn 结束状态清理、工具事件路径不受影响。
## 期望行为
后端以非流式 item 形式(或瘦负载 turn/completed)返回内容时,codex 第三方智能体仍能正常产出回复,并正确记录 usage。
## 备注
- 若维护者已在新版修复,请告知对应版本号,以便受影响用户升级;
- 本地补丁可提供完整 diff 备选 PR,欢迎评论索引。
貢獻指南
評估
這個 Issue 還沒有評估資料。