Merge pull request #3 from syui/claude/ai-memory-system-011CUps6H1mBNe6zxKdkcyUj
Add Layer 4 caching to reduce AI load
This commit is contained in:
@@ -362,10 +362,16 @@ if user.extraversion < 0.5 {
|
||||
|
||||
### Design Philosophy
|
||||
|
||||
**推測のみ、保存なし**:
|
||||
**推測ベース + 短期キャッシング**:
|
||||
- 毎回Layer 1-3.5から計算
|
||||
- キャッシュなし(シンプルさ優先)
|
||||
- 後でキャッシング追加可能
|
||||
- 5分間の短期キャッシュで負荷軽減
|
||||
- メモリ更新時にキャッシュ無効化
|
||||
|
||||
**キャッシング戦略**:
|
||||
- SQLiteテーブル(`relationship_cache`)に保存
|
||||
- 個別エンティティ: `get_relationship(entity_id)`
|
||||
- 全体リスト: `list_relationships()`
|
||||
- メモリ作成/更新/削除時に自動クリア
|
||||
|
||||
**独立性**:
|
||||
- Layer 1-3.5に依存
|
||||
|
||||
@@ -9,5 +9,5 @@ pub use analysis::UserAnalysis;
|
||||
pub use error::{MemoryError, Result};
|
||||
pub use memory::Memory;
|
||||
pub use profile::{UserProfile, TraitScore};
|
||||
pub use relationship::{RelationshipInference, infer_all_relationships};
|
||||
pub use relationship::{RelationshipInference, infer_all_relationships, get_relationship};
|
||||
pub use store::MemoryStore;
|
||||
|
||||
@@ -184,6 +184,11 @@ impl RelationshipInference {
|
||||
pub fn infer_all_relationships(
|
||||
store: &MemoryStore,
|
||||
) -> Result<Vec<RelationshipInference>> {
|
||||
// Check cache first
|
||||
if let Some(cached) = store.get_cached_all_relationships()? {
|
||||
return Ok(cached);
|
||||
}
|
||||
|
||||
// Get all memories
|
||||
let memories = store.list()?;
|
||||
|
||||
@@ -219,9 +224,41 @@ pub fn infer_all_relationships(
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
|
||||
// Cache the result
|
||||
store.save_all_relationships_cache(&relationships)?;
|
||||
|
||||
Ok(relationships)
|
||||
}
|
||||
|
||||
/// Get relationship inference for a specific entity (with caching)
|
||||
pub fn get_relationship(
|
||||
store: &MemoryStore,
|
||||
entity_id: &str,
|
||||
) -> Result<RelationshipInference> {
|
||||
// Check cache first
|
||||
if let Some(cached) = store.get_cached_relationship(entity_id)? {
|
||||
return Ok(cached);
|
||||
}
|
||||
|
||||
// Get all memories
|
||||
let memories = store.list()?;
|
||||
|
||||
// Get user profile
|
||||
let user_profile = store.get_profile()?;
|
||||
|
||||
// Infer relationship
|
||||
let relationship = RelationshipInference::infer(
|
||||
entity_id.to_string(),
|
||||
&memories,
|
||||
&user_profile,
|
||||
);
|
||||
|
||||
// Cache it
|
||||
store.save_relationship_cache(entity_id, &relationship)?;
|
||||
|
||||
Ok(relationship)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -102,6 +102,17 @@ impl MemoryStore {
|
||||
[],
|
||||
)?;
|
||||
|
||||
// Create relationship_cache table (Layer 4 - relationship inference cache)
|
||||
// entity_id = "" for all_relationships cache
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS relationship_cache (
|
||||
entity_id TEXT PRIMARY KEY,
|
||||
data TEXT NOT NULL,
|
||||
cached_at TEXT NOT NULL
|
||||
)",
|
||||
[],
|
||||
)?;
|
||||
|
||||
Ok(Self { conn })
|
||||
}
|
||||
|
||||
@@ -137,6 +148,10 @@ impl MemoryStore {
|
||||
memory.updated_at.to_rfc3339(),
|
||||
],
|
||||
)?;
|
||||
|
||||
// Clear relationship cache since memory data changed
|
||||
self.clear_relationship_cache()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -204,6 +219,9 @@ impl MemoryStore {
|
||||
return Err(MemoryError::NotFound(memory.id.clone()));
|
||||
}
|
||||
|
||||
// Clear relationship cache since memory data changed
|
||||
self.clear_relationship_cache()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -217,6 +235,9 @@ impl MemoryStore {
|
||||
return Err(MemoryError::NotFound(id.to_string()));
|
||||
}
|
||||
|
||||
// Clear relationship cache since memory data changed
|
||||
self.clear_relationship_cache()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -464,6 +485,123 @@ impl MemoryStore {
|
||||
|
||||
Ok(profile)
|
||||
}
|
||||
|
||||
// ========== Layer 4: Relationship Cache Methods ==========
|
||||
|
||||
/// Cache duration in minutes
|
||||
const RELATIONSHIP_CACHE_DURATION_MINUTES: i64 = 5;
|
||||
|
||||
/// Save relationship inference to cache
|
||||
pub fn save_relationship_cache(
|
||||
&self,
|
||||
entity_id: &str,
|
||||
relationship: &super::relationship::RelationshipInference,
|
||||
) -> Result<()> {
|
||||
let data = serde_json::to_string(relationship)?;
|
||||
let cached_at = Utc::now().to_rfc3339();
|
||||
|
||||
self.conn.execute(
|
||||
"INSERT OR REPLACE INTO relationship_cache (entity_id, data, cached_at) VALUES (?1, ?2, ?3)",
|
||||
params![entity_id, data, cached_at],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get cached relationship inference
|
||||
pub fn get_cached_relationship(
|
||||
&self,
|
||||
entity_id: &str,
|
||||
) -> Result<Option<super::relationship::RelationshipInference>> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT data, cached_at FROM relationship_cache WHERE entity_id = ?1")?;
|
||||
|
||||
let result = stmt.query_row([entity_id], |row| {
|
||||
let data: String = row.get(0)?;
|
||||
let cached_at: String = row.get(1)?;
|
||||
Ok((data, cached_at))
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok((data, cached_at_str)) => {
|
||||
// Check if cache is still valid (within 5 minutes)
|
||||
let cached_at = DateTime::parse_from_rfc3339(&cached_at_str)
|
||||
.map_err(|e| MemoryError::Parse(e.to_string()))?
|
||||
.with_timezone(&Utc);
|
||||
|
||||
let age_minutes = (Utc::now() - cached_at).num_minutes();
|
||||
|
||||
if age_minutes < Self::RELATIONSHIP_CACHE_DURATION_MINUTES {
|
||||
let relationship: super::relationship::RelationshipInference =
|
||||
serde_json::from_str(&data)?;
|
||||
Ok(Some(relationship))
|
||||
} else {
|
||||
// Cache expired
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Save all relationships list to cache (use empty string as entity_id)
|
||||
pub fn save_all_relationships_cache(
|
||||
&self,
|
||||
relationships: &[super::relationship::RelationshipInference],
|
||||
) -> Result<()> {
|
||||
let data = serde_json::to_string(relationships)?;
|
||||
let cached_at = Utc::now().to_rfc3339();
|
||||
|
||||
self.conn.execute(
|
||||
"INSERT OR REPLACE INTO relationship_cache (entity_id, data, cached_at) VALUES ('', ?1, ?2)",
|
||||
params![data, cached_at],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get cached all relationships list
|
||||
pub fn get_cached_all_relationships(
|
||||
&self,
|
||||
) -> Result<Option<Vec<super::relationship::RelationshipInference>>> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT data, cached_at FROM relationship_cache WHERE entity_id = ''")?;
|
||||
|
||||
let result = stmt.query_row([], |row| {
|
||||
let data: String = row.get(0)?;
|
||||
let cached_at: String = row.get(1)?;
|
||||
Ok((data, cached_at))
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok((data, cached_at_str)) => {
|
||||
let cached_at = DateTime::parse_from_rfc3339(&cached_at_str)
|
||||
.map_err(|e| MemoryError::Parse(e.to_string()))?
|
||||
.with_timezone(&Utc);
|
||||
|
||||
let age_minutes = (Utc::now() - cached_at).num_minutes();
|
||||
|
||||
if age_minutes < Self::RELATIONSHIP_CACHE_DURATION_MINUTES {
|
||||
let relationships: Vec<super::relationship::RelationshipInference> =
|
||||
serde_json::from_str(&data)?;
|
||||
Ok(Some(relationships))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all relationship caches (call when memories are modified)
|
||||
pub fn clear_relationship_cache(&self) -> Result<()> {
|
||||
self.conn.execute("DELETE FROM relationship_cache", [])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::Result;
|
||||
use serde_json::{json, Value};
|
||||
use std::io::{self, BufRead, Write};
|
||||
|
||||
use crate::core::{Memory, MemoryStore, UserAnalysis, RelationshipInference, infer_all_relationships};
|
||||
use crate::core::{Memory, MemoryStore, UserAnalysis, RelationshipInference, infer_all_relationships, get_relationship};
|
||||
|
||||
pub struct BaseMCPServer {
|
||||
store: MemoryStore,
|
||||
@@ -581,43 +581,26 @@ impl BaseMCPServer {
|
||||
});
|
||||
}
|
||||
|
||||
// Get memories and user profile
|
||||
let memories = match self.store.list() {
|
||||
Ok(m) => m,
|
||||
Err(e) => return json!({
|
||||
"success": false,
|
||||
"error": format!("Failed to get memories: {}", e)
|
||||
// Get relationship (with caching)
|
||||
match get_relationship(&self.store, entity_id) {
|
||||
Ok(relationship) => json!({
|
||||
"success": true,
|
||||
"relationship": {
|
||||
"entity_id": relationship.entity_id,
|
||||
"interaction_count": relationship.interaction_count,
|
||||
"avg_priority": relationship.avg_priority,
|
||||
"days_since_last": relationship.days_since_last,
|
||||
"bond_strength": relationship.bond_strength,
|
||||
"relationship_type": relationship.relationship_type,
|
||||
"confidence": relationship.confidence,
|
||||
"inferred_at": relationship.inferred_at
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
let user_profile = match self.store.get_profile() {
|
||||
Ok(p) => p,
|
||||
Err(e) => return json!({
|
||||
Err(e) => json!({
|
||||
"success": false,
|
||||
"error": format!("Failed to get profile: {}", e)
|
||||
"error": format!("Failed to get relationship: {}", e)
|
||||
}),
|
||||
};
|
||||
|
||||
// Infer relationship
|
||||
let relationship = RelationshipInference::infer(
|
||||
entity_id.to_string(),
|
||||
&memories,
|
||||
&user_profile,
|
||||
);
|
||||
|
||||
json!({
|
||||
"success": true,
|
||||
"relationship": {
|
||||
"entity_id": relationship.entity_id,
|
||||
"interaction_count": relationship.interaction_count,
|
||||
"avg_priority": relationship.avg_priority,
|
||||
"days_since_last": relationship.days_since_last,
|
||||
"bond_strength": relationship.bond_strength,
|
||||
"relationship_type": relationship.relationship_type,
|
||||
"confidence": relationship.confidence,
|
||||
"inferred_at": relationship.inferred_at
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn tool_list_relationships(&self, arguments: &Value) -> Value {
|
||||
|
||||
Reference in New Issue
Block a user