makecindy / makecindy/cindy

[Bug] Codex + DeepSeek 首轮“你好”消耗 116k input tokens,工具 schema 未按 Code Mode 压缩

Open
#1,432 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
2.7k
Forks
395
Avg merge
21h 48m
Merged PRs (30d)
776

Description

## 摘要

Cindy 的 Codex 通道中,使用 `deepseek-v4-flash` 新建会话后只发送“你好”,第一轮模型调用即报告 **116,508 input tokens**;同一工作目录、同一句话改用 `gpt-5.6-luna`,第一次调用仅 **20,931 input tokens**,执行两次 Code Mode `exec` 后 UI 最终显示 **27,060 context tokens**。

本机证据表明,这不是用户消息长度、AGENTS.md 差异或 UI 重复累计造成的。DeepSeek 走 `cindy_gateway` 的通用工具模式,Luna 走 `cindy_openai`,并在 Codex model catalog 中启用了 `use_responses_lite=true` 和 `tool_mode=code_mode_only`。DeepSeek 路径没有对应的 Code Mode 模型元数据,因而很可能把大量 App/MCP 工具 schema 全量放进首轮请求。

## 环境

- Cindy: **0.1.27**
- Bundled Codex CLI: **0.145.0**
- macOS: **26.5.2 (25F84)**
- Architecture: **arm64**
- 两个会话使用相同工作目录、相同插件/工具环境
- 上下文窗口均为 **258,400**

## 复现步骤

1. 打开 Cindy,选择 Codex agent。
2. 新建会话,模型选择 `deepseek-v4-flash`。
3. 只发送:`你好`
4. 记录第一条 `token_count.last_token_usage`。
5. 在相同工作目录新建另一会话,模型改为 `gpt-5.6-luna`。
6. 同样只发送:`你好`
7. 对比第一条 usage 和最终 context token。

## 实际结果

### DeepSeek Flash

第一条模型调用:

```json
{
"input_tokens": 116508,
"cached_input_tokens": 6528,
"cache_write_input_tokens": 0,
"output_tokens": 86,
"reasoning_output_tokens": 50,
"total_tokens": 116594
}
```

即首轮约有 **109,980 个非缓存输入 token**。该轮只是直接回复问候,没有先执行工具。

### GPT-5.6 Luna

第一次模型调用:

```json
{
"input_tokens": 20931,
"cached_input_tokens": 0,
"cache_write_input_tokens": 0,
"output_tokens": 336,
"reasoning_output_tokens": 298,
"total_tokens": 21267
}
```

随后 Luna 执行两次 `exec`,本轮最后一次 usage 为:

```json
{
"input_tokens": 27060,
"cached_input_tokens": 25344,
"cache_write_input_tokens": 0,
"output_tokens": 25,
"reasoning_output_tokens": 9,
"total_tokens": 27085
}
```

因此按第一次模型调用比较,DeepSeek 是 Luna 的约 **5.57 倍**;按 UI 最终 context 比较仍约为 **4.31 倍**。

## 排除项

对两个 rollout 的首轮实际输入记录进行了比较:

| 项目 | DeepSeek | Luna |
|---|---:|---:|
| 用户消息 | “你好” | “你好” |
| 注入的 user instructions | 11,737 chars | 11,737 chars |
| 该块内容 hash | 相同 | 相同 |
| developer message | 22,290 chars | 27,278 chars |
| 第一条 input tokens | 116,508 | 20,931 |

Luna 的 developer message 反而更长,因此差异不是由 AGENTS.md、推荐插件列表或普通系统指令造成的。

Cindy 数据库里的 `sessions.context_tokens` 与 rollout 的最新 `last_token_usage.input_tokens` 一致,也没有发现 UI 把累计 usage 错当作单轮 context 的证据。

## 工具 schema 证据

同一 Cindy Codex Home 的 App tools cache 中有:

- **132** 个工具条目
- 包装后序列化体积约 **512,333 bytes**
- 其中 GitHub:89 个工具,约 228,268 bytes
- Sites:28 个工具,约 195,153 bytes

这些数字不是上游精确 tokenizer 的计费拆分,但体积与两模型之间约 95k input-token 的缺口高度吻合。

运行行为也不同:

- DeepSeek 会话使用传统 `exec_command`/独立工具调用;
- Luna 会话使用单一 freeform `exec` Code Mode,再由运行时调度嵌套工具;
- Luna 的本机 model catalog 条目明确包含:
- `use_responses_lite: true`
- `tool_mode: "code_mode_only"`
- 本机 model catalog 中没有找到 `deepseek-v4-flash` 的等价条目。

## 初步根因

已确认的部分:

1. 两模型走不同 provider runtime:`cindy_gateway` vs `cindy_openai`。
2. Luna 具有 Code Mode/Responses Lite 模型能力元数据,DeepSeek 没有。
3. DeepSeek 的首轮输入比共同的文本指令大约多 95k tokens。
4. 工具注册表的 schema 体积足以解释该差值。
5. UI 展示的是运行时返回的 context usage,不是前端自行放大。

据此推断:**非原生 Codex 模型落入常规工具暴露模式,首轮请求全量携带 App/MCP tool schemas;Luna 则通过 Code Mode 只暴露一个紧凑的 `exec` 入口。**

## 影响

- 简单问候就占用约 45% 的 258k 上下文窗口。
- 第一轮缓存命中仅 6.5k,大部分 schema 都是新输入。
- 每次工具调用仍会报告 110k–140k 级 input context;即使后续多数命中缓存,usage/UI、延迟和上游成本仍明显放大。
- 插件和连接器越多,非 Code Mode 模型的启动成本越高。
- 用户容易误以为是消息本身或 reasoning effort 导致,实际降低 effort 基本不能解决 input schema 膨胀。

## 建议修复

优先建议做模型无关的 lazy tool loading:

1. 非原生/未知模型首轮只暴露核心文件、Shell 和一个 `tool_search`/dispatcher。
2. 模型选择具体能力后,再按 namespace 动态加载对应工具 schema。
3. 插件未被本轮请求或 skill 使用时,不把全部 schema 注入 prompt。
4. usage 诊断中区分:
- text instructions
- core tools
- app/MCP schemas
- cached input
- provider-reported vs locally estimated usage

备选方案:

- 给 Cindy Gateway 下的 DeepSeek 注册类似 Luna 的 `use_responses_lite` / `code_mode_only` 元数据。
- 但必须先验证 DeepSeek 上游/bridge 能正确处理 freeform `exec` custom tool;不建议只添加 flag 而不做协议兼容测试。

短期产品缓解:

- 对传统工具模式显示“本会话将加载 N 个工具,预计占用较大上下文”的提示。
- 允许在创建会话时选择本轮启用的插件/connector namespaces。
- 切换工具配置后提示用户新建会话,避免误以为旧上下文会缩小。

## 验收建议

在相同干净工作目录和相同插件环境下:

1. DeepSeek 新会话发送“你好”,第一条 `last_token_usage.input_tokens` 降至约 20k–40k,而非 116k。
2. rollout 中 `turn_context.model` 仍为 `deepseek-v4-flash`,不能静默回退模型。
3. 分别验证:
- 一次文件读取
- 一次 Shell 命令
- 一次 GitHub 工具
- 一次 Sites 工具
4. 未加载的 namespace 不应出现在首轮 schema。
5. 工具调用结果、取消、并行调用和 usage/cache 字段保持正确。
6. 增加 fixture,覆盖“未知/第三方 Codex 模型 + 大量已安装 Apps”的首轮 token 上限。

## 相关 Issue

- #108:多协议 provider adapter 与请求级诊断
- #203:Codex 正文流式渲染

本问题与 #108 的 provider capability/diagnostics 方向有关,但现象是独立的工具上下文膨胀问题;与 #203 的渲染问题无关。

## 隐私说明

以上数据来自本机只读检查,已移除绝对路径、账户标识、会话 ID、API endpoint、API key 和认证信息。

Contributor guide

Open the contributing guide

Research direction

Start by tracing the cindy_gateway provider runtime, model catalog capability metadata, and App/MCP tool registration path, then compare them with cindy_openai's Code Mode handling. Reproduce the DeepSeek and Luna requests and inspect token usage; done means reducing the unknown-model first-turn input, preserving the selected model, and passing the listed file, shell, GitHub, Sites, cancellation, parallel-call, and usage checks.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
ai, api, backend, tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.