feat(llm): add kon-llm stub crate with LlmEngine interface — Phase 3 will wire real model
This commit is contained in:
6
crates/llm/Cargo.toml
Normal file
6
crates/llm/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "kon-llm"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
73
crates/llm/src/lib.rs
Normal file
73
crates/llm/src/lib.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
struct LlmState {
|
||||||
|
loaded: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shared handle to the LLM engine. Cheap to clone (Arc).
|
||||||
|
/// Phase 3 will replace the stub body with a real llama-cpp-2 model.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LlmEngine {
|
||||||
|
state: Arc<Mutex<LlmState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LlmEngine {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
state: Arc::new(Mutex::new(LlmState { loaded: false })),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_loaded(&self) -> bool {
|
||||||
|
self.state.lock().unwrap().loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Break a task description into 3-7 physical micro-steps.
|
||||||
|
/// Returns Err if no model is loaded — the caller surfaces this to the UI.
|
||||||
|
pub fn decompose_task(&self, _task_text: &str) -> Result<Vec<String>, String> {
|
||||||
|
if !self.is_loaded() {
|
||||||
|
return Err(
|
||||||
|
"Download an AI model in Settings to break down tasks.".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Phase 3: call llama-cpp-2 with GBNF-constrained prompt here.
|
||||||
|
Err("LLM not yet wired.".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LlmEngine {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decompose_returns_error_when_not_loaded() {
|
||||||
|
let engine = LlmEngine::new();
|
||||||
|
assert!(!engine.is_loaded());
|
||||||
|
let result = engine.decompose_task("Write a blog post");
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(
|
||||||
|
result.unwrap_err().contains("Download an AI model"),
|
||||||
|
"error message should tell user to download a model"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_creates_unloaded_engine() {
|
||||||
|
let engine = LlmEngine::default();
|
||||||
|
assert!(!engine.is_loaded());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn engine_is_clone_and_shares_state() {
|
||||||
|
let engine = LlmEngine::new();
|
||||||
|
let clone = engine.clone();
|
||||||
|
// Both point to the same Arc — neither is loaded
|
||||||
|
assert!(!clone.is_loaded());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ kon-ai-formatting = { path = "../crates/ai-formatting" }
|
|||||||
kon-storage = { path = "../crates/storage" }
|
kon-storage = { path = "../crates/storage" }
|
||||||
kon-cloud-providers = { path = "../crates/cloud-providers" }
|
kon-cloud-providers = { path = "../crates/cloud-providers" }
|
||||||
kon-hotkey = { path = "../crates/hotkey" }
|
kon-hotkey = { path = "../crates/hotkey" }
|
||||||
|
kon-llm = { path = "../crates/llm" }
|
||||||
|
|
||||||
# Tauri
|
# Tauri
|
||||||
tauri = { version = "2", features = ["tray-icon"] }
|
tauri = { version = "2", features = ["tray-icon"] }
|
||||||
|
|||||||
Reference in New Issue
Block a user