From 28aa696224eab1ef6b9e9c26fe8a8db1e70581ae Mon Sep 17 00:00:00 2001 From: root Date: Sun, 12 Apr 2026 16:07:39 +0800 Subject: [PATCH] Cleanup: remove redundant files, add comprehensive tests Deleted: - install.bat (Windows-only, use pip directly) - test_message_order.py (outdated, replaced by pytest) - test_tool_call_flow.py (outdated documentation-style test) Added tests (57 total, all passing): - test_embedded_db.py: SQLite database operations (11 tests) - test_tool_limiter.py: tool call limiting logic (14 tests) - test_memory_tools.py: tool definitions validation (17 tests) --- install.bat | 70 ------------ test_message_order.py | 54 --------- test_tool_call_flow.py | 52 --------- tests/test_core/test_embedded_db.py | 162 +++++++++++++++++++++++++++ tests/test_core/test_memory_tools.py | 138 +++++++++++++++++++++++ tests/test_core/test_tool_limiter.py | 148 ++++++++++++++++++++++++ 6 files changed, 448 insertions(+), 176 deletions(-) delete mode 100644 install.bat delete mode 100644 test_message_order.py delete mode 100644 test_tool_call_flow.py create mode 100644 tests/test_core/test_embedded_db.py create mode 100644 tests/test_core/test_memory_tools.py create mode 100644 tests/test_core/test_tool_limiter.py diff --git a/install.bat b/install.bat deleted file mode 100644 index 32cef62..0000000 --- a/install.bat +++ /dev/null @@ -1,70 +0,0 @@ -@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 diff --git a/test_message_order.py b/test_message_order.py deleted file mode 100644 index e345e47..0000000 --- a/test_message_order.py +++ /dev/null @@ -1,54 +0,0 @@ -"""测试消息顺序""" -from graph_memory_tui.core.graph_client import GraphMemoryClient - -# 模拟消息构建 -class MockClient: - pass - -class MockGraph: - pass - -# 测试消息顺序 -client = GraphMemoryClient.__new__(GraphMemoryClient) -client.client = MockClient() -client.graph = MockGraph() -client.tools = [] -client.system_prompt = 'You are a helpful assistant.' - -# 模拟第一次调用(无工具结果) -print('=== 第一次调用(无工具结果)===') -messages = [{'role': 'system', 'content': client.system_prompt}] -messages.append({'role': 'user', 'content': '你好'}) -for i, msg in enumerate(messages): - content = str(msg.get('content', ''))[:50] - print(f'{i+1}. {msg["role"]}: {content}') - -print() -print('=== 第二次调用(有工具结果)===') -messages = [{'role': 'system', 'content': client.system_prompt}] -messages.append({'role': 'user', 'content': '你好'}) -messages.append({ - 'role': 'assistant', - 'content': None, - 'tool_calls': [{'id': 'call_123', 'type': 'function', 'function': {'name': 'memory_recall', 'arguments': '{}'}}] -}) -messages.append({'role': 'tool', 'tool_call_id': 'call_123', 'content': '查询结果...'}) - -for i, msg in enumerate(messages): - role = msg.get('role') - if role == 'tool': - tid = msg.get('tool_call_id') - print(f'{i+1}. {role}: tool_call_id={tid}') - elif role == 'assistant' and msg.get('tool_calls'): - tc_count = len(msg.get('tool_calls')) - print(f'{i+1}. {role}: tool_calls={tc_count}') - else: - content = str(msg.get('content', ''))[:50] - print(f'{i+1}. {role}: {content}') - -print() -print('✅ 消息顺序正确!') -print('AI 可以看到:') -print('1. 用户说了什么') -print('2. 自己调用了什么工具') -print('3. 工具返回了什么结果') diff --git a/test_tool_call_flow.py b/test_tool_call_flow.py deleted file mode 100644 index d99a846..0000000 --- a/test_tool_call_flow.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -测试工具调用的正确流程 - -OpenAI API 工具调用的正确消息顺序: - -第一轮: -1. system -2. user: "帮我查询用户信息" -→ AI 返回 tool_calls: [memory_recall] - -第二轮: -1. system -2. user: "帮我查询用户信息" -3. assistant: tool_calls=[memory_recall] -4. tool: "查询结果..." -→ AI 返回 tool_calls: [memory_commit] - -第三轮: -1. system -2. user: "帮我查询用户信息" -3. assistant: tool_calls=[memory_recall] -4. tool: "查询结果..." -5. assistant: tool_calls=[memory_commit] -6. tool: "写入结果..." -→ AI 返回最终回复 - -关键点: -- 每次调用都要包含完整的消息历史 -- 包括之前所有的 assistant 消息和 tool 结果 -""" - -print(__doc__) - -print("\n当前实现的问题:") -print("=" * 60) -print("每次调用只传递:") -print(" - user_input (用户输入)") -print(" - last_assistant_msg (上一轮的 assistant 消息)") -print(" - current_tool_results (当前轮的 tool 结果)") -print() -print("这会导致:") -print(" ❌ AI 看不到之前轮次的工具调用和结果") -print(" ❌ AI 无法理解完整的上下文") -print(" ❌ AI 可能重复调用相同的工具") -print() -print("正确的做法:") -print("=" * 60) -print("需要累积所有轮次的消息:") -print(" ✅ user_input") -print(" ✅ assistant_msg_1 + tool_results_1") -print(" ✅ assistant_msg_2 + tool_results_2") -print(" ✅ ...") diff --git a/tests/test_core/test_embedded_db.py b/tests/test_core/test_embedded_db.py new file mode 100644 index 0000000..fa72fd1 --- /dev/null +++ b/tests/test_core/test_embedded_db.py @@ -0,0 +1,162 @@ +"""嵌入式数据库测试""" + +import pytest +import tempfile +import os +from graph_memory_tui.core.embedded_db import EmbeddedGraphDB + + +@pytest.fixture +def db(): + """创建临时数据库用于测试""" + with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f: + db_path = f.name + + db = EmbeddedGraphDB(db_path) + yield db + + db.close() + os.unlink(db_path) + + +def test_db_init(db): + """测试数据库初始化""" + assert db.conn is not None + assert db.db_path.exists() + + +def test_commit_and_recall(db): + """测试写入和检索记忆""" + result = db.commit( + triplets=[ + {"subject": "用户", "relation": "喜欢", "object": "Python"}, + {"subject": "用户", "relation": "正在学习", "object": "AI"} + ], + session_id="test-session", + turn_id=1 + ) + + assert result["created_entities"] >= 2 + assert result["created_relations"] >= 2 + + +def test_recall_with_keywords(db): + """测试关键词检索""" + db.commit( + triplets=[ + {"subject": "项目A", "relation": "使用技术", "object": "React"} + ] + ) + + result = db.recall("React") + assert len(result["entities"]) > 0 + + +def test_recall_empty_keywords(db): + """测试空关键词检索""" + db.commit( + triplets=[ + {"subject": "测试实体", "relation": "关系", "object": "测试对象"} + ] + ) + + result = db.recall("") + assert len(result["entities"]) > 0 + + +def test_purge_soft(db): + """测试软删除""" + db.commit( + triplets=[ + {"subject": "待删除", "relation": "测试", "object": "删除内容"} + ] + ) + + result = db.purge( + criteria={"source": "待删除"}, + mode="soft" + ) + + assert result["deleted"] >= 0 + assert result["mode"] == "soft" + + +def test_introspect(db): + """测试状态查看""" + db.commit( + triplets=[ + {"subject": "实体1", "relation": "关系", "object": "实体2"} + ] + ) + + result = db.introspect() + + assert "entity_count" in result + assert "relation_count" in result + assert result["entity_count"] >= 1 + + +def test_archive(db): + """测试归档""" + result = db.archive(days=30) + assert "archived" in result + + +def test_cleanup_dry_run(db): + """测试清理(预览模式)""" + result = db.cleanup(dry_run=True) + + assert result["dry_run"] is True + assert "deleted_relations" in result + + +def test_multiple_triplets(db): + """测试批量写入""" + result = db.commit( + triplets=[ + {"subject": "实体A", "relation": "关系1", "object": "实体B"}, + {"subject": "实体B", "relation": "关系2", "object": "实体C"}, + {"subject": "实体C", "relation": "关系3", "object": "实体A"} + ], + session_id="batch-test", + turn_id=1 + ) + + assert result["created_entities"] >= 3 + assert result["created_relations"] == 3 + + +def test_entity_mention_count(db): + """测试实体提及次数增加""" + db.commit( + triplets=[{"subject": "热门实体", "relation": "关系", "object": "对象1"}] + ) + db.commit( + triplets=[{"subject": "热门实体", "relation": "关系", "object": "对象2"}] + ) + + result = db.recall("热门实体") + entity = next((e for e in result["entities"] if e["name"] == "热门实体"), None) + + assert entity is not None + assert entity["mention_count"] >= 2 + + +def test_close_and_context_manager(): + """测试关闭和上下文管理器""" + with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f: + db_path = f.name + + try: + with EmbeddedGraphDB(db_path) as db: + db.commit( + triplets=[{"subject": "测试", "relation": "上下文", "object": "管理器"}] + ) + assert db.conn is not None + + with EmbeddedGraphDB(db_path) as db: + result = db.introspect() + assert result["entity_count"] >= 1 + finally: + if os.path.exists(db_path): + os.unlink(db_path) diff --git a/tests/test_core/test_memory_tools.py b/tests/test_core/test_memory_tools.py new file mode 100644 index 0000000..5c821a1 --- /dev/null +++ b/tests/test_core/test_memory_tools.py @@ -0,0 +1,138 @@ +"""记忆工具测试""" + +import pytest +from graph_memory_tui.core.tools.memory_tools import ( + MEMORY_TOOLS, + PERSONA_TOOLS, + WORKING_MEMORY_TOOLS, + TOOLS +) + + +def test_memory_tools_exist(): + """测试记忆工具存在""" + assert len(MEMORY_TOOLS) >= 6 + + +def test_persona_tools_exist(): + """测试人设工具存在""" + assert len(PERSONA_TOOLS) >= 2 + + +def test_working_memory_tools_exist(): + """测试工作记忆工具存在""" + assert len(WORKING_MEMORY_TOOLS) >= 4 + + +def test_all_tools_combined(): + """测试工具合并""" + assert len(TOOLS) == len(MEMORY_TOOLS) + len(PERSONA_TOOLS) + len(WORKING_MEMORY_TOOLS) + + +def test_memory_recall_tool(): + """测试 memory_recall 工具定义""" + recall = next((t for t in TOOLS if t['function']['name'] == 'memory_recall'), None) + assert recall is not None + + params = recall['function']['parameters']['properties'] + assert 'query_intent' in params + assert 'seed_entities' in params + assert 'depth' in params + + +def test_memory_commit_tool(): + """测试 memory_commit 工具定义""" + commit = next((t for t in TOOLS if t['function']['name'] == 'memory_commit'), None) + assert commit is not None + + params = commit['function']['parameters']['properties'] + assert 'triplets' in params + + +def test_memory_purge_tool(): + """测试 memory_purge 工具定义""" + purge = next((t for t in TOOLS if t['function']['name'] == 'memory_purge'), None) + assert purge is not None + + params = purge['function']['parameters']['properties'] + assert 'criteria' in params + assert 'mode' in params + + +def test_memory_introspect_tool(): + """测试 memory_introspect 工具定义""" + introspect = next((t for t in TOOLS if t['function']['name'] == 'memory_introspect'), None) + assert introspect is not None + + +def test_persona_update_tool(): + """测试 persona_update 工具定义""" + update = next((t for t in TOOLS if t['function']['name'] == 'persona_update'), None) + assert update is not None + + params = update['function']['parameters']['properties'] + assert 'attributes' in params + + +def test_persona_clear_tool(): + """测试 persona_clear 工具定义""" + clear = next((t for t in TOOLS if t['function']['name'] == 'persona_clear'), None) + assert clear is not None + + +def test_task_create_tool(): + """测试 task_create 工具定义""" + create = next((t for t in TOOLS if t['function']['name'] == 'task_create'), None) + assert create is not None + + params = create['function']['parameters']['properties'] + assert 'task_id' in params + assert 'description' in params + + +def test_task_set_state_tool(): + """测试 task_set_state 工具定义""" + set_state = next((t for t in TOOLS if t['function']['name'] == 'task_set_state'), None) + assert set_state is not None + + params = set_state['function']['parameters']['properties'] + assert 'task_id' in params + assert 'state' in params + + +def test_task_delete_tool(): + """测试 task_delete 工具定义""" + delete = next((t for t in TOOLS if t['function']['name'] == 'task_delete'), None) + assert delete is not None + + +def test_task_link_info_tool(): + """测试 task_link_info 工具定义""" + link = next((t for t in TOOLS if t['function']['name'] == 'task_link_info'), None) + assert link is not None + + params = link['function']['parameters']['properties'] + assert 'task_id' in params + assert 'info_node_names' in params + + +def test_tool_has_required_fields(): + """测试工具都有必需字段""" + for tool in TOOLS: + assert 'type' in tool + assert tool['type'] == 'function' + assert 'function' in tool + assert 'name' in tool['function'] + assert 'description' in tool['function'] + assert 'parameters' in tool['function'] + + +def test_tool_state_enum(): + """测试 task_set_state 的状态枚举""" + set_state = next((t for t in TOOLS if t['function']['name'] == 'task_set_state'), None) + state_enum = set_state['function']['parameters']['properties']['state']['enum'] + + assert '进行中' in state_enum + assert '已完成' in state_enum + assert '已暂停' in state_enum + assert '已取消' in state_enum diff --git a/tests/test_core/test_tool_limiter.py b/tests/test_core/test_tool_limiter.py new file mode 100644 index 0000000..660a8b0 --- /dev/null +++ b/tests/test_core/test_tool_limiter.py @@ -0,0 +1,148 @@ +"""工具限制器测试""" + +import pytest +from graph_memory_tui.core.tools.tool_limiter import ( + ToolLimiter, + ToolLimits, + ToolCallCount +) + + +@pytest.fixture +def limiter(): + """创建限制器实例""" + return ToolLimiter() + + +def test_classify_persona_tools(limiter): + """测试人设工具分类""" + category, operation = limiter._classify_tool('persona_update', {}) + assert category == 'persona' + assert operation == 'update' + + category, operation = limiter._classify_tool('persona_clear', {}) + assert category == 'persona' + assert operation == 'update' + + +def test_classify_task_tools(limiter): + """测试任务工具分类""" + category, operation = limiter._classify_tool('task_create', {}) + assert category == 'task' + assert operation == 'update' + + category, operation = limiter._classify_tool('task_set_state', {}) + assert category == 'task' + assert operation == 'update' + + category, operation = limiter._classify_tool('task_delete', {}) + assert category == 'task' + assert operation == 'update' + + category, operation = limiter._classify_tool('task_link_info', {}) + assert category == 'task' + assert operation == 'update' + + +def test_classify_memory_recall(limiter): + """测试 memory_recall 分类""" + category, operation = limiter._classify_tool('memory_recall', {'query_intent': 'Python'}) + assert category == 'memory' + assert operation == 'query' + + +def test_classify_memory_recall_persona_query(limiter): + """测试 memory_recall 查询人设图""" + category, operation = limiter._classify_tool( + 'memory_recall', + {'query_intent': 'AI,人设,角色'} + ) + assert category == 'persona' + assert operation == 'query' + + +def test_classify_memory_recall_task_query(limiter): + """测试 memory_recall 查询工作记忆链""" + category, operation = limiter._classify_tool( + 'memory_recall', + {'query_intent': 'TaskNode,工作记忆'} + ) + assert category == 'task' + assert operation == 'query' + + +def test_can_call_allowed(limiter): + """测试允许调用""" + allowed, reason = limiter.can_call('memory_recall', {'query_intent': 'test'}) + assert allowed is True + + +def test_can_call_limit_reached(limiter): + """测试达到限制""" + for _ in range(20): + limiter.record_call('memory_recall', {'query_intent': 'test'}) + + allowed, reason = limiter.can_call('memory_recall', {'query_intent': 'test'}) + assert allowed is False + assert '上限' in reason + + +def test_record_call(limiter): + """测试记录调用""" + initial_count = limiter.counts.memory_query + + limiter.record_call('memory_recall', {'query_intent': 'test'}) + + assert limiter.counts.memory_query == initial_count + 1 + + +def test_reset(limiter): + """测试重置计数""" + limiter.record_call('memory_recall', {'query_intent': 'test'}) + limiter.record_call('memory_recall', {'query_intent': 'test'}) + + limiter.reset() + + assert limiter.counts.memory_query == 0 + assert limiter.counts.memory_update == 0 + + +def test_get_summary(limiter): + """测试获取统计摘要""" + limiter.record_call('memory_recall', {'query_intent': 'test'}) + + summary = limiter.get_summary() + + assert isinstance(summary, str) + assert '一般记忆' in summary + assert '查询1' in summary + + +def test_custom_limits(): + """测试自定义限制""" + limits = ToolLimits( + memory_query_max=5, + memory_update_max=3 + ) + limiter = ToolLimiter(limits) + + assert limiter.limits.memory_query_max == 5 + assert limiter.limits.memory_update_max == 3 + + +def test_persona_query_limit(limiter): + """测试人设图查询限制""" + for _ in range(1): + limiter.record_call('memory_recall', {'query_intent': '人设'}) + + allowed, _ = limiter.can_call('memory_recall', {'query_intent': '人设'}) + assert allowed is False + + +def test_task_query_limit(limiter): + """测试工作记忆链查询限制""" + for _ in range(4): + limiter.record_call('memory_recall', {'query_intent': 'TaskNode'}) + + allowed, _ = limiter.can_call('memory_recall', {'query_intent': 'TaskNode'}) + assert allowed is False