Refactor: Modularize tools and prompts system

This commit is contained in:
JianFeeeee
2026-04-11 16:50:33 +08:00
parent 2804ec6080
commit 8910053ea0
18 changed files with 1388 additions and 1911 deletions

5
.gitignore vendored
View File

@ -29,6 +29,8 @@ env/
# IDE
.vscode/
.idea/
.arts/
.codeartsdoer/
*.swp
*.swo
*~
@ -60,3 +62,6 @@ dist/
# Temporary
*.tmp
*.bak
# AI Generated
jimeng*.png

View File

@ -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()

View File

@ -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)

File diff suppressed because it is too large Load Diff

View 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

View File

@ -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,

View File

@ -0,0 +1,6 @@
"""
提示词管理模块
"""
from .prompt_manager import PromptManager
__all__ = ["PromptManager"]

View 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` - 关联信息
## 自主性
你有权根据对话上下文自主决定:
- 是否需要查询记忆
- 是否需要写入记忆
- 是否需要维护任务链
- 如何使用工具
记住:灵活应对,保持自然对话体验。"""

View 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. 每轮必须按顺序执行:查询人设图 → 查询工作记忆链 → 处理对话 → 更新工作记忆链

View File

@ -0,0 +1,7 @@
"""
工具定义模块
"""
from .memory_tools import TOOLS
from .tool_executor import execute_tool
__all__ = ["TOOLS", "execute_tool"]

View 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": "任务IDTask_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

View 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
View 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

View File

@ -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
View File

@ -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
View File

@ -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."

View File

@ -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)