mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
fix(channels): admit_functions 落到 effectiveModels(解析器不承担产品策略)
上一提交把「本适配器会发的通道」过滤写进了 trae 的目录解析器,导致两个解析器判据 回归:同模型在 solo_agent(有思考档位)与 solo_work_lite(无档位)同时出现时, 整组 solo_agent 被丢弃 ⇒ 档位信息丢光,模型显示"未提供推理等级"。 正确分层: - **解析器**只负责把目录如实解出来(含跨通道元数据合并、白名单准入); - **产品策略**(只暴露适配器能发的通道)放到 effectiveModels,作用在对外 暴露的 id 列表上。 实现要点:合并会选"更优"的那条(往往不在我们要发的通道里),所以判据不能看 合并后条目的 chan —— 解析时额外记 `chans`(该 id 在哪些通道出现过),策略按 chans 求交集。夹具同步调整:硬性过滤的条目放进 solo_work_lite(生产里适配器 恒发这条通道),inline_chat 仍用于白名单判据。
This commit is contained in:
@ -120,23 +120,23 @@ func newTraeCatalogHarness(t *testing.T, onBody func(string)) *traeCatalogHarnes
|
||||
// ① solo_agent:glm-5.2 有档位;另有三个必被硬性过滤的条目
|
||||
`{"function":"solo_agent","config_info_list":[`+
|
||||
`{"config_name":"glm-5.2","usage":"chat_completion","config_switch":true,`+
|
||||
`"reasoning_effort_config":{"support_thinking":true,"options":["high","low"]}},`+
|
||||
`"reasoning_effort_config":{"support_thinking":true,"options":["high","low"]}}`+
|
||||
`]},`+
|
||||
// ② solo_work_lite 排在**后面**:glm-5.2 在这里**没有**档位配置。
|
||||
// 另三个"必被过滤"的条目也放这里 —— 生产里适配器恒发这条通道,
|
||||
// 硬性过滤的判据必须在这条通道上被验证到(放 solo_agent 会因为
|
||||
// admit_functions 策略先行剔除,测试就测不到过滤逻辑了)。
|
||||
`{"function":"solo_work_lite","config_info_list":[`+
|
||||
`{"config_name":"glm-5.2","usage":"chat_completion","config_switch":true},`+
|
||||
`{"config_name":"summary-model","usage":"summary","config_switch":true},`+
|
||||
`{"config_name":"disabled-model","usage":"chat_completion","config_switch":false},`+
|
||||
`{"config_name":"hidden-model","usage":"chat_completion","config_switch":true,"is_invisible_to_user":true},`+
|
||||
`{"config_name":"no-function-model","usage":"chat_completion","config_switch":true},`+
|
||||
// is_custom_model=true ⇒ 上游以流内 4001 拒绝("仅可见但不可调用")
|
||||
`{"config_name":"custom-flag-model","usage":"chat_completion","config_switch":true,`+
|
||||
`"display_config":{"is_custom_model":true}},`+
|
||||
// 对照组:名字带 custom_model_ 前缀但**标志为 false** ⇒ 必须**保留**
|
||||
// (参考点名:判据是标志的值,不是模型名)
|
||||
`{"config_name":"custom_model_prefix_but_ok","usage":"chat_completion","config_switch":true,`+
|
||||
`"display_config":{"is_custom_model":false}}`+
|
||||
`]},`+
|
||||
// ② solo_work_lite 排在**后面**:glm-5.2 在这里**没有**档位配置
|
||||
`{"function":"solo_work_lite","config_info_list":[`+
|
||||
`{"config_name":"glm-5.2","usage":"chat_completion","config_switch":true}`+
|
||||
`]},`+
|
||||
// ③ 白名单外通道:整组必须被丢弃
|
||||
`{"function":"inline_chat","config_info_list":[`+
|
||||
`{"config_name":"only-in-bad-channel","usage":"chat_completion","config_switch":true}`+
|
||||
|
||||
@ -3780,6 +3780,23 @@ local function effectiveModels(provider)
|
||||
-- 进源只会让客户端选中后报错(trae 的 custom_model_* 槽位就是这类)。
|
||||
local unavailable = {}
|
||||
for _, m in ipairs(plat.models_unavailable or {}) do unavailable[m] = true end
|
||||
-- ★ admit_functions:只放行**本适配器真的会发**的通道里的模型。
|
||||
--
|
||||
-- 白名单(parse 里的 rankOf)只说明"该通道在本端点下可调用",不等于
|
||||
-- "适配器会发它"。trae 适配器恒发 solo_work_lite,别的通道的模型经我们
|
||||
-- 这条路发出去要么回空内容、要么被上游悄悄回落到默认模型(实测
|
||||
-- kimi-k2.8-preview / deepseek-V3 空回,sagitta 自称 Claude、aquila 自称
|
||||
-- GPT)—— 那正是用户说的"模型不正确"。
|
||||
--
|
||||
-- 放在这里而不是解析器里,是因为解析器还要负责**元数据合并**:同一模型在
|
||||
-- solo_agent 里有思考档位、在 solo_work_lite 里没有,合并必须跨通道进行
|
||||
-- (否则模型显示"未提供推理等级")。策略只决定"哪些 id 对外露出"。
|
||||
local admit = nil
|
||||
local mrAdmit = plat.models_remote and plat.models_remote.admit_functions
|
||||
if type(mrAdmit) == "table" then
|
||||
admit = {}
|
||||
for _, f in ipairs(mrAdmit) do admit[f] = true end
|
||||
end
|
||||
local remote = plugin.state.remoteModels and plugin.state.remoteModels[provider]
|
||||
local out, seen = {}, {}
|
||||
-- 远端在前(权威顺序)
|
||||
@ -3804,9 +3821,24 @@ local function effectiveModels(provider)
|
||||
-- ⚠️ 只剔这里记录过的(真正 404 过的),**绝不**拿远端目录反推下架 ——
|
||||
-- buddy/qoder 那类平台的远端目录残缺,反推会删掉可用模型。
|
||||
local dead = deadModelIds(provider)
|
||||
local metaAll = plugin.state.modelMeta and plugin.state.modelMeta[provider] or {}
|
||||
local kept = {}
|
||||
for _, id in ipairs(out) do
|
||||
if not dead[id] and not unavailable[id] then kept[#kept+1] = id end
|
||||
local keep = (not dead[id]) and (not unavailable[id])
|
||||
if keep and admit then
|
||||
local e = metaAll[id]
|
||||
-- 有目录元数据 ⇒ 按它声明的通道判;没有元数据(兜底/手工源)⇒ 不拦,
|
||||
-- 因为"目录没说"不等于"不可调用"。
|
||||
if type(e) == "table" and type(e.chans) == "table" then
|
||||
keep = false
|
||||
for _, c in ipairs(e.chans) do
|
||||
if admit[c] then keep = true break end
|
||||
end
|
||||
elseif type(e) == "table" and type(e.chan) == "string" and e.chan ~= "" then
|
||||
keep = admit[e.chan] == true
|
||||
end
|
||||
end
|
||||
if keep then kept[#kept+1] = id end
|
||||
end
|
||||
return kept
|
||||
end
|
||||
@ -4103,22 +4135,20 @@ local function parseRemoteModels(kind, body, provider)
|
||||
local groups = rec.function_configs or rec.functionConfigs
|
||||
if type(groups) == "table" then
|
||||
local byId, chosenRank, order = {}, {}, {}
|
||||
-- chansOf[id]:该 id 在**哪些通道**里出现过(合并只留一条元数据,但
|
||||
-- "在哪些通道可发"是策略判定要用的信息 —— admit_functions 不能只看
|
||||
-- 合并后那条的 chan:同模型常在有档位的 solo_agent 与我们要发的
|
||||
-- solo_work_lite 同时出现,合并会选前者,导致误判"不可发")。
|
||||
local chansOf = {}
|
||||
for _, g in ipairs(groups) do
|
||||
if type(g) == "table" then
|
||||
local chan = rstr(g, "function", "Function")
|
||||
-- 准入:未声明 function 或不在白名单 → 整组丢弃
|
||||
-- ★ 追加一道「本适配器可发通道」闸门(声明式,见 models_remote.admit_functions):
|
||||
-- 白名单只说明"这个通道在本适配器的推理端点下可调用",不代表
|
||||
-- **适配器会发它** —— trae 适配器恒发 solo_work_lite,别的通道的
|
||||
-- 模型即使可调用,经我们这条路发出去也会空回/回落默认模型。
|
||||
local admitted = nil
|
||||
do
|
||||
local pl = platformById(provider)
|
||||
local mr2 = pl and pl.models_remote
|
||||
local af = mr2 and mr2.admit_functions
|
||||
if type(af) == "table" then admitted = {} for _, f in ipairs(af) do admitted[f] = true end end
|
||||
end
|
||||
if admitted and chan and not admitted[chan] then chan = nil end
|
||||
--
|
||||
-- ⚠️ 「白名单」只回答"该通道在本适配器的推理端点下**可调用**";
|
||||
-- "适配器会不会发这条通道"是另一回事,由 effectiveModels 的
|
||||
-- models_remote.admit_functions 过滤(那是产品策略,不该写进解析器:
|
||||
-- 解析器的职责是把目录如实解出来,含元数据合并)。
|
||||
if chan and rankOf[chan] then
|
||||
local list = g.config_info_list or g.ConfigInfoList
|
||||
if type(list) == "table" then
|
||||
@ -4134,6 +4164,8 @@ local function parseRemoteModels(kind, body, provider)
|
||||
-- is_custom_model: 仅可见但不可调用(上游流内 4001 拒绝)
|
||||
if e.custom == true then ok = false end
|
||||
if ok then
|
||||
chansOf[e.id] = chansOf[e.id] or {}
|
||||
chansOf[e.id][chan] = true
|
||||
local inc = byId[e.id]
|
||||
if inc == nil then
|
||||
byId[e.id] = e
|
||||
@ -4169,7 +4201,14 @@ local function parseRemoteModels(kind, body, provider)
|
||||
-- 上层只能显示"未提供推理等级")。
|
||||
if not plugin.state.modelMeta then plugin.state.modelMeta = {} end
|
||||
plugin.state.modelMeta[provider] = plugin.state.modelMeta[provider] or {}
|
||||
plugin.state.modelMeta[provider][id] = byId[id]
|
||||
local entry = byId[id]
|
||||
if type(entry) == "table" and chansOf[id] then
|
||||
local list = {}
|
||||
for c in pairs(chansOf[id]) do list[#list+1] = c end
|
||||
table.sort(list)
|
||||
entry.chans = list
|
||||
end
|
||||
plugin.state.modelMeta[provider][id] = entry
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user