Lyra Agent hinzugefügt, Multi-Agent Routing, BaseAgent refactoring

This commit is contained in:
Sithies
2026-03-16 23:30:42 +01:00
parent 6fc1648939
commit 750fe1f5f6
22 changed files with 454 additions and 111 deletions
+43 -4
View File
@@ -186,16 +186,26 @@ async fn process(state: Arc<AppState>, payload: SynologyIncoming, agent: AgentCh
// ─── HTTP Sender ──────────────────────────────────────────────────────────────
//
// Sendet eine Nachricht an einen Synology Chat User.
// Synology erwartet Form-encoded payload mit JSON — kein reines JSON.
// user_id wird dynamisch angehängt — Basis-URL bleibt sauber in config.toml.
// Lange Nachrichten werden automatisch in Chunks aufgeteilt.
// Synology erlaubt max. ~2000 Zeichen pro Nachricht.
const MAX_CHUNK_SIZE: usize = 1800; // Puffer unter dem Limit
async fn send(client: &Client, base_url: &str, user_id: u64, text: &str) {
let chunks = split_message(text);
for chunk in chunks {
send_chunk(client, base_url, user_id, &chunk).await;
}
}
/// Sendet einen einzelnen Chunk.
async fn send_chunk(client: &Client, base_url: &str, user_id: u64, text: &str) {
let body = SynologyOutgoing {
text: text.to_string(),
user_ids: vec![user_id],
};
// JSON serialisieren und als Form-Parameter verpacken
let payload = serde_json::to_string(&body).unwrap_or_default();
match client
@@ -206,9 +216,38 @@ async fn send(client: &Client, base_url: &str, user_id: u64, text: &str) {
{
Ok(r) if r.status().is_success() => {
let response_body = r.text().await.unwrap_or_default();
info!("Nachricht gesendet an user_id={} body={}", user_id, response_body);
info!("Chunk gesendet an user_id={} body={}", user_id, response_body);
}
Ok(r) => error!(status = %r.status(), "Synology hat abgelehnt"),
Err(e) => error!(error = %e, "Senden fehlgeschlagen"),
}
}
/// Teilt einen Text in Chunks auf die Synology verarbeiten kann.
/// Schneidet an Zeilenumbrüchen oder Satzenden — nie mitten im Wort.
fn split_message(text: &str) -> Vec<String> {
if text.len() <= MAX_CHUNK_SIZE {
return vec![text.to_string()];
}
let mut chunks = Vec::new();
let mut remaining = text;
while remaining.len() > MAX_CHUNK_SIZE {
// Schnittpunkt suchen — bevorzugt Zeilenumbruch, dann Leerzeichen
let cut = remaining[..MAX_CHUNK_SIZE]
.rfind('\n')
.or_else(|| remaining[..MAX_CHUNK_SIZE].rfind(". "))
.or_else(|| remaining[..MAX_CHUNK_SIZE].rfind(' '))
.unwrap_or(MAX_CHUNK_SIZE);
chunks.push(remaining[..cut].trim().to_string());
remaining = remaining[cut..].trim_start();
}
if !remaining.is_empty() {
chunks.push(remaining.to_string());
}
chunks
}
+1 -1
View File
@@ -31,7 +31,7 @@ pub struct ChatConfig {
/// Lädt die Konfiguration aus config.toml im Arbeitsverzeichnis.
/// Gibt einen Fehler zurück wenn die Datei fehlt oder ungültig ist.
pub fn load() -> anyhow::Result<NazarickConfig> {
let content = std::fs::read_to_string("config.toml")?;
let content = std::fs::read_to_string("config/config.toml")?;
let config = toml::from_str(&content)?;
Ok(config)
}
+27 -3
View File
@@ -16,6 +16,7 @@ use tracing::info;
use api::llm::lmstudio::LmStudioProvider;
use sebas_tian::Sebas;
use lyra::Lyra;
use skills::personality::PersonalitySkill;
use chat::synology::{handle_incoming, AppState};
#[tokio::main]
@@ -27,27 +28,50 @@ async fn main() -> anyhow::Result<()> {
info!("Nazarick erwacht...");
// Arbeitsverzeichnis auf Workspace-Root setzen
// Damit relative Pfade wie "config/shared_core.md" immer funktionieren
let exe_path = std::env::current_exe()?;
let workspace_root = exe_path
.parent() // debug/
.and_then(|p| p.parent()) // target/
.and_then(|p| p.parent()) // workspace root
.ok_or_else(|| anyhow::anyhow!("Workspace-Root nicht gefunden"))?;
std::env::set_current_dir(workspace_root)?;
info!("Arbeitsverzeichnis: {}", workspace_root.display());
// Config laden
let cfg = config::load()?;
let cfg = config::load().map_err(|e| {
eprintln!("Config Fehler: {}", e);
e
})?;
let port = cfg.chat.listen_port;
// Sebas Tian — Butler Agent
let sebas = Sebas::new(
"config/shared_core.md",
"crates/sebas-tian/config/soul_core.md",
"crates/sebas-tian/config/soul_personality.md",
Box::new(LmStudioProvider::new(
"http://localhost:1234",
"dolphin3.0-llama3.1-8b-abliterated",
)),
Arc::new(PersonalitySkill::new(
"crates/sebas-tian/config/soul_personality.md",
)),
);
// Lyra — Companion Agent (eigenes Modell)
// Lyra — Companion Agent
let lyra = Lyra::new(
"config/shared_core.md",
"crates/lyra/config/soul_core.md",
"crates/lyra/config/soul_personality.md",
Box::new(LmStudioProvider::new(
"http://localhost:1234",
"dolphin3.0-llama3.1-8b-abliterated", // ← später durch Lyras Modell ersetzen
"dolphin3.0-llama3.1-8b-abliterated",
)),
Arc::new(PersonalitySkill::new(
"crates/lyra/config/soul_personality.md",
)),
);