boardx / boardx/workspacex

生产回归:PR #2410 合入后 devapp 任务模式 100% 失败(MODEL_CALL_FAILED,计划 0 步)

Open
#2,417 4 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
0
Forks
0
Avg merge
1h 7m
Merged PRs (30d)
969

Description

## 背景

产品负责人手动合并 PR #2410(#2220 方案 B,`PlanFirstToolChoiceMiddleware` 强制
`tool_choice="write_todos"`)后,在 devapp 用真实任务模式请求实测:

- 部署已确认在 `21:20:10 UTC` 完成(`backend-gates.yml` run `33334333996`,`deploy` job
日志逐字:`✅ 部署完成:147c66a1 ... (#2410)`)。
- **部署确认生效之后重测,依然失败**:右下角状态"失败"、"当前计划 0 步",红色错误条
"模型这次没能返回可用结果"——不是部署时间差假阳性。

## 已排查、判定不是主因的线索

用户第二次实测的截图里,发给模型的正文任务模式前缀出现了两次:
```
请先给出计划,经确认后再执行:请先给出计划,经确认后再执行:研究设计思维的历史,然后做一个3页的ppt
```
根因:`apps/web/components/chat/copilotkit-v2-panel-body.tsx:815`
```ts
const text = taskMode ? `请先给出计划,经确认后再执行:${rawText}` : rawText;
```
**无条件拼接,不检查 `rawText` 是否已经包含这句前缀**——这次是用户手动在输入框也打了同一句话
(可能在测试触发语句本身),加上开关本身的自动拼接,叠成两遍。这是一个真实、独立的 bug(应该
做幂等检查:`rawText` 已经以该前缀开头时不再重复拼接),**但经代码走查判断它不是这次总失败的
直接原因**——`harness.py` 的 `TASK_MODE_MARKER = "请先给出计划,经确认后再执行"` 只做
`in` 子串匹配(`marker_present = TASK_MODE_MARKER in _human_text(...)`),前缀出现一次还是
两次,子串匹配结果一样是 `True`,不会改变 `PlanFirstToolChoiceMiddleware` 的判断路径。

## 走查后的强嫌疑根因(尚未拿到真实容器日志确认,见下方"待确认")

`apps/deep-agent-service/src/deep_agent_service/model.py`:
```python
from langchain_openai import ChatOpenAI
DEFAULT_MODEL_ID = "qwen-plus"
...
return ChatOpenAI(base_url=base_url, api_key=api_key, model=model_id)
```
devapp 真实走的是**阿里云百炼(Bailian/DashScope)OpenAI 兼容模式的 `qwen-plus`**,不是原生
OpenAI。`PlanFirstToolChoiceMiddleware`(`harness.py`)的文档注释里写的假设是
"provider 的 API 契约(OpenAI 等模型收到具名 `tool_choice` 时必须调用该工具)"——但 PR #2410
新增的 TC-6 测试用的是 `ScriptedChatModel`(一个假模型/桩),**从未针对真实的
`qwen-plus`/DashScope 兼容端点验证过强制具名 `tool_choice` 这个调用形状是否被接受**。
DashScope 的 OpenAI 兼容模式对 `tool_choice` 的支持是已知有限/有版本差异的
(一些第三方 OpenAI 兼容端点在收到强制具名 function 的 `tool_choice` 时会直接返回 4xx,
而不是像原生 OpenAI 那样服从)。这与用户第一次报告里怀疑的方向①完全吻合:
"中间件强制 tool_choice 后,某些请求路径下 provider 返回了错误,中间件没有优雅降级,
直接导致整轮判失败"——`request.override(tool_choice="write_todos")` 一旦被 provider 拒绝,
`deep-agent-model-provider.ts` 里 run 会以非 `success` 状态收场,`execute-run.ts` 据此抛
`ModelCallError("MODEL_CALL_FAILED", 'deep agent run ended with status "error"')`,
即用户看到的"模型这次没能返回可用结果"+"计划 0 步"。

## 待确认(需要真实日志,当前拿不到)

coord-main 尝试通过仓库现有的只读探针工作流 `.github/workflows/devapp-probe.yml`
追加一步只读 `docker logs workspacex-deep-agent` 来实锤这个假设,但本地 `git push`
被 Claude Code 权限分类器拦截,需要人类显式批准后才能推送触发。**在没有真实报错栈之前,
以上"强嫌疑根因"仍是走查推断,不是已证实结论。**

## 建议修复方向(两处,独立生效,互不依赖)

1. **【已确认的真 bug,直接修】** web 侧任务模式前缀拼接加幂等检查——`rawText` 已经以
`TASK_MODE_MARKER` 开头时不再重复拼接一遍。
2. **【规避该嫌疑根因的防御性修复,无论根因是否是 tool_choice 兼容性都安全】**
`PlanFirstToolChoiceMiddleware` 强制 `tool_choice` 的这次模型调用如果被 provider 拒绝
/报错,捕获后**退回不强制**(等同于只有方案 A 的提示词软约束)重试一次,而不是让整个
run 直接判 `MODEL_CALL_FAILED`——`build_middleware()` 里已经有类似"配置漂移不可达分支
记 warning、不崩溃"的先例(`write_todos` 未挂载那个分支),这次是把同一条"确定性保证
不能反而降低可用性下限"的纪律用到 provider 拒绝这个新场景。真正修好之后仍要在 devapp
用真实 `qwen-plus` 端点实测收敛,不能只靠桩测试通过就断言修复。

Refs #2220(本 issue 是该诊断在生产环境的一次真实回归,范围更窄、更紧急)

🤖 Generated with [Claude Code](https://claude.ai/code)

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with apps/web/components/chat/copilotkit-v2-panel-body.tsx:815, apps/deep-agent-service/src/deep_agent_service/model.py, harness.py, deep-agent-model-provider.ts, and execute-run.ts. Use the devapp probe workflow and real qwen-plus requests to confirm the provider error before changing behavior. Done means the task prefix is idempotent, provider rejection does not make the run fail outright, and the real devapp flow no longer reports MODEL_CALL_FAILED.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, typescript
Domain
api, backend-api-design, full-stack
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.