wip: snapshot das alterações não commitadas (2026-10-20)

Origem: D:/Documents/Github/ForgeLM
Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 1
This commit is contained in:
evandro committed 2026-10-20 00:49:35 -03:00
1 parent f2481cf7e6
commit 4f293dcb3b
34 files changed
+7131 -255

No files matched your search

+32 -108
View File
@@ -1,56 +1,32 @@
//! Model alias resolution and startup model selection policy.
//!
//! Alias table:
//! 0.8B family → "Cooper"
//! 2B family → "Iron"
//! 4B family → "Steel"
use crate::model_files::ModelFile;
/// Canonical display alias for a model family.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ModelAlias {
Cooper,
Iron,
Steel,
Bonsai,
Unknown,
}
impl ModelAlias {
pub fn as_str(&self) -> &'static str {
match self {
ModelAlias::Cooper => "Cooper",
ModelAlias::Iron => "Iron",
ModelAlias::Steel => "Steel",
ModelAlias::Bonsai => "Bonsai",
ModelAlias::Unknown => "Unknown",
}
}
}
/// Resolve a model file path/name to a display alias.
/// Priority:
/// 1. filename contains "0.8b" (case-insensitive) → Cooper
/// 2. filename contains "4b" (case-insensitive) → Steel (checked before 2b to avoid false match)
/// 3. filename contains "2b" (case-insensitive) → Iron
/// 4. fallback → Unknown
pub fn resolve_alias(path: &str) -> ModelAlias {
let lower = path.to_lowercase();
// Extract just the filename for matching
let filename = lower.rsplit(['/', '\\']).next().unwrap_or(&lower);
if filename.contains("0.8b") {
ModelAlias::Cooper
} else if filename.contains("4b") {
ModelAlias::Steel
} else if filename.contains("2b") {
ModelAlias::Iron
if filename.contains("bonsai") {
ModelAlias::Bonsai
} else {
ModelAlias::Unknown
}
}
/// Result of startup model resolution.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct StartupModelResolution {
pub model_path: String,
@@ -62,18 +38,10 @@ pub struct StartupModelResolution {
#[serde(rename_all = "snake_case")]
pub enum StartupReason {
SavedPreference,
DefaultIron,
FallbackCooper,
DefaultBonsai,
MissingSavedModel,
}
/// Resolve which model to start at app launch.
///
/// Policy (from SDD):
/// 1. If user saved a preferred alias and a matching model exists → use it
/// 2. Prefer Iron (2B)
/// 3. Fallback to Cooper (0.8B)
/// 4. Steel is ignored by auto-start policy
pub fn resolve_startup_model(
preferred_alias: Option<&str>,
discovered: &[ModelFile],
@@ -82,7 +50,6 @@ pub fn resolve_startup_model(
return None;
}
// Classify all discovered models
let classified: Vec<(&ModelFile, ModelAlias)> = discovered
.iter()
.map(|m| (m, resolve_alias(&m.path)))
@@ -91,9 +58,7 @@ pub fn resolve_startup_model(
// 1. Respect saved preference if available
if let Some(pref) = preferred_alias {
let target_alias = match pref.to_lowercase().as_str() {
"cooper" => ModelAlias::Cooper,
"iron" => ModelAlias::Iron,
"steel" => ModelAlias::Steel,
"bonsai" => ModelAlias::Bonsai,
_ => ModelAlias::Unknown,
};
if target_alias != ModelAlias::Unknown {
@@ -104,30 +69,24 @@ pub fn resolve_startup_model(
reason: StartupReason::SavedPreference,
});
}
// Saved model not found — fall through to default policy with MissingSavedModel reason
}
}
// 2. Prefer Iron
if let Some((model, _)) = classified.iter().find(|(_, a)| *a == ModelAlias::Iron) {
// 2. Default: Bonsai (8B)
if let Some((model, _)) = classified.iter().find(|(_, a)| *a == ModelAlias::Bonsai) {
return Some(StartupModelResolution {
model_path: model.path.clone(),
alias: ModelAlias::Iron,
reason: StartupReason::DefaultIron,
alias: ModelAlias::Bonsai,
reason: StartupReason::DefaultBonsai,
});
}
// 3. Fallback to Cooper
if let Some((model, _)) = classified.iter().find(|(_, a)| *a == ModelAlias::Cooper) {
return Some(StartupModelResolution {
model_path: model.path.clone(),
alias: ModelAlias::Cooper,
reason: StartupReason::FallbackCooper,
});
}
// 4. No suitable model found (Steel only or Unknown only)
None
// 3. Fallback: pick the first discovered model regardless of alias
discovered.first().map(|model| StartupModelResolution {
model_path: model.path.clone(),
alias: resolve_alias(&model.path),
reason: StartupReason::DefaultBonsai,
})
}
#[cfg(test)]
@@ -143,26 +102,10 @@ mod tests {
}
#[test]
fn resolve_alias_detects_cooper_from_filename() {
fn resolve_alias_detects_bonsai_from_filename() {
assert_eq!(
resolve_alias("D:/models/rahul7star_Qwen3.5-0.8B-Coder.gguf"),
ModelAlias::Cooper
);
}
#[test]
fn resolve_alias_detects_iron_from_filename() {
assert_eq!(
resolve_alias("D:/models/Qwen3.5-2B.Q6_K.gguf"),
ModelAlias::Iron
);
}
#[test]
fn resolve_alias_detects_steel_from_filename() {
assert_eq!(
resolve_alias("D:/models/Qwen3.5-4B-Abliterated.i1-Q6_K.gguf"),
ModelAlias::Steel
resolve_alias("D:/models/Bonsai-8B.gguf"),
ModelAlias::Bonsai
);
}
@@ -175,40 +118,30 @@ mod tests {
}
#[test]
fn startup_resolver_picks_iron_when_both_available() {
fn startup_resolver_picks_bonsai_when_available() {
let models = vec![
make_model("rahul7star_Qwen3.5-0.8B-Coder.gguf"),
make_model("Qwen3.5-2B.Q6_K.gguf"),
make_model("Bonsai-8B.gguf"),
make_model("some-other-model.gguf"),
];
let result = resolve_startup_model(None, &models).unwrap();
assert_eq!(result.alias, ModelAlias::Iron);
assert_eq!(result.reason, StartupReason::DefaultIron);
}
#[test]
fn startup_resolver_falls_back_to_cooper_when_iron_missing() {
let models = vec![make_model("rahul7star_Qwen3.5-0.8B-Coder.gguf")];
let result = resolve_startup_model(None, &models).unwrap();
assert_eq!(result.alias, ModelAlias::Cooper);
assert_eq!(result.reason, StartupReason::FallbackCooper);
assert_eq!(result.alias, ModelAlias::Bonsai);
assert_eq!(result.reason, StartupReason::DefaultBonsai);
}
#[test]
fn startup_resolver_respects_saved_preference() {
let models = vec![
make_model("rahul7star_Qwen3.5-0.8B-Coder.gguf"),
make_model("Qwen3.5-2B.Q6_K.gguf"),
];
let result = resolve_startup_model(Some("cooper"), &models).unwrap();
assert_eq!(result.alias, ModelAlias::Cooper);
let models = vec![make_model("Bonsai-8B.gguf")];
let result = resolve_startup_model(Some("bonsai"), &models).unwrap();
assert_eq!(result.alias, ModelAlias::Bonsai);
assert_eq!(result.reason, StartupReason::SavedPreference);
}
#[test]
fn startup_resolver_ignores_steel_in_auto_policy() {
let models = vec![make_model("Qwen3.5-4B-Abliterated.i1-Q6_K.gguf")];
let result = resolve_startup_model(None, &models);
assert!(result.is_none(), "Steel should not be auto-selected");
fn startup_resolver_falls_back_to_first_model_when_no_bonsai() {
let models = vec![make_model("some-other-model.gguf")];
let result = resolve_startup_model(None, &models).unwrap();
assert_eq!(result.model_path, "D:/models/some-other-model.gguf");
assert_eq!(result.reason, StartupReason::DefaultBonsai);
}
#[test]
@@ -216,13 +149,4 @@ mod tests {
let result = resolve_startup_model(None, &[]);
assert!(result.is_none());
}
#[test]
fn startup_resolver_falls_through_when_saved_preference_not_found() {
// Saved preference is "iron" but only Cooper is available → fallback to Cooper
let models = vec![make_model("rahul7star_Qwen3.5-0.8B-Coder.gguf")];
let result = resolve_startup_model(Some("iron"), &models).unwrap();
assert_eq!(result.alias, ModelAlias::Cooper);
assert_eq!(result.reason, StartupReason::FallbackCooper);
}
}
+5 -3
View File
@@ -2,7 +2,8 @@ use serde::{Deserialize, Serialize};
use crate::model_library::types::RemotePreset;
pub const DEFAULT_CTX_SIZE: u32 = 4096;
pub const DEFAULT_CTX_SIZE: u32 = 131072;
pub const DEFAULT_GPU_LAYERS: i32 = -1;
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Debug)]
#[serde(rename_all = "snake_case")]
@@ -105,7 +106,7 @@ impl Default for RuntimeConfig {
model_path: None,
models_dir: None,
addr: Self::default_addr(),
gpu_layers: 0,
gpu_layers: DEFAULT_GPU_LAYERS,
ctx_size: DEFAULT_CTX_SIZE,
extra_model_paths: Vec::new(),
selected_model_id: None,
@@ -118,13 +119,14 @@ impl Default for RuntimeConfig {
#[cfg(test)]
mod tests {
use super::{ConnectionMode, RuntimeConfig, DEFAULT_CTX_SIZE};
use super::{ConnectionMode, RuntimeConfig, DEFAULT_CTX_SIZE, DEFAULT_GPU_LAYERS};
#[test]
fn runtime_config_defaults_to_safe_context_window() {
let config = RuntimeConfig::default();
assert_eq!(config.ctx_size, DEFAULT_CTX_SIZE);
assert_eq!(config.gpu_layers, DEFAULT_GPU_LAYERS);
}
#[test]