refactor: 统一项目名称从OpenFang到ZCLAW
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
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
重构所有代码和文档中的项目名称,将OpenFang统一更新为ZCLAW。包括: - 配置文件中的项目名称 - 代码注释和文档引用 - 环境变量和路径 - 类型定义和接口名称 - 测试用例和模拟数据 同时优化部分代码结构,移除未使用的模块,并更新相关依赖项。
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
//! Persistent storage backend using SQLite for production use.
|
||||
//! Provides efficient querying and full-text search capabilities.
|
||||
|
||||
use crate::retrieval::semantic::SemanticScorer;
|
||||
use crate::retrieval::semantic::{EmbeddingClient, SemanticScorer};
|
||||
use crate::types::MemoryEntry;
|
||||
use crate::viking_adapter::{FindOptions, VikingStorage};
|
||||
use async_trait::async_trait;
|
||||
@@ -36,6 +36,8 @@ struct MemoryRow {
|
||||
access_count: i32,
|
||||
created_at: String,
|
||||
last_accessed: String,
|
||||
overview: Option<String>,
|
||||
abstract_summary: Option<String>,
|
||||
}
|
||||
|
||||
impl SqliteStorage {
|
||||
@@ -83,6 +85,26 @@ impl SqliteStorage {
|
||||
Self::new(":memory:").await.expect("Failed to create in-memory database")
|
||||
}
|
||||
|
||||
/// Configure embedding client for semantic search
|
||||
/// Replaces the current scorer with a new one that has embedding support
|
||||
pub async fn configure_embedding(
|
||||
&self,
|
||||
client: Arc<dyn EmbeddingClient>,
|
||||
) -> Result<()> {
|
||||
let new_scorer = SemanticScorer::with_embedding(client);
|
||||
let mut scorer = self.scorer.write().await;
|
||||
*scorer = new_scorer;
|
||||
|
||||
tracing::info!("[SqliteStorage] Embedding client configured, re-indexing with embeddings...");
|
||||
self.warmup_scorer_with_embedding().await
|
||||
}
|
||||
|
||||
/// Check if embedding is available
|
||||
pub async fn is_embedding_available(&self) -> bool {
|
||||
let scorer = self.scorer.read().await;
|
||||
scorer.is_embedding_available()
|
||||
}
|
||||
|
||||
/// Initialize database schema with FTS5
|
||||
async fn initialize_schema(&self) -> Result<()> {
|
||||
// Create main memories table
|
||||
@@ -131,6 +153,16 @@ impl SqliteStorage {
|
||||
.await
|
||||
.map_err(|e| ZclawError::StorageError(format!("Failed to create importance index: {}", e)))?;
|
||||
|
||||
// Migration: add overview column (L1 summary)
|
||||
let _ = sqlx::query("ALTER TABLE memories ADD COLUMN overview TEXT")
|
||||
.execute(&self.pool)
|
||||
.await;
|
||||
|
||||
// Migration: add abstract_summary column (L0 keywords)
|
||||
let _ = sqlx::query("ALTER TABLE memories ADD COLUMN abstract_summary TEXT")
|
||||
.execute(&self.pool)
|
||||
.await;
|
||||
|
||||
// Create metadata table
|
||||
sqlx::query(
|
||||
r#"
|
||||
@@ -151,7 +183,7 @@ impl SqliteStorage {
|
||||
/// Warmup semantic scorer with existing entries
|
||||
async fn warmup_scorer(&self) -> Result<()> {
|
||||
let rows = sqlx::query_as::<_, MemoryRow>(
|
||||
"SELECT uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed FROM memories"
|
||||
"SELECT uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed, overview, abstract_summary FROM memories"
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
@@ -173,6 +205,32 @@ impl SqliteStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Warmup semantic scorer with embedding support for existing entries
|
||||
async fn warmup_scorer_with_embedding(&self) -> Result<()> {
|
||||
let rows = sqlx::query_as::<_, MemoryRow>(
|
||||
"SELECT uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed, overview, abstract_summary FROM memories"
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| ZclawError::StorageError(format!("Failed to load memories for warmup: {}", e)))?;
|
||||
|
||||
let mut scorer = self.scorer.write().await;
|
||||
for row in rows {
|
||||
let entry = self.row_to_entry(&row);
|
||||
scorer.index_entry_with_embedding(&entry).await;
|
||||
}
|
||||
|
||||
let stats = scorer.stats();
|
||||
tracing::info!(
|
||||
"[SqliteStorage] Warmed up scorer with {} entries ({} with embeddings), {} terms",
|
||||
stats.indexed_entries,
|
||||
stats.embedding_entries,
|
||||
stats.unique_terms
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert database row to MemoryEntry
|
||||
fn row_to_entry(&self, row: &MemoryRow) -> MemoryEntry {
|
||||
let memory_type = crate::types::MemoryType::parse(&row.memory_type);
|
||||
@@ -193,6 +251,8 @@ impl SqliteStorage {
|
||||
access_count: row.access_count as u32,
|
||||
created_at,
|
||||
last_accessed,
|
||||
overview: row.overview.clone(),
|
||||
abstract_summary: row.abstract_summary.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +283,8 @@ impl sqlx::FromRow<'_, SqliteRow> for MemoryRow {
|
||||
access_count: row.try_get("access_count")?,
|
||||
created_at: row.try_get("created_at")?,
|
||||
last_accessed: row.try_get("last_accessed")?,
|
||||
overview: row.try_get("overview").ok(),
|
||||
abstract_summary: row.try_get("abstract_summary").ok(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -241,8 +303,8 @@ impl VikingStorage for SqliteStorage {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT OR REPLACE INTO memories
|
||||
(uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
(uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed, overview, abstract_summary)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
)
|
||||
.bind(&entry.uri)
|
||||
@@ -253,6 +315,8 @@ impl VikingStorage for SqliteStorage {
|
||||
.bind(entry.access_count as i32)
|
||||
.bind(&created_at)
|
||||
.bind(&last_accessed)
|
||||
.bind(&entry.overview)
|
||||
.bind(&entry.abstract_summary)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| ZclawError::StorageError(format!("Failed to store memory: {}", e)))?;
|
||||
@@ -276,9 +340,13 @@ impl VikingStorage for SqliteStorage {
|
||||
.execute(&self.pool)
|
||||
.await;
|
||||
|
||||
// Update semantic scorer
|
||||
// Update semantic scorer (use embedding when available)
|
||||
let mut scorer = self.scorer.write().await;
|
||||
scorer.index_entry(entry);
|
||||
if scorer.is_embedding_available() {
|
||||
scorer.index_entry_with_embedding(entry).await;
|
||||
} else {
|
||||
scorer.index_entry(entry);
|
||||
}
|
||||
|
||||
tracing::debug!("[SqliteStorage] Stored memory: {}", entry.uri);
|
||||
Ok(())
|
||||
@@ -286,7 +354,7 @@ impl VikingStorage for SqliteStorage {
|
||||
|
||||
async fn get(&self, uri: &str) -> Result<Option<MemoryEntry>> {
|
||||
let row = sqlx::query_as::<_, MemoryRow>(
|
||||
"SELECT uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed FROM memories WHERE uri = ?"
|
||||
"SELECT uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed, overview, abstract_summary FROM memories WHERE uri = ?"
|
||||
)
|
||||
.bind(uri)
|
||||
.fetch_optional(&self.pool)
|
||||
@@ -309,7 +377,7 @@ impl VikingStorage for SqliteStorage {
|
||||
// Get all matching entries
|
||||
let rows = if let Some(ref scope) = options.scope {
|
||||
sqlx::query_as::<_, MemoryRow>(
|
||||
"SELECT uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed FROM memories WHERE uri LIKE ?"
|
||||
"SELECT uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed, overview, abstract_summary FROM memories WHERE uri LIKE ?"
|
||||
)
|
||||
.bind(format!("{}%", scope))
|
||||
.fetch_all(&self.pool)
|
||||
@@ -317,7 +385,7 @@ impl VikingStorage for SqliteStorage {
|
||||
.map_err(|e| ZclawError::StorageError(format!("Failed to find memories: {}", e)))?
|
||||
} else {
|
||||
sqlx::query_as::<_, MemoryRow>(
|
||||
"SELECT uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed FROM memories"
|
||||
"SELECT uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed, overview, abstract_summary FROM memories"
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
@@ -325,14 +393,49 @@ impl VikingStorage for SqliteStorage {
|
||||
};
|
||||
|
||||
// Convert to entries and compute semantic scores
|
||||
let scorer = self.scorer.read().await;
|
||||
let use_embedding = {
|
||||
let scorer = self.scorer.read().await;
|
||||
scorer.is_embedding_available()
|
||||
};
|
||||
|
||||
let mut scored_entries: Vec<(f32, MemoryEntry)> = Vec::new();
|
||||
|
||||
for row in rows {
|
||||
let entry = self.row_to_entry(&row);
|
||||
|
||||
// Compute semantic score using TF-IDF
|
||||
let semantic_score = scorer.score_similarity(query, &entry);
|
||||
// Compute semantic score: use embedding when available, fallback to TF-IDF
|
||||
let semantic_score = if use_embedding {
|
||||
let scorer = self.scorer.read().await;
|
||||
let tfidf_score = scorer.score_similarity(query, &entry);
|
||||
let entry_embedding = scorer.get_entry_embedding(&entry.uri);
|
||||
drop(scorer);
|
||||
|
||||
match entry_embedding {
|
||||
Some(entry_emb) => {
|
||||
// Try embedding the query for hybrid scoring
|
||||
let embedding_client = {
|
||||
let scorer2 = self.scorer.read().await;
|
||||
scorer2.get_embedding_client()
|
||||
};
|
||||
|
||||
match embedding_client.embed(query).await {
|
||||
Ok(query_emb) => {
|
||||
let emb_score = SemanticScorer::cosine_similarity_embedding(&query_emb, &entry_emb);
|
||||
// Hybrid: 70% embedding + 30% TF-IDF
|
||||
emb_score * 0.7 + tfidf_score * 0.3
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::debug!("[SqliteStorage] Query embedding failed, using TF-IDF only");
|
||||
tfidf_score
|
||||
}
|
||||
}
|
||||
}
|
||||
None => tfidf_score,
|
||||
}
|
||||
} else {
|
||||
let scorer = self.scorer.read().await;
|
||||
scorer.score_similarity(query, &entry)
|
||||
};
|
||||
|
||||
// Apply similarity threshold
|
||||
if let Some(min_similarity) = options.min_similarity {
|
||||
@@ -362,7 +465,7 @@ impl VikingStorage for SqliteStorage {
|
||||
|
||||
async fn find_by_prefix(&self, prefix: &str) -> Result<Vec<MemoryEntry>> {
|
||||
let rows = sqlx::query_as::<_, MemoryRow>(
|
||||
"SELECT uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed FROM memories WHERE uri LIKE ?"
|
||||
"SELECT uri, memory_type, content, keywords, importance, access_count, created_at, last_accessed, overview, abstract_summary FROM memories WHERE uri LIKE ?"
|
||||
)
|
||||
.bind(format!("{}%", prefix))
|
||||
.fetch_all(&self.pool)
|
||||
|
||||
Reference in New Issue
Block a user