From fd97ba2d817afced013783c64104231ce333b677 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Nov 2025 14:09:39 +0000 Subject: [PATCH] Implement AI memory system with psychological priority scoring Core changes: - Add AI interpreter module for content interpretation and priority scoring - Extend Memory struct with interpreted_content, priority_score (f32: 0.0-1.0), and user_context - Implement automatic memory pruning based on priority scores - Add capacity management (default: 100 memories max) - Create comprehensive design documentation Technical details: - Changed priority_score from u8 (1-100) to f32 (0.0-1.0) for better AI compatibility - Add create_memory_with_ai() method for AI-enhanced memory creation - Implement get_memories_by_priority() for priority-based sorting - Score evaluation criteria: emotional impact, user relevance, novelty, utility Philosophy: This implements a "psychological priority memory system" where AI interprets and evaluates memories rather than storing raw content. Inspired by how human memory works - interpreting and prioritizing rather than perfect recording. --- README.md | 32 +++++++++- docs/DESIGN.md | 121 +++++++++++++++++++++++++++++++++++ src/ai_interpreter.rs | 143 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- src/memory.rs | 86 ++++++++++++++++++++++++- 5 files changed, 378 insertions(+), 7 deletions(-) create mode 100644 docs/DESIGN.md create mode 100644 src/ai_interpreter.rs diff --git a/README.md b/README.md index ca8bc21..dde6d30 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,18 @@ -# aigpt - Claude Memory MCP Server +# aigpt - AI Memory System with Psychological Priority -ChatGPTのメモリ機能を参考にした、Claude Desktop/Code用のシンプルなメモリストレージシステムです。 +AI記憶装置(心理優先記憶システム)。ChatGPTのメモリ機能を参考にしながら、AIによる解釈と心理判定を加えた新しいメモリストレージシステムです。 + +## コンセプト + +従来の「会話 → 保存 → 検索」ではなく、「会話 → AI解釈 → 保存 → 検索」を実現。 +AIが記憶を解釈し、重要度を0.0-1.0のスコアで評価。優先度の高い記憶を保持し、低い記憶は自動的に削除されます。 ## 機能 +- **AI解釈付き記憶**: 元のコンテンツとAI解釈後のコンテンツを保存 +- **心理判定スコア**: 0.0-1.0のfloat値で重要度を評価 +- **優先順位管理**: スコアに基づく自動ソートとフィルタリング +- **容量制限**: 最大100件(設定可能)、低スコアから自動削除 - **メモリのCRUD操作**: メモリの作成、更新、削除、検索 - **ChatGPT JSONインポート**: ChatGPTの会話履歴からメモリを抽出 - **stdio MCP実装**: Claude Desktop/Codeとの簡潔な連携 @@ -115,7 +124,10 @@ MCPツールを使ってインポートした会話の一覧を表示してく "memories": { "uuid": { "id": "uuid", - "content": "メモリーの内容", + "content": "元のメモリー内容", + "interpreted_content": "AI解釈後のメモリー内容", + "priority_score": 0.75, + "user_context": "ユーザー固有のコンテキスト(オプション)", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } @@ -131,6 +143,20 @@ MCPツールを使ってインポートした会話の一覧を表示してく } ``` +### 心理判定スコアについて + +0.0-1.0のfloat値で重要度を表現: +- **0.0-0.25**: 低優先度(忘れられやすい) +- **0.25-0.5**: 中優先度 +- **0.5-0.75**: 高優先度 +- **0.75-1.0**: 最高優先度(重要な記憶) + +評価基準: +- 感情的インパクト (0.0-0.25) +- ユーザーとの関連性 (0.0-0.25) +- 新規性・独自性 (0.0-0.25) +- 実用性 (0.0-0.25) + ## 開発 ```bash diff --git a/docs/DESIGN.md b/docs/DESIGN.md new file mode 100644 index 0000000..caa511b --- /dev/null +++ b/docs/DESIGN.md @@ -0,0 +1,121 @@ +# AI記憶システム設計書 + +## コンセプト + +AIの記憶装置は、人間の記憶に近い形で動作する。すべてを正確に記憶するのではなく、**解釈**して保存する。 + +## 従来の記憶システムとの違い + +### 従来型 +``` +会話 → 保存 → 検索 +``` + +### 新設計(心理優先記憶装置) +``` +会話 → AI解釈 → 保存 → 検索 + ↓ + 心理判定(1-100) + ↓ + 優先順位付け + ↓ + 容量管理 +``` + +## 設計原理 + +1. **解釈保存**: 記憶する際はAIが解釈を加える + - 元のコンテンツと解釈後のコンテンツの両方を保持 + - 「覚えること自体が創造」という考え方 + +2. **心理判定**: 各記憶に重要度スコア(1-100)を付与 + - AIが自律的に判断 + - ユーザー固有性を考慮 + - 感情的重要度を評価 + +3. **優先順位管理**: スコアに基づく優先順位 + - 高スコア = 重要な記憶 + - 低スコア = 忘れられやすい記憶 + +4. **容量制限**: 人間の記憶のように限界がある + - 総容量制限(デフォルト: 100件) + - 単発保存容量制限 + - 優先度が低いものから自動削除 + +## データ構造 + +```rust +struct Memory { + id: String, // UUID + content: String, // 元のコンテンツ + interpreted_content: String, // AI解釈後のコンテンツ + priority_score: f32, // 心理判定スコア (0.0-1.0) + user_context: Option, // ユーザー固有性 + created_at: DateTime, // 作成日時 + updated_at: DateTime, // 更新日時 +} +``` + +## 実装機能 + +### 1. 心理判定機能 +- AI APIを使用して重要度を0.0-1.0で評価 +- 判定基準: + - 感情的インパクト (0.0-0.25) + - ユーザーとの関連性 (0.0-0.25) + - 新規性・独自性 (0.0-0.25) + - 実用性 (0.0-0.25) + +### 2. 保存機能 +- 保存前にAI解釈を実行 +- 心理判定スコアを自動付与 +- 容量超過時は低スコアから削除 + +### 3. 検索機能 +- 優先順位順にソート +- スコアによるフィルタリング +- セマンティック検索(オプション) + +### 4. 容量管理 +- デフォルト最大: 100件 +- 設定可能な上限 +- 自動プルーニング(低スコア削除) + +## 実装ステップ + +1. Memory構造体の拡張 +2. AI解釈機能の実装(OpenAI API使用) +3. 心理判定機能の実装 +4. 容量管理機能の実装 +5. ソート・フィルタリング機能の強化 +6. MCPツールへの統合 + +## 設定例 + +```json +{ + "max_memories": 100, + "min_priority_score": 0.3, + "auto_prune": true, + "interpretation_enabled": true +} +``` + +## スコアリングシステムの哲学 + +0.0-1.0のfloat値を採用する理由: +- **正規化**: 機械学習やAIにとって扱いやすい標準形式 +- **直感性**: 0が最低、1が最高という明確な基準 +- **精度**: 0.75などの細かい値で微妙な重要度の差を表現可能 +- **拡張性**: 時間軸(0.0-1.0)や確率(0.0-1.0)などとの統合が容易 + +この設計は、「I + o」概念(oの周りを0.0-1.0の時間軸で表す)とも整合性がある。 + +## ゲームのセーブデータとの類似性 + +- **Git = セーブ機能**: バージョン管理 +- **GitHub = クラウドセーブ**: グローバルデータ共有 +- **ATProto = データプロトコル**: 分散型データ保存 +- **AI記憶 = プレイヤー記憶**: 経験の蓄積と解釈 + +ゲームのセーブデータも「プレイヤーの行動を解釈したデータ」として扱うことで、より意味のある永続化が可能になる。 diff --git a/src/ai_interpreter.rs b/src/ai_interpreter.rs new file mode 100644 index 0000000..7f04063 --- /dev/null +++ b/src/ai_interpreter.rs @@ -0,0 +1,143 @@ +use anyhow::{Context, Result}; + +#[cfg(feature = "ai-analysis")] +use openai::{ + chat::{ChatCompletion, ChatCompletionMessage, ChatCompletionMessageRole}, + set_key, +}; + +pub struct AIInterpreter { + #[cfg(feature = "ai-analysis")] + api_key: Option, +} + +impl AIInterpreter { + pub fn new() -> Self { + #[cfg(feature = "ai-analysis")] + { + let api_key = std::env::var("OPENAI_API_KEY").ok(); + if let Some(ref key) = api_key { + set_key(key.clone()); + } + AIInterpreter { api_key } + } + #[cfg(not(feature = "ai-analysis"))] + { + AIInterpreter {} + } + } + + /// AI解釈: 元のコンテンツを解釈して新しい表現を生成 + #[cfg(feature = "ai-analysis")] + pub async fn interpret_content(&self, content: &str) -> Result { + if self.api_key.is_none() { + return Ok(content.to_string()); + } + + let messages = vec![ + ChatCompletionMessage { + role: ChatCompletionMessageRole::System, + content: Some("あなたは記憶を解釈するAIです。与えられたテキストを解釈し、より深い意味や文脈を抽出してください。元のテキストの本質を保ちながら、新しい視点や洞察を加えてください。".to_string()), + name: None, + function_call: None, + }, + ChatCompletionMessage { + role: ChatCompletionMessageRole::User, + content: Some(format!("以下のテキストを解釈してください:\n\n{}", content)), + name: None, + function_call: None, + }, + ]; + + let chat_completion = ChatCompletion::builder("gpt-3.5-turbo", messages.clone()) + .create() + .await + .context("Failed to create chat completion")?; + + let response = chat_completion + .choices + .first() + .and_then(|choice| choice.message.content.clone()) + .unwrap_or_else(|| content.to_string()); + + Ok(response) + } + + #[cfg(not(feature = "ai-analysis"))] + pub async fn interpret_content(&self, content: &str) -> Result { + Ok(content.to_string()) + } + + /// 心理判定: テキストの重要度を0.0-1.0のスコアで評価 + #[cfg(feature = "ai-analysis")] + pub async fn calculate_priority_score(&self, content: &str, user_context: Option<&str>) -> Result { + if self.api_key.is_none() { + return Ok(0.5); // デフォルトスコア + } + + let context_info = user_context + .map(|ctx| format!("\n\nユーザーコンテキスト: {}", ctx)) + .unwrap_or_default(); + + let messages = vec![ + ChatCompletionMessage { + role: ChatCompletionMessageRole::System, + content: Some(format!( + "あなたは記憶の重要度を評価するAIです。以下の基準で0.0-1.0のスコアをつけてください:\n\ + - 感情的インパクト (0.0-0.25)\n\ + - ユーザーとの関連性 (0.0-0.25)\n\ + - 新規性・独自性 (0.0-0.25)\n\ + - 実用性 (0.0-0.25)\n\n\ + スコアのみを小数で返してください。例: 0.75{}", context_info + )), + name: None, + function_call: None, + }, + ChatCompletionMessage { + role: ChatCompletionMessageRole::User, + content: Some(format!("以下のテキストの重要度を評価してください:\n\n{}", content)), + name: None, + function_call: None, + }, + ]; + + let chat_completion = ChatCompletion::builder("gpt-3.5-turbo", messages.clone()) + .create() + .await + .context("Failed to create chat completion")?; + + let response = chat_completion + .choices + .first() + .and_then(|choice| choice.message.content.clone()) + .unwrap_or_else(|| "0.5".to_string()); + + // スコアを抽出(小数を含む数字) + let score = response + .trim() + .parse::() + .unwrap_or(0.5) + .min(1.0) + .max(0.0); + + Ok(score) + } + + #[cfg(not(feature = "ai-analysis"))] + pub async fn calculate_priority_score(&self, _content: &str, _user_context: Option<&str>) -> Result { + Ok(0.5) // デフォルトスコア + } + + /// AI解釈と心理判定を同時に実行 + pub async fn analyze(&self, content: &str, user_context: Option<&str>) -> Result<(String, f32)> { + let interpreted = self.interpret_content(content).await?; + let score = self.calculate_priority_score(content, user_context).await?; + Ok((interpreted, score)) + } +} + +impl Default for AIInterpreter { + fn default() -> Self { + Self::new() + } +} diff --git a/src/lib.rs b/src/lib.rs index da25a77..d3b4c38 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod memory; -pub mod mcp; \ No newline at end of file +pub mod mcp; +pub mod ai_interpreter; \ No newline at end of file diff --git a/src/memory.rs b/src/memory.rs index d879a78..607742f 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -4,11 +4,15 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::PathBuf; use uuid::Uuid; +use crate::ai_interpreter::AIInterpreter; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Memory { pub id: String, pub content: String, + pub interpreted_content: String, // AI解釈後のコンテンツ + pub priority_score: f32, // 心理判定スコア (0.0-1.0) + pub user_context: Option, // ユーザー固有性 pub created_at: DateTime, pub updated_at: DateTime, } @@ -67,6 +71,9 @@ pub struct MemoryManager { memories: HashMap, conversations: HashMap, data_file: PathBuf, + max_memories: usize, // 最大記憶数 + min_priority_score: f32, // 最小優先度スコア (0.0-1.0) + ai_interpreter: AIInterpreter, // AI解釈エンジン } impl MemoryManager { @@ -91,23 +98,68 @@ impl MemoryManager { memories, conversations, data_file, + max_memories: 100, // デフォルト: 100件 + min_priority_score: 0.3, // デフォルト: 0.3以上 + ai_interpreter: AIInterpreter::new(), }) } pub fn create_memory(&mut self, content: &str) -> Result { let id = Uuid::new_v4().to_string(); let now = Utc::now(); - + let memory = Memory { id: id.clone(), content: content.to_string(), + interpreted_content: content.to_string(), // 後でAI解釈を実装 + priority_score: 0.5, // 後で心理判定を実装 + user_context: None, created_at: now, updated_at: now, }; - + self.memories.insert(id.clone(), memory); + + // 容量制限チェック + self.prune_memories_if_needed()?; + self.save_data()?; - + + Ok(id) + } + + /// AI解釈と心理判定を使った記憶作成 + pub async fn create_memory_with_ai( + &mut self, + content: &str, + user_context: Option<&str>, + ) -> Result { + let id = Uuid::new_v4().to_string(); + let now = Utc::now(); + + // AI解釈と心理判定を実行 + let (interpreted_content, priority_score) = self + .ai_interpreter + .analyze(content, user_context) + .await?; + + let memory = Memory { + id: id.clone(), + content: content.to_string(), + interpreted_content, + priority_score, + user_context: user_context.map(|s| s.to_string()), + created_at: now, + updated_at: now, + }; + + self.memories.insert(id.clone(), memory); + + // 容量制限チェック + self.prune_memories_if_needed()?; + + self.save_data()?; + Ok(id) } @@ -131,6 +183,34 @@ impl MemoryManager { } } + // 容量制限: 優先度が低いものから削除 + fn prune_memories_if_needed(&mut self) -> Result<()> { + if self.memories.len() <= self.max_memories { + return Ok(()); + } + + // 優先度でソートして、低いものから削除 + let mut sorted_memories: Vec<_> = self.memories.iter() + .map(|(id, mem)| (id.clone(), mem.priority_score)) + .collect(); + + sorted_memories.sort_by(|a, b| a.1.cmp(&b.1)); + + let to_remove = self.memories.len() - self.max_memories; + for (id, _) in sorted_memories.iter().take(to_remove) { + self.memories.remove(id); + } + + Ok(()) + } + + // 優先度順に記憶を取得 + pub fn get_memories_by_priority(&self) -> Vec<&Memory> { + let mut memories: Vec<_> = self.memories.values().collect(); + memories.sort_by(|a, b| b.priority_score.cmp(&a.priority_score)); + memories + } + pub fn search_memories(&self, query: &str) -> Vec<&Memory> { let query_lower = query.to_lowercase(); let mut results: Vec<_> = self.memories