llm: drop model/agent scope matching; add instance-wide compaction model picker
Nightly Build / build (push) Successful in 6m50s
Nightly Build / build (push) Successful in 6m50s
Remove the scope system end-to-end (llm_models.scope column, agent meta scope field, scope-based tier in model selection, UI checkboxes/pills): it was only a soft ranking hint, had drifted (6 UI scopes vs 3 used by agents, 'general' not even selectable) and duplicated what strength already decides. Strength stays the single AUTO-selection axis. Compaction: the summary model is now pickable from the Settings page via a new PropertyType::LlmModel config property (registry key compaction_model), instance-wide and live (no restart). Fallback chain: explicit pick -> compaction.strength from config.yml -> priority order; a deleted configured model degrades to AUTO. ContextCompactor reads the key at compact time through GlobalConfigManager.
This commit is contained in:
@@ -93,7 +93,6 @@ struct ModelRow {
|
||||
model_id: String,
|
||||
name: String,
|
||||
strength: Option<String>,
|
||||
scope: String,
|
||||
is_default: i64,
|
||||
priority: i64,
|
||||
extra_params: Option<String>,
|
||||
@@ -106,7 +105,7 @@ struct ModelRow {
|
||||
|
||||
pub async fn load_all_models(pool: &SqlitePool) -> Result<Vec<LlmModelRecord>> {
|
||||
let rows = sqlx::query_as::<_, ModelRow>(
|
||||
"SELECT id, provider_id, model_id, name, strength, scope, is_default, priority, extra_params,
|
||||
"SELECT id, provider_id, model_id, name, strength, is_default, priority, extra_params,
|
||||
context_length, max_output_tokens, knowledge_cutoff, capabilities, reasoning
|
||||
FROM llm_models
|
||||
WHERE removed_at IS NULL
|
||||
@@ -120,7 +119,6 @@ pub async fn load_all_models(pool: &SqlitePool) -> Result<Vec<LlmModelRecord>> {
|
||||
}
|
||||
|
||||
pub async fn insert_model(pool: &SqlitePool, r: &LlmModelRecord) -> Result<i64> {
|
||||
let scope = serde_json::to_string(&r.scope)?;
|
||||
let extra_params = r.extra_params.as_ref().map(|v| v.to_string());
|
||||
let capabilities = serde_json::to_string(&r.capabilities)?;
|
||||
let reasoning = r.reasoning.as_ref().map(|v| v.to_string());
|
||||
@@ -132,14 +130,13 @@ pub async fn insert_model(pool: &SqlitePool, r: &LlmModelRecord) -> Result<i64>
|
||||
// soft-deleted row. Upsert on `name` so that existing row is revived
|
||||
// (removed_at cleared) and every field overwritten.
|
||||
let id = sqlx::query_scalar::<_, i64>(
|
||||
"INSERT INTO llm_models (provider_id, model_id, name, strength, scope, is_default, priority, extra_params,
|
||||
"INSERT INTO llm_models (provider_id, model_id, name, strength, is_default, priority, extra_params,
|
||||
context_length, max_output_tokens, knowledge_cutoff, capabilities, reasoning)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)
|
||||
ON CONFLICT(name) DO UPDATE SET
|
||||
provider_id = excluded.provider_id,
|
||||
model_id = excluded.model_id,
|
||||
strength = excluded.strength,
|
||||
scope = excluded.scope,
|
||||
is_default = excluded.is_default,
|
||||
priority = excluded.priority,
|
||||
extra_params = excluded.extra_params,
|
||||
@@ -155,7 +152,6 @@ pub async fn insert_model(pool: &SqlitePool, r: &LlmModelRecord) -> Result<i64>
|
||||
.bind(&r.model_id)
|
||||
.bind(&r.name)
|
||||
.bind(r.strength.map(strength_str))
|
||||
.bind(scope)
|
||||
.bind(r.is_default as i64)
|
||||
.bind(r.priority as i64)
|
||||
.bind(extra_params)
|
||||
@@ -172,23 +168,21 @@ pub async fn insert_model(pool: &SqlitePool, r: &LlmModelRecord) -> Result<i64>
|
||||
}
|
||||
|
||||
pub async fn update_model(pool: &SqlitePool, id: i64, r: &LlmModelRecord) -> Result<()> {
|
||||
let scope = serde_json::to_string(&r.scope)?;
|
||||
let extra_params = r.extra_params.as_ref().map(|v| v.to_string());
|
||||
let capabilities = serde_json::to_string(&r.capabilities)?;
|
||||
let reasoning = r.reasoning.as_ref().map(|v| v.to_string());
|
||||
sqlx::query(
|
||||
"UPDATE llm_models
|
||||
SET provider_id=?1, model_id=?2, name=?3, strength=?4,
|
||||
scope=?5, is_default=?6, priority=?7, extra_params=?8,
|
||||
context_length=?9, max_output_tokens=?10, knowledge_cutoff=?11, capabilities=?12,
|
||||
reasoning=?13
|
||||
WHERE id=?14",
|
||||
is_default=?5, priority=?6, extra_params=?7,
|
||||
context_length=?8, max_output_tokens=?9, knowledge_cutoff=?10, capabilities=?11,
|
||||
reasoning=?12
|
||||
WHERE id=?13",
|
||||
)
|
||||
.bind(r.provider_id)
|
||||
.bind(&r.model_id)
|
||||
.bind(&r.name)
|
||||
.bind(r.strength.map(strength_str))
|
||||
.bind(scope)
|
||||
.bind(r.is_default as i64)
|
||||
.bind(r.priority as i64)
|
||||
.bind(extra_params)
|
||||
@@ -267,7 +261,6 @@ fn provider_row_to_record(r: ProviderRow) -> Result<LlmProviderRecord> {
|
||||
}
|
||||
|
||||
fn model_row_to_record(r: ModelRow) -> Result<LlmModelRecord> {
|
||||
let scope: Vec<String> = serde_json::from_str(&r.scope).unwrap_or_default();
|
||||
let extra_params = r.extra_params
|
||||
.as_deref()
|
||||
.and_then(|s| serde_json::from_str(s).ok());
|
||||
@@ -281,7 +274,6 @@ fn model_row_to_record(r: ModelRow) -> Result<LlmModelRecord> {
|
||||
model_id: r.model_id,
|
||||
name: r.name,
|
||||
strength: r.strength.as_deref().and_then(parse_strength),
|
||||
scope,
|
||||
is_default: r.is_default != 0,
|
||||
priority: r.priority as i32,
|
||||
extra_params,
|
||||
|
||||
@@ -100,12 +100,11 @@ impl LlmManager {
|
||||
pub async fn resolve(
|
||||
&self,
|
||||
client_name: Option<&str>,
|
||||
required_scope: Option<&str>,
|
||||
required_strength: Option<LlmStrength>,
|
||||
) -> Result<(String, Arc<LlmEntry>)> {
|
||||
let name = match client_name {
|
||||
None | Some(AUTO_CLIENT) => {
|
||||
let (name, entry) = self.select(required_scope, required_strength).await?;
|
||||
let (name, entry) = self.select(required_strength).await?;
|
||||
self.maybe_refresh_meta(&name).await;
|
||||
return Ok((name, entry));
|
||||
}
|
||||
@@ -368,7 +367,6 @@ impl LlmManager {
|
||||
model_id: slot.model.model_id.clone(),
|
||||
name: slot.model.name.clone(),
|
||||
strength: slot.model.strength,
|
||||
scope: slot.model.scope.clone(),
|
||||
is_default: slot.model.is_default,
|
||||
priority: slot.model.priority,
|
||||
extra_params: slot.model.extra_params.clone(),
|
||||
@@ -391,7 +389,6 @@ impl LlmManager {
|
||||
pub async fn select_excluding(
|
||||
&self,
|
||||
excluded: &[&str],
|
||||
required_scope: Option<&str>,
|
||||
required_strength: Option<LlmStrength>,
|
||||
) -> Result<(String, Arc<LlmEntry>)> {
|
||||
let state = self.state.read().await;
|
||||
@@ -401,7 +398,7 @@ impl LlmManager {
|
||||
if slots.is_empty() {
|
||||
anyhow::bail!("no alternative LLM models available");
|
||||
}
|
||||
sort_slots_for_agent(&mut slots, required_scope, required_strength);
|
||||
sort_slots_for_agent(&mut slots, required_strength);
|
||||
if let Some((name, slot)) = slots.iter().find(|(_, s)| s.health.status != ClientStatus::Down) {
|
||||
return Ok((name.to_string(), slot.entry.clone()));
|
||||
}
|
||||
@@ -414,7 +411,6 @@ impl LlmManager {
|
||||
|
||||
async fn select(
|
||||
&self,
|
||||
required_scope: Option<&str>,
|
||||
required_strength: Option<LlmStrength>,
|
||||
) -> Result<(String, Arc<LlmEntry>)> {
|
||||
let state = self.state.read().await;
|
||||
@@ -424,7 +420,7 @@ impl LlmManager {
|
||||
}
|
||||
|
||||
let mut slots: Vec<(&String, &ModelSlot)> = state.models.iter().collect();
|
||||
sort_slots_for_agent(&mut slots, required_scope, required_strength);
|
||||
sort_slots_for_agent(&mut slots, required_strength);
|
||||
|
||||
if let Some((name, slot)) = slots.iter().find(|(_, s)| s.health.status != ClientStatus::Down) {
|
||||
return Ok((name.to_string(), slot.entry.clone()));
|
||||
@@ -526,7 +522,6 @@ fn build_entry(
|
||||
model: model.model_id.clone(),
|
||||
model_db_id,
|
||||
strength: model.strength,
|
||||
scope: model.scope.clone(),
|
||||
extra_params: extra,
|
||||
context_length: model.context_length,
|
||||
prompt_cache,
|
||||
@@ -548,28 +543,24 @@ fn build_entry(
|
||||
|
||||
pub fn sort_models_for_agent(
|
||||
mut models: Vec<LlmModelInfo>,
|
||||
scope: Option<&str>,
|
||||
strength: Option<LlmStrength>,
|
||||
) -> Vec<LlmModelInfo> {
|
||||
models.sort_by_key(|m| (model_tier(m.strength, m.scope.as_slice(), scope, strength), m.priority));
|
||||
models.sort_by_key(|m| (model_tier(m.strength, strength), m.priority));
|
||||
models
|
||||
}
|
||||
|
||||
fn sort_slots_for_agent(
|
||||
slots: &mut Vec<(&String, &ModelSlot)>,
|
||||
scope: Option<&str>,
|
||||
strength: Option<LlmStrength>,
|
||||
) {
|
||||
slots.sort_by_key(|(_, s)| (
|
||||
model_tier(s.model.strength, s.model.scope.as_slice(), scope, strength),
|
||||
model_tier(s.model.strength, strength),
|
||||
s.model.priority,
|
||||
));
|
||||
}
|
||||
|
||||
fn model_tier(
|
||||
model_strength: Option<LlmStrength>,
|
||||
model_scope: &[String],
|
||||
req_scope: Option<&str>,
|
||||
req_strength: Option<LlmStrength>,
|
||||
) -> u8 {
|
||||
let strength_ok = match (req_strength, model_strength) {
|
||||
@@ -583,11 +574,9 @@ fn model_tier(
|
||||
(Some(req), Some(avail)) => avail == req,
|
||||
_ => true,
|
||||
};
|
||||
let scope_ok = req_scope.map_or(true, |sc| model_scope.iter().any(|x| x == sc));
|
||||
match (strength_ok && scope_ok, exact_match && scope_ok, strength_ok) {
|
||||
(true, true, _) => 0, // exact strength + scope ok
|
||||
(true, false, _) => 1, // over-qualified but scope ok
|
||||
(false, _, true) => 2, // strength ok, scope mismatch
|
||||
_ => 3, // doesn't meet minimum bar
|
||||
match (strength_ok, exact_match) {
|
||||
(true, true) => 0, // exact strength
|
||||
(true, false) => 1, // over-qualified
|
||||
_ => 3, // doesn't meet minimum bar
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ pub struct LlmEntry {
|
||||
pub model: String,
|
||||
pub model_db_id: i64,
|
||||
pub strength: Option<LlmStrength>,
|
||||
pub scope: Vec<String>,
|
||||
pub extra_params: Option<serde_json::Value>,
|
||||
/// Max input context window in tokens, if known.
|
||||
pub context_length: Option<i64>,
|
||||
@@ -96,7 +95,6 @@ pub struct LlmModelInfo {
|
||||
pub model_id: String,
|
||||
pub name: String,
|
||||
pub strength: Option<LlmStrength>,
|
||||
pub scope: Vec<String>,
|
||||
pub is_default: bool,
|
||||
pub priority: i32,
|
||||
pub extra_params: Option<serde_json::Value>,
|
||||
|
||||
Reference in New Issue
Block a user