Files
zclaw_openfang/desktop/src-tauri/src/summarizer_adapter.rs
iven e1af3cca03
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
fix(routing): 消除模型路由链路硬编码不匹配模型名
summarizer_adapter.rs 和 saas-relay-client.ts 中的 fallback 模型名
(glm-4-flash / glm-4-flash-250414) 在 SaaS relay 中不存在,导致请求被拒绝。
改为未配置时明确报错(fail fast),不再静默使用错误模型。
2026-04-11 23:08:06 +08:00

136 lines
4.3 KiB
Rust

//! Summarizer Adapter - Bridges zclaw_growth::SummaryLlmDriver with Tauri LLM Client
//!
//! Implements the SummaryLlmDriver trait using the local LlmClient,
//! enabling L0/L1 summary generation via the user's configured LLM.
use zclaw_growth::{MemoryEntry, SummaryLlmDriver, summarizer::{overview_prompt, abstract_prompt}};
/// Tauri-side implementation of SummaryLlmDriver using llm::LlmClient
pub struct TauriSummaryDriver {
endpoint: String,
api_key: String,
model: Option<String>,
}
impl TauriSummaryDriver {
/// Create a new Tauri summary driver
pub fn new(endpoint: String, api_key: String, model: Option<String>) -> Self {
Self {
endpoint,
api_key,
model,
}
}
/// Check if the driver is configured (has endpoint and api_key)
pub fn is_configured(&self) -> bool {
!self.endpoint.is_empty() && !self.api_key.is_empty()
}
/// Call the LLM API with a simple prompt
async fn call_llm(&self, prompt: String) -> Result<String, String> {
let client = reqwest::Client::new();
let model = self.model.clone().ok_or_else(|| {
"Summary driver model not configured — kernel_init must be called first".to_string()
})?;
let request = serde_json::json!({
"model": model,
"messages": [
{ "role": "user", "content": prompt }
],
"temperature": 0.3,
"max_tokens": 200,
});
let response = client
.post(format!("{}/chat/completions", self.endpoint))
.header("Authorization", format!("Bearer {}", self.api_key))
.header("Content-Type", "application/json")
.json(&request)
.send()
.await
.map_err(|e| format!("Summary LLM request failed: {}", e))?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
return Err(format!("Summary LLM error {}: {}", status, body));
}
let json: serde_json::Value = response
.json()
.await
.map_err(|e| format!("Failed to parse summary response: {}", e))?;
json.get("choices")
.and_then(|c| c.get(0))
.and_then(|c| c.get("message"))
.and_then(|m| m.get("content"))
.and_then(|c| c.as_str())
.map(|s| s.to_string())
.ok_or_else(|| "Invalid summary LLM response format".to_string())
}
}
#[async_trait::async_trait]
impl SummaryLlmDriver for TauriSummaryDriver {
async fn generate_overview(&self, entry: &MemoryEntry) -> Result<String, String> {
let prompt = overview_prompt(entry);
self.call_llm(prompt).await
}
async fn generate_abstract(&self, entry: &MemoryEntry) -> Result<String, String> {
let prompt = abstract_prompt(entry);
self.call_llm(prompt).await
}
}
/// Global summary driver instance (lazy-initialized)
static SUMMARY_DRIVER: tokio::sync::OnceCell<std::sync::Arc<TauriSummaryDriver>> =
tokio::sync::OnceCell::const_new();
/// Configure the global summary driver
pub fn configure_summary_driver(driver: TauriSummaryDriver) {
let _ = SUMMARY_DRIVER.set(std::sync::Arc::new(driver));
tracing::info!("[SummarizerAdapter] Summary driver configured");
}
/// Check if summary driver is available
pub fn is_summary_driver_configured() -> bool {
SUMMARY_DRIVER
.get()
.map(|d| d.is_configured())
.unwrap_or(false)
}
/// Get the global summary driver
pub fn get_summary_driver() -> Option<std::sync::Arc<TauriSummaryDriver>> {
SUMMARY_DRIVER.get().cloned()
}
#[cfg(test)]
mod tests {
use super::*;
use zclaw_growth::MemoryType;
#[test]
fn test_summary_driver_not_configured_by_default() {
assert!(!is_summary_driver_configured());
}
#[test]
fn test_summary_driver_configure_and_check() {
let driver = TauriSummaryDriver::new(
"https://example.com/v1".to_string(),
"test-key".to_string(),
None,
);
assert!(driver.is_configured());
let empty_driver = TauriSummaryDriver::new(String::new(), String::new(), None);
assert!(!empty_driver.is_configured());
}
}