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.
This commit is contained in:
32
README.md
32
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操作**: メモリの作成、更新、削除、検索
|
- **メモリのCRUD操作**: メモリの作成、更新、削除、検索
|
||||||
- **ChatGPT JSONインポート**: ChatGPTの会話履歴からメモリを抽出
|
- **ChatGPT JSONインポート**: ChatGPTの会話履歴からメモリを抽出
|
||||||
- **stdio MCP実装**: Claude Desktop/Codeとの簡潔な連携
|
- **stdio MCP実装**: Claude Desktop/Codeとの簡潔な連携
|
||||||
@@ -115,7 +124,10 @@ MCPツールを使ってインポートした会話の一覧を表示してく
|
|||||||
"memories": {
|
"memories": {
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"id": "uuid",
|
"id": "uuid",
|
||||||
"content": "メモリーの内容",
|
"content": "元のメモリー内容",
|
||||||
|
"interpreted_content": "AI解釈後のメモリー内容",
|
||||||
|
"priority_score": 0.75,
|
||||||
|
"user_context": "ユーザー固有のコンテキスト(オプション)",
|
||||||
"created_at": "2024-01-01T00:00:00Z",
|
"created_at": "2024-01-01T00:00:00Z",
|
||||||
"updated_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
|
```bash
|
||||||
|
|||||||
121
docs/DESIGN.md
Normal file
121
docs/DESIGN.md
Normal file
@@ -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<String>, // ユーザー固有性
|
||||||
|
created_at: DateTime<Utc>, // 作成日時
|
||||||
|
updated_at: DateTime<Utc>, // 更新日時
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 実装機能
|
||||||
|
|
||||||
|
### 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記憶 = プレイヤー記憶**: 経験の蓄積と解釈
|
||||||
|
|
||||||
|
ゲームのセーブデータも「プレイヤーの行動を解釈したデータ」として扱うことで、より意味のある永続化が可能になる。
|
||||||
143
src/ai_interpreter.rs
Normal file
143
src/ai_interpreter.rs
Normal file
@@ -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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String> {
|
||||||
|
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<String> {
|
||||||
|
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<f32> {
|
||||||
|
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::<f32>()
|
||||||
|
.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<f32> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
pub mod memory;
|
pub mod memory;
|
||||||
pub mod mcp;
|
pub mod mcp;
|
||||||
|
pub mod ai_interpreter;
|
||||||
@@ -4,11 +4,15 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use crate::ai_interpreter::AIInterpreter;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Memory {
|
pub struct Memory {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
pub interpreted_content: String, // AI解釈後のコンテンツ
|
||||||
|
pub priority_score: f32, // 心理判定スコア (0.0-1.0)
|
||||||
|
pub user_context: Option<String>, // ユーザー固有性
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
@@ -67,6 +71,9 @@ pub struct MemoryManager {
|
|||||||
memories: HashMap<String, Memory>,
|
memories: HashMap<String, Memory>,
|
||||||
conversations: HashMap<String, Conversation>,
|
conversations: HashMap<String, Conversation>,
|
||||||
data_file: PathBuf,
|
data_file: PathBuf,
|
||||||
|
max_memories: usize, // 最大記憶数
|
||||||
|
min_priority_score: f32, // 最小優先度スコア (0.0-1.0)
|
||||||
|
ai_interpreter: AIInterpreter, // AI解釈エンジン
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemoryManager {
|
impl MemoryManager {
|
||||||
@@ -91,23 +98,68 @@ impl MemoryManager {
|
|||||||
memories,
|
memories,
|
||||||
conversations,
|
conversations,
|
||||||
data_file,
|
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<String> {
|
pub fn create_memory(&mut self, content: &str) -> Result<String> {
|
||||||
let id = Uuid::new_v4().to_string();
|
let id = Uuid::new_v4().to_string();
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
|
|
||||||
let memory = Memory {
|
let memory = Memory {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
content: content.to_string(),
|
content: content.to_string(),
|
||||||
|
interpreted_content: content.to_string(), // 後でAI解釈を実装
|
||||||
|
priority_score: 0.5, // 後で心理判定を実装
|
||||||
|
user_context: None,
|
||||||
created_at: now,
|
created_at: now,
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.memories.insert(id.clone(), memory);
|
self.memories.insert(id.clone(), memory);
|
||||||
|
|
||||||
|
// 容量制限チェック
|
||||||
|
self.prune_memories_if_needed()?;
|
||||||
|
|
||||||
self.save_data()?;
|
self.save_data()?;
|
||||||
|
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// AI解釈と心理判定を使った記憶作成
|
||||||
|
pub async fn create_memory_with_ai(
|
||||||
|
&mut self,
|
||||||
|
content: &str,
|
||||||
|
user_context: Option<&str>,
|
||||||
|
) -> Result<String> {
|
||||||
|
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)
|
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> {
|
pub fn search_memories(&self, query: &str) -> Vec<&Memory> {
|
||||||
let query_lower = query.to_lowercase();
|
let query_lower = query.to_lowercase();
|
||||||
let mut results: Vec<_> = self.memories
|
let mut results: Vec<_> = self.memories
|
||||||
|
|||||||
Reference in New Issue
Block a user