added Tokens, openrouter, memory system
CI / check (push) Successful in 4m11s
CI / test (push) Successful in 3m57s
CI / clippy (push) Has been cancelled

This commit is contained in:
Sithies
2026-03-21 19:59:07 +01:00
parent 4e6b2c6759
commit 18b666f45d
41 changed files with 3217 additions and 258 deletions
+6 -4
View File
@@ -5,7 +5,9 @@ edition = "2024"
[dependencies]
nazarick-core = { path = "../nazarick-core" }
tracing = "0.1.44"
anyhow = "1.0.102"
async-trait = "0.1.89"
inventory = "0.3.22"
memory = { path = "../memory" }
tracing = "0.1.44"
anyhow = "1.0.102"
async-trait = "0.1.89"
inventory = "0.3.22"
serde_json = "1.0.149"
+3 -4
View File
@@ -1,6 +1,5 @@
// crates/skills/src/lib.rs
pub mod skills;
// Stellt sicher dass alle inventory::submit! ausgeführt werden.
// Ohne diesen Import würden Skills nie eingesammelt.
pub use skills::personality;
pub mod skills;
pub use skills::personality;
pub use skills::remember;
+2 -1
View File
@@ -1 +1,2 @@
pub mod personality;
pub mod personality;
pub mod remember;
+46 -22
View File
@@ -77,40 +77,62 @@ impl PersonalitySkill {
#[async_trait]
impl Skill for PersonalitySkill {
fn summary(&self) -> &str {
"Liest und schreibt den PERSONALITY [MUTABLE] Block — speichert dauerhaft Eigenschaften wie Ton, Stil oder Präferenzen des Herrn die das Verhalten des Agenten beeinflussen"
"Liest und schreibt den PERSONALITY [MUTABLE] Block — speichert dauerhaft Eigenschaften wie Ton, Stil oder Präferenzen des Herrn"
}
fn details(&self) -> &str {
"Verwaltet Persönlichkeitswerte in soul_personality.md.
## update
Setzt oder überschreibt einen Wert:
<skill name=\"personality\">
<action>update</action>
<field>Ton</field>
<value>kurz und direkt</value>
</skill>
## update — Wert setzen oder überschreiben
action: update, field: <name>, value: <wert>
## remove
Entfernt einen Wert:
<skill name=\"personality\">
<action>remove</action>
<field>Ton</field>
</skill>"
## remove — Wert entfernen
action: remove, field: <name>"
}
fn tool_definition(&self) -> serde_json::Value {
serde_json::json!({
"type": "function",
"function": {
"name": "personality",
"description": self.summary(),
"parameters": {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["update", "remove"],
"description": "update = setzen/überschreiben, remove = entfernen"
},
"field": {
"type": "string",
"description": "Name des Persönlichkeitswerts, z.B. 'Ton', 'Stil'"
},
"value": {
"type": "string",
"description": "Neuer Wert — nur bei action=update nötig"
}
},
"required": ["action", "field"]
}
}
})
}
async fn execute(&self, input: SkillInput, ctx: AgentContext) -> Result<SkillOutput> {
let path = Self::path(&ctx.agent_id);
let field = input.require("field")?;
// action ist optional — fehlt es, wird aus value abgeleitet
let action = input.get("action").unwrap_or_else(|| {
if input.get("value").is_some() { "update" } else { "remove" }
});
let action = input.get("action").unwrap_or("update");
let field = match input.get("field") {
Some(f) => f,
None => return Ok(SkillOutput::err("Parameter 'field' fehlt")),
};
match action {
"update" => {
let value = input.require("value")?;
let value = match input.get("value") {
Some(v) => v,
None => return Ok(SkillOutput::err("Parameter 'value' fehlt bei action=update")),
};
Self::do_update(&path, field, value)?;
Ok(SkillOutput::ok(format!("'{}' gesetzt auf '{}'", field, value)))
}
@@ -118,7 +140,9 @@ impl Skill for PersonalitySkill {
Self::do_remove(&path, field)?;
Ok(SkillOutput::ok(format!("'{}' entfernt", field)))
}
unknown => Ok(SkillOutput::err(format!("Unbekannte Action '{}'", unknown)))
unknown => Ok(SkillOutput::err(format!(
"Unbekannte Action '{}'. Erlaubt: update, remove", unknown
)))
}
}
}
+125
View File
@@ -0,0 +1,125 @@
use std::sync::Arc;
use async_trait::async_trait;
use anyhow::Result;
use tracing::info;
use nazarick_core::agent::traits::{Skill, SkillInput, SkillOutput};
use nazarick_core::agent::context::AgentContext;
use nazarick_core::agent::skill_registry::SkillMeta;
pub struct RememberSkill;
#[async_trait]
impl Skill for RememberSkill {
fn summary(&self) -> &str {
"Speichert, aktualisiert, löscht oder liest dauerhaft Fakten über den User"
}
fn details(&self) -> &str {
"Verwaltet Fakten über den User in kategorisierten Einträgen.
## Vordefinierte Kategorien
persönlich, präferenzen, gewohnheiten, beziehungen, arbeit
## update
action: update, category: <kategorie>, key: <schlüssel>, value: <wert>
## delete
action: delete, category: <kategorie>, key: <schlüssel>
## get
action: get, category: <kategorie>"
}
fn tool_definition(&self) -> serde_json::Value {
serde_json::json!({
"type": "function",
"function": {
"name": "remember",
"description": self.summary(),
"parameters": {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["update", "delete", "get"]
},
"category": {
"type": "string",
"description": "persönlich, präferenzen, gewohnheiten, beziehungen, arbeit"
},
"key": {
"type": "string"
},
"value": {
"type": "string"
}
},
"required": ["action", "category"]
}
}
})
}
async fn execute(&self, input: SkillInput, ctx: AgentContext) -> Result<SkillOutput> {
let action = input.get("action").unwrap_or("update");
let category = input.get("category").unwrap_or("persönlich");
match action {
"update" => {
let key = match input.get("key") {
Some(k) => k,
None => return Ok(SkillOutput::err("Parameter 'key' fehlt")),
};
let value = match input.get("value") {
Some(v) => v,
None => return Ok(SkillOutput::err("Parameter 'value' fehlt")),
};
ctx.memory.upsert_fact(category, key, value).await
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
info!(category = %category, key = %key, "Fakt gespeichert");
Ok(SkillOutput::ok(format!("[{}] '{}' = '{}' gespeichert", category, key, value)))
}
"delete" => {
let key = match input.get("key") {
Some(k) => k,
None => return Ok(SkillOutput::err("Parameter 'key' fehlt")),
};
ctx.memory.delete_fact(category, key).await
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
info!(category = %category, key = %key, "Fakt gelöscht");
Ok(SkillOutput::ok(format!("[{}] '{}' gelöscht", category, key)))
}
"get" => {
let facts = ctx.memory.get_category(category).await
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
if facts.is_empty() {
return Ok(SkillOutput::ok_with_feedback(
format!("Keine Fakten in '{}'", category),
format!("Kategorie '{}' ist leer.", category),
));
}
let list = facts.iter()
.map(|f| format!("- {}: {}", f.key, f.value))
.collect::<Vec<_>>()
.join("\n");
Ok(SkillOutput::ok_with_feedback(
format!("Fakten aus '{}' geladen", category),
format!("## Fakten: {}\n{}", category, list),
))
}
unknown => Ok(SkillOutput::err(format!(
"Unbekannte Action '{}'. Erlaubt: update, delete, get", unknown
)))
}
}
}
inventory::submit!(SkillMeta {
name: "remember",
allowed: &["all"],
awaits_result: true,
skill: || Arc::new(RememberSkill),
});