llm: drop model/agent scope matching; add instance-wide compaction model picker
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:
2026-07-25 10:48:09 +01:00
parent 9dafc4bfaa
commit 5081ec2afe
39 changed files with 174 additions and 160 deletions
+56 -10
View File
@@ -49,12 +49,40 @@ use serde_json::json;
use sqlx::SqlitePool;
use tracing::{debug, info, warn};
use core_api::{ConfigProperty, ConfigSet, PropertyType};
use crate::chat_event_bus::{ChatEventBus, CompactionEvent};
use crate::chatbot::ChatOptions;
use crate::config::CompactionConfig;
use crate::config_store::GlobalConfigManager;
use crate::db::{chat_history, chat_llm_tools, chat_summaries};
use crate::llm::LlmManager;
/// Registry `config` key holding the name of the LLM model to use for
/// compaction summaries. Set from the Settings page (instance-wide); empty /
/// unset means AUTO selection by `CompactionConfig.strength` (config.yml).
pub const COMPACTION_MODEL_KEY: &str = "compaction_model";
/// Settings-page section for compaction (see `i18n::config_set` for the
/// pattern). Registered in `Runtime::config_properties`.
pub fn config_set() -> ConfigSet {
ConfigSet {
name: "Compaction".into(),
description: "How conversation history is summarised when the context grows too large.".into(),
properties: vec![
ConfigProperty {
key: COMPACTION_MODEL_KEY.into(),
name: "Compaction model".into(),
description: "Model used to summarise compacted history, for the whole instance. \
A cheap model is usually enough. Leave empty to auto-select \
(by `compaction.strength` in config.yml).".into(),
property_type: PropertyType::LlmModel,
default_value: None,
},
],
}
}
// ── Compaction constants (ported from Hermes context_compressor.py) ──────────
//
// SUMMARY_PREFIX — prepended to every stored summary when injected as context.
@@ -158,18 +186,20 @@ Write only the summary body. Do not include any preamble or prefix.";
// ── Public API ────────────────────────────────────────────────────────────────
pub struct ContextCompactor {
config: CompactionConfig,
llm_manager: Arc<LlmManager>,
event_bus: Arc<ChatEventBus>,
config: CompactionConfig,
llm_manager: Arc<LlmManager>,
event_bus: Arc<ChatEventBus>,
config_store: Arc<GlobalConfigManager>,
}
impl ContextCompactor {
pub fn new(
config: CompactionConfig,
llm_manager: Arc<LlmManager>,
event_bus: Arc<ChatEventBus>,
config: CompactionConfig,
llm_manager: Arc<LlmManager>,
event_bus: Arc<ChatEventBus>,
config_store: Arc<GlobalConfigManager>,
) -> Self {
Self { config, llm_manager, event_bus }
Self { config, llm_manager, event_bus, config_store }
}
/// Attempt to compact the conversation history for `stack_id`.
@@ -291,9 +321,25 @@ impl ContextCompactor {
.format_for_summary(pool, to_summarise, prior_summary.as_ref().map(|s| s.content.as_str()))
.await?;
let (client_name, llm) = self.llm_manager
.resolve(None, None, self.config.strength)
.await?;
// Model for the summary call: the instance-wide Settings pick
// (`compaction_model`) wins; empty/unset falls back to AUTO selection by
// `compaction.strength` from config.yml. A configured model that no
// longer exists (renamed/deleted) degrades to the same AUTO path.
let configured = self.config_store.get(COMPACTION_MODEL_KEY).await
.ok()
.flatten()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
let (client_name, llm) = match configured {
Some(name) => match self.llm_manager.resolve(Some(&name), None).await {
Ok(r) => r,
Err(e) => {
warn!(model = %name, error = %e, "compactor: configured compaction model unavailable, falling back to AUTO selection");
self.llm_manager.resolve(None, self.config.strength).await?
}
},
None => self.llm_manager.resolve(None, self.config.strength).await?,
};
info!(
stack_id,