fix: prevent hybrid mode from leaking OpenRouter model to local llamacpp client

When backend=hybrid with LLM_BACKEND=llamacpp, the user-selected model
(an OpenRouter id like "google/gemini-3-flash-preview") was being applied
to the local LlamaCppClient's primary_model and vision_model. This caused
describe_image to send the OpenRouter model name to llama-swap, which
returned 400 because it has no such slot.

Guard the local-client model override with !is_hybrid so it only applies
in local-only mode (where the user is selecting a different local model).
Bump to v1.2.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cameron Cordes
2026-05-26 09:55:16 -04:00
parent 0a627f4880
commit b03ee60342
7 changed files with 172 additions and 79 deletions
+20 -17
View File
@@ -308,7 +308,9 @@ impl InsightChatService {
let stored_model = insight.model_version.clone();
let overrides = SamplingOverrides {
model: req.model.clone()
model: req
.model
.clone()
.or_else(|| Some(stored_model.clone()))
.filter(|m| !m.is_empty()),
num_ctx: req.num_ctx,
@@ -731,7 +733,9 @@ impl InsightChatService {
let stored_model = insight.model_version.clone();
let overrides = SamplingOverrides {
model: req.model.clone()
model: req
.model
.clone()
.or_else(|| Some(stored_model.clone()))
.filter(|m| !m.is_empty()),
num_ctx: req.num_ctx,
@@ -928,17 +932,15 @@ impl InsightChatService {
// images_inline backends send images directly to the chat model.
let visual_block = if !backend.images_inline {
match image_base64.as_deref() {
Some(b64) => {
match backend.local().describe_image(b64).await {
Ok(desc) => {
format!("Visual description (from local vision model):\n{}\n", desc)
}
Err(e) => {
log::warn!("{} bootstrap: describe_image failed: {}", kind.as_str(), e);
String::new()
}
Some(b64) => match backend.local().describe_image(b64).await {
Ok(desc) => {
format!("Visual description (from local vision model):\n{}\n", desc)
}
}
Err(e) => {
log::warn!("{} bootstrap: describe_image failed: {}", kind.as_str(), e);
String::new()
}
},
None => String::new(),
}
} else {
@@ -1328,10 +1330,7 @@ fn resolve_bootstrap_backend(supplied: Option<&str>) -> Result<String> {
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "local".to_string());
if !matches!(lower.as_str(), "local" | "hybrid") {
bail!(
"unknown backend '{}'; expected 'local' or 'hybrid'",
lower
);
bail!("unknown backend '{}'; expected 'local' or 'hybrid'", lower);
}
Ok(lower)
}
@@ -1971,7 +1970,11 @@ mod tests {
// Both "openrouter" and the former "llamacpp" value are unknown now.
for label in &["openrouter", "llamacpp"] {
let err = validate_cross_replay("local", label).unwrap_err();
assert!(format!("{}", err).contains("unknown backend"), "label={}", label);
assert!(
format!("{}", err).contains("unknown backend"),
"label={}",
label
);
}
}