Skip to content

Commit fe4d5be

Browse files
committed
fix(MOC-236): #477 bot review round-6(池化模式禁用 legacy slug 路由到已排除 provider)
StaticResolver 加显式 pool_enabled(反查表非空即置位):池化模式下 decide_provider 反查表 miss 时不再走 <slug>/<model> legacy 拆分 —— 否则旧会话 / 手输的 excluded-provider/model 会被路由到用户已移出整合的 provider(违反子集语义)→ 改退默认 provider。单 provider / 池空回退(表空)仍保留 legacy 拆分(向后兼容)。+1 回归测试。 (thread 2「unseeded 当显式空池」round-5 已修:空池统一回退单 provider、不下发空 catalog)
1 parent ab88ca6 commit fe4d5be

1 file changed

Lines changed: 40 additions & 3 deletions

File tree

crates/proxy/src/resolver.rs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ pub struct StaticResolver {
128128
/// `settings.exposeAllProviderModels` gate 后用 `registry::unique_pool_slugs`
129129
/// 构建(与 catalog 生成端**同一 helper**,保证 slug 逐字一致、不错路由)。
130130
pub catalog_slug_map: HashMap<String, (usize, String)>,
131+
/// 是否处于池化(整合)模式。`true` = catalog 由 `catalog_slug_map` 这个**子集**定义,
132+
/// `decide_provider` 在反查表 miss 时**不再**走 `<slug>/<model>` legacy 拆分(否则会把
133+
/// 旧会话 / 手输的 `excluded-provider/model` 路由到用户已移出整合的 provider,违反子集
134+
/// 语义,#477 P2 round-6)→ 直接退默认 provider。`false` = 单 provider / 池空回退,保留
135+
/// legacy 拆分(向后兼容)。由 `with_catalog_slug_map` 按「反查表非空」自动置位。
136+
pub pool_enabled: bool,
131137
}
132138

133139
impl StaticResolver {
@@ -141,11 +147,15 @@ impl StaticResolver {
141147
providers,
142148
default_provider_id,
143149
catalog_slug_map: HashMap::new(),
150+
pool_enabled: false,
144151
}
145152
}
146153

147154
/// 装配池化反查表(builder 形式,保持 `new` 三参签名不变 → 既有调用 / 测试不破)。
155+
/// 反查表非空 ⟺ 池化模式(proxy_runner 仅在 expose 开 + 池非空时灌非空表)→ 同步置
156+
/// `pool_enabled`,作为 legacy slug-split 是否禁用的**显式**状态。
148157
pub fn with_catalog_slug_map(mut self, map: HashMap<String, (usize, String)>) -> Self {
158+
self.pool_enabled = !map.is_empty();
149159
self.catalog_slug_map = map;
150160
self
151161
}
@@ -349,9 +359,13 @@ fn decide_provider<'a>(
349359
);
350360
}
351361
// 1. "<slug>/<model>" 约定:按 provider slug 路由(手动调用 / 池化前兼容)。
352-
if let Some((slug, real)) = model.split_once('/') {
353-
if let Some(p) = res.find_by_slug(slug) {
354-
return Some((p, Some(strip_internal_model_suffix(real))));
362+
// **仅非池化模式**才走:池化模式下反查表 = 整合子集的全集,miss 必须退默认 provider,
363+
// 不能用 legacy 拆分把 `excluded-provider/model`(用户移出整合的)路由回去(#477 P2 round-6)。
364+
if !res.pool_enabled {
365+
if let Some((slug, real)) = model.split_once('/') {
366+
if let Some(p) = res.find_by_slug(slug) {
367+
return Some((p, Some(strip_internal_model_suffix(real))));
368+
}
355369
}
356370
}
357371
}
@@ -875,4 +889,27 @@ mod tests {
875889
assert_eq!(res.provider_id, "deepseek");
876890
assert_eq!(res.rewritten_model.as_deref(), Some("deepseek-v4-pro"));
877891
}
892+
893+
#[test]
894+
fn pool_mode_does_not_legacy_route_excluded_provider_slug() {
895+
// #477 P2 round-6:池化模式(反查表非空)下,请求一个**不在整合子集**的 provider slug
896+
// (用户已移出整合 / 旧会话遗留)→ 反查表 miss → **不**走 legacy split 路由回该 provider,
897+
// 退默认 provider。子集语义:移出整合的 provider 不该再被路由到。
898+
let providers = vec![
899+
provider("openai", "https://up-1", "sk-1"), // default + 在整合子集
900+
provider("deepseek", "https://up-2", "sk-2"), // 已移出整合(不在反查表)
901+
];
902+
let mut map = HashMap::new();
903+
map.insert("openai/o-pro".to_owned(), (0usize, "o-pro".to_owned()));
904+
let r =
905+
StaticResolver::new(None, providers, Some("openai".into())).with_catalog_slug_map(map);
906+
let p = parts_with(&[]);
907+
let res = r
908+
.resolve(&p, br#"{"model":"deepseek/deepseek-v4-pro"}"#)
909+
.unwrap();
910+
assert_eq!(
911+
res.provider_id, "openai",
912+
"池化下 excluded provider 的 slug 应退默认 provider,绝不 legacy 路由回 deepseek"
913+
);
914+
}
878915
}

0 commit comments

Comments
 (0)