feat: Phase 3 Performance — KB-Cache, Bridge-Warmstart, maxTurns 200
All checks were successful
Build AppImage / build (push) Has been skipped
All checks were successful
Build AppImage / build (push) Has been skipped
- KB-Cache im RAM: Suchergebnisse 60s gecacht, kein MySQL-Roundtrip pro Nachricht - Bridge wird beim App-Start sofort gestartet (kein Cold-Start bei erster Nachricht) - Bridge-Start-Wait von 500ms auf 200ms reduziert - maxTurns von 25 auf 200 erhoeht (verhindert "maximum turns reached" bei komplexen Tasks) - invalidate_kb_cache Command fuer manuelles Cache-Leeren Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5b857ebba4
commit
3d84feab6f
4 changed files with 111 additions and 7 deletions
|
|
@ -332,7 +332,7 @@ async function sendMessage(message, requestId, model = null, contextOverride = n
|
|||
model: useModel,
|
||||
promptLength: message.length,
|
||||
contextLength: useContext?.length || 0,
|
||||
maxTurns: 25,
|
||||
maxTurns: 200,
|
||||
resumeSessionId: resumeSessionId || null,
|
||||
});
|
||||
|
||||
|
|
@ -373,7 +373,7 @@ async function sendMessage(message, requestId, model = null, contextOverride = n
|
|||
// Query-Optionen zusammenstellen
|
||||
const queryOptions = {
|
||||
model: useModel,
|
||||
maxTurns: 25,
|
||||
maxTurns: 200,
|
||||
abortController: activeAbort,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -390,8 +390,8 @@ pub async fn send_message(app: AppHandle, message: String) -> Result<String, Str
|
|||
|
||||
if needs_start {
|
||||
start_bridge(&app)?;
|
||||
// Kurz warten bis Bridge bereit
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
// Kurz warten bis Bridge bereit — parallel den Context laden
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
}
|
||||
|
||||
// Context aus DB laden (Schicht 1: Sticky Context)
|
||||
|
|
@ -534,7 +534,7 @@ pub async fn set_model(app: AppHandle, model: String) -> Result<String, String>
|
|||
|
||||
if needs_start {
|
||||
start_bridge(&app)?;
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
}
|
||||
|
||||
// Modell an Bridge senden
|
||||
|
|
@ -603,7 +603,7 @@ pub async fn set_agent_mode(app: AppHandle, mode: String) -> Result<String, Stri
|
|||
|
||||
if needs_start {
|
||||
start_bridge(&app)?;
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
}
|
||||
|
||||
// Modus an Bridge senden
|
||||
|
|
|
|||
|
|
@ -6,8 +6,68 @@ use mysql_async::{Pool, prelude::*};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use chrono::NaiveDateTime;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::time::{Duration, Instant};
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
// ============ KB-Cache (RAM) ============
|
||||
// Cached KB-Suchergebnisse im RAM. Spart MySQL-Roundtrip pro Nachricht.
|
||||
// Invalidierung: automatisch nach 60 Sekunden (TTL).
|
||||
|
||||
struct CacheEntry {
|
||||
result: String,
|
||||
created_at: Instant,
|
||||
}
|
||||
|
||||
struct KbCache {
|
||||
entries: HashMap<String, CacheEntry>,
|
||||
ttl: Duration,
|
||||
}
|
||||
|
||||
impl KbCache {
|
||||
fn new(ttl_secs: u64) -> Self {
|
||||
Self {
|
||||
entries: HashMap::new(),
|
||||
ttl: Duration::from_secs(ttl_secs),
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, key: &str) -> Option<&str> {
|
||||
if let Some(entry) = self.entries.get(key) {
|
||||
if entry.created_at.elapsed() < self.ttl {
|
||||
return Some(&entry.result);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn insert(&mut self, key: String, result: String) {
|
||||
// Max 100 Eintraege — aelteste raus wenn voll
|
||||
if self.entries.len() >= 100 {
|
||||
self.evict_oldest();
|
||||
}
|
||||
self.entries.insert(key, CacheEntry { result, created_at: Instant::now() });
|
||||
}
|
||||
|
||||
fn evict_oldest(&mut self) {
|
||||
if let Some(oldest_key) = self.entries.iter()
|
||||
.min_by_key(|(_, v)| v.created_at)
|
||||
.map(|(k, _)| k.clone())
|
||||
{
|
||||
self.entries.remove(&oldest_key);
|
||||
}
|
||||
}
|
||||
|
||||
fn invalidate_expired(&mut self) {
|
||||
self.entries.retain(|_, v| v.created_at.elapsed() < self.ttl);
|
||||
}
|
||||
}
|
||||
|
||||
// Globaler Cache — 60 Sekunden TTL
|
||||
static KB_CACHE: std::sync::LazyLock<Mutex<KbCache>> =
|
||||
std::sync::LazyLock::new(|| Mutex::new(KbCache::new(60)));
|
||||
|
||||
/// Verbindungskonfiguration — aus ENV-Variablen, Fallback auf Defaults
|
||||
const MYSQL_PORT: u16 = 3306;
|
||||
|
||||
|
|
@ -168,7 +228,20 @@ pub async fn search_knowledge_internal(query: &str, limit: i32) -> Result<String
|
|||
|
||||
/// Sucht in der KB mit einem vorbereiteten Query-String
|
||||
/// Zentrale Suchfunktion die von search_knowledge_internal und proactive_session_hints genutzt wird
|
||||
/// Phase 3: Mit RAM-Cache — gleiche Query innerhalb 60s wird instant aus dem Cache bedient
|
||||
pub async fn search_knowledge_by_query(search_query: &str, limit: i32) -> Result<String, String> {
|
||||
// Cache-Key: query + limit
|
||||
let cache_key = format!("{}:{}", search_query, limit);
|
||||
|
||||
// Cache-Hit prüfen
|
||||
{
|
||||
let cache = KB_CACHE.lock().unwrap();
|
||||
if let Some(cached) = cache.get(&cache_key) {
|
||||
println!("⚡ KB-Cache HIT für '{}'", &search_query[..search_query.len().min(40)]);
|
||||
return Ok(cached.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let pool = create_pool();
|
||||
|
||||
// Verbindung mit Timeout — DB nicht erreichbar soll nicht blockieren
|
||||
|
|
@ -209,6 +282,9 @@ pub async fn search_knowledge_by_query(search_query: &str, limit: i32) -> Result
|
|||
|
||||
if results.is_empty() {
|
||||
println!("🔍 KB-Hints für '{}': keine Treffer", &search_query[..search_query.len().min(40)]);
|
||||
// Leere Ergebnisse auch cachen — verhindert wiederholte DB-Abfragen
|
||||
let mut cache = KB_CACHE.lock().unwrap();
|
||||
cache.insert(cache_key, String::new());
|
||||
return Ok(String::new());
|
||||
}
|
||||
|
||||
|
|
@ -230,9 +306,15 @@ pub async fn search_knowledge_by_query(search_query: &str, limit: i32) -> Result
|
|||
hints.push("</knowledge-hints>".to_string());
|
||||
|
||||
let block = hints.join("\n");
|
||||
println!("🔍 KB-Hints für '{}': {} Treffer, {} Bytes",
|
||||
println!("🔍 KB-Hints für '{}': {} Treffer, {} Bytes (cached)",
|
||||
&search_query[..search_query.len().min(40)], results.len(), block.len());
|
||||
|
||||
// In Cache speichern
|
||||
{
|
||||
let mut cache = KB_CACHE.lock().unwrap();
|
||||
cache.insert(cache_key, block.clone());
|
||||
}
|
||||
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
|
|
@ -718,3 +800,14 @@ pub async fn auto_save_error_pattern(
|
|||
) -> Result<i64, String> {
|
||||
save_error_pattern_to_kb(&error_hash, &error_message, &tool, occurrence_count).await
|
||||
}
|
||||
|
||||
/// Phase 3: KB-Cache invalidieren (alle Eintraege loeschen)
|
||||
/// Aufrufen wenn neue KB-Eintraege gespeichert wurden
|
||||
#[tauri::command]
|
||||
pub async fn invalidate_kb_cache() -> Result<String, String> {
|
||||
let mut cache = KB_CACHE.lock().unwrap();
|
||||
let count = cache.entries.len();
|
||||
cache.entries.clear();
|
||||
println!("🗑️ KB-Cache invalidiert ({} Eintraege geloescht)", count);
|
||||
Ok(format!("{} Cache-Eintraege geloescht", count))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ pub fn run() {
|
|||
knowledge::extract_message_keywords,
|
||||
knowledge::get_session_hints,
|
||||
knowledge::auto_save_error_pattern,
|
||||
// Phase 3: KB-Cache
|
||||
knowledge::invalidate_kb_cache,
|
||||
// Context-Management
|
||||
context::get_sticky_context,
|
||||
context::set_sticky_context,
|
||||
|
|
@ -240,6 +242,15 @@ pub fn run() {
|
|||
// Lock-Datei erstellen (Instanz-Schutz + Update-Safety)
|
||||
update::create_lock_file();
|
||||
|
||||
// Phase 3: Bridge sofort beim App-Start starten (kein Cold-Start bei erster Nachricht)
|
||||
let bridge_handle = app.handle().clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
match claude::start_bridge(&bridge_handle) {
|
||||
Ok(()) => println!("🔌 Bridge beim Start initialisiert (warm)"),
|
||||
Err(e) => println!("⚠️ Bridge-Start beim App-Start fehlgeschlagen: {} (wird bei erster Nachricht erneut versucht)", e),
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.build(tauri::generate_context!())
|
||||
|
|
|
|||
Loading…
Reference in a new issue