config: merge ModelConfig into BackendConfig, keyed by name
AppConfig had one BackendConfig for credentials and a separate
HashMap<String, ModelConfig> for named model entries. In practice each
named model was always paired with exactly one backend's credentials
— the split bought nothing except an extra struct and the awkward
two-lookup shape in resolve_model (find model → get backend creds →
combine).
Merge them: BackendConfig now carries api_key, base_url, model_id,
and context_window. AppConfig has a single
HashMap<String, BackendConfig> backends map and a default_backend
name. resolve_model is one lookup.
ModelConfig struct deleted. default_model renamed to default_backend.
Config shape changes from
backend: { api_key, base_url }
models: { "27b": { model_id, context_window } }
default_model: "27b"
to
backends: { "27b": { api_key, base_url, model_id, context_window } }
default_backend: "27b"
Updated ~/.consciousness/config.json5 to match.
One small side effect: dropped the --api-key / --api-base figment
merge-opts for "backend.*" targets — those would need to know which
backend to target now and there's no sensible default. The CLI flags
still function as post-resolution overrides on the eventual
SessionConfig.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
2989a6afaa
commit
3e05331608
1 changed files with 45 additions and 51 deletions
|
|
@ -219,19 +219,19 @@ pub fn reload() -> bool {
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AppConfig {
|
||||
/// Credentials for the single model backend.
|
||||
/// Named model endpoints — credentials, base URL, and model id bundled
|
||||
/// into one entry per backend. Keyed by name, selected by
|
||||
/// `default_backend` or by `--model <name>` on the CLI.
|
||||
#[serde(default)]
|
||||
pub backend: BackendConfig,
|
||||
pub backends: HashMap<String, BackendConfig>,
|
||||
#[serde(default)]
|
||||
pub default_backend: String,
|
||||
pub debug: bool,
|
||||
pub compaction: CompactionConfig,
|
||||
pub dmn: DmnConfig,
|
||||
#[serde(default)]
|
||||
pub learn: LearnConfig,
|
||||
#[serde(default)]
|
||||
pub models: HashMap<String, ModelConfig>,
|
||||
#[serde(default = "default_model_name")]
|
||||
pub default_model: String,
|
||||
#[serde(default)]
|
||||
pub mcp_servers: Vec<McpServerConfig>,
|
||||
#[serde(default)]
|
||||
pub lsp_servers: Vec<LspServerConfig>,
|
||||
|
|
@ -257,10 +257,17 @@ pub struct LspServerConfig {
|
|||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct BackendConfig {
|
||||
/// API key for the backend.
|
||||
#[serde(default)]
|
||||
pub api_key: String,
|
||||
/// Base URL for the backend's OpenAI-compatible endpoint.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub base_url: Option<String>,
|
||||
/// Model identifier sent to the API.
|
||||
pub model_id: String,
|
||||
/// Context window size in tokens.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub context_window: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -298,19 +305,11 @@ impl Default for LearnConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ModelConfig {
|
||||
/// Model identifier sent to the API.
|
||||
pub model_id: String,
|
||||
/// Context window size in tokens.
|
||||
#[serde(default)]
|
||||
pub context_window: Option<usize>,
|
||||
}
|
||||
|
||||
impl Default for AppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
backend: BackendConfig::default(),
|
||||
backends: HashMap::new(),
|
||||
default_backend: String::new(),
|
||||
debug: false,
|
||||
compaction: CompactionConfig {
|
||||
hard_threshold_pct: 90,
|
||||
|
|
@ -318,16 +317,12 @@ impl Default for AppConfig {
|
|||
},
|
||||
dmn: DmnConfig { max_turns: 20 },
|
||||
learn: LearnConfig::default(),
|
||||
models: HashMap::new(),
|
||||
default_model: String::new(),
|
||||
mcp_servers: Vec::new(),
|
||||
lsp_servers: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_model_name() -> String { String::new() }
|
||||
|
||||
/// Resolved, ready-to-use agent session config.
|
||||
pub struct SessionConfig {
|
||||
pub api_base: String,
|
||||
|
|
@ -352,17 +347,17 @@ pub struct ResolvedModel {
|
|||
}
|
||||
|
||||
impl AppConfig {
|
||||
/// Resolve the active model and assemble prompts into a SessionConfig.
|
||||
/// Resolve the active backend and assemble prompts into a SessionConfig.
|
||||
pub async fn resolve(&self, cli: &crate::user::CliArgs) -> Result<SessionConfig> {
|
||||
if self.models.is_empty() {
|
||||
if self.backends.is_empty() {
|
||||
anyhow::bail!(
|
||||
"no models configured in {}. Add a `models` section with at least one entry.",
|
||||
"no backends configured in {}. Add a `backends` section with at least one entry.",
|
||||
config_path().display()
|
||||
);
|
||||
}
|
||||
|
||||
let model_name = cli.model.as_deref().unwrap_or(&self.default_model);
|
||||
let resolved = self.resolve_model(model_name)?;
|
||||
let name = cli.model.as_deref().unwrap_or(&self.default_backend);
|
||||
let resolved = self.resolve_model(name)?;
|
||||
|
||||
let personality_nodes = get().personality_nodes.clone();
|
||||
let context_parts = crate::mind::identity::personality_nodes(&personality_nodes).await;
|
||||
|
|
@ -387,34 +382,33 @@ impl AppConfig {
|
|||
})
|
||||
}
|
||||
|
||||
/// Look up a named model and resolve its credentials from the backend config.
|
||||
/// Look up a named backend and resolve its credentials.
|
||||
pub fn resolve_model(&self, name: &str) -> Result<ResolvedModel> {
|
||||
let model = self.models.get(name)
|
||||
let b = self.backends.get(name)
|
||||
.ok_or_else(|| anyhow::anyhow!(
|
||||
"Unknown model '{}'. Available: {}",
|
||||
"Unknown backend '{}'. Available: {}",
|
||||
name,
|
||||
self.model_names().join(", "),
|
||||
))?;
|
||||
|
||||
let api_base = self.backend.base_url.clone()
|
||||
let api_base = b.base_url.clone()
|
||||
.ok_or_else(|| anyhow::anyhow!(
|
||||
"backend.base_url not set in {}",
|
||||
config_path().display()
|
||||
"backends.{}.base_url not set in {}",
|
||||
name, config_path().display()
|
||||
))?;
|
||||
let api_key = self.backend.api_key.clone();
|
||||
|
||||
Ok(ResolvedModel {
|
||||
name: name.to_string(),
|
||||
api_base,
|
||||
api_key,
|
||||
model_id: model.model_id.clone(),
|
||||
context_window: model.context_window,
|
||||
api_key: b.api_key.clone(),
|
||||
model_id: b.model_id.clone(),
|
||||
context_window: b.context_window,
|
||||
})
|
||||
}
|
||||
|
||||
/// List available model names, sorted.
|
||||
/// List available backend names, sorted.
|
||||
pub fn model_names(&self) -> Vec<String> {
|
||||
let mut names: Vec<_> = self.models.keys().cloned().collect();
|
||||
let mut names: Vec<_> = self.backends.keys().cloned().collect();
|
||||
names.sort();
|
||||
names
|
||||
}
|
||||
|
|
@ -456,8 +450,6 @@ fn build_figment(cli: &crate::user::CliArgs) -> Figment {
|
|||
let mut f = Figment::from(Serialized::defaults(AppConfig::default()))
|
||||
.merge(Json5File(config_path()));
|
||||
|
||||
merge_opt!(f, cli.api_key, "backend.api_key");
|
||||
merge_opt!(f, cli.api_base, "backend.base_url");
|
||||
merge_opt!(f, cli.dmn_max_turns, "dmn.max_turns");
|
||||
if cli.debug {
|
||||
f = f.merge(Serialized::default("debug", true));
|
||||
|
|
@ -532,24 +524,26 @@ pub fn show_config(app: &AppConfig, figment: &Figment) {
|
|||
}
|
||||
|
||||
println!("# Effective configuration\n");
|
||||
println!("backend:");
|
||||
println!(" api_key: {} ({})", mask(&app.backend.api_key), src(figment, "backend.api_key"));
|
||||
if let Some(ref url) = app.backend.base_url {
|
||||
println!(" base_url: {:?} ({})", url, src(figment, "backend.base_url"));
|
||||
}
|
||||
println!("\ndebug: {} ({})", app.debug, src(figment, "debug"));
|
||||
println!("debug: {} ({})", app.debug, src(figment, "debug"));
|
||||
println!("\ncompaction:");
|
||||
println!(" hard_threshold_pct: {} ({})", app.compaction.hard_threshold_pct, src(figment, "compaction.hard_threshold_pct"));
|
||||
println!(" soft_threshold_pct: {} ({})", app.compaction.soft_threshold_pct, src(figment, "compaction.soft_threshold_pct"));
|
||||
println!("\ndmn:");
|
||||
println!(" max_turns: {} ({})", app.dmn.max_turns, src(figment, "dmn.max_turns"));
|
||||
println!("\ndefault_model: {:?}", app.default_model);
|
||||
if !app.models.is_empty() {
|
||||
println!("\nmodels:");
|
||||
for (name, m) in &app.models {
|
||||
println!("\ndefault_backend: {:?} ({})", app.default_backend, src(figment, "default_backend"));
|
||||
if !app.backends.is_empty() {
|
||||
println!("\nbackends:");
|
||||
let mut names: Vec<_> = app.backends.keys().cloned().collect();
|
||||
names.sort();
|
||||
for name in names {
|
||||
let b = &app.backends[&name];
|
||||
println!(" {}:", name);
|
||||
println!(" model_id: {:?}", m.model_id);
|
||||
if let Some(cw) = m.context_window {
|
||||
println!(" api_key: {} ({})", mask(&b.api_key), src(figment, &format!("backends.{name}.api_key")));
|
||||
if let Some(ref url) = b.base_url {
|
||||
println!(" base_url: {:?} ({})", url, src(figment, &format!("backends.{name}.base_url")));
|
||||
}
|
||||
println!(" model_id: {:?}", b.model_id);
|
||||
if let Some(cw) = b.context_window {
|
||||
println!(" context_window: {}", cw);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue