fix(config): an explicit [model.X] api_backend survives same-slug propagation
The slug-match step treated an entry whose api_backend equals the default as unset and overwrote it with a same-slug sibling's backend, so `api_backend = "chat_completions"` silently became messages/responses. Config keys whose override sets api_backend are recorded and skipped by the donor step; an unset backend still inherits. Ported from upstream 1.0.22 (grok-build agent/config.rs explicit_api_backend_keys). Claude-Session: https://claude.ai/code/session_01RUqV8HVGXcAETyYbZ2tPQ4
This commit is contained in:
1 parent
017a570d7d
commit
00a5e318bb
1 file changed
+49
-2
@@ -2601,6 +2601,7 @@ pub(crate) fn resolve_model_list(
|
|||||||
}
|
}
|
||||||
resolved = prefetched;
|
resolved = prefetched;
|
||||||
}
|
}
|
||||||
|
let mut explicit_api_backend_keys = std::collections::HashSet::new();
|
||||||
for (key, model_override) in &cfg.config_models {
|
for (key, model_override) in &cfg.config_models {
|
||||||
let had_base = resolved.contains_key(key);
|
let had_base = resolved.contains_key(key);
|
||||||
let base = resolved.shift_remove(key);
|
let base = resolved.shift_remove(key);
|
||||||
@@ -2617,6 +2618,9 @@ pub(crate) fn resolve_model_list(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if model_override.api_backend.is_some() {
|
||||||
|
explicit_api_backend_keys.insert(key.as_str());
|
||||||
|
}
|
||||||
let entry = model_override.apply(key, base, &cfg.endpoints);
|
let entry = model_override.apply(key, base, &cfg.endpoints);
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
model_key = % key, base_url = % entry.info.base_url, has_api_key = entry
|
model_key = % key, base_url = % entry.info.base_url, has_api_key = entry
|
||||||
@@ -2638,7 +2642,7 @@ pub(crate) fn resolve_model_list(
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
for entry in resolved.values_mut() {
|
for (key, entry) in resolved.iter_mut() {
|
||||||
if let Some((donor_cw, donor_backend)) = donors.get(&entry.info.model) {
|
if let Some((donor_cw, donor_backend)) = donors.get(&entry.info.model) {
|
||||||
if entry.info.context_window.get() == default_cw {
|
if entry.info.context_window.get() == default_cw {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
@@ -2648,7 +2652,9 @@ pub(crate) fn resolve_model_list(
|
|||||||
);
|
);
|
||||||
entry.info.context_window = *donor_cw;
|
entry.info.context_window = *donor_cw;
|
||||||
}
|
}
|
||||||
if entry.info.api_backend == ApiBackend::default()
|
// An explicit `[model.X] api_backend` wins even when it is the default.
|
||||||
|
if !explicit_api_backend_keys.contains(key.as_str())
|
||||||
|
&& entry.info.api_backend == ApiBackend::default()
|
||||||
&& *donor_backend != ApiBackend::default()
|
&& *donor_backend != ApiBackend::default()
|
||||||
{
|
{
|
||||||
entry.info.api_backend.clone_from(donor_backend);
|
entry.info.api_backend.clone_from(donor_backend);
|
||||||
@@ -9067,6 +9073,47 @@ default = "kigi-4.5"
|
|||||||
sibling 'kigi-build' (same model slug), not stay at {default_cw}"
|
sibling 'kigi-build' (same model slug), not stay at {default_cw}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/// An explicit `[model.X] api_backend` survives a same-slug sibling even when it is the default.
|
||||||
|
#[test]
|
||||||
|
fn slug_propagation_keeps_an_explicit_default_api_backend() {
|
||||||
|
// Only kigi-messages has a non-default context window: the slug's single donor.
|
||||||
|
let default_cw = DEFAULT_CONTEXT_WINDOW;
|
||||||
|
let raw: toml::Value = toml::from_str(&format!(
|
||||||
|
r#"
|
||||||
|
[model.kigi-messages]
|
||||||
|
model = "kigi-4.5"
|
||||||
|
context_window = 500000
|
||||||
|
base_url = "https://test.example.com/v1"
|
||||||
|
api_backend = "messages"
|
||||||
|
|
||||||
|
[model.kigi-chat]
|
||||||
|
model = "kigi-4.5"
|
||||||
|
context_window = {default_cw}
|
||||||
|
base_url = "https://test.example.com/v1"
|
||||||
|
api_backend = "chat_completions"
|
||||||
|
|
||||||
|
[model.kigi-plain]
|
||||||
|
model = "kigi-4.5"
|
||||||
|
context_window = {default_cw}
|
||||||
|
base_url = "https://test.example.com/v1"
|
||||||
|
"#
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
let cfg = Config::new_from_toml_cfg(&raw).expect("config should parse");
|
||||||
|
let resolved = resolve_model_list(&cfg, None, &Default::default());
|
||||||
|
let backend = |key: &str| resolved.get(key).expect(key).info.api_backend.clone();
|
||||||
|
assert_eq!(
|
||||||
|
backend("kigi-chat"),
|
||||||
|
ApiBackend::ChatCompletions,
|
||||||
|
"explicit default backend must not be overwritten by the sibling"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
backend("kigi-plain"),
|
||||||
|
ApiBackend::Messages,
|
||||||
|
"unset backend still inherits"
|
||||||
|
);
|
||||||
|
assert_eq!(backend("kigi-messages"), ApiBackend::Messages);
|
||||||
|
}
|
||||||
/// Slug propagation should carry over api_backend but NOT agent_type.
|
/// Slug propagation should carry over api_backend but NOT agent_type.
|
||||||
#[test]
|
#[test]
|
||||||
fn slug_propagation_inherits_api_backend_but_not_agent_type() {
|
fn slug_propagation_inherits_api_backend_but_not_agent_type() {
|
||||||
|
|||||||
Reference in new issue
Block a user