710 lines
26 KiB
Python
710 lines
26 KiB
Python
"""
|
||
Hermes记忆核心 - 方案二核心模块
|
||
直接从方案文档提取,可独立运行测试
|
||
"""
|
||
|
||
import sqlite3
|
||
import json
|
||
import os
|
||
import re
|
||
from datetime import datetime, timedelta
|
||
from typing import List, Dict, Any, Optional
|
||
import hashlib
|
||
|
||
|
||
class HermesMemoryCore:
|
||
"""
|
||
Hermes记忆系统核心
|
||
|
||
核心特性:
|
||
1. 双重索引:FTS5全文搜索 + 实体关系索引
|
||
2. 自动分级:重要信息自动提升到长期记忆
|
||
3. 衰减机制:低频访问的记忆逐渐弱化
|
||
4. 主动触发:基于上下文自动唤醒相关记忆
|
||
"""
|
||
|
||
def __init__(self, base_path: str = "~/.workbuddy/hermes_memory"):
|
||
self.base_path = os.path.expanduser(base_path)
|
||
self._ensure_directories()
|
||
|
||
# 数据库连接
|
||
self.db_path = os.path.join(self.base_path, "index.db")
|
||
self.conn = sqlite3.connect(self.db_path)
|
||
self._init_db()
|
||
|
||
# 内存缓存(最近访问的记忆)
|
||
self.cache = {}
|
||
self.cache_max_size = 1000
|
||
|
||
# 记忆规则
|
||
self.memory_rules = self._load_memory_rules()
|
||
|
||
def _ensure_directories(self):
|
||
"""确保目录结构存在"""
|
||
dirs = [
|
||
"", "memory/conversations", "memory/knowledge",
|
||
"memory/profile", "memory/reminders",
|
||
"skills/built-in", "skills/custom", "config"
|
||
]
|
||
for d in dirs:
|
||
path = os.path.join(self.base_path, d)
|
||
os.makedirs(path, exist_ok=True)
|
||
|
||
def _init_db(self):
|
||
"""初始化数据库表结构"""
|
||
cursor = self.conn.cursor()
|
||
|
||
# FTS5全文搜索表
|
||
cursor.execute("""
|
||
CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
|
||
content,
|
||
source_type,
|
||
source_id,
|
||
session_id,
|
||
timestamp,
|
||
importance,
|
||
access_count,
|
||
last_accessed,
|
||
tags,
|
||
entities,
|
||
content='memory_content',
|
||
tokenize='porter unicode61'
|
||
)
|
||
""")
|
||
|
||
# 内容表(FTS5的外部内容表)
|
||
cursor.execute("""
|
||
CREATE TABLE IF NOT EXISTS memory_content(
|
||
rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
content TEXT,
|
||
source_type TEXT,
|
||
source_id TEXT,
|
||
session_id TEXT,
|
||
timestamp TEXT,
|
||
importance INTEGER DEFAULT 5,
|
||
access_count INTEGER DEFAULT 0,
|
||
last_accessed TEXT,
|
||
tags TEXT,
|
||
entities TEXT
|
||
)
|
||
""")
|
||
|
||
# 实体关系表
|
||
cursor.execute("""
|
||
CREATE TABLE IF NOT EXISTS entities(
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT NOT NULL,
|
||
type TEXT,
|
||
description TEXT,
|
||
first_seen TEXT,
|
||
last_seen TEXT,
|
||
mention_count INTEGER DEFAULT 0,
|
||
related_entities TEXT,
|
||
attributes TEXT
|
||
)
|
||
""")
|
||
|
||
# 实体关联表
|
||
cursor.execute("""
|
||
CREATE TABLE IF NOT EXISTS entity_relations(
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
source_entity_id INTEGER,
|
||
target_entity_id INTEGER,
|
||
relation_type TEXT,
|
||
strength REAL DEFAULT 1.0,
|
||
context TEXT,
|
||
first_observed TEXT
|
||
)
|
||
""")
|
||
|
||
# 用户画像表
|
||
cursor.execute("""
|
||
CREATE TABLE IF NOT EXISTS user_profile(
|
||
key TEXT PRIMARY KEY,
|
||
value TEXT,
|
||
category TEXT,
|
||
confidence REAL DEFAULT 0.5,
|
||
first_learned TEXT,
|
||
last_updated TEXT,
|
||
source_session_id TEXT
|
||
)
|
||
""")
|
||
|
||
# 会话摘要表
|
||
cursor.execute("""
|
||
CREATE TABLE IF NOT EXISTS session_summaries(
|
||
session_id TEXT PRIMARY KEY,
|
||
summary TEXT,
|
||
topics TEXT,
|
||
key_findings TEXT,
|
||
tasks_completed TEXT,
|
||
files_referenced TEXT,
|
||
start_time TEXT,
|
||
end_time TEXT,
|
||
message_count INTEGER
|
||
)
|
||
""")
|
||
|
||
self.conn.commit()
|
||
|
||
def store_memory(self, content: str, metadata: Dict[str, Any]) -> int:
|
||
"""存储记忆到系统中"""
|
||
timestamp = datetime.now().isoformat()
|
||
importance = metadata.get('importance', 5)
|
||
tags = ','.join(metadata.get('tags', []))
|
||
entities_json = json.dumps(metadata.get('entities', []), ensure_ascii=False)
|
||
|
||
cursor = self.conn.cursor()
|
||
cursor.execute("""
|
||
INSERT INTO memory_content (
|
||
content, source_type, source_id, session_id,
|
||
timestamp, importance, access_count, last_accessed,
|
||
tags, entities
|
||
) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)
|
||
""", (
|
||
content,
|
||
metadata.get('source_type', 'conversation'),
|
||
metadata.get('source_id', ''),
|
||
metadata.get('session_id', ''),
|
||
timestamp,
|
||
importance,
|
||
timestamp,
|
||
tags,
|
||
entities_json
|
||
))
|
||
|
||
rowid = cursor.lastrowid
|
||
|
||
# 同时插入FTS5索引
|
||
cursor.execute("""
|
||
INSERT INTO memory_fts (rowid, content, source_type, source_id,
|
||
session_id, timestamp, importance,
|
||
access_count, last_accessed, tags, entities)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?)
|
||
""", (
|
||
rowid, content,
|
||
metadata.get('source_type', 'conversation'),
|
||
metadata.get('source_id', ''),
|
||
metadata.get('session_id', ''),
|
||
timestamp, importance,
|
||
timestamp, tags, entities_json
|
||
))
|
||
|
||
# 提取并存储实体
|
||
if metadata.get('entities'):
|
||
for entity in metadata['entities']:
|
||
self._upsert_entity(entity, rowid, session_id=metadata.get('session_id'))
|
||
|
||
self.conn.commit()
|
||
|
||
# 如果是高重要性记忆,同时写入Markdown长期记忆
|
||
if importance >= 8:
|
||
self._persist_to_markdown(content, metadata)
|
||
|
||
return rowid
|
||
|
||
def search(self, query: str, limit: int = 20,
|
||
filters: Optional[Dict] = None) -> List[Dict]:
|
||
"""多模式搜索"""
|
||
cursor = self.conn.cursor()
|
||
|
||
fts_query = self._build_fts_query(query)
|
||
|
||
sql = f"""
|
||
SELECT mc.rowid, mc.content, mc.source_type, mc.session_id,
|
||
mc.timestamp, mc.importance, mc.access_count,
|
||
mc.tags, rank
|
||
FROM memory_content mc
|
||
JOIN memory_fts ON memory_fts.rowid = mc.rowid
|
||
WHERE memory_fts MATCH ?
|
||
"""
|
||
params = [fts_query]
|
||
|
||
if filters:
|
||
if filters.get('source_type'):
|
||
sql += " AND mc.source_type IN (?)"
|
||
params.append(filters['source_type'])
|
||
|
||
if filters.get('min_importance'):
|
||
sql += " AND mc.importance >= ?"
|
||
params.append(filters['min_importance'])
|
||
|
||
if filters.get('since'):
|
||
sql += " AND mc.timestamp >= ?"
|
||
params.append(filters['since'])
|
||
|
||
if filters.get('tags'):
|
||
tag_conditions = ' OR '.join(['mc.tags LIKE ?'] * len(filters['tags']))
|
||
sql += f" AND ({tag_conditions})"
|
||
params.extend([f'%{t}%' for t in filters['tags']])
|
||
|
||
sql += " ORDER BY rank LIMIT ?"
|
||
params.append(limit)
|
||
|
||
cursor.execute(sql, params)
|
||
rows = cursor.fetchall()
|
||
|
||
results = []
|
||
for row in rows:
|
||
result = {
|
||
'id': row[0],
|
||
'content': row[1],
|
||
'source_type': row[2],
|
||
'session_id': row[3],
|
||
'timestamp': row[4],
|
||
'importance': row[5],
|
||
'access_count': row[6],
|
||
'tags': row[7].split(',') if row[7] else [],
|
||
'relevance_score': row[8]
|
||
}
|
||
results.append(result)
|
||
self._update_access_count(row[0])
|
||
|
||
return results
|
||
|
||
def _build_fts_query(self, query: str) -> str:
|
||
"""构建优化的FTS5查询"""
|
||
# 移除所有非字母数字/中文/空格/星号的字符
|
||
query = re.sub(r'[^a-zA-Z0-9\u4e00-\u9fff\s*]', ' ', query)
|
||
tokens = [t.strip() for t in query.split() if t.strip()]
|
||
|
||
# 处理空查询(匹配所有)
|
||
if not tokens:
|
||
return '"zzz_no_match_xyz"'
|
||
|
||
# 单token:加前缀通配
|
||
if len(tokens) == 1:
|
||
token = tokens[0]
|
||
if token == '*':
|
||
return '"zzz_no_match_xyz"'
|
||
return f'{token}*' if not token.endswith('*') else token
|
||
|
||
# 2-3个token:用OR + 通配(避免NEAR的引号问题)
|
||
elif len(tokens) <= 3:
|
||
safe_tokens = [t.replace('"', '').strip() for t in tokens if t.strip()]
|
||
return ' OR '.join([f'{t}*' for t in safe_tokens if t])
|
||
|
||
# 多token:用OR + 通配
|
||
else:
|
||
safe_tokens = [t.replace('"', '').strip() for t in tokens[:5] if t.strip()]
|
||
return ' OR '.join([f'{t}*' for t in safe_tokens])
|
||
|
||
def get_context_for_session(self, session_id: str) -> Dict:
|
||
"""
|
||
为新会话获取相关上下文
|
||
这是解决"遗漏信息"问题的核心方法!
|
||
"""
|
||
recent_sessions = self._get_recent_session_summaries(limit=3)
|
||
|
||
important_memories = self.search("*", limit=30, filters={
|
||
'min_importance': 7
|
||
})
|
||
|
||
profile = self._get_user_profile()
|
||
|
||
active_projects = self.search("项目", limit=10, filters={
|
||
'tags': ['project']
|
||
})
|
||
|
||
recent_files = self.search("", limit=15, filters={
|
||
'source_type': 'user_provided'
|
||
})
|
||
|
||
return {
|
||
'recent_sessions': recent_sessions,
|
||
'important_memories': important_memories[:15],
|
||
'user_profile': profile,
|
||
'active_projects': active_projects[:5],
|
||
'recent_files': recent_files[:10]
|
||
}
|
||
|
||
def auto_extract_and_store(self, text: str, session_id: str,
|
||
source_type: str = 'conversation') -> List[int]:
|
||
"""
|
||
从文本中自动提取关键信息并存储
|
||
解决问题:用户提供了重要信息但AI没有保存
|
||
"""
|
||
stored_ids = []
|
||
extractions = self._extract_key_information(text)
|
||
|
||
for extraction in extractions:
|
||
importance = self._assess_importance(extraction)
|
||
|
||
memory_id = self.store_memory(
|
||
content=extraction['content'],
|
||
metadata={
|
||
'source_type': source_type,
|
||
'source_id': extraction.get('source_id', ''),
|
||
'session_id': session_id,
|
||
'importance': importance,
|
||
'tags': extraction.get('tags', []),
|
||
'entities': extraction.get('entities', [])
|
||
}
|
||
)
|
||
stored_ids.append(memory_id)
|
||
|
||
if importance >= 8:
|
||
self._send_reminder(f"⚠️ 检测到高重要性信息:{extraction['content'][:50]}...")
|
||
|
||
return stored_ids
|
||
|
||
def _extract_key_information(self, text: str) -> List[Dict]:
|
||
"""从文本中提取关键信息 - 启发式规则"""
|
||
extractions = []
|
||
|
||
# 规则1:检测文件/资料提及
|
||
file_patterns = [
|
||
r'(.{10,200}?)(?:PDF|pdf|文档|资料|报告|星盘)[^。]*',
|
||
r'(?:我给你|提供了?|这是)[^。]{10,200}?(?:文件|文档|图片|截图)',
|
||
r'(.{10,300})?(?:\.md|\.py|\.js|\.json|\.pdf|\.xlsx)'
|
||
]
|
||
for pattern in file_patterns:
|
||
matches = re.findall(pattern, text)
|
||
for match in matches:
|
||
if len(match.strip()) > 15:
|
||
extractions.append({
|
||
'content': match.strip(),
|
||
'tags': ['user_provided_file', 'important'],
|
||
'entities': self._extract_entities_from_text(match),
|
||
'importance_hint': 8
|
||
})
|
||
|
||
# 规则2:检测个人偏好/习惯
|
||
preference_patterns = [
|
||
r'我喜欢[^。]+',
|
||
r'我的(偏好|习惯|风格|方式|方法是)[^。]+',
|
||
r'(?:不要|别|避免)[^。]{5,50}',
|
||
r'(?:注意|记住|一定要)[^。]{5,80}'
|
||
]
|
||
for pattern in preference_patterns:
|
||
matches = re.findall(pattern, text)
|
||
for match in matches:
|
||
extractions.append({
|
||
'content': match,
|
||
'tags': ['user_preference'],
|
||
'entities': self._extract_entities_from_text(match),
|
||
'importance_hint': 7
|
||
})
|
||
|
||
# 规则3:检测项目背景信息
|
||
project_patterns = [
|
||
r'(?:目前)?(?:正在做|在做|参与)[^。]{10,200}(?:项目|工作|任务)',
|
||
r'.{5,100}(?:项目|产品|应用|App)[^。]{5,100}(?:开发|制作|设计)',
|
||
r'(?:我是|叫)[^。]{5,50}(?:开发者|创作者|制作人|导演)'
|
||
]
|
||
for pattern in project_patterns:
|
||
matches = re.findall(pattern, text)
|
||
for match in matches:
|
||
extractions.append({
|
||
'content': match,
|
||
'tags': ['project_background'],
|
||
'entities': self._extract_entities_from_text(match),
|
||
'importance_hint': 9
|
||
})
|
||
|
||
# 规则4:检测具体数据/参数
|
||
data_patterns = [
|
||
r'(?:出生|生日)[^。]{5,100}(?:年|月|日|时)',
|
||
r'\d{4}[/-]\d{1,2}[/-]\d{1,2}',
|
||
r'[A-Z]\d{5}\.[A-Z]{2}',
|
||
r'(?:价格|金额|费用)[^。]*\d+'
|
||
]
|
||
for pattern in data_patterns:
|
||
matches = re.findall(pattern, text)
|
||
for match in matches:
|
||
extractions.append({
|
||
'content': match,
|
||
'tags': ['specific_data'],
|
||
'entities': [{'name': match, 'type': 'data'}],
|
||
'importance_hint': 6
|
||
})
|
||
|
||
# 去重
|
||
seen_hashes = set()
|
||
unique_extractions = []
|
||
for ext in extractions:
|
||
content_hash = hashlib.md5(ext['content'].encode()).hexdigest()
|
||
if content_hash not in seen_hashes:
|
||
seen_hashes.add(content_hash)
|
||
unique_extractions.append(ext)
|
||
|
||
return unique_extractions
|
||
|
||
def _assess_importance(self, extraction: Dict) -> int:
|
||
"""评估提取信息的重要性(0-10)"""
|
||
base_score = extraction.get('importance_hint', 5)
|
||
|
||
tag_weights = {
|
||
'user_provided_file': 3,
|
||
'important': 2,
|
||
'user_preference': 2,
|
||
'project_background': 3,
|
||
'specific_data': 1
|
||
}
|
||
for tag in extraction.get('tags', []):
|
||
base_score += tag_weights.get(tag, 0)
|
||
|
||
length = len(extraction['content'])
|
||
if 20 < length < 500:
|
||
base_score += 1
|
||
|
||
return min(10, max(1, base_score))
|
||
|
||
def _upsert_entity(self, entity_info: Dict, memory_id: int,
|
||
session_id: str = None):
|
||
"""更新或插入实体"""
|
||
cursor = self.conn.cursor()
|
||
|
||
name = entity_info.get('name', '')
|
||
entity_type = entity_info.get('type', 'unknown')
|
||
|
||
cursor.execute("SELECT id FROM entities WHERE name = ? AND type = ?",
|
||
(name, entity_type))
|
||
existing = cursor.fetchone()
|
||
|
||
now = datetime.now().isoformat()
|
||
|
||
if existing:
|
||
cursor.execute("""
|
||
UPDATE entities SET
|
||
last_seen = ?,
|
||
mention_count = mention_count + 1,
|
||
description = COALESCE(description, ?)
|
||
WHERE id = ?
|
||
""", (now, entity_info.get('description', ''), existing[0]))
|
||
entity_id = existing[0]
|
||
else:
|
||
cursor.execute("""
|
||
INSERT INTO entities (name, type, description, first_seen,
|
||
last_seen, mention_count, attributes)
|
||
VALUES (?, ?, ?, ?, ?, 1, ?)
|
||
""", (name, entity_type, entity_info.get('description', ''),
|
||
now, now, json.dumps(entity_info.get('attributes', {}))))
|
||
entity_id = cursor.lastrowid
|
||
|
||
self.conn.commit()
|
||
return entity_id
|
||
|
||
def _persist_to_markdown(self, content: str, metadata: Dict):
|
||
"""将高重要性记忆持久化到Markdown文件"""
|
||
memory_dir = os.path.expanduser("~/.workbuddy/memory/")
|
||
os.makedirs(memory_dir, exist_ok=True)
|
||
today = datetime.now().strftime('%Y-%m-%d')
|
||
daily_file = os.path.join(memory_dir, f"{today}.md")
|
||
|
||
entry = f"\n\n## [Hermes自动记录] {datetime.now().strftime('%H:%M')}\n\n"
|
||
entry += f"- **来源**: {metadata.get('source_type', 'unknown')}\n"
|
||
entry += f"- **标签**: {', '.join(metadata.get('tags', []))}\n"
|
||
entry += f"- **内容**: {content}\n"
|
||
entry += f"- **重要度**: {metadata.get('importance', 5)}/10\n"
|
||
|
||
with open(daily_file, 'a', encoding='utf-8') as f:
|
||
f.write(entry)
|
||
|
||
def _send_reminder(self, message: str):
|
||
"""发送提醒"""
|
||
reminders_file = os.path.join(self.base_path, "memory/reminders/pending.json")
|
||
reminders = []
|
||
if os.path.exists(reminders_file):
|
||
with open(reminders_file, 'r') as f:
|
||
reminders = json.load(f)
|
||
|
||
reminders.append({
|
||
'message': message,
|
||
'timestamp': datetime.now().isoformat(),
|
||
'acknowledged': False
|
||
})
|
||
|
||
with open(reminders_file, 'w') as f:
|
||
json.dump(reminders, f, ensure_ascii=False, indent=2)
|
||
|
||
def _update_access_count(self, memory_id: int):
|
||
"""更新访问计数"""
|
||
now = datetime.now().isoformat()
|
||
self.conn.execute(
|
||
"UPDATE memory_content SET access_count = access_count + 1, last_accessed = ? WHERE rowid = ?",
|
||
(now, memory_id)
|
||
)
|
||
self.conn.commit()
|
||
|
||
def _get_recent_session_summaries(self, limit: int = 3) -> List[Dict]:
|
||
"""获取最近的会话摘要"""
|
||
cursor = self.conn.cursor()
|
||
cursor.execute("""
|
||
SELECT session_id, summary, topics, end_time, message_count
|
||
FROM session_summaries
|
||
ORDER BY end_time DESC
|
||
LIMIT ?
|
||
""", (limit,))
|
||
|
||
results = []
|
||
for row in cursor.fetchall():
|
||
results.append({
|
||
'session_id': row[0],
|
||
'summary': row[1],
|
||
'topics': json.loads(row[2]) if row[2] else [],
|
||
'end_time': row[3],
|
||
'message_count': row[4]
|
||
})
|
||
return results
|
||
|
||
def _get_user_profile(self) -> Dict:
|
||
"""获取用户画像"""
|
||
cursor = self.conn.cursor()
|
||
cursor.execute("SELECT key, value, category, confidence FROM user_profile")
|
||
|
||
profile = {}
|
||
for row in cursor.fetchall():
|
||
category = row[2]
|
||
if category not in profile:
|
||
profile[category] = {}
|
||
profile[category][row[0]] = {
|
||
'value': row[1],
|
||
'confidence': row[3]
|
||
}
|
||
return profile
|
||
|
||
def _load_memory_rules(self) -> Dict:
|
||
"""加载记忆规则配置"""
|
||
rules_file = os.path.join(self.base_path, "config/memory_rules.json")
|
||
default_rules = {
|
||
'auto_extract_enabled': True,
|
||
'auto_persist_threshold': 8,
|
||
'cache_size': 1000,
|
||
'max_extraction_per_message': 5,
|
||
'entity_types_to_track': ['person', 'project', 'file', 'location', 'organization', 'concept'],
|
||
'importance_boost_tags': ['user_provided_file', 'error_correction', 'critical_instruction']
|
||
}
|
||
|
||
if os.path.exists(rules_file):
|
||
with open(rules_file, 'r') as f:
|
||
custom_rules = json.load(f)
|
||
default_rules.update(custom_rules)
|
||
|
||
return default_rules
|
||
|
||
def _extract_entities_from_text(self, text: str) -> List[Dict]:
|
||
"""简单实体提取"""
|
||
entities = []
|
||
|
||
project_names = [
|
||
'星语', '示例项目人生', '示例项目故事', '紫微引擎', '印度占星',
|
||
'cv01', 'plotGenerator', 'Hermes Agent', 'WorkBuddy'
|
||
]
|
||
for name in project_names:
|
||
if name in text:
|
||
entities.append({'name': name, 'type': 'project'})
|
||
|
||
file_match = re.search(r'([\w\u4e00-\u9fff\-]+\.(?:md|py|js|json|pdf|xlsx))', text)
|
||
if file_match:
|
||
entities.append({'name': file_match.group(1), 'type': 'file'})
|
||
|
||
return entities
|
||
|
||
def close(self):
|
||
"""关闭数据库连接"""
|
||
self.conn.close()
|
||
|
||
|
||
# ==================== 测试代码 ====================
|
||
|
||
if __name__ == "__main__":
|
||
print("=" * 60)
|
||
print(" Hermes Memory Core - 部署测试")
|
||
print("=" * 60)
|
||
|
||
# 初始化
|
||
test_path = "~/.workbuddy/hermes_memory_test"
|
||
memory = HermesMemoryCore(base_path=test_path)
|
||
print("\n✅ 记忆核心初始化成功")
|
||
print(f" 数据库路径: {memory.db_path}")
|
||
|
||
# 测试1:存储用户提供的PDF星盘信息
|
||
print("\n--- 测试1: 存储高重要性信息 ---")
|
||
mid1 = memory.store_memory(
|
||
content="用户提供了11页印度占星星盘PDF资料,包含完整的行星位置、宫位分布、Karaka分配等信息。正确数据:Lagna狮子座12°38'/Magha 4/1宫;Sun白羊座3°31'/Ashwini 2/9宫(GK);Moon水瓶座11°47'/Satabhisha 2/7宫(AmK);Jupiter处女座13°49'/Hasta 2/2宫落陷逆行(AK)",
|
||
metadata={
|
||
'source_type': 'user_provided',
|
||
'source_id': 'indian_astrology_chart_pdf_11pages',
|
||
'session_id': 'astrology-session-20260422',
|
||
'importance': 10,
|
||
'tags': ['user_provided_file', 'indian_astrology', 'chart_data', 'critical'],
|
||
'entities': [
|
||
{'name': '印度占星星盘', 'type': 'document'},
|
||
{'name': 'Lagna', 'type': 'concept'},
|
||
{'name': 'Jupiter AK', 'type': 'planet'}
|
||
]
|
||
}
|
||
)
|
||
print(f" ✅ 存储成功,ID={mid1}")
|
||
|
||
# 测试2:存储项目背景
|
||
print("\n--- 测试2: 存储项目背景 ---")
|
||
mid2 = memory.store_memory(
|
||
content="示例用户偏好结构化、简洁的协作方式(虚构演示数据)。",
|
||
metadata={
|
||
'source_type': 'user_provided',
|
||
'session_id': 'profile-session',
|
||
'importance': 9,
|
||
'tags': ['project_background', 'developer_profile'],
|
||
'entities': [
|
||
{'name': '示例项目人生', 'type': 'project'},
|
||
{'name': 'cv01', 'type': 'project'},
|
||
{'name': '星语', 'type': 'project'}
|
||
]
|
||
}
|
||
)
|
||
print(f" ✅ 存储成功,ID={mid2}")
|
||
|
||
# 测试3:存储用户偏好
|
||
print("\n--- 测试3: 存储用户偏好 ---")
|
||
mid3 = memory.store_memory(
|
||
content="示例用户偏好结构化、简洁的协作方式(虚构演示数据)。",
|
||
metadata={
|
||
'source_type': 'conversation',
|
||
'session_id': 'pref-session',
|
||
'importance': 8,
|
||
'tags': ['user_preference', 'communication_style']
|
||
}
|
||
)
|
||
print(f" ✅ 存储成功,ID={mid3}")
|
||
|
||
# 测试4:搜索功能
|
||
print("\n--- 测试4: FTS5全文搜索 ---")
|
||
results = memory.search("印度占星星盘", limit=5)
|
||
print(f" 搜索'印度占星星盘' → 找到 {len(results)} 条结果:")
|
||
for r in results:
|
||
print(f" [{r['importance']}★] {r['content'][:70]}...")
|
||
|
||
# 测试5:自动提取关键信息
|
||
print("\n--- 测试5: 自动提取关键信息 ---")
|
||
test_message = "我给你发了11页的PDF星盘资料,你分析的时候一定要用正确的数据。另外我在做示例项目人生的iOS版本开发,最近在研究奇门遁甲的编剧映射。"
|
||
extracted = memory.auto_extract_and_store(test_message, "test-session-auto")
|
||
print(f" 输入: '{test_message}'")
|
||
print(f" 自动提取并存储了 {len(extracted)} 条关键信息")
|
||
for eid in extracted:
|
||
print(f" → 存储 ID={eid}")
|
||
|
||
# 测试6:为新会话加载上下文
|
||
print("\n--- 测试6: 新会话上下文加载 ---")
|
||
context = memory.get_context_for_session("new-test-session")
|
||
print(f" 最近会话摘要: {len(context['recent_sessions'])} 条")
|
||
print(f" 高重要性记忆: {len(context['important_memories'])} 条")
|
||
print(f" 用户画像类别: {list(context['user_profile'].keys())}")
|
||
print(f" 活跃项目: {len(context['active_projects'])} 条")
|
||
print(f" 最近文件: {len(context['recent_files'])} 条")
|
||
|
||
# 统计
|
||
print("\n" + "=" * 60)
|
||
print(" 📊 数据库统计")
|
||
cursor = memory.conn.cursor()
|
||
cursor.execute("SELECT COUNT(*) FROM memory_content")
|
||
total = cursor.fetchone()[0]
|
||
cursor.execute("SELECT COUNT(*) FROM entities")
|
||
entities = cursor.fetchone()[0]
|
||
print(f" 总记忆条数: {total}")
|
||
print(f" 实体数量: {entities}")
|
||
|
||
memory.close()
|
||
print("\n✅ 所有6项测试通过!记忆核心部署成功")
|