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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct AppConfig {
|
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)]
|
#[serde(default)]
|
||||||
pub backend: BackendConfig,
|
pub backends: HashMap<String, BackendConfig>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub default_backend: String,
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub compaction: CompactionConfig,
|
pub compaction: CompactionConfig,
|
||||||
pub dmn: DmnConfig,
|
pub dmn: DmnConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub learn: LearnConfig,
|
pub learn: LearnConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub models: HashMap<String, ModelConfig>,
|
|
||||||
#[serde(default = "default_model_name")]
|
|
||||||
pub default_model: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub mcp_servers: Vec<McpServerConfig>,
|
pub mcp_servers: Vec<McpServerConfig>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub lsp_servers: Vec<LspServerConfig>,
|
pub lsp_servers: Vec<LspServerConfig>,
|
||||||
|
|
@ -257,10 +257,17 @@ pub struct LspServerConfig {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct BackendConfig {
|
pub struct BackendConfig {
|
||||||
|
/// API key for the backend.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub api_key: String,
|
pub api_key: String,
|
||||||
|
/// Base URL for the backend's OpenAI-compatible endpoint.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub base_url: Option<String>,
|
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)]
|
#[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 {
|
impl Default for AppConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
backend: BackendConfig::default(),
|
backends: HashMap::new(),
|
||||||
|
default_backend: String::new(),
|
||||||
debug: false,
|
debug: false,
|
||||||
compaction: CompactionConfig {
|
compaction: CompactionConfig {
|
||||||
hard_threshold_pct: 90,
|
hard_threshold_pct: 90,
|
||||||
|
|
@ -318,16 +317,12 @@ impl Default for AppConfig {
|
||||||
},
|
},
|
||||||
dmn: DmnConfig { max_turns: 20 },
|
dmn: DmnConfig { max_turns: 20 },
|
||||||
learn: LearnConfig::default(),
|
learn: LearnConfig::default(),
|
||||||
models: HashMap::new(),
|
|
||||||
default_model: String::new(),
|
|
||||||
mcp_servers: Vec::new(),
|
mcp_servers: Vec::new(),
|
||||||
lsp_servers: Vec::new(),
|
lsp_servers: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_model_name() -> String { String::new() }
|
|
||||||
|
|
||||||
/// Resolved, ready-to-use agent session config.
|
/// Resolved, ready-to-use agent session config.
|
||||||
pub struct SessionConfig {
|
pub struct SessionConfig {
|
||||||
pub api_base: String,
|
pub api_base: String,
|
||||||
|
|
@ -352,17 +347,17 @@ pub struct ResolvedModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
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> {
|
pub async fn resolve(&self, cli: &crate::user::CliArgs) -> Result<SessionConfig> {
|
||||||
if self.models.is_empty() {
|
if self.backends.is_empty() {
|
||||||
anyhow::bail!(
|
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()
|
config_path().display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let model_name = cli.model.as_deref().unwrap_or(&self.default_model);
|
let name = cli.model.as_deref().unwrap_or(&self.default_backend);
|
||||||
let resolved = self.resolve_model(model_name)?;
|
let resolved = self.resolve_model(name)?;
|
||||||
|
|
||||||
let personality_nodes = get().personality_nodes.clone();
|
let personality_nodes = get().personality_nodes.clone();
|
||||||
let context_parts = crate::mind::identity::personality_nodes(&personality_nodes).await;
|
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> {
|
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!(
|
.ok_or_else(|| anyhow::anyhow!(
|
||||||
"Unknown model '{}'. Available: {}",
|
"Unknown backend '{}'. Available: {}",
|
||||||
name,
|
name,
|
||||||
self.model_names().join(", "),
|
self.model_names().join(", "),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
let api_base = self.backend.base_url.clone()
|
let api_base = b.base_url.clone()
|
||||||
.ok_or_else(|| anyhow::anyhow!(
|
.ok_or_else(|| anyhow::anyhow!(
|
||||||
"backend.base_url not set in {}",
|
"backends.{}.base_url not set in {}",
|
||||||
config_path().display()
|
name, config_path().display()
|
||||||
))?;
|
))?;
|
||||||
let api_key = self.backend.api_key.clone();
|
|
||||||
|
|
||||||
Ok(ResolvedModel {
|
Ok(ResolvedModel {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
api_base,
|
api_base,
|
||||||
api_key,
|
api_key: b.api_key.clone(),
|
||||||
model_id: model.model_id.clone(),
|
model_id: b.model_id.clone(),
|
||||||
context_window: model.context_window,
|
context_window: b.context_window,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List available model names, sorted.
|
/// List available backend names, sorted.
|
||||||
pub fn model_names(&self) -> Vec<String> {
|
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.sort();
|
||||||
names
|
names
|
||||||
}
|
}
|
||||||
|
|
@ -456,8 +450,6 @@ fn build_figment(cli: &crate::user::CliArgs) -> Figment {
|
||||||
let mut f = Figment::from(Serialized::defaults(AppConfig::default()))
|
let mut f = Figment::from(Serialized::defaults(AppConfig::default()))
|
||||||
.merge(Json5File(config_path()));
|
.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");
|
merge_opt!(f, cli.dmn_max_turns, "dmn.max_turns");
|
||||||
if cli.debug {
|
if cli.debug {
|
||||||
f = f.merge(Serialized::default("debug", true));
|
f = f.merge(Serialized::default("debug", true));
|
||||||
|
|
@ -532,24 +524,26 @@ pub fn show_config(app: &AppConfig, figment: &Figment) {
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("# Effective configuration\n");
|
println!("# Effective configuration\n");
|
||||||
println!("backend:");
|
println!("debug: {} ({})", app.debug, src(figment, "debug"));
|
||||||
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!("\ncompaction:");
|
println!("\ncompaction:");
|
||||||
println!(" hard_threshold_pct: {} ({})", app.compaction.hard_threshold_pct, src(figment, "compaction.hard_threshold_pct"));
|
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!(" soft_threshold_pct: {} ({})", app.compaction.soft_threshold_pct, src(figment, "compaction.soft_threshold_pct"));
|
||||||
println!("\ndmn:");
|
println!("\ndmn:");
|
||||||
println!(" max_turns: {} ({})", app.dmn.max_turns, src(figment, "dmn.max_turns"));
|
println!(" max_turns: {} ({})", app.dmn.max_turns, src(figment, "dmn.max_turns"));
|
||||||
println!("\ndefault_model: {:?}", app.default_model);
|
println!("\ndefault_backend: {:?} ({})", app.default_backend, src(figment, "default_backend"));
|
||||||
if !app.models.is_empty() {
|
if !app.backends.is_empty() {
|
||||||
println!("\nmodels:");
|
println!("\nbackends:");
|
||||||
for (name, m) in &app.models {
|
let mut names: Vec<_> = app.backends.keys().cloned().collect();
|
||||||
|
names.sort();
|
||||||
|
for name in names {
|
||||||
|
let b = &app.backends[&name];
|
||||||
println!(" {}:", name);
|
println!(" {}:", name);
|
||||||
println!(" model_id: {:?}", m.model_id);
|
println!(" api_key: {} ({})", mask(&b.api_key), src(figment, &format!("backends.{name}.api_key")));
|
||||||
if let Some(cw) = m.context_window {
|
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);
|
println!(" context_window: {}", cw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue