Lyra Agent hinzugefügt, Multi-Agent Routing, BaseAgent refactoring
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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",
|
||||
)),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user