Skip to content

fix(anthropic): map thinking to reasoning_effort — upstream only honours that spelling - #7

Merged
godx999 merged 1 commit into
mainfrom
fix/anthropic-thinking-effort
Oct 16, 2026
Merged

godx999 merged 1 commit into
mainfrom
fix/anthropic-thinking-effort

Conversation

@godx999

@godx999 godx999 commented Oct 16, 2026

Copy link
Copy Markdown
Owner

问题

上一个 PR(#6)加了 CNB2API_ALLOW_THINKING 开关,但 Anthropic 路径仍然拿不到思考。
实测(直连 cnb2api,绕开 new-api,硬 prompt):

用例 结果
/v1/messages + thinking:{type:"enabled"} ❌ 只有 text 块(2233 字符),无 thinking
/v1/chat/completions + reasoning_effort:high ✅ reasoning_content 9616 字符 + 正文 1306 字符

说明开关本身生效了(OpenAI 路径能出思考),问题出在映射用了上游不认的拼写。

根因:上游只认 reasoning_effort

逐个拼写打到真实 CNB 上游(每次只改一个参数,同一 prompt):

参数 reasoning_content 结论
reasoning_effort: 'high' 134 字符 ✅
reasoning_effort: 'low' 257 字符 ✅
reasoning_effort: 'medium' 206 字符 ✅
reasoning: { effort: 'high' } 0 ❌
enable_thinking: true 0 ❌
thinking: { type: 'enabled' } 0 ❌
thinking: { type: 'enabled', budget_tokens: 8000 } 0 ❌
thinking: true 0 ❌
无参数(对照) 0 —

用硬 prompt 复核,enable_thinking / thinking 的表现与"不带任何参数"完全一致。

顺带更正:src/proxy.mjs 里 REASONING_TRIGGERS 的注释声称这四个拼写都会路由到
thinking 变体 —— 实测不成立,只有 reasoning_effort 会。剥除列表本身保持不变
(多剥无害,且能防上游将来引入新拼写)。

改动

  • src/anthropic.mjs — 开启 opt-in 时不再输出 thinking,改输出 reasoning_effort;
    档位由 Anthropic 的 budget_tokens 折算(<=4096 low / <=16384 medium / 其余 high,
    缺失或非法值取 high —— 取低会让推理吃穿 max_tokens)。客户端显式给了 reasoning_effort
    时以它为准。
  • src/proxy.mjs — 修正剥除表的注释,与实际行为对齐。
  • test/anthropic.test.mjs — 断言映射目标是 reasoning_effort(并显式断言不输出 thinking)、
    覆盖 budget→档位 的阶梯、新增两条 E2E 钉住 /v1/messages → 上游 reasoning_effort。

端到端验证(走真实 CNB 上游)

本地运行本分支的 cnb2api,上游指向已部署的 cnb2api(它再访问真实 CNB):

A. /v1/messages + thinking:{enabled, budget_tokens:31999}
   → content 块 ["thinking","text"],thinking 3114 字符,正文 1268 字符,stop_reason end_turn  ✅
B. 对照:不带 thinking
   → content 块 ["text"],无 thinking 块                                                  ✅

测试

# tests 79
# pass 79
# fail 0

…urs that spelling

The previous opt-in mapped Anthropic `thinking:{type:"enabled"}` straight through as an
OpenAI-side `thinking` object. Measured against the real CNB upstream on 2026-10-16, that
spelling does NOT trigger the thinking variant, so `/v1/messages` stayed thinking-less even
with `CNB2API_ALLOW_THINKING=1`:

  param                                  reasoning_content
  reasoning_effort: high/low/medium      > 0      (triggers)
  reasoning: {effort}                    0
  enable_thinking: true                  0
  thinking: {type:"enabled"}             0
  thinking: {type:"enabled",budget}      0
  thinking: true                         0
  (no param, control)                    0

Re-verified with a hard prompt, and `enable_thinking` / `thinking` behave exactly like the
no-param control. `reasoning_effort` is the only switch the upstream honours.

Change:
- anthropic.mjs: emit `reasoning_effort` instead of `thinking`; derive the level from
  Anthropic `budget_tokens` (<=4096 low, <=16384 medium, else high; missing/invalid -> high,
  since too low lets reasoning eat the whole max_tokens). A client-supplied
  `reasoning_effort` still wins.
- proxy.mjs: correct the strip-table comment — it claimed all four spellings route to the
  thinking variant; only `reasoning_effort` does. The strip list itself is unchanged
  (extra stripping is harmless and guards against future spelling changes).
- tests: assert the mapping targets reasoning_effort, cover the budget->level ladder,
  and add an E2E that pins `/v1/messages` -> upstream `reasoning_effort`.

Verified end to end against the live CNB upstream (local patched proxy -> deployed cnb2api
-> CNB): `thinking:{enabled,budget_tokens:31999}` now returns a `thinking` block (3114 chars)
plus the answer (1268 chars), stop_reason `end_turn`; the no-thinking control returns text only.

Tests: 79/79.
@godx999
godx999 force-pushed the fix/anthropic-thinking-effort branch from f98d039 to 572b671 Compare October 16, 2026 06:26
@godx999
godx999 merged commit c3549df into main Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant