refactor: LLM Typen und Traits nach nazarick-core verschoben, BaseAgent extrahiert

This commit is contained in:
Sithies
2026-03-16 22:06:30 +01:00
parent df29fdfa60
commit 30d63debd9
11 changed files with 127 additions and 66 deletions
Generated
+1
View File
@@ -760,6 +760,7 @@ dependencies = [
name = "nazarick-core" name = "nazarick-core"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait",
"thiserror", "thiserror",
"uuid", "uuid",
] ]
+9 -1
View File
@@ -15,5 +15,13 @@ bot_token = "k1RMRh0NbcROtVlPbUg2GNgtGzb3AKmiHzgIt0E1VcmtWkZFAic7Sv6s
incoming_webhook_url = "https://sithies-tb.de6.quickconnect.to/direct/webapi/entry.cgi?api=SYNO.Chat.External&method=chatbot&version=2&token=%22k1RMRh0NbcROtVlPbUg2GNgtGzb3AKmiHzgIt0E1VcmtWkZFAic7Sv6sS3ZPHO1D%22" incoming_webhook_url = "https://sithies-tb.de6.quickconnect.to/direct/webapi/entry.cgi?api=SYNO.Chat.External&method=chatbot&version=2&token=%22k1RMRh0NbcROtVlPbUg2GNgtGzb3AKmiHzgIt0E1VcmtWkZFAic7Sv6sS3ZPHO1D%22"
allowed_user_ids = [5] allowed_user_ids = [5]
agent_id = "lyra"
bot_token = "e8Hg50YgD1YcfmfaKCr1B3lgAE3c2s8QyJOTXyfkPJulKzcqgqq7EBrT4MNw1gUy"
incoming_webhook_url = "https://sithies-tb.de6.quickconnect.to/direct/webapi/entry.cgi?api=SYNO.Chat.External&method=chatbot&version=2&token=%22e8Hg50YgD1YcfmfaKCr1B3lgAE3c2s8QyJOTXyfkPJulKzcqgqq7EBrT4MNw1gUy%22"
allowed_user_ids = [5]
[agents.sebas_tian] [agents.sebas_tian]
# Sebas-spezifisches # Sebas-spezifisches
[agents.lyra]
# Lyra-spezifisches
+2 -3
View File
@@ -1,7 +1,6 @@
/// Abstraktionsschicht für alle LLM-Provider. /// Abstraktionsschicht für alle LLM-Provider.
/// Neue Provider (Ollama, Mistral) werden hier als weitere Submodule ergänzt. /// Neue Provider (Ollama, Mistral) werden hier als weitere Submodule ergänzt.
pub mod provider;
pub mod lmstudio; pub mod lmstudio;
// Re-export der wichtigsten Typen damit Nutzer nur `api::llm::X` schreiben müssen // Re-export aus nazarick-core damit bestehende Importe `api::llm::X` weiter funktionieren
pub use provider::{LlmProvider, LlmRequest, LlmResponse, Message}; pub use nazarick_core::llm::{LlmProvider, LlmRequest, LlmResponse, Message};
+1 -1
View File
@@ -3,7 +3,7 @@ use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use nazarick_core::types::Result; use nazarick_core::types::Result;
use nazarick_core::error::NazarickError; use nazarick_core::error::NazarickError;
use crate::llm::provider::{LlmProvider, LlmRequest, LlmResponse, Message}; use nazarick_core::llm::{LlmProvider, LlmRequest, LlmResponse, Message};
/// LM Studio Provider — für lokale Entwicklung auf dem Entwicklungsrechner. /// LM Studio Provider — für lokale Entwicklung auf dem Entwicklungsrechner.
/// LM Studio emuliert die OpenAI Chat Completions API, daher nutzen /// LM Studio emuliert die OpenAI Chat Completions API, daher nutzen
+2 -1
View File
@@ -5,4 +5,5 @@ edition = "2024"
[dependencies] [dependencies]
thiserror = "2.0.18" thiserror = "2.0.18"
uuid = { version = "1.22.0", features = ["v4"] } uuid = { version = "1.22.0", features = ["v4"] }
async-trait = "0.1.89"
+65
View File
@@ -0,0 +1,65 @@
// nazarick-core/src/agent.rs
//
// BaseAgent — gemeinsame Logik für alle Agenten.
// Sebas, Lyra und zukünftige Agenten sind nur noch dünne Wrapper darum.
use crate::prompt::PromptBuilder;
use crate::types::{AgentId, Result};
use crate::llm::{LlmProvider, LlmRequest, Message};
pub struct BaseAgent {
/// Eindeutige ID dieser Agent-Instanz
pub id: AgentId,
/// Baut den System-Prompt aus soul_core + soul_personality
prompt_builder: PromptBuilder,
/// Das LLM-Backend (LmStudio, Ollama, Mistral)
llm: Box<dyn LlmProvider>,
/// Konversationsverlauf — damit der Agent den Kontext behält
history: Vec<Message>,
}
impl BaseAgent {
/// Erstellt eine neue BaseAgent-Instanz.
/// Wird von jedem Agenten in seinem new() aufgerufen.
pub fn new(
soul_core_path: impl Into<String>,
soul_personality_path: impl Into<String>,
llm: Box<dyn LlmProvider>,
) -> Self {
Self {
id: AgentId::new_v4(),
prompt_builder: PromptBuilder::new(soul_core_path, soul_personality_path),
llm,
history: Vec::new(),
}
}
/// Sendet eine Nachricht und gibt die Antwort zurück.
/// Konversationsverlauf wird automatisch mitgeführt.
pub async fn chat(&mut self, user_message: &str) -> Result<String> {
let system_prompt = self.prompt_builder.build()?;
self.history.push(Message::user(user_message));
let mut messages = vec![Message::system(system_prompt)];
messages.extend(self.history.clone());
let request = LlmRequest {
messages,
max_tokens: 4096,
temperature: 0.7,
};
let response = self.llm.complete(request).await?;
self.history.push(Message::assistant(&response.content));
Ok(response.content)
}
/// Löscht den Konversationsverlauf.
/// Nützlich wenn ein neues Gespräch beginnen soll.
pub fn clear_history(&mut self) {
self.history.clear();
}
}
+3 -1
View File
@@ -9,4 +9,6 @@ pub mod usage;
/// Persönlichkeit basiert auf zwei Dateien: /// Persönlichkeit basiert auf zwei Dateien:
/// - soul_core.md → unveränderlicher Kern (Regeln, Sicherheit) /// - soul_core.md → unveränderlicher Kern (Regeln, Sicherheit)
/// - soul_personality.md → entwickelbarer Teil (Ton, Präferenzen) /// - soul_personality.md → entwickelbarer Teil (Ton, Präferenzen)
pub mod prompt; pub mod prompt;
pub mod llm;
pub mod agent;
+10
View File
@@ -0,0 +1,10 @@
// nazarick-core/src/llm/mod.rs
//
// LLM-Modul — Typen und Traits für alle LLM-Provider.
// Re-exportiert alles damit Nutzer nur `nazarick_core::llm::X` schreiben müssen.
mod types;
mod traits;
pub use types::{Message, LlmRequest, LlmResponse};
pub use traits::LlmProvider;
+19
View File
@@ -0,0 +1,19 @@
// nazarick-core/src/llm/traits.rs
//
// LlmProvider Trait — gemeinsame Schnittstelle für alle LLM-Backends.
// Neue Provider (Ollama, Mistral) implementieren diesen Trait.
use crate::types::Result;
use crate::llm::types::{LlmRequest, LlmResponse};
/// Zentraler Trait für alle LLM-Provider.
/// Jeder Provider (LmStudio, Ollama, Mistral) implementiert diesen Trait.
#[async_trait::async_trait]
pub trait LlmProvider: Send + Sync {
/// Sendet eine Anfrage an das LLM und gibt die Antwort zurück.
async fn complete(&self, request: LlmRequest) -> Result<LlmResponse>;
/// Gibt den Namen des Providers zurück.
/// Wird für Logging und Usage-Tracking verwendet.
fn name(&self) -> &str;
}
@@ -1,4 +1,7 @@
use nazarick_core::types::Result; // nazarick-core/src/llm/types.rs
//
// Gemeinsame Datentypen für alle LLM-Provider.
// Jeder Provider (LmStudio, Ollama, Mistral) nutzt diese Typen.
/// Repräsentiert eine einzelne Nachricht in einem Gespräch. /// Repräsentiert eine einzelne Nachricht in einem Gespräch.
/// Entspricht dem Message-Format das alle gängigen LLM APIs verwenden. /// Entspricht dem Message-Format das alle gängigen LLM APIs verwenden.
@@ -47,15 +50,4 @@ pub struct LlmResponse {
pub tokens_input: u64, pub tokens_input: u64,
/// Anzahl der Output-Token (für Usage-Tracking) /// Anzahl der Output-Token (für Usage-Tracking)
pub tokens_output: u64, pub tokens_output: u64,
}
/// Zentraler Trait für alle LLM-Provider.
#[async_trait::async_trait]
pub trait LlmProvider: Send + Sync {
/// Sendet eine Anfrage an das LLM und gibt die Antwort zurück.
async fn complete(&self, request: LlmRequest) -> Result<LlmResponse>;
/// Gibt den Namen des Providers zurück.
/// Wird für Logging und Usage-Tracking verwendet.
fn name(&self) -> &str;
} }
+11 -47
View File
@@ -1,74 +1,38 @@
/// Sebas Tian — Haupt-Butler-Agent von Nazarick. // crates/sebas-tian/src/lib.rs
/// Implementiert den Agent-Trait und orchestriert //
/// LLM-Kommunikation, Prompt-Aufbau und Konversationsverlauf. // Sebas Tian — Haupt-Butler-Agent.
// Dünner Wrapper um BaseAgent — nur name() ist Sebas-spezifisch.
use nazarick_core::agent::BaseAgent;
use nazarick_core::traits::Agent; use nazarick_core::traits::Agent;
use nazarick_core::types::AgentId; use nazarick_core::types::AgentId;
use nazarick_core::prompt::PromptBuilder; use nazarick_core::llm::LlmProvider;
use api::llm::{LlmProvider, LlmRequest, Message};
pub struct Sebas { pub struct Sebas {
/// Eindeutige ID dieser Agent-Instanz base: BaseAgent,
id: AgentId,
/// Baut den System-Prompt aus soul_core + soul_personality
prompt_builder: PromptBuilder,
/// Das LLM-Backend (LmStudio, Ollama, Mistral)
llm: Box<dyn LlmProvider>,
/// Konversationsverlauf — damit Sebas den Kontext behält
history: Vec<Message>,
} }
impl Sebas { impl Sebas {
/// Erstellt eine neue Sebas-Instanz. /// Erstellt eine neue Sebas-Instanz.
/// `soul_core_path` → Pfad zu soul_core.md
/// `soul_personality_path` → Pfad zu soul_personality.md
/// `llm` → LLM-Provider (z.B. LmStudioProvider)
pub fn new( pub fn new(
soul_core_path: impl Into<String>, soul_core_path: impl Into<String>,
soul_personality_path: impl Into<String>, soul_personality_path: impl Into<String>,
llm: Box<dyn LlmProvider>, llm: Box<dyn LlmProvider>,
) -> Self { ) -> Self {
Self { Self {
id: AgentId::new_v4(), base: BaseAgent::new(soul_core_path, soul_personality_path, llm),
prompt_builder: PromptBuilder::new(soul_core_path, soul_personality_path),
llm,
history: Vec::new(),
} }
} }
/// Sendet eine Nachricht an Sebas und gibt seine Antwort zurück. /// Delegiert chat() an BaseAgent.
/// Der Konversationsverlauf wird automatisch mitgeführt.
pub async fn chat(&mut self, user_message: &str) -> nazarick_core::types::Result<String> { pub async fn chat(&mut self, user_message: &str) -> nazarick_core::types::Result<String> {
// System-Prompt aus soul-Dateien aufbauen self.base.chat(user_message).await
let system_prompt = self.prompt_builder.build()?;
// User-Nachricht zum Verlauf hinzufügen
self.history.push(Message::user(user_message));
// Vollständige Nachrichtenliste aufbauen:
// System-Prompt + gesamter bisheriger Verlauf
let mut messages = vec![Message::system(system_prompt)];
messages.extend(self.history.clone());
// LLM anfragen
let request = LlmRequest {
messages,
max_tokens: 4096, // ← erhöht damit Thinking + Antwort reinpassen
temperature: 0.7,
};
let response = self.llm.complete(request).await?;
// Antwort zum Verlauf hinzufügen damit Sebas sich erinnert
self.history.push(Message::assistant(&response.content));
Ok(response.content)
} }
} }
impl Agent for Sebas { impl Agent for Sebas {
fn id(&self) -> AgentId { fn id(&self) -> AgentId {
self.id self.base.id
} }
fn name(&self) -> &str { fn name(&self) -> &str {