Skip to content

fix(gateway): 关闭跨 Provider 模型池时,模型列表返回带前缀的 ID / prefix model ids in list endpoints while the pool is off - #476

Merged
caigee-cmd merged 1 commit into
caigee-cmd:mainfrom
BrianBoyCN:fix/models-prefix-when-pool-off
Nov 8, 2026
Merged

caigee-cmd merged 1 commit into
caigee-cmd:mainfrom
BrianBoyCN:fix/models-prefix-when-pool-off

Conversation

@BrianBoyCN

Copy link
Copy Markdown
Contributor

中文

问题现象

关闭「跨 Provider 模型池」后,控制台「接入与调试」页面无法使用:模型选择器从 /api/models 拿到的仍是裸 ID(如 glm-5.2),选中发送后网关直接拒绝:

provider_prefix_required: cross-provider model pool is disabled; use a
provider-prefixed model ID such as qoder/glm-5.2

/v1/models 的消费方(OpenAI 兼容客户端)同样受影响。列表所见与网关所收不一致——关闭开关后,所有按列表选择的请求必然 400,包括控制台自己的试聊。

根因

两处行为未对齐:

  • 列表侧(catalog_source.go ProviderModelEntry):条目 id 始终是裸 ID,provider 仅作为 owned_by/provider 元数据字段
  • 校验侧(executor/prepare.go RejectsBareModel):CrossProviderPool=false 时 ProviderPrefix(model)=="" 直接 400

ResolveProviderFilter 关闭时对裸 ID 的兜底是 provider=qoder,但该校验发生在兜底之后,兜底永远没有机会执行——裸请求在校验处就已死。

修复

新增 control.PrefixModelEntries:按条目的 provider/owned_by 与 regions 把裸 ID 改写为网关接受的前缀形态:

条目 改写后
qoder 的 glm-5.2 qoder/glm-5.2
workbuddy 仅 cn 区域 workbuddy-cn/glm-5.3-flash(区域钉)
trae 多区域 trae/deepseek-v4.1-flash(不带区域钉,池自选)
已带 / 前缀 原样透传

两个列表出口在开关关闭时套用:

  • console /api/models(HandleModelsAPI)
  • gateway /v1/models(HandleModels)

开关开启时列表保持裸 ID 不变(此时网关本来就接受)。改写发生在过滤/装饰之后,且对条目做克隆——不会污染共享 catalog 缓存。前端无需改动。

测试

新增 TestPrefixModelEntriesAddsProviderPrefix(前缀改写、单一区域 dash 形式、多区域裸前缀、已带前缀透传、qoder 视图、源条目不可变)与 TestPrefixModelEntriesEmptyAndNil。

go build ./...、go vet、go test ./... 全部通过;已在真实部署上验证:关闭开关后接入与调试页面恢复可用。


English

Problem

With the cross-provider model pool disabled, the console access page breaks: the model picker pulls bare ids (glm-5.2) from /api/models, and every request built from them is refused by the gateway:

provider_prefix_required: cross-provider model pool is disabled; use a
provider-prefixed model ID such as qoder/glm-5.2

/v1/models consumers (OpenAI-compatible clients) hit the same wall. The list shows what the gateway refuses — with the pool off, every list-driven request is a guaranteed 400, including the console's own test chat.

Root cause

Two sides disagree:

  • List side (catalog_source.go ProviderModelEntry): entries expose bare ids as id; the provider rides only as owned_by/provider metadata
  • Gate side (executor/prepare.go RejectsBareModel): with CrossProviderPool=false, a missing provider prefix is an immediate 400

ResolveProviderFilter's bare-id fallback (provider=qoder) can never run — the gate fires first.

Fix

New control.PrefixModelEntries rewrites bare ids into the prefixed shapes the gateway accepts, driven by each entry's provider/owned_by and regions:

  • qoder glm-5.2 → qoder/glm-5.2
  • workbuddy with only a cn region → workbuddy-cn/glm-5.3-flash (region pin)
  • trae across regions → trae/deepseek-v4.1-flash (no region pin, pool picks)
  • entries already carrying / pass through untouched

Applied at both list endpoints while the pool is off — console /api/models (HandleModelsAPI) and gateway /v1/models (HandleModels). With the pool on, lists stay bare (the gateway accepts them). Rewrites happen after filtering/decoration and operate on cloned entries, so the shared catalog cache is never mutated. No frontend changes needed.

Tests

New TestPrefixModelEntriesAddsProviderPrefix (rewrites, single-region dash form, multi-region bare prefix, pre-prefixed passthrough, qoder view, source immutability) and TestPrefixModelEntriesEmptyAndNil.

go build ./..., go vet, go test ./... all pass; verified on a live deployment — the access page works again with the pool disabled.

…ider pool is off

## 问题现象

关闭「跨 Provider 模型池」后,控制台「接入与调试」页面无法使用:模型选择器从
/api/models 拿到的仍是裸 ID(如 glm-5.2),选中发送后网关直接拒绝:

    provider_prefix_required: cross-provider model pool is disabled; use a
    provider-prefixed model ID such as qoder/glm-5.2

/v1/models 的消费方(OpenAI 兼容客户端)同样受影响。列表所见与网关所收不一致,
关闭开关等于让所有按列表选择的请求必然 400。

## 根因

两处行为未对齐:

- 列表侧(catalog_source.go ProviderModelEntry):条目 id 始终是裸 ID,
  provider 仅作为 owned_by/provider 元数据字段
- 校验侧(executor/prepare.go RejectsBareModel):CrossProviderPool=false 时
  ProviderPrefix(model)=="" 直接 400

ResolveProviderFilter 关闭时的裸 ID 兜底是 provider=qoder,但这发生在
RejectsBareModel 之后,兜底永远没有机会执行。

## 修复

新增 control.PrefixModelEntries:按条目的 provider/owned_by 与 regions 把裸 ID
改写为网关接受的前缀形态(workbuddy 单一 cn 区域 -> workbuddy-cn/…,多区域不带
区域钉 -> trae/…)。两个出口在开关关闭时套用:

- console /api/models(HandleModelsAPI)
- gateway /v1/models(HandleModels)

开关开启时列表保持裸 ID 不变(此时网关本来就接受)。改写发生在过滤/装饰之后,
且对条目做克隆,不污染共享 catalog 缓存。

## 测试

新增 TestPrefixModelEntriesAddsProviderPrefix(含已带前缀透传、单一区域 dash
形式、多区域裸前缀、qoder 视图、源条目不可变)与空列表/nil 用例。

go build ./... / go vet / go test ./... 全部通过。
@BrianBoyCN
BrianBoyCN force-pushed the fix/models-prefix-when-pool-off branch from 4ac8a84 to e802cb2 Compare November 8, 2026 04:24
@caigee-cmd
caigee-cmd merged commit ac3b2fc into caigee-cmd:main Nov 8, 2026
6 checks passed
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.

2 participants