feat(model-server): source Hugging Face (-hf) + options structurées llama.cpp + migration store V2
Backend du support natif d'une source modèle Hugging Face en alternative au
chemin .gguf local pour le serveur llama.cpp intégré, et refonte des options.
- domaine : ModelSource {LocalPath|HuggingFace} + HfModelRef,
LlamaCppOptions {host,gpu_layers,context_size,jinja}, invariant auto_start
exigeant une source, validate_free_args (rejet des flags réservés).
- infra : build_argv partagé avec build_spawn_spec, émission -hf vs --model,
ordre argv figé ; migration model-servers.json V1 -> V2.
- app-tauri : DTO V2 (modelSource, compat modelPath, conflit = INVALID),
commande preview_model_server_command.
QA : cargo test ciblés verts (domain 7, application 8, infra model_server 4,
app-tauri dto 5). Échecs des suites complètes infra/app-tauri environnementaux
(sandbox bind/socket), hors périmètre.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -4,8 +4,8 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::model_server::{
|
||||
ExecutablePath, LocalModelRef, LocalModelServerConfig, LocalModelServerKind, ModelPath,
|
||||
ModelServerEndpoint, StopPolicy,
|
||||
ExecutablePath, HfModelRef, LlamaCppOptions, LocalModelRef, LocalModelServerConfig,
|
||||
LocalModelServerKind, ModelPath, ModelServerEndpoint, ModelSource, StopPolicy,
|
||||
};
|
||||
use domain::ports::{FileSystem, ModelServerRegistry, ModelServerRuntime, RemotePath};
|
||||
use domain::LocalModelServerId;
|
||||
@ -53,12 +53,15 @@ fn config(id: LocalModelServerId, port: u16) -> LocalModelServerConfig {
|
||||
LocalModelRef::new(
|
||||
"qwen",
|
||||
"Qwen",
|
||||
Some(ModelPath::new("/models/qwen.gguf").unwrap()),
|
||||
Some(ModelSource::LocalPath {
|
||||
path: ModelPath::new("/models/qwen.gguf").unwrap(),
|
||||
}),
|
||||
"qwen3-coder-30b",
|
||||
)
|
||||
.unwrap(),
|
||||
Some(ExecutablePath::new(binary).unwrap()),
|
||||
vec!["--ctx-size".to_owned(), "8192".to_owned()],
|
||||
LlamaCppOptions::new("127.0.0.1", Some(35), Some(8192), true).unwrap(),
|
||||
vec!["--threads".to_owned(), "8".to_owned()],
|
||||
true,
|
||||
StopPolicy::StopOnAppExit,
|
||||
)
|
||||
@ -67,23 +70,54 @@ fn config(id: LocalModelServerId, port: u16) -> LocalModelServerConfig {
|
||||
|
||||
#[test]
|
||||
fn llamacpp_runtime_builds_structured_argv() {
|
||||
let spec = LlamaCppRuntime::new()
|
||||
.build_spawn_spec(&config(sid(1), 8080))
|
||||
let argv = LlamaCppRuntime::new()
|
||||
.build_argv(&config(sid(1), 8080))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
spec.command,
|
||||
argv.command,
|
||||
std::env::current_exe().unwrap().to_string_lossy()
|
||||
);
|
||||
assert_eq!(
|
||||
spec.args,
|
||||
argv.args,
|
||||
vec![
|
||||
"--model",
|
||||
"/models/qwen.gguf",
|
||||
"--port",
|
||||
"8080",
|
||||
"--ctx-size",
|
||||
"8192"
|
||||
"--host",
|
||||
"127.0.0.1",
|
||||
"-ngl",
|
||||
"35",
|
||||
"-c",
|
||||
"8192",
|
||||
"--jinja",
|
||||
"--threads",
|
||||
"8"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn llamacpp_runtime_builds_hf_argv() {
|
||||
let mut config = config(sid(3), 8083);
|
||||
config.model.source = Some(ModelSource::HuggingFace {
|
||||
repo: HfModelRef::new("Qwen/Qwen3-Coder:Q4_K_M").unwrap(),
|
||||
});
|
||||
config.options = LlamaCppOptions::default();
|
||||
config.args = Vec::new();
|
||||
|
||||
let spec = LlamaCppRuntime::new().build_spawn_spec(&config).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
spec.args,
|
||||
vec![
|
||||
"-hf",
|
||||
"Qwen/Qwen3-Coder:Q4_K_M",
|
||||
"--port",
|
||||
"8083",
|
||||
"--host",
|
||||
"127.0.0.1"
|
||||
]
|
||||
);
|
||||
assert!(spec.context_plan.is_none());
|
||||
@ -109,3 +143,49 @@ async fn fs_model_server_registry_roundtrips_global_json() {
|
||||
registry.save(updated.clone()).await.unwrap();
|
||||
assert_eq!(registry.list().await.unwrap(), vec![updated]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fs_model_server_registry_migrates_v1_json_to_v2() {
|
||||
let tmp = TempDir::new();
|
||||
let fs: Arc<dyn FileSystem> = Arc::new(LocalFileSystem::new());
|
||||
fs.create_dir_all(&RemotePath::new(tmp.app_data_dir()))
|
||||
.await
|
||||
.unwrap();
|
||||
fs.write(
|
||||
&tmp.child("model-servers.json"),
|
||||
br#"{
|
||||
"version": 1,
|
||||
"servers": [{
|
||||
"id": "00000000-0000-0000-0000-000000000004",
|
||||
"kind": "llamaCpp",
|
||||
"name": "Legacy Qwen",
|
||||
"endpoint": { "baseURL": "http://localhost:8084/v1", "port": 8084 },
|
||||
"model": {
|
||||
"id": "qwen",
|
||||
"label": "Qwen",
|
||||
"path": "/models/qwen.gguf",
|
||||
"servedName": "qwen3-coder"
|
||||
},
|
||||
"binary": "/usr/bin/llama-server",
|
||||
"args": ["--ctx-size", "4096"],
|
||||
"autoStart": true,
|
||||
"stopPolicy": "stopOnAppExit"
|
||||
}]
|
||||
}"#,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let registry = FsModelServerRegistry::new(Arc::clone(&fs), tmp.app_data_dir());
|
||||
|
||||
let servers = registry.list().await.unwrap();
|
||||
|
||||
assert_eq!(servers.len(), 1);
|
||||
assert_eq!(servers[0].options, LlamaCppOptions::default());
|
||||
assert_eq!(servers[0].args, vec!["--ctx-size", "4096"]);
|
||||
assert_eq!(
|
||||
servers[0].model.source,
|
||||
Some(ModelSource::LocalPath {
|
||||
path: ModelPath::new("/models/qwen.gguf").unwrap()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user