Files
dingtalk-workspace-cli/docs/dws-multi-im-error-optimization-plan.md
T

493 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DWS Multi IM 错误专项优化方案
> 日期:2026-08-03
> 分支:`codex/multi-im-optimization`
> 当前提交:`30314311`
> 评测基线:`2a8c6c87`,155 条 accepted 轨迹、74 次失败调用
## 1. 结论
74 次失败已经能够完整归因,不应继续使用“API 业务错误”或“其他命令失败”这类粗分类:
| 根因 | 次数 | 判断 |
|---|---:|---|
| 群名解析歧义 | 37 | Runtime 正确 fail-closed;评测夹具和 Agent 消歧流程问题 |
| 稳定 ID 进入群名参数 | 17 | Shortcut 目标参数合同不一致 |
| 群名进入稳定 ID 参数并打到后端 | 7 | Shortcut 目标参数合同不一致,且缺少调用前校验 |
| 不存在的命令或 flag | 11 | 选路/Help 漂移及缺少无歧义兼容别名 |
| `auth whoami` 不存在 | 1 | MCP 错误误导后的错误恢复分支 |
| MCP 内部依赖不可用 | 1 | 后端 `queryToolMeta` 内部连接拒绝;CLI 错分为业务错误 |
| **合计** | **74** | |
其中真正带 `NETWORK_ERROR / UNAVAILABLE / Connection refused` 证据的只有 1 次。它不是用户侧公网故障,也不是 `+conversation-list` 参数拼装错误,而是 MCP 后端内部依赖不可用。
## 2. 本地复测
### 2.1 真实 MCP
使用当前提交重新构建 CLI,执行:
```bash
dws chat +conversation-list --page-all --format json --timeout 60
```
6 轮已验证结果全部为:
```text
exit=0
complete=true
count=100
failedCount=0
```
此外,评测提交 `2a8c6c87` 重新构建后同一命令也成功;该提交到当前提交之间,`+conversation-list` 的 MCP 工具名、参数和分页代码没有变化。
### 2.2 原始失败确定性回放
本地 loopback MCP 返回评测中的等价内容:
```json
{
"success": false,
"server_error_code": "NETWORK_ERROR",
"technical_detail": "McpService.queryToolMeta ... StatusCode.UNAVAILABLE ... Connection refused",
"retryable": true
}
```
当前 CLI 稳定输出:
```text
category=api
reason=business_error
message=business error: success=false
hint=Check required parameters and values
server_error_code=NETWORK_ERROR
retryable=true
```
这证明需要修的是中央错误分类:诊断字段已经保留,但没有参与 `reason/message/hint` 决策。
### 2.3 其他代表错误复测
当前 Runtime 可稳定复现:
1. `+chat-members-list --group <cid>` 在调用后端前失败,并提示“请使用 --group 或 --chat”,但当前命令的稳定 ID 参数实际是 `--conversation-id`;提示与命令合同矛盾。
2. `+chat-messages --group <群名>` 把群名直接交给只接受 CID 的下层,后端返回 `openCid or cid is required`;应在 CLI 内先解析,不能打到后端。
3. `+search-msg --text ...` 返回 `unknown_flag`;当前正式参数是 `--query`。
## 3. P0:修中央错误分类
### 3.1 分类顺序
修改 `internal/app/runner.go`,在通用 `business_error` 之前执行结构化分类:
```text
ExtractServerDiagnostics
→ ClassifyServerFailure
→ 构造 typed Error
→ 无已知类别才回退 business_error
```
建议新增独立分类器,例如 `internal/app/server_failure_classifier.go`。输入为 MCP 内容和 `ServerDiagnostics`,输出:
```go
type ServerFailureClass struct {
Reason string
Origin string
Stage string
ExecutionStarted *bool
Hint string
Actions []string
}
```
首批规则:
| 条件 | reason | origin | stage |
|---|---|---|---|
| `NETWORK_ERROR` 且 detail 含 `queryToolMeta`、`UNAVAILABLE` | `backend_dependency_unavailable` | `mcp_gateway` | `tool_metadata_lookup` |
| `PARAM_ERROR`、缺 required 参数 | `invalid_request` | `dingtalk_api` 或 `mcp_gateway` | `tool_validation` |
| 权限/Scope | `permission_denied` | 实际返回方 | `authorization` |
| 资源不存在 | `resource_not_found` | `dingtalk_api` | `tool_execution` |
| 无结构化证据 | `business_error` | `unknown` | `unknown` |
兼容期可以用 `technical_detail` 补充 stage,但 `server_error_code` 必须是主分类依据。原始 `trace_id`、`technical_detail` 和 `retryable` 原样保留。
### 3.2 重试边界
当前响应没有稳定的 `execution_started` 字段,因此第一阶段只修分类和恢复提示,不对所有工具自动重试。
- 后端应优先在 `queryToolMeta` 内部做短重试,因为它明确发生在业务执行前;
- CLI 只有在后端提供 `execution_started=false` 后,才可对读写命令统一安全重试;
- 过渡期最多只对最终 Schema 标记为只读的命令重试一次;
- 写命令执行状态未知时禁止自动重试。
## 4. P0:统一群目标参数合同
24 次失败来自同一个设计问题:不同 Shortcut 对 `--group` 的含义不同。
统一公开合同:
| 参数 | 统一语义 |
|---|---|
| `--group` | 接受群名或 `openConversationId`;CLI 自动识别 |
| `--conversation-id` | 显式稳定 ID |
| `--chat-query` | 显式自然群名 |
| `--chat` / `--open-conversation-id` | 只作为无歧义兼容别名,不作为文档默认入口 |
新增共享入口,例如:
```go
ResolveChatTarget(rt, directValue, queryValue)
```
规则:
1. directValue 形似 CID:直接返回,不进行群名搜索;
2. directValue 不是 CID:按自然群名走完整分页唯一解析;
3. queryValue 形似 CID:规范化为稳定 ID,而不是送入群名搜索;
4. 零命中或多命中继续 fail-closed;
5. 任何自然群名都不得直接发送到只接受 CID 的后端工具。
首批应用:
- `internal/shortcut/smart/group_members.go`
- `internal/shortcut/smart/chat_messages.go`
- `internal/shortcut/smart/search_msg.go`
- 复核 `send_to_group.go`、`at_me.go` 和 `chat/chat_group.go`,消除同类局部实现。
同时修改 `ResolveChat` 的类型错误:不要输出与调用命令不一致的具体 flag,改为 typed `target_type_mismatch`,并提示读取当前 leaf Help。
## 5. P0:处理 11 次命令/flag 漂移
只增加语义唯一、不会制造第二入口的兼容项:
| 观察到的错误 | 决策 |
|---|---|
| `+search-msg --text`(3 次) | 增加 `--text` → `--query` 隐藏兼容别名 |
| `+search-msg --text-query`(1 次) | 增加 `--text-query` → `--query` 隐藏兼容别名 |
| `+chat-messages --page-size`(1 次) | 增加 `--page-size` → `--limit` 隐藏兼容别名 |
| `+chat-messages --open-conversation-id`(1 次) | 增加稳定 ID 兼容别名 |
| `+chat-members-list --chat`(2 次) | 增加稳定 ID 兼容别名 |
| `+chat-members-list --open-conversation-id`(2 次) | 增加稳定 ID 兼容别名 |
| `+chat-rename`(1 次) | 作为 `+chat-update` 的隐藏兼容别名,`--group` 复用群名/CID 唯一解析;Skill 默认仍只推荐 reviewed `chat group rename --id --name` |
所有新增 flag 必须进入真实 Cobra、Shortcut constraints、Schema 参数投影和示例校验;不能只改 Skill。
`auth whoami` 不建议补一个语义不清的新命令。MCP 内部依赖错误修正后,恢复动作应直接提示“稍后重试并保留 trace ID”,不得诱导 Agent 检查认证。认证确需检查时只推荐现有 `dws auth status` 和 `dws profile list`。
## 6. P1:同名群与评测口径
37 次 `resolution_ambiguous` 是安全机制正常工作,不能通过默认第一个候选来消除。
需要同时处理两层:
1. 真实产品:继续返回结构化 candidates,Agent 展示候选并停止写入;可在下层真实提供时增加群主、群类型等可理解的区分字段。
2. 固定评测:所有要求命中 `dws测试群01/02` 的 case 都必须在 accepted run 前保证目标名称唯一,而不是只处理 H15/H18/H20;夹具状态和恢复记录继续留在 Prompt 外。
评测报告应把错误维度分开:
```text
agent_error
request_contract_error
expected_resolution_stop
business_error
infrastructure_error
recovered_infrastructure_error
```
若环境真实存在同名群,`resolution_ambiguous` 应计入安全停机事实,不应伪装成业务成功;若 case 设计要求唯一群,则应判夹具失败并重跑,而不是污染 Agent clean。
## 7. 测试与验收门禁
### 7.1 错误分类
- `success=false + NETWORK_ERROR + queryToolMeta/UNAVAILABLE` → `backend_dependency_unavailable`;
- `success=false + PARAM_ERROR` → `invalid_request`;
- 无诊断字段的 `success=false` 保持 `business_error`;
- JSON 输出中的 `reason/hint/retryable/server_error_code/trace_id` 与 typed error 一致;
- 不再对内部依赖错误提示“检查参数或认证”。
### 7.2 目标合同
对 `+chat-members-list`、`+chat-messages`、`+search-msg` 分别覆盖:
- `--group <cid>` 不搜索,且下层收到同一 CID;
- `--group <name>` 唯一解析后下层只收到 CID;
- `--conversation-id <cid>` 不搜索;
- `--chat-query <name>` 唯一解析;
- `--chat-query <cid>` 不再触发“ID 被当群名”;
- 多候选时下层业务调用为 0;
- 群名绝不进入 `openCid/cid/openConversationId` 参数。
### 7.3 兼容参数
- 每个兼容 alias 与 canonical flag 产生完全相同的 invocation;
- alias 与 canonical 同时出现时按 constraints 拒绝,禁止静默覆盖;
- Help 只突出 canonical flag,Schema 保留 Agent 可执行事实;
- Skill 和 reference 不把兼容 alias 写成新的默认路线。
### 7.4 最终门禁
```bash
gofmt -w <modified-go-files>
DWS_PACKAGE_VERSION=0.0.0-test go test ./internal/errors ./internal/transport ./internal/app ./internal/shortcut/targetresolver ./internal/shortcut/chat ./internal/shortcut/smart
go generate ./internal/cli
./scripts/policy/check-generated-drift.sh
./scripts/policy/check-schema-catalog.sh
./scripts/policy/check-skill-commands.sh
DWS_PACKAGE_VERSION=0.0.0-test go test ./...
```
真实环境至少再跑:
```bash
dws chat +conversation-list --page-all --format json
dws chat +chat-members-list --group <真实群名或CID> --format json
dws chat +chat-messages --group <真实群名或CID> --limit 5 --format json
dws chat +search-msg --chat-query <真实群名> --query <关键词> --format json
```
验收不是简单要求 74 变 0,而是:
- 1 次 MCP 内部故障被正确分类并进入独立可靠性指标;
- 24 次目标类型错误在 CLI 内确定性消除;
- 11 次无歧义 flag 漂移不再发生;
- 1 次错误认证分支不再由误导 hint 触发;
- 37 次同名群由正确夹具或显式用户消歧处理,Runtime 继续禁止猜第一个候选。
## 8. 优化实施拆分
优化按错误归属拆成四个可以独立验证的交付单元,避免 Runtime、Skill 和评测夹具混在同一个改动里。
### 8.1 交付 A:错误语义与恢复动作
范围:
- `internal/app/runner.go`
- `internal/transport/diagnostics.go`
- `internal/errors/`
- 新增中央 server failure classifier 及测试
交付结果:
1. `NETWORK_ERROR + queryToolMeta/UNAVAILABLE` 不再落入 `business_error`;
2. JSON 输出具备稳定的 `origin/stage/reason`;
3. 参数、权限、资源和基础设施错误给出不同恢复动作;
4. 未确认 `execution_started=false` 前不对写请求自动重试;
5. 错误提示不再诱导 Agent 执行无关认证命令。
该交付只改变错误解释和恢复合同,不改变 `+conversation-list` 的请求拼装。
### 8.2 交付 B:统一群目标 Runtime
范围:
- `internal/shortcut/targetresolver/`
- `internal/shortcut/smart/group_members.go`
- `internal/shortcut/smart/chat_messages.go`
- `internal/shortcut/smart/search_msg.go`
- 其他调用 `ResolveChat` 的 Chat Shortcut
交付结果:
1. 群名和 CID 在进入下层前完成类型规范化;
2. 稳定 CID 永远不进入 `search_groups`;
3. 自然群名永远不直接进入 `openCid/cid/openConversationId`;
4. 相同的参数名在高频 Shortcut 中表达相同语义;
5. 零命中、多候选和类型不匹配都使用 typed resolution error。
这是消除 24 次目标类型错误的主要 Runtime 改动。
### 8.3 交付 C:兼容参数、Schema 与 Skill 收敛
范围:
- 对应 Shortcut 的真实 Cobra flags/constraints
- `internal/cli/schema_hints/metadata/chat.json`
- `internal/cli/schema_hints/selection/chat.json`
- `skills/multi/dingtalk-chat/SKILL.md`
- 精确 Chat references
- 生成的 Schema/Agent metadata/Catalog
交付结果:
1. 增加经过评审的无歧义兼容 flag,但 Help/Skill 只推荐 canonical flag;
2. 修改群名只推荐现有 reviewed `chat group rename --id --name`;已观测的 `+chat-rename` 仅作为隐藏兼容别名,不成为第二默认入口;
3. `unknown command/unknown flag` 后只查精确 leaf Help并最多重试一次;
4. 示例必须通过真实 Cobra 和 Schema constraints;
5. Skill 不保存固定测试群名、CID 或评测 case 文案。
### 8.4 交付 D:评测夹具与 judge 分层
范围:评测仓库或 runner,不进入 DWS Runtime 业务逻辑。
交付结果:
1. 所有依赖指定测试群名的 case 在 accepted run 前保证目标唯一;
2. 夹具创建、隔离和恢复有稳定 ID 审计记录,但不注入 Prompt;
3. `expected_resolution_stop`、`request_contract_error` 和 `infrastructure_error` 分开统计;
4. infrastructure retry 即使恢复成功,也保留独立可靠性计数;
5. clean 不再把正确的安全停机和错误请求拼装混成一种失败。
## 9. 评测拟合方案
### 9.1 拟合对象
只拟合以下稳定模式:
- 错误结构:`server_error_code`、MCP stage、retryability;
- 意图结构:群名、稳定 CID、搜索关键词、分页参数;
- 命令合同:canonical flag、无歧义兼容 alias、constraints;
- 安全语义:多候选禁止默认第一项、写操作禁止不安全重试;
- 结果合同:complete/partial/failures 和 typed error。
禁止拟合:
- `H4/H8/...` 等 case 编号;
- `dws测试群01/02` 字面值;
- 评测中出现的固定 CID、用户 ID 或 trace ID;
- 针对某条 Prompt 的字符串分支;
- 为提高 clean 而把非零退出统一改为成功。
### 9.2 数据集拆分
采用三层数据集:
| 数据集 | 用途 | 内容 |
|---|---|---|
| observed | 修复已观察问题 | 当前 155 条 accepted、74 次失败调用 |
| variant | 验证泛化 | 替换群名、CID、关键词、flag 顺序、是否显式指定 ID |
| safety holdout | 防止过拟合 | 同名群、零命中、跨 profile ID、写请求执行状态未知、分页中途失败 |
Runtime 单元测试和本地 MCP E2E 使用合成稳定 ID,例如 `cid-test-a`,不得依赖真实评测资源。真实环境 smoke 只验证集成连通性和输出合同,不把真实业务数据写入测试快照。
### 9.3 变体矩阵
每个群目标型 Shortcut 至少覆盖:
| 维度 | 变体 |
|---|---|
| 目标输入 | 唯一群名、CID、模糊群名、同名群、零命中 |
| 参数入口 | canonical flag、兼容 alias、canonical+alias 冲突 |
| 命令类型 | 只读、普通写、高风险写 |
| 后端错误 | 参数错误、权限错误、资源不存在、限流、内部依赖不可用 |
| 执行阶段 | 调用前、元数据查询、业务执行、分页后续页 |
| 结果状态 | complete、partial、fail-closed、retry recovered |
兼容 alias 只需证明与 canonical invocation 等价,不为每个 alias 复制完整业务测试矩阵。
### 9.4 回归顺序
```text
typed unit tests
→ fake MCP CLI E2E
→ Schema/Skill policy
→ observed 31×5 回放
→ variant 集
→ safety holdout
→ 小规模真实环境 smoke
```
只有前四层通过后才看 clean 指标。若 clean 提升但 safety holdout 出现自动选第一个群、重复写或 partial 冒充 complete,则判优化失败。
### 9.5 指标
主指标:
- 请求合同错误数;
- 首次 canonical 命令/flag 命中率;
- 自然目标到稳定 ID 的一次成功率;
- false success、错误写入和重复写入;
- 错误分类准确率;
- complete/partial 真实性。
分层可靠性指标:
- MCP/网关基础设施失败率;
- 安全自动重试恢复率;
- 未恢复 infrastructure error 数;
- trace ID 可关联率。
次级指标:
- clean run 比例;
- 每 case 业务 CLI 次数;
- unknown command/flag 后的纠错次数;
- Token 和执行时长。
clean 不能替代主指标,也不能通过过滤真实失败直接提升。
## 10. 预期收敛与发布判定
基于当前 74 次失败,实施后的预期归宿是:
| 当前错误 | 目标状态 |
|---|---|
| MCP 内部依赖失败 1 次 | 正确分类;满足安全条件时恢复,否则进入 infrastructure 指标 |
| 错误认证分支 1 次 | 由精确恢复动作消除 |
| 目标类型错误 24 次 | Runtime 规范化后消除 |
| 命令/flag 漂移 11 次 | canonical 路由或兼容 alias 消除 |
| 同名群歧义 37 次 | 唯一夹具下正常执行;真实歧义下继续安全停机 |
发布必须同时满足:
1. observed 集中的确定性合同错误归零;
2. variant 集不依赖固定群名和 CID;
3. safety holdout 的误选、误写和重复写为 0;
4. MCP 基础设施错误分类准确,不再显示参数/认证误导;
5. 全仓、Schema、Skill 和真实只读 smoke 全部通过;
6. 评测报告同时给出 clean、业务正确性、安全停机和基础设施可靠性,不再只给一个混合分数。
## 11. 当前分支实施与验证记录
> 实施日期:2026-08-03
> 实施分支:`codex/multi-im-optimization`
### 11.1 已实施
1. 中央错误分类已在两条 HTTP MCP 失败路径生效:
- `NETWORK_ERROR` / `queryToolMeta` / `UNAVAILABLE` 分类为 `backend_dependency_unavailable`;
- 输出 `origin` / `stage` / `reason`,保留 trace、server code 和 retryable;
- 后端未显式返回时不猜测 `execution_started`;
- multi-profile 聚合保留错误恢复语义、resolution candidates 和诊断字段。
2. 群目标合同已收敛到 `ResolveChatTarget`:
- CID 不进入 `search_groups`;
- 群名唯一解析后才传入业务工具;
- 零命中、多候选、不完整分页继续 fail-closed;
- 已应用到成员、会话消息、消息搜索、@ 我、群机器人/邀请、按群发送和群重命名。
3. 11 次命令/flag 漂移的 Runtime 兼容已补齐:
- `--text` / `--text-query`、`--page-size`、`--open-conversation-id`、成员命令的 `--chat`;
- `+chat-rename` 作为 `+chat-update` 的隐藏兼容别名,群名依然在写前唯一解析;
- canonical 与 alias 同时出现时拒绝静默覆盖。
4. Chat Skill、群/消息 reference、reviewed selection hints 与生成 Schema Catalog 已同步。
### 11.2 自动验证
- 本地可控 MCP 重放通过真实 `executeInvocation → HTTP MCP → success=false → typed Error` 路径;
- 聚焦错误、resolver、Chat Shortcut 和 app 回归通过;
- `go generate ./internal/cli` 通过;
- `check-generated-drift.sh` 通过;
- `check-schema-catalog.sh` 通过:26 products / 849 tools;
- `check-skill-commands.sh` 通过:1009 executable command paths;
- Skill `quick_validate.py` 通过;
- Agent 示例合同通过:1047 total / 1023 contract / 24 dry-run;
- `DWS_PACKAGE_VERSION=0.0.0-test go test ./...` 全仓通过;
- `go build -o <temp>/dws ./cmd` 真实构建通过。仓库文档中的裸 `go build ./cmd` 会因根目录已有 `cmd/` 目录而报输出同名冲突,因此验证时显式指定了临时 `-o`。
### 11.3 真实环境 smoke
使用当前登录 profile 和本次构建产物执行,不保存真实群名或 CID 到仓库:
- `+conversation-list --page-all`:完整返回;
- `+chat-members-list --group <真实群名>`:唯一解析成功,`complete=true`;
- `+chat-members-list --open-conversation-id <真实CID>`:兼容 alias 成功;
- `+chat-messages --group <真实群名>` 与 `--open-conversation-id/--page-size`:均成功,单页返回正确标记 `complete=false / hasMore=true / stopReason=single_page`;
- `+search-msg --chat-query <真实CID> --text-query <无命中词>`:不搜群名,搜索完整返回;
- `+chat-rename --group <真实CID> ... --dry-run`:返回 `executed=false`且 invocation 使用正确 CID,未修改真实群。
### 11.4 仓库外后续项
评测夹具唯一性与 judge 分层属于评测 runner,不在本 DWS CLI 仓库中。Runtime 修复后应重跑 31×5 observed、variant 和 safety holdout;真实同名群仍必须保持 `resolution_ambiguous` 安全停机,不以默认第一个候选换 clean。