Skip to content

fix(agent-runtime): adopt pi-ai Anthropic metadata for custom anthropic_messages rows - #908

Closed
csmsqj wants to merge 2 commits into
vastsa:mainfrom
csmsqj:fix/anthropic-catalog-thinking-map
Closed

csmsqj wants to merge 2 commits into
vastsa:mainfrom
csmsqj:fix/anthropic-catalog-thinking-map

Conversation

@csmsqj

@csmsqj csmsqj commented Sep 22, 2026 •

Copy link
Copy Markdown

问题背景(中文)

这是 #869 按 review 意见收窄后的重提。基于当前 main(0.15.6,pi-ai 0.87.1)确认:

  • pi-agent-core 的循环已把 state.thinkingLevel 映射成 provider 中立的 reasoning 交给 streamFn(off → undefined),PI-Desktop 侧 ...options 透传,因此 kimi / glm / deepseek 等 chat_completions 模型的档位下发已经正常,runtime 侧不再需要注入 reasoning(fix(runtime): forward thinking level to every provider via the neutral reasoning knob #869 中的这部分改动已删除)。
  • 仍存在的真实缺口:自建 Anthropic 兼容网关(new-api 等)上的 adaptive-era Claude 模型(如 claude-opus-4-8)。这类自定义 provider 的模型配置来自 models.dev 或 generic fallback,两者都不携带 thinkingLevelMap 的 xhigh/max 条目,也没有 compat.forceAdaptiveThinking。pi-ai 的 Anthropic 适配器只有在 forceAdaptiveThinking === true 时才走 output_config.effort 路径,且 mapThinkingLevelToEffort() 对读不到 thinkingLevelMap 的 xhigh/max 一律回落 "high"(pi-ai/dist/api/anthropic-messages.js,0.87.1 已复核逻辑不变)—— 即会话里调到 max,线上请求实际仍是 high。

改动内容

buildProviderModel(packages/agent-runtime/src/provider-binding.ts):当 wire API 为 anthropic-messages 时,在 pi-ai 内置目录(ANTHROPIC_MODELS)中查找该模型,命中则采纳 pi-ai 自己生成的元数据 —— thinkingLevelMap(xhigh/max 条目)与 compat.forceAdaptiveThinking。合并顺序为 catalog(models.dev / binding / generic)优先、内置目录只补缺:

  • 查找走 catalogModelIdsMatch(@pi-desktop/shared,与 modelsDevCatalog.findModel 同一套目录元数据别名规则):这里读的是元数据、不决定绑定身份,因此用目录级规则而非绑定身份用的 modelIdsMatch——命名空间(anthropic/claude-opus-4-8)、区域后缀(@region)、大小写差异、网关变体后缀(proxy/claude-opus-4-8-agent-thinking 等 -thinking/-agent/-latest 形式)都能命中;
  • 未知模型 id、或 budget 时代的旧模型(claude-sonnet-4.x、claude-haiku-4-5 等在 pi-ai 目录中没有 adaptive 元数据)完全不触发,请求形状不变;
  • models.dev 已有的映射(包括 off: null,表示 off 时省略 reasoning)优先于内置默认值,语义保持「关闭时不下发」;
  • 非 anthropic-messages 协议不经过此路径。

runtime 侧不再注入 reasoning(pi 循环已覆盖),故 #869 中的 runtime.ts 改动不再保留。

spec 边界(对应 ADR 0134)

models.dev 仍是唯一模型元数据来源。docs/spec/03-runtime/11-provider-model-system.md §6.2 第 5 条和 docs/spec/03-runtime/02-agent-runtime.md §5c(均含 zh-CN 镜像同步)明确这一窄例外:anthropic-messages 行仅从匹配的 pi-ai Anthropic 记录采纳 compat.forceAdaptiveThinking 标志与 thinkingLevelMap 的 xhigh/max 条目(models.dev 不发布这两项);models.dev 有定义的值(含 off: null)一律优先;目录不认识的 id 不匹配任何条目。

测试(packages/agent-runtime/src/provider-binding.test.ts,新增 10 条)

  1. claude-opus-4-8(models.dev 配置、无 thinkingLevelMap):构建出的模型带 thinkingLevelMap: { xhigh: "xhigh", max: "max" } + compat.forceAdaptiveThinking: true;捕获请求体断言 reasoning: "max" 时线上收到 thinking: { type: "adaptive" } + output_config: { effort: "max" };
  2. claude-haiku-4-5 及 claude-haiku-4-5-thinking 变体:不会被误开 adaptive / thinkingLevelMap;
  3. 未知 Anthropic 兼容 id(含 proxy/my-gateway-claude-agent-thinking 变体后缀形式):不会凭空生成 adaptive 元数据;
  4. models.dev 的 { off: null } 映射能覆盖内置默认值;
  5. 别名/变体回归 it.each × 7:anthropic/claude-opus-4-8、claude-opus-4-8@us-east、Anthropic/Claude-Opus-4-8、proxy/claude-opus-4-8-agent-thinking、claude-opus-4-8-thinking、claude-opus-4-8-agent、claude-opus-4-8-latest。

验证方式

基于最新 main(45471587,pi-ai 0.87.1):

  • pnpm --filter @pi-desktop/agent-runtime typecheck:通过。
  • pnpm lint:biome / node scripts/check-architecture.mjs / pnpm docs:check(80 对中英 spec 镜像):通过。
  • node scripts/check-pr-base-main.mjs:通过(origin/main 是 head 祖先)。
  • pnpm --filter @pi-desktop/agent-runtime exec vitest run src/provider-binding.test.ts:37/37 通过。
  • 全量 pnpm --filter @pi-desktop/agent-runtime test:1011/1013。3 个失败文件(native-pi-session 的 Windows 路径断言、hosted-search-compaction 的 pi-coding-agent dist 解析、hosted-search-contract 在满载下的超时——单跑通过)在未含本 PR 的基线对照中同样复现,属本机环境既有问题,与本改动无关。

Copilot AI lite review requested due to automatic review settings September 22, 2026 23:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The metadata lookup bypasses model-ID aliases, leaving valid aliases on fallback behavior.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

Updates Anthropic-compatible custom provider rows to use pi-ai adaptive-thinking metadata.

Changes:

  • Adds Anthropic metadata overlays and fallback behavior.
  • Preserves explicit mappings and adds adaptive-thinking tests.
File Summary
packages/​agent-runtime/​src/​provider-binding.ts Applies Anthropic compatibility metadata.
packages/​agent-runtime/​src/​provider-binding.test.ts Tests adaptive metadata and fallback behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +245 to +247
? (ANTHROPIC_MODELS as Record<string, Model<Api> | undefined>)[
provider.modelId
]
@csmsqj

csmsqj commented Sep 23, 2026

Copy link
Copy Markdown
Author

已处理 Copilot 的 review 意见:

  • ANTHROPIC_MODELS 的查找从精确键改为通过 modelIdsMatch(@pi-desktop/shared,与 modelsDevCatalog.findModel 同一套别名规则)匹配,覆盖 anthropic/claude-opus-4-8(命名空间)、claude-opus-4-8@region(区域后缀)、大小写差异等别名形式;pi-ai 内置目录只有 14 条记录,线性扫描成本可以忽略。
  • 新增 3 条别名回归用例(it.each:anthropic/claude-opus-4-8、claude-opus-4-8@us-east、Anthropic/Claude-Opus-4-8),断言别名行同样采纳 thinkingLevelMap + forceAdaptiveThinking。

验证:typecheck 通过;provider-binding.test.ts 30/30 通过(新增 3 条);runtime.test.ts / subagent.test.ts / subagent-fallback.test.ts / model-capabilities.test.ts 302/302 通过;lint:biome 通过。

@vastsa

vastsa commented Sep 23, 2026

Copy link
Copy Markdown
Owner

Review 结论:Request changes,当前不建议合入。

  1. P1:别名匹配仍会漏掉有效模型变体

packages/agent-runtime/src/provider-binding.ts:252 使用 modelIdsMatch 查找 pi-ai 的 Anthropic 元数据,但当前 main 已明确区分“绑定身份匹配”和“catalog 元数据匹配”:modelIdsMatch 不匹配 -thinking、-agent、-latest 变体,catalogModelIdsMatch 才会匹配这些 models.dev 别名。

我在 latest main + 本 PR 的合成候选上验证了:proxy/claude-opus-4-8-agent-thinking 不会得到 forceAdaptiveThinking / thinkingLevelMap,因此请求中的 max 仍会回落为 high。建议这里改用 catalog 元数据匹配函数(或专用的 pi-ai 元数据 alias matcher),并补充这些变体的回归测试。

  1. 合入阻塞:head 落后当前 main

pnpm check:pr-base 失败:origin/main 不是该 head 的祖先,分支落后当前 main 87 个提交。当前 main 已升级到 pi-ai 0.87.0,请先 rebase/merge 最新 main 后重新验证。

  1. 架构/spec 漂移

本 PR 新增从 pi-ai 内置目录读取模型兼容元数据,但 ADR 0134 和 runtime spec 明确规定 models.dev 是唯一模型元数据来源。若这是有意的窄例外,请同步 ADR/spec 说明边界;否则应将 adaptive 映射纳入 models.dev-owned 配置。

本地验证:

  • PR head:provider-binding 30/30、agent-runtime 1010/1010、typecheck/lint/architecture 通过;
  • latest main + PR 合成候选:provider-binding 31/31、agent-runtime 1029/1029、typecheck/lint/architecture 通过;
  • models.dev catalog alias tests 27/27 通过;
  • GitHub PR 当前没有 CI check 记录。

@csmsqj
csmsqj force-pushed the fix/anthropic-catalog-thinking-map branch from c2d3617 to de66d71 Compare September 23, 2026 05:33
@csmsqj

csmsqj commented Sep 23, 2026

Copy link
Copy Markdown
Author

Thanks for the precise review — all three items are addressed in the rebased head (de66d71):

  1. Alias matching (P1): provider-binding.ts now looks the pi-ai Anthropic catalog up through catalogModelIdsMatch instead of the strict modelIdsMatch. This lookup reads metadata and never decides binding identity, so the broader catalog rule is the right one: gateway variants such as proxy/claude-opus-4-8-agent-thinking, claude-opus-4-8-thinking, -agent, and -latest now resolve the catalog entry instead of silently falling back to high. Added those four as regression cases, plus two negatives: a variant-suffixed budget-era id (claude-haiku-4-5-thinking) and an unknown id with variant suffixes (proxy/my-gateway-claude-agent-thinking) still adopt no adaptive metadata.
  2. Rebase (merge blocker): the branch is rebased onto latest main (510c3b57); node scripts/check-pr-base-main.mjs passes.
  3. Spec/ADR 0134 boundary: docs/spec/03-runtime/11-provider-model-system.md §6.2 item 5 now documents the narrow exception explicitly — for anthropic-messages rows the runtime adopts only the matching pi-ai Anthropic record's compat.forceAdaptiveThinking flag and the xhigh/max entries of its thinkingLevelMap (catalog-alias matched); models.dev publishes neither and still wins wherever it defines a value, including an off: null mapping; unknown ids match nothing. Cross-referenced from docs/spec/03-runtime/02-agent-runtime.md §5c.

Validation on latest main + PR head: @pi-desktop/agent-runtime typecheck clean; provider-binding.test.ts 37/37; full agent-runtime suite 1011/1013 — the two failures (native-pi-session Windows path assertion, hosted-search-compaction pi-coding-agent dist resolution) reproduce identically on the base commits without this PR on this machine, and the hosted-search-contract timeout seen under full-suite load passes standalone; pnpm lint:biome clean; check-pr-base-main passes.

@csmsqj
csmsqj force-pushed the fix/anthropic-catalog-thinking-map branch from de66d71 to b919a12 Compare September 23, 2026 06:16
@csmsqj

csmsqj commented Sep 23, 2026

Copy link
Copy Markdown
Author

Follow-up on the force-push to b919a120: same content as reviewed above, plus the zh-CN mirrors of the two spec sections are now in sync (docs/zh-CN/spec/03-runtime/11-provider-model-system.md §6.2 and docs/zh-CN/spec/03-runtime/02-agent-runtime.md §5c). pnpm docs:check passes (80 locale pairs verified).

@csmsqj
csmsqj force-pushed the fix/anthropic-catalog-thinking-map branch from b919a12 to a637ba8 Compare September 23, 2026 06:25
…ic_messages rows

A custom Anthropic-compatible provider resolves its model config from
models.dev or the generic fallback, and neither carries the xhigh/max
entries of thinkingLevelMap nor compat.forceAdaptiveThinking. pi-ai's
Anthropic adapter then has no way to honor those effort levels:
streamSimple only reaches the effort-capable path when
forceAdaptiveThinking is set, and mapThinkingLevelToEffort() falls back
to "high" for xhigh/max without a thinkingLevelMap translation.

The provider-neutral reasoning level already reaches streamSimple via
the pi-agent-core loop, so the only missing piece is the model
metadata. Adopt pi-ai's own generated catalog (ANTHROPIC_MODELS) for
the exact model id: merge the built-in thinkingLevelMap under the
resolved config and set forceAdaptiveThinking only when pi-ai marks
that model adaptive. Unknown ids and budget-era Claude models match
nothing, and a models.dev mapping (including off: null) still wins,
so request shapes for other providers stay untouched.

Refs vastsa#869.
…s rule

A custom row may configure the model with a namespaced, regional, or
gateway-variant alias (anthropic/claude-opus-4-8, claude-opus-4-8@region,
proxy/claude-opus-4-8-agent-thinking). This lookup reads pi-ai catalog
metadata and never decides binding identity, so use catalogModelIdsMatch --
the metadata alias rule that also collapses -thinking/-agent/-latest
variants -- instead of the strict modelIdsMatch, which missed those
variants and let xhigh/max fall back to high on gateways that append them.
Adds variant regression cases and documents the narrow models.dev metadata
exception in the runtime spec, in the English source and the zh-CN mirror.
@csmsqj
csmsqj force-pushed the fix/anthropic-catalog-thinking-map branch from a637ba8 to e590ba1 Compare September 23, 2026 06:54
@csmsqj

csmsqj commented Sep 23, 2026 •

Copy link
Copy Markdown
Author

Small follow-ups since the re-review:

  • Fixed a markdown typo in the zh-CN 02-agent-runtime.md mirror (a dropped backtick around forceAdaptiveThinking); both zh-CN passages now match the English source character-for-character, and pnpm docs:check passes.
  • Rebased onto the newest main (45471587, v0.15.6 / pi-ai 0.87.1); head is now e590ba1d. No code changes beyond the typo fix above — full validation results are in the PR description.

@vastsa

vastsa commented Sep 23, 2026

Copy link
Copy Markdown
Owner

感谢你在这个问题上的深入分析和精心实现!你对 adaptive thinking 缺口的定位完全正确。

不过 #949 用了一个更轻量的方式解决了同一问题:直接从 models.dev 自身发布的 reasoningOptions 推导 forceAdaptiveThinking,不需要引入 pi-ai 内置目录的例外(避免了与 ADR 0134 的冲突)。因此我们选择合并 #949。

如果你发现 #949 未覆盖的边缘场景(比如特定网关变体的别名匹配),欢迎在此基础上提交后续修复。再次感谢!

@vastsa vastsa closed this Sep 23, 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.

3 participants