Thinking Mode Filter, SkillFormat Strategy, Persönlichkeitsanpassung funktioniert
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,4 @@ mod types;
|
||||
mod traits;
|
||||
|
||||
pub use types::{Message, LlmRequest, LlmResponse};
|
||||
pub use traits::LlmProvider;
|
||||
pub use traits::{LlmProvider, SkillFormat};
|
||||
@@ -6,14 +6,30 @@
|
||||
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.
|
||||
/// Format für Skill-Calls das dieser Provider unterstützt.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum SkillFormat {
|
||||
/// XML-Tags — funktioniert mit lokalen Modellen
|
||||
/// <skill name="update_personality">...</skill>
|
||||
Xml,
|
||||
/// Native Tool Use — Claude, GPT-4, Mistral API
|
||||
/// Strukturierter JSON-basierter Funktionsaufruf
|
||||
ToolUse,
|
||||
/// Skills deaktiviert — Modell folgt keinem Format zuverlässig
|
||||
None,
|
||||
}
|
||||
|
||||
#[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;
|
||||
|
||||
/// Gibt das Skill-Format zurück das dieser Provider unterstützt.
|
||||
/// Standard: Xml — für lokale Modelle.
|
||||
fn skill_format(&self) -> SkillFormat {
|
||||
SkillFormat::Xml
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,6 @@ impl PromptBuilder {
|
||||
parts.push(personality);
|
||||
}
|
||||
|
||||
Ok(parts.join("\n\n---\n\n"))
|
||||
Ok(parts.join("\n\n"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user