Thinking Mode Filter, SkillFormat Strategy, Persönlichkeitsanpassung funktioniert

This commit is contained in:
Sithies
2026-03-17 18:11:31 +01:00
parent 0190089c90
commit 389c759166
10 changed files with 167 additions and 41 deletions
+4 -1
View File
@@ -36,6 +36,9 @@ impl BaseAgent {
llm: Box<dyn LlmProvider>,
personality_writer: Arc<dyn PersonalityWriter>,
) -> Self {
// Skill-Format vom Provider abfragen bevor llm konsumiert wird
let skill_format = llm.skill_format();
Self {
id: AgentId::new_v4(),
prompt_builder: PromptBuilder::new(
@@ -45,7 +48,7 @@ impl BaseAgent {
),
llm,
history: Vec::new(),
skill_executor: SkillExecutor::new(personality_writer),
skill_executor: SkillExecutor::new(personality_writer, skill_format),
}
}
@@ -12,6 +12,7 @@
use std::sync::Arc;
use tracing::{error, info};
use crate::agent::traits::PersonalityWriter;
use crate::llm::SkillFormat;
/// Ein einzelner geparster Skill-Call aus einer Agenten-Antwort.
#[derive(Debug)]
@@ -22,55 +23,117 @@ pub struct SkillCall {
pub params: Vec<(String, String)>,
}
/// Führt Skills aus die in Agenten-Antworten als XML-Tags kodiert sind.
/// Konkrete Implementierungen werden via Dependency Injection übergeben.
/// Führt Skills aus die in Agenten-Antworten kodiert sind.
/// Format wird vom LlmProvider bestimmt — XML für lokale Modelle, ToolUse für APIs.
pub struct SkillExecutor {
/// Konkrete Implementierung für Persönlichkeits-Updates
personality_writer: Arc<dyn PersonalityWriter>,
/// Format das der aktuelle Provider für Skill-Calls nutzt
skill_format: SkillFormat,
}
impl SkillExecutor {
/// Erstellt einen neuen SkillExecutor.
/// `personality_writer` → konkrete Impl aus skills-Crate
pub fn new(personality_writer: Arc<dyn PersonalityWriter>) -> Self {
Self { personality_writer }
/// `skill_format` → vom LlmProvider bestimmt
pub fn new(personality_writer: Arc<dyn PersonalityWriter>, skill_format: SkillFormat) -> Self {
Self { personality_writer, skill_format }
}
/// Parst XML-Tags aus der Antwort, führt Skills aus, gibt sauberen Text zurück.
/// Wird von BaseAgent nach jedem LLM-Call aufgerufen.
pub fn process(&self, response: &str) -> String {
let (clean_text, calls) = Self::parse(response);
for call in calls {
self.execute(call);
match self.skill_format {
SkillFormat::None => response.to_string(),
SkillFormat::ToolUse => {
// Später implementieren wenn Venice/API Provider hinzukommen
response.to_string()
}
SkillFormat::Xml => {
let (clean_text, calls) = Self::parse(response);
for call in calls {
self.execute(call);
}
clean_text
}
}
clean_text
}
/// Parst alle Skill-Calls aus einem Text.
/// Unterstützt zwei Formate:
/// 1. <skill name="update_personality">...</skill>
/// 2. <update_personality>...</update_personality>
fn parse(response: &str) -> (String, Vec<SkillCall>) {
let mut calls = Vec::new();
let mut clean = response.to_string();
while let Some(start) = clean.find("<skill name=\"") {
let name_start = start + "<skill name=\"".len();
if let Some(name_end) = clean[name_start..].find('"') {
let name = clean[name_start..name_start + name_end].to_string();
// Format 1: <skill name="...">...</skill>
loop {
let start = match clean.find("<skill name=\"") {
Some(s) => s,
None => break,
};
if let Some(end) = clean.find("</skill>") {
let inner_start = clean[start..].find('>').map(|i| start + i + 1).unwrap_or(start);
let inner = &clean[inner_start..end];
let params = Self::extract_params(inner);
calls.push(SkillCall { name, params });
let tag = clean[start..end + "</skill>".len()].to_string();
clean = clean.replace(&tag, "").trim().to_string();
} else {
let end = match clean[start..].find("</skill>") {
Some(e) => start + e,
None => {
clean = clean[..start].trim().to_string();
break;
}
} else {
break;
};
let tag_content = clean[start..end + "</skill>".len()].to_string();
let name_start = start + "<skill name=\"".len();
let name_end = match clean[name_start..].find('"') {
Some(e) => name_start + e,
None => {
clean = clean.replace(&tag_content, "").trim().to_string();
continue;
}
};
let name = clean[name_start..name_end].to_string();
let inner_start = match clean[start..end].find('>') {
Some(i) => start + i + 1,
None => {
clean = clean.replace(&tag_content, "").trim().to_string();
continue;
}
};
let inner = &clean[inner_start..end];
let params = Self::extract_params(inner);
calls.push(SkillCall { name, params });
clean = clean.replace(&tag_content, "").trim().to_string();
}
// Format 2: <update_personality>...</update_personality>
// und <remove_personality>...</remove_personality>
for skill_name in &["update_personality", "remove_personality"] {
loop {
let open_tag = format!("<{}>", skill_name);
let close_tag = format!("</{}>", skill_name);
let start = match clean.find(&open_tag) {
Some(s) => s,
None => break,
};
let end = match clean[start..].find(&close_tag) {
Some(e) => start + e,
None => {
clean = clean[..start].trim().to_string();
break;
}
};
let tag_content = clean[start..end + close_tag.len()].to_string();
let inner = &clean[start + open_tag.len()..end];
let params = Self::extract_params(inner);
calls.push(SkillCall {
name: skill_name.to_string(),
params,
});
clean = clean.replace(&tag_content, "").trim().to_string();
}
}