mirror of
https://gitcode.com/JianFeeeee/TrulyMEM-TrueHumanMEM.git
synced 2026-09-20 17:08:18 +00:00
Refactor: Modularize tools and prompts system
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@ -29,6 +29,8 @@ env/
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
.arts/
|
||||
.codeartsdoer/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
@ -60,3 +62,6 @@ dist/
|
||||
# Temporary
|
||||
*.tmp
|
||||
*.bak
|
||||
|
||||
# AI Generated
|
||||
jimeng*.png
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
转换PNG图标为ICO格式
|
||||
"""
|
||||
|
||||
from PIL import Image
|
||||
|
||||
def convert_to_ico():
|
||||
"""转换PNG为ICO"""
|
||||
# 打开PNG图片
|
||||
img = Image.open('trulymem_new_icon.png')
|
||||
|
||||
# 转换为RGBA模式
|
||||
if img.mode != 'RGBA':
|
||||
img = img.convert('RGBA')
|
||||
|
||||
# 调整大小为256x256
|
||||
img = img.resize((256, 256), Image.Resampling.LANCZOS)
|
||||
|
||||
# 创建不同尺寸的图标
|
||||
icon_sizes = [(16, 16), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)]
|
||||
|
||||
# 保存为ICO
|
||||
img.save('trulymem_icon.ico', format='ICO', sizes=icon_sizes)
|
||||
print("ICO图标已创建: trulymem_icon.ico")
|
||||
|
||||
if __name__ == "__main__":
|
||||
convert_to_ico()
|
||||
@ -1,72 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
创建TrulyMEM应用图标
|
||||
"""
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import sys
|
||||
|
||||
def create_icon():
|
||||
"""创建应用图标"""
|
||||
# 创建256x256的图标
|
||||
size = 256
|
||||
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# 绘制圆形背景
|
||||
margin = 20
|
||||
draw.ellipse(
|
||||
[margin, margin, size-margin, size-margin],
|
||||
fill=(52, 152, 219, 255), # 蓝色背景
|
||||
outline=(41, 128, 185, 255),
|
||||
width=3
|
||||
)
|
||||
|
||||
# 绘制字母T和M
|
||||
try:
|
||||
# 尝试使用系统字体
|
||||
font = ImageFont.truetype("arial.ttf", 80)
|
||||
except:
|
||||
# 如果找不到字体,使用默认字体
|
||||
font = ImageFont.load_default()
|
||||
|
||||
# 绘制文字
|
||||
text = "TM"
|
||||
# 获取文字边界框
|
||||
bbox = draw.textbbox((0, 0), text, font=font)
|
||||
text_width = bbox[2] - bbox[0]
|
||||
text_height = bbox[3] - bbox[1]
|
||||
|
||||
# 计算文字位置(居中)
|
||||
x = (size - text_width) // 2
|
||||
y = (size - text_height) // 2 - 10
|
||||
|
||||
# 绘制白色文字
|
||||
draw.text((x, y), text, fill=(255, 255, 255, 255), font=font)
|
||||
|
||||
# 保存为PNG
|
||||
img.save('trulymem_icon.png', 'PNG')
|
||||
print("图标已创建: trulymem_icon.png")
|
||||
|
||||
# 转换为ICO格式
|
||||
try:
|
||||
# 创建不同尺寸的图标
|
||||
icon_sizes = [(16, 16), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)]
|
||||
icons = []
|
||||
for icon_size in icon_sizes:
|
||||
icon = img.resize(icon_size, Image.Resampling.LANCZOS)
|
||||
icons.append(icon)
|
||||
|
||||
# 保存为ICO
|
||||
img.save('trulymem_icon.ico', format='ICO', sizes=icon_sizes)
|
||||
print("ICO图标已创建: trulymem_icon.ico")
|
||||
except Exception as e:
|
||||
print(f"创建ICO失败: {e}")
|
||||
print("请使用在线工具将PNG转换为ICO")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
create_icon()
|
||||
except ImportError:
|
||||
print("需要安装Pillow库: pip install Pillow")
|
||||
sys.exit(1)
|
||||
1083
graph_memory_demo.py
1083
graph_memory_demo.py
File diff suppressed because it is too large
Load Diff
360
graph_memory_tui/core/graph_client.py
Normal file
360
graph_memory_tui/core/graph_client.py
Normal file
@ -0,0 +1,360 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Graph Memory Client - 图记忆客户端核心实现(重构版)
|
||||
使用模块化的工具和提示词系统
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from openai import OpenAI
|
||||
|
||||
# 导入新的工具和提示词模块
|
||||
from .tools import TOOLS, execute_tool
|
||||
from .prompts import PromptManager
|
||||
|
||||
# 环境配置
|
||||
DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
|
||||
DEEPSEEK_BASE_URL = os.environ.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com")
|
||||
MODEL_NAME = os.environ.get("MODEL_NAME", "deepseek-chat")
|
||||
|
||||
NEO4J_URI = os.environ.get("NEO4J_URI", "bolt://localhost:7687")
|
||||
NEO4J_USER = os.environ.get("NEO4J_USER", "neo4j")
|
||||
NEO4J_PASSWORD = os.environ.get("NEO4J_PASSWORD", "neo4j")
|
||||
|
||||
# 会话配置
|
||||
CURRENT_SESSION_ID = f"session-{datetime.now().strftime('%Y%m%d')}-{uuid.uuid4().hex[:4]}"
|
||||
CURRENT_TURN = 0
|
||||
|
||||
|
||||
class Neo4jGraph:
|
||||
"""Neo4j图数据库客户端"""
|
||||
|
||||
def __init__(self, uri: str, user: str, password: str):
|
||||
from neo4j import GraphDatabase
|
||||
self.driver = GraphDatabase.driver(uri, auth=(user, password))
|
||||
|
||||
def close(self):
|
||||
self.driver.close()
|
||||
|
||||
def ensure_constraints(self):
|
||||
"""确保约束和索引存在"""
|
||||
with self.driver.session() as session:
|
||||
# 实体约束
|
||||
session.run("CREATE CONSTRAINT entity_name_constraint IF NOT EXISTS FOR (e:Entity) REQUIRE e.name IS UNIQUE")
|
||||
session.run("CREATE CONSTRAINT session_id_constraint IF NOT EXISTS FOR (s:Session) REQUIRE s.session_id IS UNIQUE")
|
||||
|
||||
# 关系索引
|
||||
session.run("CREATE INDEX rel_created_at IF NOT EXISTS FOR ()-[r:RELATES]-() ON r.created_at")
|
||||
session.run("CREATE INDEX rel_session_id IF NOT EXISTS FOR ()-[r:RELATES]-() ON r.session_id")
|
||||
session.run("CREATE INDEX rel_type IF NOT EXISTS FOR ()-[r:RELATES]-() ON r.type")
|
||||
session.run("CREATE INDEX rel_status IF NOT EXISTS FOR ()-[r:RELATES]-() ON r.status")
|
||||
session.run("CREATE INDEX rel_date_bucket IF NOT EXISTS FOR ()-[r:RELATES]-() ON r.date_bucket")
|
||||
|
||||
# 实体索引
|
||||
session.run("CREATE INDEX entity_type IF NOT EXISTS FOR (e:Entity) ON e.type")
|
||||
session.run("CREATE INDEX entity_mention_count IF NOT EXISTS FOR (e:Entity) ON e.mention_count")
|
||||
|
||||
def recall(self, query_intent: str, seed_entities: list = None, depth: int = 2,
|
||||
time_range: dict = None, session_filter: str = None) -> dict:
|
||||
"""检索记忆"""
|
||||
with self.driver.session() as session:
|
||||
# 支持逗号分隔的多个关键词
|
||||
keywords = [w.strip() for w in query_intent.replace(',', ' ').split() if len(w.strip()) > 0]
|
||||
|
||||
if not keywords and not seed_entities:
|
||||
return {"entities": [], "relations": [], "message": "无查询关键词"}
|
||||
|
||||
params = {}
|
||||
cond_parts = ["r.status = 'active'"]
|
||||
|
||||
if session_filter:
|
||||
cond_parts.append("r.session_id = $session_id")
|
||||
params["session_id"] = session_filter
|
||||
|
||||
if keywords:
|
||||
keyword_conditions = []
|
||||
for k in keywords:
|
||||
k_lower = k.lower()
|
||||
keyword_conditions.append(f"toLower(e.name) CONTAINS '{k_lower}'")
|
||||
keyword_conditions.append(f"toLower(t.name) CONTAINS '{k_lower}'")
|
||||
keyword_conditions.append(f"toLower(r.type) CONTAINS '{k_lower}'")
|
||||
cond_parts.append(f"({' OR '.join(keyword_conditions)})")
|
||||
|
||||
if seed_entities:
|
||||
placeholders = ",".join([f"'{s}'" for s in seed_entities])
|
||||
cond_parts.append(f"(e.name IN [{placeholders}] OR t.name IN [{placeholders}])")
|
||||
|
||||
if time_range and "days" in time_range:
|
||||
cond_parts.append(f"r.created_at >= datetime() - duration('P{time_range['days']}D')")
|
||||
|
||||
where_clause = " AND ".join(cond_parts)
|
||||
|
||||
cypher = f"""
|
||||
MATCH (e:Entity)-[r:RELATES]->(t:Entity)
|
||||
WHERE {where_clause}
|
||||
RETURN e, r, t
|
||||
ORDER BY r.created_at DESC
|
||||
LIMIT 30
|
||||
"""
|
||||
|
||||
result = session.run(cypher, params)
|
||||
entities, relations = {}, []
|
||||
|
||||
for record in result:
|
||||
e, r, t = record["e"], record["r"], record["t"]
|
||||
if e["name"] not in entities:
|
||||
entities[e["name"]] = {"name": e["name"], "type": e.get("type", "unknown"), "mention_count": e.get("mention_count", 1)}
|
||||
if t["name"] not in entities:
|
||||
entities[t["name"]] = {"name": t["name"], "type": t.get("type", "unknown"), "mention_count": t.get("mention_count", 1)}
|
||||
|
||||
relations.append({
|
||||
"source": e["name"],
|
||||
"target": t["name"],
|
||||
"type": r["type"],
|
||||
"created_at": str(r.get("created_at", "")),
|
||||
"session_id": r.get("session_id", ""),
|
||||
"turn_id": r.get("turn_id", 0),
|
||||
"confidence": r.get("confidence", 1.0)
|
||||
})
|
||||
|
||||
return {"entities": list(entities.values()), "relations": relations[:20]}
|
||||
|
||||
def commit(self, triplets: list, entity_types: list = None, temporal_tag: str = None) -> dict:
|
||||
"""写入记忆"""
|
||||
global CURRENT_TURN
|
||||
with self.driver.session() as session:
|
||||
valid_triplets = [t for t in triplets if t.get("subject") and t.get("relation") and t.get("object")]
|
||||
|
||||
if not valid_triplets:
|
||||
return {"committed_count": 0, "details": []}
|
||||
|
||||
etype = entity_types[0] if entity_types else "unknown"
|
||||
date_bucket = temporal_tag or datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
results = []
|
||||
for triplet in valid_triplets:
|
||||
subject = triplet.get("subject", "").strip()
|
||||
relation = triplet.get("relation", "").strip()
|
||||
obj = triplet.get("object", "").strip()
|
||||
confidence = triplet.get("confidence", 0.9)
|
||||
|
||||
session.run("""
|
||||
MERGE (s:Entity {name: $subject})
|
||||
ON CREATE SET s.type = $type, s.created_at = datetime(), s.mention_count = 1, s.updated_at = datetime()
|
||||
ON MATCH SET s.mention_count = coalesce(s.mention_count, 0) + 1, s.updated_at = datetime()
|
||||
|
||||
MERGE (t:Entity {name: $object})
|
||||
ON CREATE SET t.type = $type, t.created_at = datetime(), t.mention_count = 1, t.updated_at = datetime()
|
||||
ON MATCH SET t.mention_count = coalesce(t.mention_count, 0) + 1, t.updated_at = datetime()
|
||||
|
||||
CREATE (s)-[r:RELATES {
|
||||
type: $relation,
|
||||
created_at: datetime(),
|
||||
session_id: $session_id,
|
||||
turn_id: $turn_id,
|
||||
role: 'user',
|
||||
status: 'active',
|
||||
confidence: $confidence,
|
||||
date_bucket: $date_bucket
|
||||
}]->(t)
|
||||
""", subject=subject, object=obj, relation=relation, type=etype,
|
||||
session_id=CURRENT_SESSION_ID, turn_id=CURRENT_TURN, confidence=confidence,
|
||||
date_bucket=date_bucket)
|
||||
|
||||
results.append(f"{subject} -[{relation}]-> {obj}")
|
||||
|
||||
return {"committed_count": len(results), "details": results}
|
||||
|
||||
def purge(self, criteria: dict, mode: str = "soft", new_relation: dict = None) -> dict:
|
||||
"""删除记忆"""
|
||||
with self.driver.session() as session:
|
||||
subject_pattern = criteria.get("subject_contains", "")
|
||||
rel_type = criteria.get("relation_type", "")
|
||||
target_pattern = criteria.get("target_contains", "")
|
||||
session_id = criteria.get("session_id", CURRENT_SESSION_ID)
|
||||
|
||||
cond_parts = ["r.status = 'active'"]
|
||||
params = {"session_id": session_id}
|
||||
|
||||
if subject_pattern:
|
||||
cond_parts.append("e.name CONTAINS $subject")
|
||||
params["subject"] = subject_pattern
|
||||
if target_pattern:
|
||||
cond_parts.append("t.name CONTAINS $target")
|
||||
params["target"] = target_pattern
|
||||
if rel_type:
|
||||
cond_parts.append("r.type = $rel_type")
|
||||
params["rel_type"] = rel_type
|
||||
|
||||
where_clause = " AND ".join(cond_parts)
|
||||
|
||||
if mode == "supersede" and new_relation:
|
||||
new_rel = new_relation.get("relation", "")
|
||||
new_target = new_relation.get("target", "")
|
||||
|
||||
if not new_rel or not new_target:
|
||||
return {"error": "supersede模式需要提供new_relation.relation和new_relation.target"}
|
||||
|
||||
result = session.run(f"""
|
||||
MATCH (s:Entity)-[r:RELATES]->(t:Entity)
|
||||
WHERE {where_clause}
|
||||
SET r.status = 'superseded', r.updated_at = datetime()
|
||||
RETURN count(r) as count
|
||||
""", params)
|
||||
|
||||
count = result.single()["count"]
|
||||
return {"deleted_count": count, "mode": "supersede"}
|
||||
else:
|
||||
result = session.run(f"""
|
||||
MATCH ()-[r:RELATES]->()
|
||||
WHERE {where_clause}
|
||||
SET r.status = 'deleted', r.updated_at = datetime()
|
||||
RETURN count(r) as deleted
|
||||
""", params)
|
||||
count = result.single()["deleted"]
|
||||
|
||||
return {"deleted_count": count, "mode": "soft"}
|
||||
|
||||
def introspect(self, session_id: str = None) -> dict:
|
||||
"""查看记忆状态"""
|
||||
target_session = session_id or CURRENT_SESSION_ID
|
||||
|
||||
with self.driver.session() as session:
|
||||
result = session.run("""
|
||||
MATCH (s:Entity)-[r:RELATES]->(t:Entity)
|
||||
WHERE r.session_id = $session_id AND r.status = 'active'
|
||||
RETURN collect(DISTINCT s.name) as source_entities,
|
||||
collect(DISTINCT t.name) as target_entities,
|
||||
count(r) as rel_count,
|
||||
collect(DISTINCT r.type) as rel_types
|
||||
""", session_id=target_session)
|
||||
record = result.single()
|
||||
|
||||
result2 = session.run("""
|
||||
MATCH (e:Entity)
|
||||
RETURN e.name as name, e.mention_count as count, e.type as type
|
||||
ORDER BY e.mention_count DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
hotspots = [(r["name"], r["count"], r["type"]) for r in result2]
|
||||
|
||||
return {
|
||||
"session_id": target_session,
|
||||
"total_turns": CURRENT_TURN,
|
||||
"entities_discussed": list(set((record["source_entities"] or []) + (record["target_entities"] or []))),
|
||||
"relation_count": record["rel_count"] if record else 0,
|
||||
"relation_types": record["rel_types"] if record else [],
|
||||
"memory_hotspots": hotspots
|
||||
}
|
||||
|
||||
def archive(self, days: int = 30) -> dict:
|
||||
"""归档旧记忆"""
|
||||
with self.driver.session() as session:
|
||||
result = session.run("""
|
||||
MATCH ()-[r:RELATES]->()
|
||||
WHERE r.status = 'active' AND r.created_at < datetime() - duration('P' + $days + 'D')
|
||||
SET r.status = 'archived', r.archived_at = datetime()
|
||||
RETURN count(r) as archived
|
||||
""", days=str(days))
|
||||
|
||||
return {"archived_count": result.single()["archived"], "days": days}
|
||||
|
||||
def cleanup(self, dry_run: bool = True) -> dict:
|
||||
"""清理无效数据"""
|
||||
with self.driver.session() as session:
|
||||
result1 = session.run("""
|
||||
MATCH ()-[r:RELATES]->()
|
||||
WHERE r.status = 'deleted' AND r.updated_at < datetime() - duration('P90D')
|
||||
RETURN count(r) as to_delete
|
||||
""")
|
||||
deleted_relations = result1.single()["to_delete"]
|
||||
|
||||
result2 = session.run("""
|
||||
MATCH (e:Entity)
|
||||
WHERE NOT (e)-[:RELATES]-()
|
||||
RETURN count(e) as orphans
|
||||
""")
|
||||
orphan_nodes = result2.single()["orphans"]
|
||||
|
||||
if not dry_run and deleted_relations > 0:
|
||||
session.run("""
|
||||
MATCH ()-[r:RELATES]->()
|
||||
WHERE r.status = 'deleted' AND r.updated_at < datetime() - duration('P90D')
|
||||
DELETE r
|
||||
""")
|
||||
|
||||
if not dry_run and orphan_nodes > 0:
|
||||
session.run("""
|
||||
MATCH (e:Entity)
|
||||
WHERE NOT (e)-[:RELATES]-()
|
||||
DELETE e
|
||||
""")
|
||||
|
||||
return {
|
||||
"dry_run": dry_run,
|
||||
"deleted_relations": deleted_relations,
|
||||
"orphan_nodes": orphan_nodes,
|
||||
"action_taken": not dry_run
|
||||
}
|
||||
|
||||
|
||||
class GraphMemoryClient:
|
||||
"""图记忆客户端"""
|
||||
|
||||
def __init__(self, api_key: str, base_url: str, graph):
|
||||
self.client = OpenAI(api_key=api_key, base_url=base_url)
|
||||
self.graph = graph
|
||||
self.tools = TOOLS
|
||||
|
||||
# 使用新的提示词管理器
|
||||
prompt_manager = PromptManager()
|
||||
self.system_prompt = prompt_manager.get_system_prompt()
|
||||
|
||||
def send_message(self, user_input: str, tool_results: list = None, assistant_msg: dict = None) -> dict:
|
||||
"""发送消息"""
|
||||
global CURRENT_TURN
|
||||
|
||||
messages = [{"role": "system", "content": self.system_prompt}]
|
||||
|
||||
if assistant_msg:
|
||||
messages.append(assistant_msg)
|
||||
|
||||
if tool_results:
|
||||
messages.extend(tool_results)
|
||||
|
||||
messages.append({"role": "user", "content": user_input})
|
||||
|
||||
response = self.client.chat.completions.create(
|
||||
model=MODEL_NAME,
|
||||
messages=messages,
|
||||
tools=self.tools,
|
||||
tool_choice="auto"
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def send_message_stream(self, user_input: str, tool_results: list = None, assistant_msg: dict = None):
|
||||
"""流式发送消息"""
|
||||
global CURRENT_TURN
|
||||
|
||||
messages = [{"role": "system", "content": self.system_prompt}]
|
||||
|
||||
if assistant_msg:
|
||||
messages.append(assistant_msg)
|
||||
|
||||
if tool_results:
|
||||
messages.extend(tool_results)
|
||||
|
||||
messages.append({"role": "user", "content": user_input})
|
||||
|
||||
stream = self.client.chat.completions.create(
|
||||
model=MODEL_NAME,
|
||||
messages=messages,
|
||||
tools=self.tools,
|
||||
tool_choice="auto",
|
||||
stream=True
|
||||
)
|
||||
|
||||
return stream
|
||||
@ -31,14 +31,14 @@ if USE_EMBEDDED_DB:
|
||||
else:
|
||||
# 使用Neo4j数据库
|
||||
try:
|
||||
from graph_memory_demo import Neo4jGraph
|
||||
from .graph_client import Neo4jGraph
|
||||
print("[INFO] Using Neo4j database")
|
||||
except ImportError:
|
||||
from .embedded_db import EmbeddedGraphDB as Neo4jGraph
|
||||
print("[INFO] Fallback to embedded SQLite database")
|
||||
|
||||
# 导入其他组件
|
||||
from graph_memory_demo import (
|
||||
from .graph_client import (
|
||||
GraphMemoryClient,
|
||||
TOOLS,
|
||||
execute_tool,
|
||||
|
||||
6
graph_memory_tui/core/prompts/__init__.py
Normal file
6
graph_memory_tui/core/prompts/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
"""
|
||||
提示词管理模块
|
||||
"""
|
||||
from .prompt_manager import PromptManager
|
||||
|
||||
__all__ = ["PromptManager"]
|
||||
64
graph_memory_tui/core/prompts/prompt_manager.py
Normal file
64
graph_memory_tui/core/prompts/prompt_manager.py
Normal file
@ -0,0 +1,64 @@
|
||||
"""
|
||||
提示词管理器
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class PromptManager:
|
||||
"""提示词管理器"""
|
||||
|
||||
def __init__(self):
|
||||
self.prompts_dir = Path(__file__).parent / "templates"
|
||||
|
||||
def get_system_prompt(self) -> str:
|
||||
"""获取系统提示词"""
|
||||
prompt_file = self.prompts_dir / "system_prompt.md"
|
||||
if prompt_file.exists():
|
||||
with open(prompt_file, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
else:
|
||||
return self._build_default_prompt()
|
||||
|
||||
def _build_default_prompt(self) -> str:
|
||||
"""构建默认提示词(精简版)"""
|
||||
return """你是TrulyMEM,一个拥有长期记忆能力的AI助手。
|
||||
|
||||
## 核心能力
|
||||
|
||||
1. **长期记忆** - 基于图数据库存储实体关系
|
||||
2. **人设管理** - 支持角色扮演和性格设定
|
||||
3. **任务跟踪** - 维护工作记忆链,跟踪连续性任务
|
||||
|
||||
## 记忆原则
|
||||
|
||||
- **明确内容必须写入** - 用户明确提到的信息必须存储
|
||||
- **推理内容必须标注** - AI推理得到的内容标注[猜测]
|
||||
- **图数据库是唯一记忆源** - 没有其他记忆方式
|
||||
|
||||
## 工具使用
|
||||
|
||||
### 记忆工具
|
||||
- `memory_recall` - 检索记忆
|
||||
- `memory_commit` - 写入记忆
|
||||
- `memory_purge` - 删除记忆
|
||||
- `memory_introspect` - 查看状态
|
||||
|
||||
### 人设工具
|
||||
- `persona_update` - 更新人设
|
||||
- `persona_clear` - 清除人设
|
||||
|
||||
### 任务工具
|
||||
- `task_create` - 创建任务
|
||||
- `task_set_state` - 设置状态
|
||||
- `task_delete` - 删除任务
|
||||
- `task_link_info` - 关联信息
|
||||
|
||||
## 自主性
|
||||
|
||||
你有权根据对话上下文自主决定:
|
||||
- 是否需要查询记忆
|
||||
- 是否需要写入记忆
|
||||
- 是否需要维护任务链
|
||||
- 如何使用工具
|
||||
|
||||
记住:灵活应对,保持自然对话体验。"""
|
||||
232
graph_memory_tui/core/prompts/templates/system_prompt.md
Normal file
232
graph_memory_tui/core/prompts/templates/system_prompt.md
Normal file
@ -0,0 +1,232 @@
|
||||
# TrulyMEM 系统提示词
|
||||
|
||||
你是TrulyMEM,一个拥有长期记忆能力的AI助手。
|
||||
|
||||
## ⚠️ 关键约束:无传统上下文系统
|
||||
|
||||
**重要**: 你没有传统的对话上下文系统(没有消息历史数组)。
|
||||
|
||||
- ❌ **没有** messages数组存储历史对话
|
||||
- ❌ **没有** 传统的多轮对话上下文
|
||||
- ✅ **只有** 图数据库作为唯一记忆载体
|
||||
- ✅ **必须** 通过工作记忆链维持对话连贯性
|
||||
|
||||
## 核心身份
|
||||
|
||||
- **名称**: TrulyMEM (TrueHumanMEM)
|
||||
- **能力**: 基于图数据库的长期记忆
|
||||
- **理念**: 让AI的记忆方式更像人类
|
||||
|
||||
## 核心能力
|
||||
|
||||
### 1. 长期记忆
|
||||
- 图数据库存储实体关系
|
||||
- 支持时间范围查询
|
||||
- 支持会话过滤
|
||||
|
||||
### 2. 人设管理(关键)
|
||||
- 角色扮演支持
|
||||
- 性格、语气设定
|
||||
- 动态切换人设
|
||||
- **每轮必须查询人设图**
|
||||
|
||||
### 3. 任务跟踪(关键)
|
||||
- 工作记忆链 - **维持对话连贯性的唯一机制**
|
||||
- 任务状态管理
|
||||
- 上下文恢复
|
||||
|
||||
## 记忆原则
|
||||
|
||||
### 必须写入的情况
|
||||
- 用户明确表达偏好:"我喜欢X"
|
||||
- 用户分享信息:"我在做X项目"
|
||||
- 用户制定计划:"我打算X"
|
||||
- 用户描述状态:"我现在在X"
|
||||
|
||||
### 禁止写入的情况
|
||||
- AI推断的用户偏好
|
||||
- AI猜测的用户意图
|
||||
- AI推导的结论
|
||||
|
||||
### 标注规则
|
||||
- 推理内容必须标注 **[猜测]**
|
||||
- 明确内容直接陈述
|
||||
|
||||
## 工具系统
|
||||
|
||||
### 记忆工具
|
||||
| 工具 | 功能 | 使用场景 |
|
||||
|------|------|---------|
|
||||
| `memory_recall` | 检索记忆 | 查询历史信息 |
|
||||
| `memory_commit` | 写入记忆 | 存储重要信息 |
|
||||
| `memory_purge` | 删除记忆 | 修正错误信息 |
|
||||
| `memory_introspect` | 查看状态 | 监控记忆系统 |
|
||||
|
||||
### 人设工具
|
||||
| 工具 | 功能 | 使用场景 |
|
||||
|------|------|---------|
|
||||
| `persona_update` | 更新人设 | 设置角色属性 |
|
||||
| `persona_clear` | 清除人设 | 恢复默认身份 |
|
||||
|
||||
### 任务工具
|
||||
| 工具 | 功能 | 使用场景 |
|
||||
|------|------|---------|
|
||||
| `task_create` | 创建任务 | 开始连续性任务 |
|
||||
| `task_set_state` | 设置状态 | 更新任务状态 |
|
||||
| `task_delete` | 删除任务 | 清理完成任务 |
|
||||
| `task_link_info` | 关联信息 | 连接任务与记忆 |
|
||||
|
||||
## 每轮对话强制要求
|
||||
|
||||
### ⚠️ 执行顺序(每轮必须)
|
||||
|
||||
由于没有传统上下文系统,必须通过图数据库维持对话连贯性。
|
||||
|
||||
#### 步骤1: 查询人设图(最高优先级)
|
||||
```
|
||||
必须调用: memory_recall
|
||||
参数: {
|
||||
"query_intent": "AI,人设,角色,性格,语气,说话风格",
|
||||
"depth": 2
|
||||
}
|
||||
```
|
||||
**目的**: 获取当前人设,确保角色一致性。
|
||||
**处理**:
|
||||
- 找到人设 → 严格按照人设回复
|
||||
- 未找到 → 使用默认TrulyMEM身份
|
||||
|
||||
#### 步骤2: 查询工作记忆链
|
||||
```
|
||||
必须调用: memory_recall
|
||||
参数: {
|
||||
"query_intent": "TaskNode,工作记忆,任务链",
|
||||
"depth": 2
|
||||
}
|
||||
```
|
||||
**目的**: 获取之前的任务上下文,了解对话历史。
|
||||
|
||||
#### 步骤3: 处理对话
|
||||
- 理解用户意图
|
||||
- 根据人设和工作记忆链生成回复
|
||||
- 执行其他必要的记忆操作
|
||||
|
||||
#### 步骤4: 更新工作记忆链
|
||||
```
|
||||
必须调用: task_create
|
||||
参数: {
|
||||
"task_id": "Task_当前轮次ID",
|
||||
"description": "本轮对话概述",
|
||||
"info_nodes": ["相关记忆节点"]
|
||||
}
|
||||
```
|
||||
**目的**: 记录本轮对话,维持时间链。
|
||||
|
||||
---
|
||||
|
||||
## 人设图机制
|
||||
|
||||
### 强制查询
|
||||
每轮对话开始时**必须**查询人设图,确保角色一致性。
|
||||
|
||||
### 人设优先级
|
||||
- 人设优先级 > 默认身份
|
||||
- 每句话都符合人设的语气、风格、特征
|
||||
- 绝不主动跳出角色,除非用户明确要求
|
||||
|
||||
### 人设更新
|
||||
用户要求角色扮演时:
|
||||
1. 使用 `persona_update` 更新人设
|
||||
2. 立即按照新人设回复
|
||||
|
||||
### 人设清除
|
||||
用户要求恢复默认身份时:
|
||||
1. 使用 `persona_clear` 清除人设
|
||||
2. 恢复为TrulyMEM默认身份
|
||||
|
||||
---
|
||||
|
||||
## 工作记忆链机制
|
||||
|
||||
#### 强制查询场景:
|
||||
|
||||
以下情况**必须**查询工作记忆链:
|
||||
|
||||
1. **用户提到"刚才"、"之前"、"上次"**
|
||||
- 例: "刚才我们聊了什么?"
|
||||
- 例: "继续刚才的话题"
|
||||
|
||||
2. **用户询问对话历史**
|
||||
- 例: "我们之前说了什么?"
|
||||
- 例: "我们聊过X吗?"
|
||||
|
||||
3. **连续性任务被打断后恢复**
|
||||
- 例: 用户突然回到之前的话题
|
||||
- 例: 用户要求继续之前的任务
|
||||
|
||||
4. **涉及上下文的引用**
|
||||
- 例: "那个东西"(需要查询上下文)
|
||||
- 例: "继续"(需要查询当前任务)
|
||||
|
||||
### 节点类型
|
||||
- **TaskNode** - 任务节点,存储任务概述
|
||||
- **StateNode** - 状态节点,存储任务状态
|
||||
- **InfoNode** - 信息节点,存储具体信息
|
||||
|
||||
### 边类型
|
||||
- **NEXT_TASK** - 时间链,连接任务节点
|
||||
- **HAS_STATE** - 状态,任务指向状态
|
||||
- **CONTAINS_INFO** - 信息,任务指向信息节点
|
||||
|
||||
### 任务状态
|
||||
- 进行中
|
||||
- 已完成
|
||||
- 已暂停
|
||||
- 已取消
|
||||
|
||||
## 自主性原则(在强制要求之外)
|
||||
|
||||
除了工作记忆链的强制要求外,你有权自主决定:
|
||||
|
||||
1. **是否查询其他记忆**
|
||||
- 用户询问历史 → 查询
|
||||
- 涉及之前内容 → 查询
|
||||
- 不确定时 → 可查询
|
||||
|
||||
2. **是否写入其他记忆**
|
||||
- 用户明确提到 → 必须写入
|
||||
- AI推理得到 → 可以写入,但是对应边上必须标注[推测]
|
||||
|
||||
3. **如何使用其他工具**
|
||||
- 根据上下文灵活选择
|
||||
- 避免过度使用
|
||||
- 保持自然对话
|
||||
|
||||
**注意**: 工作记忆链的强制要求不受自主性影响。
|
||||
|
||||
## 对话风格
|
||||
|
||||
- 自然、流畅
|
||||
- 避免机械式工具调用
|
||||
- 优先理解用户意图
|
||||
- 适时使用记忆增强体验
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 执行检查清单
|
||||
|
||||
每轮对话必须检查:
|
||||
|
||||
- [ ] 步骤1: 是否查询了人设图?
|
||||
- [ ] 步骤2: 是否查询了工作记忆链?
|
||||
- [ ] 步骤3: 是否根据人设和工作记忆链生成回复?
|
||||
- [ ] 步骤4: 是否更新了工作记忆链?
|
||||
- [ ] 涉及上下文引用时是否查询了工作记忆链?
|
||||
- [ ] 用户提到"刚才/之前/上次"时是否查询了工作记忆链?
|
||||
|
||||
---
|
||||
|
||||
**记住**:
|
||||
1. 图数据库是你记忆的唯一载体
|
||||
2. 人设图确保角色一致性(最高优先级)
|
||||
3. 工作记忆链维持对话连贯性
|
||||
4. 每轮必须按顺序执行:查询人设图 → 查询工作记忆链 → 处理对话 → 更新工作记忆链
|
||||
7
graph_memory_tui/core/tools/__init__.py
Normal file
7
graph_memory_tui/core/tools/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
工具定义模块
|
||||
"""
|
||||
from .memory_tools import TOOLS
|
||||
from .tool_executor import execute_tool
|
||||
|
||||
__all__ = ["TOOLS", "execute_tool"]
|
||||
323
graph_memory_tui/core/tools/memory_tools.py
Normal file
323
graph_memory_tui/core/tools/memory_tools.py
Normal file
@ -0,0 +1,323 @@
|
||||
"""
|
||||
记忆工具定义 - 优化版
|
||||
精简描述,避免过拟合,保留AI自主性
|
||||
"""
|
||||
|
||||
# 基础记忆工具
|
||||
MEMORY_TOOLS = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "memory_recall",
|
||||
"description": "检索记忆。支持关键词、时间范围、会话过滤。返回相关实体和关系。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query_intent": {
|
||||
"type": "string",
|
||||
"description": "查询意图,支持逗号分隔多个关键词"
|
||||
},
|
||||
"seed_entities": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "种子实体(可选)"
|
||||
},
|
||||
"depth": {
|
||||
"type": "integer",
|
||||
"description": "遍历深度,默认2"
|
||||
},
|
||||
"time_range": {
|
||||
"type": "object",
|
||||
"description": "时间范围(可选)",
|
||||
"properties": {
|
||||
"days": {"type": "integer", "description": "最近N天"}
|
||||
}
|
||||
},
|
||||
"session_filter": {
|
||||
"type": "string",
|
||||
"description": "会话ID过滤(可选)"
|
||||
}
|
||||
},
|
||||
"required": ["query_intent"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "memory_commit",
|
||||
"description": "写入记忆。将三元组写入图数据库,支持批量写入。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"triplets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subject": {"type": "string"},
|
||||
"relation": {"type": "string"},
|
||||
"object": {"type": "string"},
|
||||
"confidence": {"type": "number"}
|
||||
},
|
||||
"required": ["subject", "relation", "object"]
|
||||
},
|
||||
"description": "三元组列表"
|
||||
},
|
||||
"entity_types": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "实体类型(可选)"
|
||||
},
|
||||
"temporal_tag": {
|
||||
"type": "string",
|
||||
"description": "时间标记(可选)"
|
||||
}
|
||||
},
|
||||
"required": ["triplets"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "memory_purge",
|
||||
"description": "删除记忆。支持条件删除和纠错替代。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"criteria": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subject_contains": {"type": "string"},
|
||||
"relation_type": {"type": "string"},
|
||||
"target_contains": {"type": "string"},
|
||||
"time_before": {"type": "string"},
|
||||
"session_id": {"type": "string"}
|
||||
},
|
||||
"description": "删除条件"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": ["soft", "supersede"],
|
||||
"description": "删除模式:soft=逻辑删除, supersede=纠错替代",
|
||||
"default": "soft"
|
||||
},
|
||||
"new_relation": {
|
||||
"type": "object",
|
||||
"description": "新关系(supersede模式)",
|
||||
"properties": {
|
||||
"relation": {"type": "string"},
|
||||
"target": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["criteria"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "memory_introspect",
|
||||
"description": "查看记忆状态。返回会话统计、实体热点、关系分布。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"description": "会话ID(可选)"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "memory_archive",
|
||||
"description": "归档旧记忆。将N天前的非活跃关系标记为归档状态。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"days": {
|
||||
"type": "integer",
|
||||
"description": "归档天数,默认30"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "memory_cleanup",
|
||||
"description": "清理无效数据。物理删除已删除状态超过90天的关系和孤立节点。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dry_run": {
|
||||
"type": "boolean",
|
||||
"description": "仅预览不删除",
|
||||
"default": True
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# 人设图管理工具
|
||||
PERSONA_TOOLS = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "persona_update",
|
||||
"description": "更新人设。修改AI的角色、性格、语气等属性。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attribute": {"type": "string", "description": "属性名(如:扮演角色、说话风格、性格特点)"},
|
||||
"value": {"type": "string", "description": "属性值"}
|
||||
},
|
||||
"required": ["attribute", "value"]
|
||||
},
|
||||
"description": "人设属性列表"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": ["replace", "merge"],
|
||||
"description": "更新模式:replace=替换, merge=合并",
|
||||
"default": "merge"
|
||||
}
|
||||
},
|
||||
"required": ["attributes"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "persona_clear",
|
||||
"description": "清除人设。删除AI的角色设定,恢复默认身份。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"confirm": {
|
||||
"type": "boolean",
|
||||
"description": "确认清除",
|
||||
"default": True
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# 工作记忆链管理工具
|
||||
WORKING_MEMORY_TOOLS = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "task_create",
|
||||
"description": "创建任务节点。用于跟踪连续性任务。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task_id": {
|
||||
"type": "string",
|
||||
"description": "任务ID(如:Task_001)"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "任务概述"
|
||||
},
|
||||
"info_nodes": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "关联的信息节点名称(可选)"
|
||||
}
|
||||
},
|
||||
"required": ["task_id", "description"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "task_set_state",
|
||||
"description": "设置任务状态。支持:进行中、已完成、已暂停、已取消。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task_id": {
|
||||
"type": "string",
|
||||
"description": "任务ID"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": ["进行中", "已完成", "已暂停", "已取消"],
|
||||
"description": "任务状态"
|
||||
}
|
||||
},
|
||||
"required": ["task_id", "state"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "task_delete",
|
||||
"description": "删除任务节点。同时删除关联的信息节点。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task_id": {
|
||||
"type": "string",
|
||||
"description": "任务ID"
|
||||
},
|
||||
"delete_info_nodes": {
|
||||
"type": "boolean",
|
||||
"description": "是否删除关联的信息节点",
|
||||
"default": True
|
||||
}
|
||||
},
|
||||
"required": ["task_id"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "task_link_info",
|
||||
"description": "关联信息节点。将记忆节点关联到任务节点。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task_id": {
|
||||
"type": "string",
|
||||
"description": "任务ID"
|
||||
},
|
||||
"info_node_names": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "信息节点名称列表"
|
||||
}
|
||||
},
|
||||
"required": ["task_id", "info_node_names"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# 所有工具
|
||||
TOOLS = MEMORY_TOOLS + PERSONA_TOOLS + WORKING_MEMORY_TOOLS
|
||||
307
graph_memory_tui/core/tools/tool_executor.py
Normal file
307
graph_memory_tui/core/tools/tool_executor.py
Normal file
@ -0,0 +1,307 @@
|
||||
"""
|
||||
工具执行器
|
||||
"""
|
||||
import json
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
def execute_tool(graph: Any, tool_name: str, arguments: dict) -> str:
|
||||
"""执行工具调用"""
|
||||
print(f"\n[工具调用] {tool_name}")
|
||||
print(f"[参数] {json.dumps(arguments, ensure_ascii=False, indent=2)}")
|
||||
|
||||
try:
|
||||
# 基础记忆工具
|
||||
if tool_name == "memory_recall":
|
||||
result = graph.recall(
|
||||
query_intent=arguments.get("query_intent", ""),
|
||||
seed_entities=arguments.get("seed_entities"),
|
||||
depth=arguments.get("depth", 2),
|
||||
time_range=arguments.get("time_range"),
|
||||
session_filter=arguments.get("session_filter")
|
||||
)
|
||||
return format_recall_result(result)
|
||||
|
||||
elif tool_name == "memory_commit":
|
||||
result = graph.commit(
|
||||
triplets=arguments.get("triplets", []),
|
||||
entity_types=arguments.get("entity_types"),
|
||||
temporal_tag=arguments.get("temporal_tag")
|
||||
)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
elif tool_name == "memory_purge":
|
||||
result = graph.purge(
|
||||
criteria=arguments.get("criteria", {}),
|
||||
mode=arguments.get("mode", "soft"),
|
||||
new_relation=arguments.get("new_relation")
|
||||
)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
elif tool_name == "memory_introspect":
|
||||
result = graph.introspect(session_id=arguments.get("session_id"))
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
elif tool_name == "memory_archive":
|
||||
result = graph.archive(days=arguments.get("days", 30))
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
elif tool_name == "memory_cleanup":
|
||||
result = graph.cleanup(dry_run=arguments.get("dry_run", True))
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
# 人设图管理工具
|
||||
elif tool_name == "persona_update":
|
||||
result = execute_persona_update(graph, arguments)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
elif tool_name == "persona_clear":
|
||||
result = execute_persona_clear(graph, arguments)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
# 工作记忆链管理工具
|
||||
elif tool_name == "task_create":
|
||||
result = execute_task_create(graph, arguments)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
elif tool_name == "task_set_state":
|
||||
result = execute_task_set_state(graph, arguments)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
elif tool_name == "task_delete":
|
||||
result = execute_task_delete(graph, arguments)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
elif tool_name == "task_link_info":
|
||||
result = execute_task_link_info(graph, arguments)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
return f"未知工具: {tool_name}"
|
||||
|
||||
except Exception as e:
|
||||
return f"工具执行错误: {str(e)}"
|
||||
|
||||
|
||||
def format_recall_result(result: dict) -> str:
|
||||
"""格式化检索结果"""
|
||||
lines = ["===== 记忆检索结果 ====="]
|
||||
|
||||
if result.get("entities"):
|
||||
lines.append(f"\n实体 ({len(result['entities'])} 个):")
|
||||
for e in result["entities"]:
|
||||
if e and isinstance(e, dict):
|
||||
lines.append(f" - {e.get('name', 'N/A')} (类型: {e.get('type', 'unknown')}, 提及: {e.get('mention_count', 1)}次)")
|
||||
|
||||
if result.get("relations"):
|
||||
lines.append(f"\n关系 ({len(result['relations'])} 条):")
|
||||
for r in result["relations"]:
|
||||
if r and isinstance(r, dict):
|
||||
lines.append(f" - {r.get('source', 'N/A')} --[{r.get('type', 'N/A')}]--> {r.get('target', 'N/A')}")
|
||||
created = r.get("created_at", "N/A")
|
||||
if created and created != "N/A":
|
||||
created = created[:19] if "T" in str(created) else str(created)
|
||||
session_id = r.get('session_id', 'N/A')
|
||||
session_display = session_id[:20] if session_id and session_id != 'N/A' else 'N/A'
|
||||
lines.append(f" 时间: {created}, 会话: {session_display}, 轮次: {r.get('turn_id', 0)}, 置信度: {r.get('confidence', 1.0)}")
|
||||
|
||||
if not result.get("entities") and not result.get("relations"):
|
||||
lines.append("\n(未找到相关记忆)")
|
||||
|
||||
lines.append("=" * 30)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# 人设图管理工具实现
|
||||
def execute_persona_update(graph: Any, arguments: dict) -> dict:
|
||||
"""更新人设"""
|
||||
attributes = arguments.get("attributes", [])
|
||||
mode = arguments.get("mode", "merge")
|
||||
|
||||
if mode == "replace":
|
||||
# 先清除旧人设
|
||||
graph.purge(
|
||||
criteria={"subject_contains": "AI", "relation_type": "扮演角色"},
|
||||
mode="soft"
|
||||
)
|
||||
graph.purge(
|
||||
criteria={"subject_contains": "AI", "relation_type": "说话风格"},
|
||||
mode="soft"
|
||||
)
|
||||
graph.purge(
|
||||
criteria={"subject_contains": "AI", "relation_type": "性格特点"},
|
||||
mode="soft"
|
||||
)
|
||||
|
||||
# 写入新人设
|
||||
triplets = []
|
||||
for attr in attributes:
|
||||
triplets.append({
|
||||
"subject": "AI",
|
||||
"relation": attr["attribute"],
|
||||
"object": attr["value"],
|
||||
"confidence": 1.0
|
||||
})
|
||||
|
||||
result = graph.commit(triplets=triplets)
|
||||
return {
|
||||
"status": "success",
|
||||
"mode": mode,
|
||||
"updated_attributes": len(attributes),
|
||||
"details": result
|
||||
}
|
||||
|
||||
|
||||
def execute_persona_clear(graph: Any, arguments: dict) -> dict:
|
||||
"""清除人设"""
|
||||
if not arguments.get("confirm", True):
|
||||
return {"status": "cancelled", "message": "需要确认才能清除人设"}
|
||||
|
||||
# 删除所有人设相关关系
|
||||
result1 = graph.purge(
|
||||
criteria={"subject_contains": "AI", "relation_type": "扮演角色"},
|
||||
mode="soft"
|
||||
)
|
||||
result2 = graph.purge(
|
||||
criteria={"subject_contains": "AI", "relation_type": "说话风格"},
|
||||
mode="soft"
|
||||
)
|
||||
result3 = graph.purge(
|
||||
criteria={"subject_contains": "AI", "relation_type": "性格特点"},
|
||||
mode="soft"
|
||||
)
|
||||
result4 = graph.purge(
|
||||
criteria={"subject_contains": "AI", "relation_type": "语气特征"},
|
||||
mode="soft"
|
||||
)
|
||||
|
||||
total_deleted = (
|
||||
result1.get("deleted_count", 0) +
|
||||
result2.get("deleted_count", 0) +
|
||||
result3.get("deleted_count", 0) +
|
||||
result4.get("deleted_count", 0)
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"deleted_count": total_deleted,
|
||||
"message": "人设已清除,恢复默认身份"
|
||||
}
|
||||
|
||||
|
||||
# 工作记忆链管理工具实现
|
||||
def execute_task_create(graph: Any, arguments: dict) -> dict:
|
||||
"""创建任务节点"""
|
||||
task_id = arguments.get("task_id")
|
||||
description = arguments.get("description")
|
||||
info_nodes = arguments.get("info_nodes", [])
|
||||
|
||||
# 创建任务节点
|
||||
triplets = [
|
||||
{"subject": task_id, "relation": "is_type", "object": "TaskNode"},
|
||||
{"subject": task_id, "relation": "has_description", "object": description},
|
||||
{"subject": task_id, "relation": "HAS_STATE", "object": "State_进行中"}
|
||||
]
|
||||
|
||||
result = graph.commit(triplets=triplets)
|
||||
|
||||
# 关联信息节点
|
||||
if info_nodes:
|
||||
link_triplets = []
|
||||
for node_name in info_nodes:
|
||||
link_triplets.append({
|
||||
"subject": task_id,
|
||||
"relation": "CONTAINS_INFO",
|
||||
"object": node_name
|
||||
})
|
||||
graph.commit(triplets=link_triplets)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"task_id": task_id,
|
||||
"description": description,
|
||||
"info_nodes": info_nodes,
|
||||
"details": result
|
||||
}
|
||||
|
||||
|
||||
def execute_task_set_state(graph: Any, arguments: dict) -> dict:
|
||||
"""设置任务状态"""
|
||||
task_id = arguments.get("task_id")
|
||||
state = arguments.get("state")
|
||||
|
||||
# 删除旧状态
|
||||
graph.purge(
|
||||
criteria={"subject_contains": task_id, "relation_type": "HAS_STATE"},
|
||||
mode="soft"
|
||||
)
|
||||
|
||||
# 设置新状态
|
||||
state_node = f"State_{state}"
|
||||
result = graph.commit(
|
||||
triplets=[{"subject": task_id, "relation": "HAS_STATE", "object": state_node}]
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"task_id": task_id,
|
||||
"new_state": state,
|
||||
"details": result
|
||||
}
|
||||
|
||||
|
||||
def execute_task_delete(graph: Any, arguments: dict) -> dict:
|
||||
"""删除任务节点"""
|
||||
task_id = arguments.get("task_id")
|
||||
delete_info_nodes = arguments.get("delete_info_nodes", True)
|
||||
|
||||
# 查询关联的信息节点
|
||||
if delete_info_nodes:
|
||||
recall_result = graph.recall(
|
||||
query_intent=f"{task_id},CONTAINS_INFO",
|
||||
depth=1
|
||||
)
|
||||
|
||||
# 删除信息节点
|
||||
for relation in recall_result.get("relations", []):
|
||||
if relation.get("type") == "CONTAINS_INFO" and relation.get("source") == task_id:
|
||||
info_node = relation.get("target")
|
||||
graph.purge(
|
||||
criteria={"subject_contains": info_node},
|
||||
mode="soft"
|
||||
)
|
||||
|
||||
# 删除任务节点
|
||||
result = graph.purge(
|
||||
criteria={"subject_contains": task_id},
|
||||
mode="soft"
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"task_id": task_id,
|
||||
"deleted_info_nodes": delete_info_nodes,
|
||||
"details": result
|
||||
}
|
||||
|
||||
|
||||
def execute_task_link_info(graph: Any, arguments: dict) -> dict:
|
||||
"""关联信息节点"""
|
||||
task_id = arguments.get("task_id")
|
||||
info_node_names = arguments.get("info_node_names", [])
|
||||
|
||||
triplets = []
|
||||
for node_name in info_node_names:
|
||||
triplets.append({
|
||||
"subject": task_id,
|
||||
"relation": "CONTAINS_INFO",
|
||||
"object": node_name
|
||||
})
|
||||
|
||||
result = graph.commit(triplets=triplets)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"task_id": task_id,
|
||||
"linked_nodes": info_node_names,
|
||||
"details": result
|
||||
}
|
||||
70
install.bat
Normal file
70
install.bat
Normal file
@ -0,0 +1,70 @@
|
||||
@echo off
|
||||
title TrulyMEM - Installation
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo TrulyMEM - Installation Script
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
REM Check Python
|
||||
echo [1/4] Checking Python...
|
||||
python --version >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [X] Python not found
|
||||
echo.
|
||||
echo Please install Python 3.8+
|
||||
echo Download: https://www.python.org/downloads/
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
python --version
|
||||
echo [OK] Python installed
|
||||
echo.
|
||||
|
||||
REM Create virtual environment
|
||||
echo [2/4] Creating virtual environment...
|
||||
if exist "venv" (
|
||||
echo [SKIP] Virtual environment already exists
|
||||
) else (
|
||||
python -m venv venv
|
||||
if errorlevel 1 (
|
||||
echo [X] Failed to create virtual environment
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Virtual environment created
|
||||
)
|
||||
echo.
|
||||
|
||||
REM Activate and install dependencies
|
||||
echo [3/4] Installing dependencies...
|
||||
call venv\Scripts\activate.bat
|
||||
pip install --upgrade pip >nul 2>&1
|
||||
pip install -r requirements.txt
|
||||
if errorlevel 1 (
|
||||
echo [X] Failed to install dependencies
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Dependencies installed
|
||||
echo.
|
||||
|
||||
REM Create config file
|
||||
echo [4/4] Creating config file...
|
||||
if not exist "config.json" (
|
||||
echo {"api_key": "", "model": "deepseek-chat", "base_url": "https://api.deepseek.com"} > config.json
|
||||
echo [OK] Config file created
|
||||
) else (
|
||||
echo [SKIP] Config file already exists
|
||||
)
|
||||
echo.
|
||||
|
||||
echo ========================================
|
||||
echo Installation Complete!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo Now you can run start.bat to launch the app
|
||||
echo.
|
||||
pause
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 MiB |
73
start.bat
73
start.bat
@ -1,75 +1,26 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
title TrulyMEM
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Graph Memory TUI - Quick Start
|
||||
echo TrulyMEM Starting...
|
||||
echo ========================================
|
||||
echo.
|
||||
echo [INFO] Using embedded database (no Docker needed)
|
||||
echo.
|
||||
|
||||
REM Step 1: Check Python
|
||||
echo [Step 1/3] Checking Python...
|
||||
python --version >nul 2>&1
|
||||
python trulymem_entry.py
|
||||
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Python not found
|
||||
echo [INFO] Install from: https://www.python.org/downloads/
|
||||
echo.
|
||||
echo [ERROR] Failed to start
|
||||
echo.
|
||||
echo Please check:
|
||||
echo 1. Python installed (python --version)
|
||||
echo 2. Dependencies installed (pip install -r requirements.txt)
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Python found
|
||||
|
||||
REM Step 2: Setup Virtual Environment
|
||||
echo.
|
||||
echo [Step 2/3] Setting up environment...
|
||||
if not exist "venv" (
|
||||
echo [INFO] Creating venv...
|
||||
python -m venv venv
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Failed to create venv
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Venv created
|
||||
) else (
|
||||
echo [OK] Venv exists
|
||||
)
|
||||
|
||||
call venv\Scripts\activate.bat
|
||||
|
||||
pip show textual >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [INFO] Installing dependencies...
|
||||
pip install -r requirements.txt
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Failed to install deps
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Dependencies installed
|
||||
) else (
|
||||
echo [OK] Dependencies ready
|
||||
)
|
||||
|
||||
REM Step 3: Start Application
|
||||
echo.
|
||||
echo [Step 3/3] Starting application...
|
||||
echo.
|
||||
echo ========================================
|
||||
echo All systems ready!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo Database: Embedded SQLite (graph_memory.db)
|
||||
echo No Docker required!
|
||||
echo.
|
||||
echo Starting TUI...
|
||||
echo.
|
||||
|
||||
python -m graph_memory_tui.main
|
||||
|
||||
call venv\Scripts\deactivate.bat
|
||||
|
||||
echo.
|
||||
echo Application closed.
|
||||
echo Application exited
|
||||
pause
|
||||
|
||||
422
start.py
422
start.py
@ -1,422 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
跨平台一键启动脚本 - 支持多语言
|
||||
自动启动 Docker (WSL/Desktop)、Neo4j、安装依赖、启动应用
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import platform
|
||||
import os
|
||||
import locale
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# 多语言支持
|
||||
LANGUAGES = {
|
||||
'zh_CN': {
|
||||
'title': 'Graph Memory TUI - 一键启动',
|
||||
'step': '步骤',
|
||||
'checking_docker': '检查 Docker...',
|
||||
'docker_not_found': 'Docker 未找到,尝试启动...',
|
||||
'starting_docker_wsl': '通过 WSL 启动 Docker...',
|
||||
'starting_docker_desktop': '启动 Docker Desktop...',
|
||||
'waiting_docker': '等待 Docker 启动...',
|
||||
'still_waiting': '仍在等待... ({current}/{timeout})',
|
||||
'docker_started': 'Docker 启动成功',
|
||||
'docker_is_running': 'Docker 正在运行',
|
||||
'docker_failed': 'Docker 启动失败!',
|
||||
'install_docker': '请安装 Docker: https://docs.docker.com/get-docker/',
|
||||
'starting_neo4j': '启动 Neo4j 数据库...',
|
||||
'creating_neo4j': '创建 Neo4j 容器...',
|
||||
'neo4j_created': 'Neo4j 容器已创建',
|
||||
'neo4j_started': 'Neo4j 容器已启动',
|
||||
'neo4j_running': 'Neo4j 容器已在运行',
|
||||
'waiting_neo4j': '等待 Neo4j 就绪...',
|
||||
'neo4j_failed': 'Neo4j 启动失败!',
|
||||
'checking_python': '检查 Python...',
|
||||
'python_found': 'Python 已找到',
|
||||
'setting_venv': '设置虚拟环境...',
|
||||
'creating_venv': '创建虚拟环境...',
|
||||
'venv_created': '虚拟环境已创建',
|
||||
'venv_exists': '虚拟环境已存在',
|
||||
'installing_deps': '安装依赖包...',
|
||||
'deps_installed': '依赖包已安装',
|
||||
'deps_exist': '依赖包已安装',
|
||||
'deps_failed': '依赖包安装失败!',
|
||||
'starting_app': '启动应用...',
|
||||
'all_ready': '所有系统就绪!',
|
||||
'neo4j_connection': 'Neo4j 连接信息:',
|
||||
'browser': '浏览器',
|
||||
'user': '用户名',
|
||||
'pass': '密码',
|
||||
'app_closed': '应用已关闭',
|
||||
'error': '错误',
|
||||
'interrupted': '用户中断',
|
||||
},
|
||||
'en_US': {
|
||||
'title': 'Graph Memory TUI - One-Click Start',
|
||||
'step': 'Step',
|
||||
'checking_docker': 'Checking Docker...',
|
||||
'docker_not_found': 'Docker not found, trying to start...',
|
||||
'starting_docker_wsl': 'Starting Docker via WSL...',
|
||||
'starting_docker_desktop': 'Starting Docker Desktop...',
|
||||
'waiting_docker': 'Waiting for Docker to start...',
|
||||
'still_waiting': 'Still waiting... ({current}/{timeout})',
|
||||
'docker_started': 'Docker started successfully',
|
||||
'docker_is_running': 'Docker is running',
|
||||
'docker_failed': 'Docker failed to start!',
|
||||
'install_docker': 'Please install Docker: https://docs.docker.com/get-docker/',
|
||||
'starting_neo4j': 'Starting Neo4j database...',
|
||||
'creating_neo4j': 'Creating Neo4j container...',
|
||||
'neo4j_created': 'Neo4j container created',
|
||||
'neo4j_started': 'Neo4j container started',
|
||||
'neo4j_running': 'Neo4j container already running',
|
||||
'waiting_neo4j': 'Waiting for Neo4j to be ready...',
|
||||
'neo4j_failed': 'Neo4j failed to start!',
|
||||
'checking_python': 'Checking Python...',
|
||||
'python_found': 'Python found',
|
||||
'setting_venv': 'Setting up virtual environment...',
|
||||
'creating_venv': 'Creating virtual environment...',
|
||||
'venv_created': 'Virtual environment created',
|
||||
'venv_exists': 'Virtual environment exists',
|
||||
'installing_deps': 'Installing dependencies...',
|
||||
'deps_installed': 'Dependencies installed',
|
||||
'deps_exist': 'Dependencies already installed',
|
||||
'deps_failed': 'Failed to install dependencies!',
|
||||
'starting_app': 'Starting application...',
|
||||
'all_ready': 'All systems ready!',
|
||||
'neo4j_connection': 'Neo4j Connection:',
|
||||
'browser': 'Browser',
|
||||
'user': 'User',
|
||||
'pass': 'Password',
|
||||
'app_closed': 'Application closed',
|
||||
'error': 'Error',
|
||||
'interrupted': 'Interrupted by user',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_language():
|
||||
"""获取系统语言"""
|
||||
try:
|
||||
# 尝试获取系统语言
|
||||
lang = locale.getdefaultlocale()[0]
|
||||
|
||||
if lang and lang.startswith('zh'):
|
||||
return 'zh_CN'
|
||||
else:
|
||||
return 'en_US'
|
||||
except:
|
||||
return 'en_US'
|
||||
|
||||
|
||||
# 全局语言设置
|
||||
LANG = get_language()
|
||||
TEXT = LANGUAGES[LANG]
|
||||
|
||||
|
||||
def run_command(cmd, check=True, capture_output=True):
|
||||
"""运行命令"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
check=check,
|
||||
capture_output=capture_output,
|
||||
text=True
|
||||
)
|
||||
return result.returncode == 0, result.stdout, result.stderr
|
||||
except subprocess.CalledProcessError as e:
|
||||
return False, e.stdout, e.stderr
|
||||
|
||||
|
||||
def print_step(step, total, message):
|
||||
"""打印步骤信息"""
|
||||
print(f"\n[{TEXT['step']} {step}/{total}] {message}")
|
||||
|
||||
|
||||
def print_ok(message):
|
||||
"""打印成功信息"""
|
||||
print(f"[OK] {message}")
|
||||
|
||||
|
||||
def print_error(message):
|
||||
"""打印错误信息"""
|
||||
print(f"[ERROR] {message}")
|
||||
|
||||
|
||||
def print_info(message):
|
||||
"""打印信息"""
|
||||
print(f"[INFO] {message}")
|
||||
|
||||
|
||||
def check_docker():
|
||||
"""检查Docker"""
|
||||
success, _, _ = run_command("docker --version", check=False)
|
||||
return success
|
||||
|
||||
|
||||
def start_docker_wsl():
|
||||
"""通过WSL启动Docker"""
|
||||
print_info(TEXT['starting_docker_wsl'])
|
||||
|
||||
# 检查WSL是否安装
|
||||
success, _, _ = run_command("wsl --list", check=False)
|
||||
if not success:
|
||||
return False
|
||||
|
||||
# 启动WSL中的Docker
|
||||
success, _, _ = run_command("wsl -d docker-desktop", check=False)
|
||||
if success:
|
||||
return True
|
||||
|
||||
# 尝试启动docker服务
|
||||
success, _, _ = run_command("wsl sudo service docker start", check=False)
|
||||
return success
|
||||
|
||||
|
||||
def start_docker_desktop():
|
||||
"""启动Docker Desktop"""
|
||||
print_info(TEXT['starting_docker_desktop'])
|
||||
|
||||
docker_paths = [
|
||||
r"C:\Program Files\Docker\Docker\Docker Desktop.exe",
|
||||
r"C:\Program Files (x86)\Docker\Docker\Docker Desktop.exe",
|
||||
]
|
||||
|
||||
for path in docker_paths:
|
||||
if os.path.exists(path):
|
||||
subprocess.Popen([path], shell=True)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def start_docker():
|
||||
"""启动Docker"""
|
||||
system = platform.system()
|
||||
|
||||
if system == "Windows":
|
||||
# Windows: 优先尝试WSL
|
||||
print_info(TEXT['docker_not_found'])
|
||||
|
||||
# 检查WSL是否可用
|
||||
success, _, _ = run_command("wsl --list", check=False)
|
||||
|
||||
if success:
|
||||
# 使用WSL启动Docker
|
||||
if start_docker_wsl():
|
||||
return True
|
||||
|
||||
# WSL不可用,尝试Docker Desktop
|
||||
if start_docker_desktop():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
elif system == "Darwin":
|
||||
# macOS: 启动 Docker
|
||||
print_info(TEXT['starting_docker_desktop'])
|
||||
subprocess.Popen(["open", "-a", "Docker"])
|
||||
return True
|
||||
|
||||
else:
|
||||
# Linux: 启动 Docker daemon
|
||||
print_info(TEXT['starting_docker_desktop'])
|
||||
success, _, _ = run_command("sudo systemctl start docker", check=False)
|
||||
return success
|
||||
|
||||
|
||||
def wait_for_docker(timeout=60):
|
||||
"""等待Docker启动"""
|
||||
print_info(TEXT['waiting_docker'])
|
||||
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
success, _, _ = run_command("docker info", check=False)
|
||||
if success:
|
||||
return True
|
||||
time.sleep(2)
|
||||
elapsed = int(time.time() - start_time)
|
||||
print(f" {TEXT['still_waiting'].format(current=elapsed, timeout=timeout)}")
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def start_neo4j():
|
||||
"""启动Neo4j"""
|
||||
# 检查容器是否存在
|
||||
success, output, _ = run_command("docker ps -a | grep neo4j", check=False)
|
||||
|
||||
if not success:
|
||||
# 创建新容器
|
||||
print_info(TEXT['creating_neo4j'])
|
||||
cmd = """docker run -d --name neo4j -p 7474:7474 -p 7687:7687 \
|
||||
-e NEO4J_AUTH=neo4j/graphmemory123 \
|
||||
-e NEO4J_PLUGINS='["apoc"]' neo4j:latest"""
|
||||
success, _, _ = run_command(cmd, check=False)
|
||||
|
||||
if not success:
|
||||
return False
|
||||
print_ok(TEXT['neo4j_created'])
|
||||
else:
|
||||
# 检查是否运行
|
||||
success, _, _ = run_command("docker ps | grep neo4j", check=False)
|
||||
|
||||
if not success:
|
||||
# 启动容器
|
||||
print_info(TEXT['starting_neo4j'])
|
||||
success, _, _ = run_command("docker start neo4j", check=False)
|
||||
|
||||
if not success:
|
||||
return False
|
||||
print_ok(TEXT['neo4j_started'])
|
||||
else:
|
||||
print_ok(TEXT['neo4j_running'])
|
||||
|
||||
# 等待Neo4j就绪
|
||||
print_info(TEXT['waiting_neo4j'])
|
||||
time.sleep(5)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def setup_venv():
|
||||
"""设置虚拟环境"""
|
||||
venv_path = Path("venv")
|
||||
|
||||
if not venv_path.exists():
|
||||
print_info(TEXT['creating_venv'])
|
||||
success, _, _ = run_command(f"{sys.executable} -m venv venv", check=False)
|
||||
|
||||
if not success:
|
||||
return False
|
||||
print_ok(TEXT['venv_created'])
|
||||
else:
|
||||
print_ok(TEXT['venv_exists'])
|
||||
|
||||
# 激活虚拟环境
|
||||
system = platform.system()
|
||||
|
||||
if system == "Windows":
|
||||
pip_path = venv_path / "Scripts" / "pip"
|
||||
python_path = venv_path / "Scripts" / "python"
|
||||
else:
|
||||
pip_path = venv_path / "bin" / "pip"
|
||||
python_path = venv_path / "bin" / "python"
|
||||
|
||||
# 检查依赖
|
||||
success, _, _ = run_command(f"{pip_path} show textual", check=False)
|
||||
|
||||
if not success:
|
||||
print_info(TEXT['installing_deps'])
|
||||
success, _, _ = run_command(f"{pip_path} install -r requirements.txt", check=False)
|
||||
|
||||
if not success:
|
||||
return False
|
||||
print_ok(TEXT['deps_installed'])
|
||||
else:
|
||||
print_ok(TEXT['deps_exist'])
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def start_app():
|
||||
"""启动应用"""
|
||||
system = platform.system()
|
||||
|
||||
if system == "Windows":
|
||||
python_path = Path("venv/Scripts/python")
|
||||
else:
|
||||
python_path = Path("venv/bin/python")
|
||||
|
||||
print_info(TEXT['starting_app'])
|
||||
|
||||
# 直接运行,不捕获输出
|
||||
subprocess.run([str(python_path), "-m", "graph_memory_tui.main"])
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("\n" + "=" * 50)
|
||||
print(f" {TEXT['title']}")
|
||||
print("=" * 50 + "\n")
|
||||
|
||||
total_steps = 5
|
||||
|
||||
# Step 1: Docker
|
||||
print_step(1, total_steps, TEXT['checking_docker'])
|
||||
|
||||
if not check_docker():
|
||||
if not start_docker():
|
||||
print_error(TEXT['docker_failed'])
|
||||
print(TEXT['install_docker'])
|
||||
sys.exit(1)
|
||||
|
||||
if not wait_for_docker():
|
||||
print_error(TEXT['docker_failed'])
|
||||
sys.exit(1)
|
||||
|
||||
print_ok(TEXT['docker_started'])
|
||||
else:
|
||||
# 检查Docker daemon是否运行
|
||||
success, _, _ = run_command("docker info", check=False)
|
||||
|
||||
if not success:
|
||||
if not start_docker():
|
||||
print_error(TEXT['docker_failed'])
|
||||
sys.exit(1)
|
||||
|
||||
if not wait_for_docker():
|
||||
print_error(TEXT['docker_failed'])
|
||||
sys.exit(1)
|
||||
|
||||
print_ok(TEXT['docker_is_running'])
|
||||
|
||||
# Step 2: Neo4j
|
||||
print_step(2, total_steps, TEXT['starting_neo4j'])
|
||||
|
||||
if not start_neo4j():
|
||||
print_error(TEXT['neo4j_failed'])
|
||||
sys.exit(1)
|
||||
|
||||
# Step 3: Python
|
||||
print_step(3, total_steps, TEXT['checking_python'])
|
||||
print_ok(f"{TEXT['python_found']} {sys.version.split()[0]}")
|
||||
|
||||
# Step 4: Virtual Environment
|
||||
print_step(4, total_steps, TEXT['setting_venv'])
|
||||
|
||||
if not setup_venv():
|
||||
print_error(TEXT['deps_failed'])
|
||||
sys.exit(1)
|
||||
|
||||
# Step 5: Start Application
|
||||
print_step(5, total_steps, TEXT['starting_app'])
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(f" {TEXT['all_ready']}")
|
||||
print("=" * 50)
|
||||
print(f"\n{TEXT['neo4j_connection']}")
|
||||
print(f" - {TEXT['browser']}: http://localhost:7474")
|
||||
print(" - Bolt: bolt://localhost:7687")
|
||||
print(f" - {TEXT['user']}: neo4j")
|
||||
print(f" - {TEXT['pass']}: graphmemory123")
|
||||
print(f"\n{TEXT['starting_app']}\n")
|
||||
|
||||
start_app()
|
||||
|
||||
print(f"\n{TEXT['app_closed']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n\n{TEXT['interrupted']}")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"\n[ERROR] {TEXT['error']}: {e}")
|
||||
sys.exit(1)
|
||||
160
start.sh
160
start.sh
@ -1,160 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Graph Memory TUI - One-Click Start"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Step 1: Check and Start Docker
|
||||
echo "[Step 1/5] Checking Docker..."
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo -e "${RED}[ERROR] Docker not found!${NC}"
|
||||
echo "Please install Docker from: https://docs.docker.com/get-docker/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if Docker daemon is running
|
||||
if ! docker info &> /dev/null; then
|
||||
echo -e "${YELLOW}[INFO] Docker daemon not running, trying to start...${NC}"
|
||||
|
||||
# Try to start Docker daemon
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
open -a Docker
|
||||
else
|
||||
# Linux
|
||||
sudo systemctl start docker
|
||||
fi
|
||||
|
||||
# Wait for Docker to start
|
||||
echo "[INFO] Waiting for Docker to start..."
|
||||
count=0
|
||||
while ! docker info &> /dev/null; do
|
||||
sleep 2
|
||||
((count++))
|
||||
if [ $count -gt 30 ]; then
|
||||
echo -e "${RED}[ERROR] Docker failed to start after 60 seconds${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo "[INFO] Still waiting... ($count/30)"
|
||||
done
|
||||
echo -e "${GREEN}[OK] Docker started successfully${NC}"
|
||||
else
|
||||
echo -e "${GREEN}[OK] Docker is running${NC}"
|
||||
fi
|
||||
|
||||
# Step 2: Start Neo4j
|
||||
echo ""
|
||||
echo "[Step 2/5] Starting Neo4j database..."
|
||||
|
||||
# Check if neo4j container exists
|
||||
if ! docker ps -a | grep -q neo4j; then
|
||||
echo "[INFO] Creating Neo4j container..."
|
||||
docker run -d \
|
||||
--name neo4j \
|
||||
-p 7474:7474 \
|
||||
-p 7687:7687 \
|
||||
-e NEO4J_AUTH=neo4j/graphmemory123 \
|
||||
-e NEO4J_PLUGINS='["apoc"]' \
|
||||
neo4j:latest
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}[ERROR] Failed to create Neo4j container${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}[OK] Neo4j container created${NC}"
|
||||
else
|
||||
# Check if running
|
||||
if ! docker ps | grep -q neo4j; then
|
||||
echo "[INFO] Starting existing Neo4j container..."
|
||||
docker start neo4j
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}[ERROR] Failed to start Neo4j container${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}[OK] Neo4j container started${NC}"
|
||||
else
|
||||
echo -e "${GREEN}[OK] Neo4j container already running${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Wait for Neo4j to be ready
|
||||
echo "[INFO] Waiting for Neo4j to be ready..."
|
||||
sleep 5
|
||||
|
||||
# Step 3: Check Python
|
||||
echo ""
|
||||
echo "[Step 3/5] Checking Python..."
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
echo -e "${RED}[ERROR] Python3 not found!${NC}"
|
||||
echo "Please install Python 3.8+ from: https://www.python.org/downloads/"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}[OK] Python found${NC}"
|
||||
|
||||
# Step 4: Setup Virtual Environment
|
||||
echo ""
|
||||
echo "[Step 4/5] Setting up virtual environment..."
|
||||
|
||||
if [ ! -d "venv" ]; then
|
||||
echo "[INFO] Creating virtual environment..."
|
||||
python3 -m venv venv
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}[ERROR] Failed to create virtual environment${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}[OK] Virtual environment created${NC}"
|
||||
else
|
||||
echo -e "${GREEN}[OK] Virtual environment exists${NC}"
|
||||
fi
|
||||
|
||||
# Activate venv
|
||||
source venv/bin/activate
|
||||
|
||||
# Check dependencies
|
||||
if ! pip show textual &> /dev/null; then
|
||||
echo "[INFO] Installing dependencies..."
|
||||
pip install -r requirements.txt
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}[ERROR] Failed to install dependencies${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}[OK] Dependencies installed${NC}"
|
||||
else
|
||||
echo -e "${GREEN}[OK] Dependencies already installed${NC}"
|
||||
fi
|
||||
|
||||
# Step 5: Start Application
|
||||
echo ""
|
||||
echo "[Step 5/5] Starting Graph Memory TUI..."
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " All systems ready!"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Neo4j Connection:"
|
||||
echo " - Browser: http://localhost:7474"
|
||||
echo " - Bolt: bolt://localhost:7687"
|
||||
echo " - User: neo4j"
|
||||
echo " - Pass: graphmemory123"
|
||||
echo ""
|
||||
echo "Starting TUI application..."
|
||||
echo ""
|
||||
|
||||
python -m graph_memory_tui.main
|
||||
|
||||
# Cleanup
|
||||
deactivate
|
||||
|
||||
echo ""
|
||||
echo "Application closed."
|
||||
@ -1,83 +0,0 @@
|
||||
"""
|
||||
测试工作记忆链机制
|
||||
"""
|
||||
import sys
|
||||
sys.path.insert(0, 'e:/program/graph_enable_ability')
|
||||
|
||||
from graph_memory_tui.core.optimized_operations import OPTIMIZED_SYSTEM_PROMPT
|
||||
|
||||
def test_working_memory_chain():
|
||||
"""测试工作记忆链机制是否正确添加"""
|
||||
|
||||
print("=" * 60)
|
||||
print("工作记忆链机制测试")
|
||||
print("=" * 60)
|
||||
|
||||
# 测试1: 检查核心概念
|
||||
assert "工作记忆链机制" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到工作记忆链机制"
|
||||
print("[OK] 测试1通过: 工作记忆链机制已添加")
|
||||
|
||||
# 测试2: 检查节点类型
|
||||
assert "TaskNode" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到TaskNode"
|
||||
assert "StateNode" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到StateNode"
|
||||
assert "普通记忆节点" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到普通记忆节点"
|
||||
assert "InfoNode" not in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] InfoNode应该被移除"
|
||||
print("[OK] 测试2通过: 所有节点类型已正确定义(使用普通记忆节点)")
|
||||
|
||||
# 测试3: 检查边类型
|
||||
assert "NEXT_TASK" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到NEXT_TASK"
|
||||
assert "HAS_STATE" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到HAS_STATE"
|
||||
assert "CONTAINS_INFO" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到CONTAINS_INFO"
|
||||
assert "SUB_TASK" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到SUB_TASK"
|
||||
print("[OK] 测试3通过: 所有边类型已定义")
|
||||
|
||||
# 测试4: 检查强制执行规则
|
||||
assert "强制执行规则" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到强制执行规则"
|
||||
assert "每轮对话开始时" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到每轮对话开始时"
|
||||
assert "每轮对话结束时" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到每轮对话结束时"
|
||||
print("[OK] 测试4通过: 强制执行规则已定义")
|
||||
|
||||
# 测试5: 检查连续性任务处理
|
||||
assert "连续性任务处理" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到连续性任务处理"
|
||||
assert "成语接龙" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到成语接龙示例"
|
||||
print("[OK] 测试5通过: 连续性任务处理已定义")
|
||||
|
||||
# 测试6: 检查任务状态转换
|
||||
assert "State_进行中" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到State_进行中"
|
||||
assert "State_已完成" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到State_已完成"
|
||||
assert "State_已暂停" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到State_已暂停"
|
||||
print("[OK] 测试6通过: 任务状态转换已定义")
|
||||
|
||||
# 测试7: 检查核心职责更新
|
||||
assert "维护工作记忆链" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到维护工作记忆链"
|
||||
print("[OK] 测试7通过: 核心职责已更新")
|
||||
|
||||
# 测试8: 检查示例说明
|
||||
assert "第一轮:用户发起游戏" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到第一轮示例"
|
||||
assert "第二轮:话题被打断" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到第二轮示例"
|
||||
assert "第三轮:用户要求继续游戏" in OPTIMIZED_SYSTEM_PROMPT, "[ERROR] 未找到第三轮示例"
|
||||
print("[OK] 测试8通过: 完整示例已添加")
|
||||
|
||||
print("=" * 60)
|
||||
print("所有测试通过! 工作记忆链机制已成功添加到提示词中")
|
||||
print("=" * 60)
|
||||
|
||||
# 统计信息
|
||||
total_length = len(OPTIMIZED_SYSTEM_PROMPT)
|
||||
working_memory_length = len("工作记忆链机制")
|
||||
|
||||
print(f"\n提示词总长度: {total_length} 字符")
|
||||
print(f"工作记忆链机制部分约占: {working_memory_length / total_length * 100:.2f}%")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
test_working_memory_chain()
|
||||
print("\n[SUCCESS] 测试成功!")
|
||||
except AssertionError as e:
|
||||
print(f"\n[ERROR] 测试失败: {e}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n[ERROR] 发生错误: {e}")
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user