From 5ae771f35717d35f8fb56d4fe9f1b2fca0319061 Mon Sep 17 00:00:00 2001 From: wangyu-ustc <1294837640@qq.com> Date: Thu, 4 Sep 2025 11:53:53 -0700 Subject: [PATCH 1/9] fix a minor bug with openai query --- mirix/llm_api/openai_client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mirix/llm_api/openai_client.py b/mirix/llm_api/openai_client.py index 667cae45..b802b8c2 100644 --- a/mirix/llm_api/openai_client.py +++ b/mirix/llm_api/openai_client.py @@ -158,6 +158,10 @@ def build_request_data( except ValueError as e: logger.warning(f"Failed to convert tool function to structured output, tool={tool}, error={e}") + else: + # When there are no tools, delete tool_choice entirely from the request + delattr(data, 'tool_choice') + return data.model_dump(exclude_unset=True) def fill_image_content_in_messages(self, openai_message_list): From bdea322c92d8b03e691ca2a21f1a4aa65cb67f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=AE=81=E7=A4=BE?= <524395609@qq.com> Date: Sun, 7 Sep 2025 15:50:13 +0800 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=E6=94=B9=E8=BF=9B=E5=86=85?= =?UTF-8?q?=E5=AD=98=E6=B5=8B=E8=AF=95=E7=B3=BB=E7=BB=9F=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=92=8C=E4=BC=98=E9=9B=85=E9=99=8D=E7=BA=A7=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要改���: 1. ������������������ BUILD_EMBEDDINGS_FOR_MEMORY ������������������������������������������ 2. ���������������������������������������������������������������������������BM25������ 3. ��������������������������������������������������������������������� 4. ������������������������������������������������������ 5. ������������������������������������������������������������������������������������ ������������: - ������ constants.py ������������������������������������������ - ������ episodic_memory_manager.py ��������������������������� - ������ test_memory.py ��� delete_event_by_id ������������������ - ������ run_specific_memory_test.py ������������������ - ������ MEMORY_TEST_USAGE.md ������������������ ������������������������������������������������������������������������������������������������ --- mirix/constants.py | 2 +- mirix/services/episodic_memory_manager.py | 6 +- tests/MEMORY_TEST_USAGE.md | 290 ++++++++++++++++++++++ tests/__init__.py | 6 + tests/run_specific_memory_test.py | 141 +++++++++++ tests/test_memory.py | 288 ++++++++++++++++++++- 6 files changed, 730 insertions(+), 3 deletions(-) create mode 100644 tests/MEMORY_TEST_USAGE.md create mode 100644 tests/__init__.py create mode 100644 tests/run_specific_memory_test.py diff --git a/mirix/constants.py b/mirix/constants.py index 2585d447..13530ec1 100644 --- a/mirix/constants.py +++ b/mirix/constants.py @@ -194,4 +194,4 @@ CHAINING_FOR_MEMORY_UPDATE = False LOAD_IMAGE_CONTENT_FOR_LAST_MESSAGE_ONLY = False -BUILD_EMBEDDINGS_FOR_MEMORY = True \ No newline at end of file +BUILD_EMBEDDINGS_FOR_MEMORY = os.getenv("BUILD_EMBEDDINGS_FOR_MEMORY", "true").lower() in ("true", "1", "yes", "on") \ No newline at end of file diff --git a/mirix/services/episodic_memory_manager.py b/mirix/services/episodic_memory_manager.py index c7a2f303..67b5babf 100755 --- a/mirix/services/episodic_memory_manager.py +++ b/mirix/services/episodic_memory_manager.py @@ -374,8 +374,12 @@ def list_episodic_memory(self, EpisodicEvent.user_id == actor.id ) - if search_method == 'embedding': + # 根据环境变量调整搜索方法 + if search_method == 'embedding' and not BUILD_EMBEDDINGS_FOR_MEMORY: + print(f"⚠️ 警告: 请求嵌入搜索但环境变量 BUILD_EMBEDDINGS_FOR_MEMORY=false,改用BM25搜索") + search_method = 'bm25' + if search_method == 'embedding': embed_query = True embedding_config = agent_state.embedding_config diff --git a/tests/MEMORY_TEST_USAGE.md b/tests/MEMORY_TEST_USAGE.md new file mode 100644 index 00000000..9cbb3b99 --- /dev/null +++ b/tests/MEMORY_TEST_USAGE.md @@ -0,0 +1,290 @@ +# MIRIX 内存系统指定测试使用指南 + +## 概述 + +现在您可以使用新的指定测试功能来运行单个或多个内存测试,而不需要运行所有测试。这大大提高了测试的灵活性和效率。 + +## 新增功能 + +### 1. `run_specific_memory_test(test_name, agent=None, delete_after_test=True)` + +运行指定的单个内存测试函数。 + +**参数:** +- `test_name` (str): 要运行的测试名称 +- `agent` (AgentWrapper, optional): AgentWrapper实例,如果为None则自动创建 +- `delete_after_test` (bool): 是否在测试后清理测试数据,默认为True + +**返回值:** +- `bool`: 测试是否成功完成 + +### 2. `run_multiple_memory_tests(test_names, agent=None, delete_after_test=True)` + +运行多个指定的内存测试函数。 + +**参数:** +- `test_names` (list): 要运行的测试名称列表 +- `agent` (AgentWrapper, optional): AgentWrapper实例,如果为None则自动创建 +- `delete_after_test` (bool): 是否在测试后清理测试数据,默认为True + +**返回值:** +- `dict`: 每个测试的结果 {'test_name': success_bool} + +## 使用方法 + +### 方法1: 在Python代码中直接调用 + +```python +from tests.test_memory import run_specific_memory_test, run_multiple_memory_tests + +# 运行单个测试(默认清理测试数据) +success = run_specific_memory_test('episodic_memory_direct') +if success: + print("测试通过!") +else: + print("测试失败!") + +# 运行单个测试(保留测试数据) +success = run_specific_memory_test('episodic_memory_direct', delete_after_test=False) + +# 运行多个测试(默认清理测试数据) +results = run_multiple_memory_tests([ + 'episodic_memory_direct', + 'procedural_memory_direct', + 'resource_memory_direct' +]) + +# 运行多个测试(保留测试数据) +results = run_multiple_memory_tests([ + 'episodic_memory_direct', + 'procedural_memory_direct' +], delete_after_test=False) + +# 检查结果 +for test_name, success in results.items(): + print(f"{test_name}: {'通过' if success else '失败'}") +``` + +### 方法2: 使用独立的测试脚本 + +```bash +# 从项目根目录运行 +python tests/run_specific_memory_test.py episodic_memory_direct + +# 运行多个测试 +python tests/run_specific_memory_test.py episodic_memory_direct procedural_memory_direct resource_memory_direct + +# 运行所有直接内存操作测试 +python tests/run_specific_memory_test.py all_direct_memory_operations + +# 运行搜索相关测试 +python tests/run_specific_memory_test.py search_methods fts5_comprehensive + +# 保留测试数据(不清理) +python tests/run_specific_memory_test.py episodic_memory_direct --keep-data + +# 查看帮助 +python -m tests.run_specific_memory_test --help + +# 或者进入tests目录运行 +cd tests +python -m testsrun_specific_memory_test episodic_memory_direct +``` + +### 方法3: 修改 test_memory.py 主函数 + +在 `test_memory.py` 文件的 `if __name__ == "__main__":` 部分,取消注释相应的测试调用: + +```python +if __name__ == "__main__": + # 运行单个测试 + run_specific_memory_test('episodic_memory_direct') + + # 或者运行多个测试 + # run_multiple_memory_tests([ + # 'episodic_memory_direct', + # 'procedural_memory_direct', + # 'resource_memory_direct' + # ]) +``` + +## 可用的测试名称 + +### 直接内存操作 (manager methods) +- `episodic_memory_direct`: 测试情节记忆直接操作 +- `procedural_memory_direct`: 测试程序记忆直接操作 +- `resource_memory_direct`: 测试资源记忆直接操作 +- `knowledge_vault_direct`: 测试知识库直接操作 +- `semantic_memory_direct`: 测试语义记忆直接操作 +- `resource_memory_update_direct`: 测试资源记忆更新直接操作 +- `tree_path_functionality_direct`: 测试树形路径功能直接操作 + +### 间接内存操作 (message-based) +- `episodic_memory_indirect`: 测试情节记忆间接操作 +- `procedural_memory_indirect`: 测试程序记忆间接操作 +- `resource_memory_indirect`: 测试资源记忆间接操作 +- `knowledge_vault_indirect`: 测试知识库间接操作 +- `semantic_memory_indirect`: 测试语义记忆间接操作 +- `resource_memory_update_indirect`: 测试资源记忆更新间接操作 + +### 搜索和性能测试 +- `search_methods`: 测试不同搜索方法 +- `fts5_comprehensive`: 测试FTS5综合功能 +- `fts5_performance_comparison`: 测试FTS5性能对比 +- `fts5_advanced_features`: 测试FTS5高级功能 +- `text_only_memorization`: 测试纯文本记忆功能 + +### 核心记忆测试 +- `core_memory_update_using_chat_agent`: 测试使用聊天代理更新核心记忆 + +### 文件处理测试 +- `greeting_with_files`: 测试文件处理功能 +- `file_types`: 测试不同文件类型 +- `file_with_memory`: 测试带记忆的文件处理 + +### 综合测试 +- `all_direct_memory_operations`: 运行所有直接内存操作测试 +- `all_indirect_memory_operations`: 运行所有间接内存操作测试 +- `all_search_and_performance_operations`: 运行所有搜索和性能测试 +- `all_memories`: 运行所有内存测试 + +## 使用场景示例 + +### 场景1: 开发时快速测试特定功能 +```python +# 只测试情节记忆功能 +run_specific_memory_test('episodic_memory_direct') +``` + +### 场景2: 测试所有直接操作 +```python +# 测试所有直接内存操作,不包含消息传递 +run_specific_memory_test('all_direct_memory_operations') +``` + +### 场景3: 性能测试 +```python +# 只运行搜索和性能相关测试 +run_multiple_memory_tests([ + 'search_methods', + 'fts5_performance_comparison', + 'fts5_advanced_features' +]) +``` + +### 场景4: 调试特定问题 +```python +# 如果怀疑某个特定内存类型有问题 +run_specific_memory_test('resource_memory_direct') +``` + +## 优势 + +1. **快速反馈**: 只运行需要的测试,节省时间 +2. **精确调试**: 可以针对特定功能进行测试 +3. **灵活组合**: 可以自由组合不同的测试 +4. **易于集成**: 可以轻松集成到CI/CD流程中 +5. **详细报告**: 提供详细的测试结果和摘要 + +## 测试数据清理机制 + +### 自动清理机制 + +MIRIX 的测试系统具有智能的数据清理机制: + +#### **直接内存操作测试** +- 每个直接操作测试函数都会在测试完成后自动清理创建的测试数据 +- 例如:`test_episodic_memory_direct` 会删除插入的事件记录 +- 例如:`test_resource_memory_direct` 会删除插入的资源记录 + +#### **间接内存操作测试** +- 间接操作测试(通过消息传递)通常**不清理数据** +- 这些数据会保留在数据库中,供后续测试使用 +- 这是为了测试消息传递和记忆累积的效果 + +#### **搜索和性能测试** +- 这些测试通常不创建持久数据,因此不需要清理 + +### 控制清理行为 + +您可以通过 `delete_after_test` 参数控制是否清理测试数据: + +```python +# 默认行为:清理测试数据 +run_specific_memory_test('episodic_memory_direct') + +# 保留测试数据 +run_specific_memory_test('episodic_memory_direct', delete_after_test=False) + +# 命令行方式:保留测试数据 +python tests/run_specific_memory_test.py episodic_memory_direct --keep-data +``` + +### 清理策略说明 + +1. **直接操作测试**: 数据在测试函数内部清理,`delete_after_test` 参数主要影响额外的清理逻辑 +2. **间接操作测试**: 通常保留数据,`delete_after_test=False` 时明确保留 +3. **搜索测试**: 不创建持久数据,无需清理 +4. **综合测试**: 继承各个子测试的清理行为 + +## 嵌入模型配置 + +### 控制嵌入向量计算 + +MIRIX 支持通过环境变量控制是否计算嵌入向量: + +```bash +# 禁用嵌入向量计算(推荐用于测试) +export BUILD_EMBEDDINGS_FOR_MEMORY=false + +# 启用嵌入向量计算(默认) +export BUILD_EMBEDDINGS_FOR_MEMORY=true +``` + +### 搜索方法说明 + +MIRIX 支持多种搜索方法,严格按照环境变量配置执行: + +#### **1. `embedding` 搜索** +- **需要**: 嵌入模型API密钥配置 + `BUILD_EMBEDDINGS_FOR_MEMORY=true` +- **优势**: 语义相似度搜索,理解上下文 +- **行为**: 如果环境变量不支持,给出警告并使用 `bm25` 搜索 + +#### **2. `bm25` 搜索(推荐)** +- **需要**: 无需额外配置 +- **优势**: 使用数据库原生全文搜索,性能优异 +- **支持**: PostgreSQL 和 SQLite + +#### **3. `string_match` 搜索** +- **需要**: 无需额外配置 +- **优势**: 简单字符串匹配,快速可靠 +- **适用**: 精确匹配场景 + +### 测试建议 + +```bash +# 推荐:禁用嵌入向量计算进行测试 +export BUILD_EMBEDDINGS_FOR_MEMORY=false +python -m tests.run_specific_memory_test episodic_memory_direct + +# 启用嵌入向量计算(需要配置API密钥) +export BUILD_EMBEDDINGS_FOR_MEMORY=true +export GEMINI_API_KEY=your_api_key_here +python -m tests.run_specific_memory_test episodic_memory_direct +``` + +### 行为说明 + +- **插入时**: 严格按照 `BUILD_EMBEDDINGS_FOR_MEMORY` 环境变量决定是否计算嵌入向量 +- **查询时**: 如果请求嵌入搜索但环境变量不支持,会显示警告并使用 BM25 搜索 +- **无自动降级**: 不再有异常捕获和自动降级,完全按照配置执行 + +## 注意事项 + +1. 某些测试(如文件处理测试)需要特定的测试文件存在 +2. 测试会使用现有的数据库连接配置 +3. 建议在测试环境中运行,避免影响生产数据 +4. 如果测试失败,会显示详细的错误信息和堆栈跟踪 +5. **测试数据清理**: 默认情况下会清理测试数据,使用 `--keep-data` 或 `delete_after_test=False` 可以保留数据 +6. **间接操作测试**: 通常保留数据以测试记忆累积效果,这是正常行为 +7. **嵌入模型配置**: 严格按照环境变量 `BUILD_EMBEDDINGS_FOR_MEMORY` 执行,不支持自动降级 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..73b7e450 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,6 @@ +# MIRIX Tests Package +""" +MIRIX 测试包 + +包含所有测试相关的模块和工具。 +""" \ No newline at end of file diff --git a/tests/run_specific_memory_test.py b/tests/run_specific_memory_test.py new file mode 100644 index 00000000..94976882 --- /dev/null +++ b/tests/run_specific_memory_test.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +""" +MIRIX 内存系统指定测试运行器 + +这个脚本允许您运行特定的内存测试,而不需要运行所有测试。 + +使用方法: + # 从项目根目录运行 + python tests/run_specific_memory_test.py + + # 或者进入tests目录运行 + cd tests + python run_specific_memory_test.py + +或者运行多个测试: + python tests/run_specific_memory_test.py ... + +示例: + # 运行情节记忆直接操作测试 + python tests/run_specific_memory_test.py episodic_memory_direct + + # 运行多个直接操作测试 + python tests/run_specific_memory_test.py episodic_memory_direct procedural_memory_direct resource_memory_direct + + # 运行所有直接内存操作测试 + python tests/run_specific_memory_test.py all_direct_memory_operations + + # 运行搜索相关测试 + python tests/run_specific_memory_test.py search_methods fts5_comprehensive + + # 保留测试数据(不清理) + python tests/run_specific_memory_test.py episodic_memory_direct --keep-data + +可用的测试名称: + # 直接内存操作 (manager methods) + - episodic_memory_direct: 测试情节记忆直接操作 + - procedural_memory_direct: 测试程序记忆直接操作 + - resource_memory_direct: 测试资源记忆直接操作 + - knowledge_vault_direct: 测试知识库直接操作 + - semantic_memory_direct: 测试语义记忆直接操作 + - resource_memory_update_direct: 测试资源记忆更新直接操作 + - tree_path_functionality_direct: 测试树形路径功能直接操作 + + # 间接内存操作 (message-based) + - episodic_memory_indirect: 测试情节记忆间接操作 + - procedural_memory_indirect: 测试程序记忆间接操作 + - resource_memory_indirect: 测试资源记忆间接操作 + - knowledge_vault_indirect: 测试知识库间接操作 + - semantic_memory_indirect: 测试语义记忆间接操作 + - resource_memory_update_indirect: 测试资源记忆更新间接操作 + + # 搜索和性能测试 + - search_methods: 测试不同搜索方法 + - fts5_comprehensive: 测试FTS5综合功能 + - fts5_performance_comparison: 测试FTS5性能对比 + - fts5_advanced_features: 测试FTS5高级功能 + - text_only_memorization: 测试纯文本记忆功能 + + # 核心记忆测试 + - core_memory_update_using_chat_agent: 测试使用聊天代理更新核心记忆 + + # 文件处理测试 + - greeting_with_files: 测试文件处理功能 + - file_types: 测试不同文件类型 + - file_with_memory: 测试带记忆的文件处理 + + # 综合测试 + - all_direct_memory_operations: 运行所有直接内存操作测试 + - all_indirect_memory_operations: 运行所有间接内存操作测试 + - all_search_and_performance_operations: 运行所有搜索和性能测试 + - all_memories: 运行所有内存测试 +""" + +import sys +import os + +# 添加项目根目录到Python路径 +project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +sys.path.insert(0, project_root) + +# 导入测试函数 +from tests.test_memory import run_specific_memory_test, run_multiple_memory_tests + +def print_usage(): + """打印使用说明""" + print(__doc__) + +def main(): + """主函数""" + if len(sys.argv) < 2: + print("❌ 请提供至少一个测试名称") + print_usage() + sys.exit(1) + + # 获取测试名称列表 + test_names = sys.argv[1:] + + # 检查是否是帮助请求 + if any(arg in ['-h', '--help', 'help'] for arg in test_names): + print_usage() + sys.exit(0) + + print(f"🎯 准备运行 {len(test_names)} 个测试") + print(f"测试列表: {', '.join(test_names)}") + print("="*80) + + # 检查是否要保留测试数据 + delete_after_test = True + if '--keep-data' in test_names: + delete_after_test = False + test_names.remove('--keep-data') + print("⚠️ 将保留测试数据(不进行清理)") + + # 运行测试 + if len(test_names) == 1: + # 单个测试 + test_name = test_names[0] + success = run_specific_memory_test(test_name, delete_after_test=delete_after_test) + + if success: + print(f"\n🎉 测试 '{test_name}' 成功完成!") + sys.exit(0) + else: + print(f"\n💥 测试 '{test_name}' 失败!") + sys.exit(1) + else: + # 多个测试 + results = run_multiple_memory_tests(test_names, delete_after_test=delete_after_test) + + # 检查是否有失败的测试 + failed_tests = [name for name, success in results.items() if not success] + + if not failed_tests: + print(f"\n🎉 所有 {len(test_names)} 个测试都成功完成!") + sys.exit(0) + else: + print(f"\n💥 {len(failed_tests)} 个测试失败: {', '.join(failed_tests)}") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/tests/test_memory.py b/tests/test_memory.py index 90b5cd53..15573d9d 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -1417,6 +1417,19 @@ def test_episodic_memory_direct(agent): test_tracker.fail_subtest(e, subtest_idx) # Don't raise here as this might be expected behavior + # Test 4: Cleanup - Delete the test event + subtest_idx = test_tracker.start_subtest("Direct Event Cleanup") + try: + agent.client.server.episodic_memory_manager.delete_event_by_id( + id=event.id, + actor=agent.client.user + ) + print(f"Cleaned up test event with ID: {event.id}") + test_tracker.pass_subtest(subtest_idx, "Test event cleaned up successfully") + except Exception as e: + test_tracker.fail_subtest(e, subtest_idx) + print(f"Warning: Failed to cleanup test event: {e}") + test_tracker.pass_test("All direct episodic memory operations completed successfully") except Exception as e: @@ -2050,10 +2063,283 @@ def test_resource_memory_update_indirect(agent): print("Indirect resource memory update tests completed.\n") +def cleanup_test_data(agent, test_name): + """ + 清理测试数据 + + Args: + agent: AgentWrapper实例 + test_name: 测试名称 + """ + if not agent: + return + + try: + # 根据测试类型进行不同的清理 + if 'indirect' in test_name: + # 间接操作测试通常不清理,因为数据是通过消息传递创建的 + # 但我们可以尝试清理一些明显的测试数据 + print(f" 间接操作测试 '{test_name}' 通常保留数据以供后续测试使用") + return + + # 对于直接操作测试,数据已经在测试函数内部清理了 + if 'direct' in test_name: + print(f" 直接操作测试 '{test_name}' 的数据已在测试函数内部清理") + return + + # 对于搜索和性能测试,通常不创建持久数据 + if any(keyword in test_name for keyword in ['search', 'fts5', 'performance']): + print(f" 搜索/性能测试 '{test_name}' 不创建持久数据") + return + + # 对于其他测试,尝试清理可能的测试数据 + print(f" 尝试清理测试 '{test_name}' 的数据...") + + # 这里可以添加更具体的清理逻辑 + # 例如根据测试名称清理特定的测试数据 + + except Exception as e: + print(f" 清理过程中出现错误: {e}") + +def run_specific_memory_test(test_name, agent=None, delete_after_test=True): + """ + 运行指定的内存测试函数 + + Args: + test_name (str): 要运行的测试名称 + agent (AgentWrapper, optional): AgentWrapper实例,如果为None则自动创建 + delete_after_test (bool): 是否在测试后清理测试数据,默认为True + + Returns: + bool: 测试是否成功完成 + + Available test names: + # Direct memory operations (manager methods) + - 'episodic_memory_direct': 测试情节记忆直接操作 + - 'procedural_memory_direct': 测试程序记忆直接操作 + - 'resource_memory_direct': 测试资源记忆直接操作 + - 'knowledge_vault_direct': 测试知识库直接操作 + - 'semantic_memory_direct': 测试语义记忆直接操作 + - 'resource_memory_update_direct': 测试资源记忆更新直接操作 + - 'tree_path_functionality_direct': 测试树形路径功能直接操作 + + # Indirect memory operations (message-based) + - 'episodic_memory_indirect': 测试情节记忆间接操作 + - 'procedural_memory_indirect': 测试程序记忆间接操作 + - 'resource_memory_indirect': 测试资源记忆间接操作 + - 'knowledge_vault_indirect': 测试知识库间接操作 + - 'semantic_memory_indirect': 测试语义记忆间接操作 + - 'resource_memory_update_indirect': 测试资源记忆更新间接操作 + + # Search and performance tests + - 'search_methods': 测试不同搜索方法 + - 'fts5_comprehensive': 测试FTS5综合功能 + - 'fts5_performance_comparison': 测试FTS5性能对比 + - 'fts5_advanced_features': 测试FTS5高级功能 + - 'text_only_memorization': 测试纯文本记忆功能 + + # Core memory tests + - 'core_memory_update_using_chat_agent': 测试使用聊天代理更新核心记忆 + + # File handling tests + - 'greeting_with_files': 测试文件处理功能 + - 'file_types': 测试不同文件类型 + - 'file_with_memory': 测试带记忆的文件处理 + + # All-in-one tests + - 'all_direct_memory_operations': 运行所有直接内存操作测试 + - 'all_indirect_memory_operations': 运行所有间接内存操作测试 + - 'all_search_and_performance_operations': 运行所有搜索和性能测试 + - 'all_memories': 运行所有内存测试 + """ + + # 测试函数映射字典 + test_functions = { + # Direct memory operations + 'episodic_memory_direct': test_episodic_memory_direct, + 'procedural_memory_direct': test_procedural_memory_direct, + 'resource_memory_direct': test_resource_memory_direct, + 'knowledge_vault_direct': test_knowledge_vault_direct, + 'semantic_memory_direct': test_semantic_memory_direct, + 'resource_memory_update_direct': test_resource_memory_update_direct, + 'tree_path_functionality_direct': test_tree_path_functionality_direct, + + # Indirect memory operations + 'episodic_memory_indirect': test_episodic_memory_indirect, + 'procedural_memory_indirect': test_procedural_memory_indirect, + 'resource_memory_indirect': test_resource_memory_indirect, + 'knowledge_vault_indirect': test_knowledge_vault_indirect, + 'semantic_memory_indirect': test_semantic_memory_indirect, + 'resource_memory_update_indirect': test_resource_memory_update_indirect, + + # Search and performance tests + 'search_methods': test_search_methods, + 'fts5_comprehensive': test_fts5_comprehensive, + 'fts5_performance_comparison': test_fts5_performance_comparison, + 'fts5_advanced_features': test_fts5_advanced_features, + 'text_only_memorization': test_text_only_memorization, + + # Core memory tests + 'core_memory_update_using_chat_agent': test_core_memory_update_using_chat_agent, + + # File handling tests + 'greeting_with_files': test_greeting_with_files, + 'file_types': test_file_types, + 'file_with_memory': test_file_with_memory, + + # All-in-one tests + 'all_direct_memory_operations': test_all_direct_memory_operations, + 'all_indirect_memory_operations': test_all_indirect_memory_operations, + 'all_search_and_performance_operations': test_all_search_and_performance_operations, + 'all_memories': test_all_memories, + } + + # 检查测试名称是否有效 + if test_name not in test_functions: + available_tests = list(test_functions.keys()) + print(f"❌ 无效的测试名称: '{test_name}'") + print(f"可用的测试名称: {', '.join(available_tests)}") + return False + + # 如果没有提供agent,则创建一个 + if agent is None: + print(f"🚀 初始化AgentWrapper...") + import sys + from pathlib import Path + + if getattr(sys, 'frozen', False): + # Running in PyInstaller bundle + bundle_dir = Path(sys._MEIPASS) + config_path = bundle_dir / 'mirix' / 'configs' / 'mirix_monitor.yaml' + else: + # Running in development + config_path = Path('mirix/configs/mirix_monitor.yaml') + + agent = AgentWrapper(str(config_path)) + print(f"✅ AgentWrapper初始化完成") + + # 获取测试函数 + test_function = test_functions[test_name] + + print(f"\n🎯 开始运行测试: {test_name}") + print("="*60) + + try: + # 运行测试 + if test_name in ['greeting_with_files']: + # 需要文件路径参数的测试 + file_path = "exp1.pdf" # 默认测试文件 + if not os.path.exists(file_path): + print(f"⚠️ 测试文件 {file_path} 不存在,跳过文件相关测试") + return False + result = test_function(file_path) + else: + # 标准测试函数 + result = test_function(agent) + + print("="*60) + print(f"✅ 测试 '{test_name}' 完成") + + # 测试后清理(如果需要) + if delete_after_test: + print(f"\n🧹 清理测试数据...") + try: + cleanup_test_data(agent, test_name) + print(f"✅ 测试数据清理完成") + except Exception as e: + print(f"⚠️ 测试数据清理失败: {e}") + + # 打印测试摘要 + summary = test_tracker.get_summary() + if summary['total_tests'] > 0: + print(f"\n📊 测试摘要:") + print(f" 总测试数: {summary['total_tests']}") + print(f" 通过测试: {summary['passed_tests']}") + print(f" 失败测试: {summary['failed_tests']}") + if summary['total_subtests'] > 0: + print(f" 总子测试数: {summary['total_subtests']}") + print(f" 通过子测试: {summary['passed_subtests']}") + print(f" 失败子测试: {summary['failed_subtests']}") + + return summary['failed_tests'] == 0 + + except Exception as e: + print("="*60) + print(f"❌ 测试 '{test_name}' 失败: {e}") + traceback.print_exc() + return False + +def run_multiple_memory_tests(test_names, agent=None, delete_after_test=True): + """ + 运行多个指定的内存测试函数 + + Args: + test_names (list): 要运行的测试名称列表 + agent (AgentWrapper, optional): AgentWrapper实例,如果为None则自动创建 + delete_after_test (bool): 是否在测试后清理测试数据,默认为True + + Returns: + dict: 每个测试的结果 {'test_name': success_bool} + """ + results = {} + + for test_name in test_names: + print(f"\n{'='*80}") + print(f"🔄 运行测试 {test_names.index(test_name) + 1}/{len(test_names)}: {test_name}") + print(f"{'='*80}") + + success = run_specific_memory_test(test_name, agent, delete_after_test) + results[test_name] = success + + if success: + print(f"✅ {test_name} 测试通过") + else: + print(f"❌ {test_name} 测试失败") + + # 打印总体结果 + print(f"\n{'='*80}") + print("🏁 所有测试完成") + print(f"{'='*80}") + + passed_tests = [name for name, success in results.items() if success] + failed_tests = [name for name, success in results.items() if not success] + + print(f"✅ 通过的测试 ({len(passed_tests)}): {', '.join(passed_tests)}") + if failed_tests: + print(f"❌ 失败的测试 ({len(failed_tests)}): {', '.join(failed_tests)}") + + return results + if __name__ == "__main__": delete_after_test = True - test_all_memories() + # 运行所有测试(原始方式) + # test_all_memories() + + # 使用新的指定测试功能 + # 示例1: 运行单个测试 + # run_specific_memory_test('episodic_memory_direct') + + # 示例2: 运行多个测试 + # run_multiple_memory_tests([ + # 'episodic_memory_direct', + # 'procedural_memory_direct', + # 'resource_memory_direct' + # ]) + + # 示例3: 运行所有直接内存操作测试 + # run_specific_memory_test('all_direct_memory_operations') + + # 示例4: 运行搜索相关测试 + # run_multiple_memory_tests([ + # 'search_methods', + # 'fts5_comprehensive', + # 'fts5_performance_comparison' + # ]) + + # 默认运行所有测试 + test_all_memories() + # test_greeting_with_images() # run_file_tests() From 8d68517535dc8c4b439c5bcf7978364f78dc1147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=AE=81=E7=A4=BE?= <524395609@qq.com> Date: Tue, 9 Sep 2025 11:03:43 +0800 Subject: [PATCH 3/9] =?UTF-8?q?1.=20=E6=B5=8B=E8=AF=95=E6=97=B6=E5=8F=AF?= =?UTF-8?q?=E5=B8=A6=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E5=8F=82=E6=95=B0?= =?UTF-8?q?=202.=20=E5=8F=AF=E4=BB=A5=E9=80=9A=E8=BF=87=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=EF=BC=8C=E8=AE=B0=E5=BD=95=20LLM=20=E7=9A=84=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E8=BF=87=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + LLM_DEBUG_README.md | 172 +++++++++++++++ enable_llm_debug.sh | 19 ++ mirix/agent/agent_wrapper.py | 2 + mirix/llm_api/helpers.py | 14 ++ mirix/llm_api/llm_client_base.py | 61 +++++- mirix/llm_api/llm_debug_logger.py | 336 ++++++++++++++++++++++++++++++ tests/MEMORY_TEST_USAGE.md | 124 +++++++++-- tests/run_specific_memory_test.py | 91 +++++--- tests/test_memory.py | 30 ++- 10 files changed, 800 insertions(+), 51 deletions(-) create mode 100644 LLM_DEBUG_README.md create mode 100755 enable_llm_debug.sh create mode 100644 mirix/llm_api/llm_debug_logger.py diff --git a/.gitignore b/.gitignore index dce5f827..74fb8ca5 100755 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ local_evaluations .claude/ mirix.egg-info .local +logs/ +mirix_qwen3.yaml diff --git a/LLM_DEBUG_README.md b/LLM_DEBUG_README.md new file mode 100644 index 00000000..3ab85b82 --- /dev/null +++ b/LLM_DEBUG_README.md @@ -0,0 +1,172 @@ +# LLM 调试日志记录功能 + +这个功能提供了详细的 LLM 调用和响应日志记录,帮助诊断和解决 LLM API 调用中的问题。 + +## 功能特性 + +- 📝 **详细的请求记录**: 记录发送给 LLM 的完整请求内容 +- 📥 **完整的响应记录**: 记录 LLM 返回的完整响应内容 +- ⏱️ **性能监控**: 记录请求响应时间 +- ❌ **错误跟踪**: 详细记录错误信息和上下文 +- 🔧 **JSON 解析错误诊断**: 专门记录 JSON 解析失败的情况 +- 📁 **文件日志**: 将日志保存到文件中,便于后续分析 +- 🖥️ **控制台输出**: 实时显示关键信息 + +## 使用方法 + +### 方法 1: 使用环境变量 + +```bash +# 启用调试日志记录 +source enable_llm_debug.sh + +# 运行测试 +python -m tests.run_specific_memory_test episodic_memory_indirect --config mirix/configs/mirix_qwen3.yaml +``` + +### 方法 2: 在代码中启用 + +```python +from mirix.llm_api.llm_debug_logger import enable_llm_debug_logging + +# 启用调试日志记录 +enable_llm_debug_logging( + log_dir="./logs/llm_debug", + enable_file_logging=True +) + +# 运行你的测试代码 +``` + +## 日志文件说明 + +调试日志会保存到 `./logs/llm_debug/` 目录下: + +- `llm_requests_YYYYMMDD.log`: 所有 LLM 请求的详细记录 +- `llm_responses_YYYYMMDD.log`: 所有 LLM 响应的详细记录 +- `llm_errors_YYYYMMDD.log`: 所有错误和异常的详细记录 +- `session_*.json`: 完整的调试会话记录(如果使用会话功能) + +## 日志内容示例 + +### 请求日志 +```json +{ + "timestamp": "2025-01-08T12:30:00.000Z", + "request_id": "req_1704715800000", + "model_name": "qwen-plus", + "endpoint": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "request_data": { + "model": "qwen-plus", + "messages": [...], + "tools": [...], + "temperature": 0.6, + "max_tokens": 4096 + }, + "additional_info": { + "tools_count": 5, + "messages_count": 3, + "stream": false + } +} +``` + +### 响应日志 +```json +{ + "timestamp": "2025-01-08T12:30:01.500Z", + "request_id": "req_1704715800000", + "response_data": { + "choices": [...], + "usage": { + "prompt_tokens": 1000, + "completion_tokens": 200, + "total_tokens": 1200 + } + }, + "response_time_ms": 1500.25, + "additional_info": { + "success": true + } +} +``` + +### 错误日志 +```json +{ + "timestamp": "2025-01-08T12:30:01.500Z", + "request_id": "req_1704715800000", + "error_type": "JSONDecodeError", + "error_message": "Extra data: line 1 column 473 (char 472)", + "error_context": { + "stage": "response_conversion", + "response_data_keys": ["choices", "usage"] + } +} +``` + +## 控制台输出示例 + +``` +🚀 LLM Request [req_1704715800000] + Model: qwen-plus + Endpoint: https://dashscope.aliyuncs.com/compatible-mode/v1 + Messages Count: 3 + Message 1: user - This is a test memory about going to the grocery store... + Message 2: assistant - I understand you want to store this information... + Message 3: user - Please add this to episodic memory + Tools: 5 tools available + - episodic_memory_insert + - episodic_memory_search + - episodic_memory_update + - episodic_memory_delete + - finish_memory_update + +📥 LLM Response [req_1704715800000] + Response Time: 1500.25ms + Choices Count: 1 + Choice 1: assistant + Content: I'll help you store this information in episodic memory... + Tool Calls: 1 + Tool 1: episodic_memory_insert + Args: {"items": [{"actor": "user", "details": "The user mentioned going to the grocery store..."}]} + Usage: 1000 prompt + 200 completion = 1200 total +``` + +## 故障排除 + +### 常见问题 + +1. **JSON 解析错误** + - 查看 `llm_errors_*.log` 文件中的 JSON 解析错误详情 + - 检查 LLM 返回的 JSON 格式是否正确 + +2. **API 调用失败** + - 查看请求日志确认发送的数据格式 + - 查看错误日志了解具体的错误原因 + +3. **响应时间过长** + - 查看响应日志中的 `response_time_ms` 字段 + - 分析是否有网络或模型性能问题 + +### 调试技巧 + +1. **关联请求和响应**: 使用 `request_id` 字段关联请求和响应 +2. **分析错误模式**: 查看错误日志中的重复错误模式 +3. **性能分析**: 使用响应时间数据分析性能瓶颈 +4. **内容验证**: 检查请求和响应内容是否符合预期 + +## 环境变量配置 + +| 变量名 | 默认值 | 说明 | +|--------|--------|------| +| `LLM_DEBUG_ENABLE_FILE` | `true` | 是否启用文件日志记录 | +| `LLM_DEBUG_LOG_DIR` | `./logs/llm_debug` | 日志文件保存目录 | +| `LOG_LEVEL` | `INFO` | 日志级别 | + +## 注意事项 + +- 调试日志记录会增加一些性能开销 +- 日志文件可能会变得很大,建议定期清理 +- 敏感信息(如 API 密钥)会被自动过滤 +- 在生产环境中建议关闭详细的调试日志记录 diff --git a/enable_llm_debug.sh b/enable_llm_debug.sh new file mode 100755 index 00000000..744792b0 --- /dev/null +++ b/enable_llm_debug.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# 启用 LLM 调试日志记录的环境变量设置脚本 + +echo "🔍 启用 LLM 调试日志记录..." + +# 设置环境变量 +export LLM_DEBUG_ENABLE_FILE=true +export LLM_DEBUG_LOG_DIR="./logs/llm_debug" +export LOG_LEVEL=DEBUG + +echo "✅ 环境变量已设置:" +echo " LLM_DEBUG_ENABLE_FILE=$LLM_DEBUG_ENABLE_FILE" +echo " LLM_DEBUG_LOG_DIR=$LLM_DEBUG_LOG_DIR" +echo " LOG_LEVEL=$LOG_LEVEL" +echo "" +echo "📁 日志文件将保存到: $LLM_DEBUG_LOG_DIR" +echo "" +echo "🚀 现在可以运行测试了:" +echo " python -m tests.run_specific_memory_test episodic_memory_indirect --config mirix/configs/mirix_qwen3.yaml" diff --git a/mirix/agent/agent_wrapper.py b/mirix/agent/agent_wrapper.py index 3d57c14a..4b722999 100644 --- a/mirix/agent/agent_wrapper.py +++ b/mirix/agent/agent_wrapper.py @@ -791,6 +791,7 @@ def _create_llm_config_for_provider(self, model_name: str, provider: str, custom model_endpoint=custom_agent_config['model_endpoint'], model_wrapper=None, api_key=custom_agent_config.get('api_key'), + put_inner_thoughts_in_kwargs=custom_agent_config.get('put_inner_thoughts_in_kwargs', True), **custom_agent_config.get('generation_config', {}) ) else: @@ -801,6 +802,7 @@ def _create_llm_config_for_provider(self, model_name: str, provider: str, custom model_endpoint=self.agent_config['model_endpoint'], model_wrapper=None, api_key=self.agent_config.get('api_key'), + put_inner_thoughts_in_kwargs=self.agent_config.get('put_inner_thoughts_in_kwargs', True), **self.agent_config.get('generation_config', {}) ) diff --git a/mirix/llm_api/helpers.py b/mirix/llm_api/helpers.py index 2068fbf4..be0f516b 100755 --- a/mirix/llm_api/helpers.py +++ b/mirix/llm_api/helpers.py @@ -1,5 +1,6 @@ import copy import json +import time import warnings from collections import OrderedDict from typing import Any, List, Union @@ -293,6 +294,19 @@ def unpack_inner_thoughts_from_kwargs(choice: Choice, inner_thoughts_key: str) - warnings.warn(f"Did not find inner thoughts in tool call: {str(tool_call)}") except json.JSONDecodeError as e: + # 记录详细的 JSON 解析错误信息 + try: + from mirix.llm_api.llm_debug_logger import get_llm_debug_logger + debug_logger = get_llm_debug_logger() + debug_logger.log_json_parse_error( + request_id=f"inner_thoughts_{int(time.time() * 1000)}", + json_string=tool_call.function.arguments, + error=e, + context="inner_thoughts_parsing" + ) + except ImportError: + pass # 如果调试日志记录器不可用,继续使用原来的警告 + warnings.warn(f"Failed to strip inner thoughts from kwargs: {e}") raise e else: diff --git a/mirix/llm_api/llm_client_base.py b/mirix/llm_api/llm_client_base.py index 2998d101..42f568ad 100644 --- a/mirix/llm_api/llm_client_base.py +++ b/mirix/llm_api/llm_client_base.py @@ -1,3 +1,4 @@ +import time from abc import abstractmethod from typing import Dict, List, Optional, Union @@ -7,6 +8,7 @@ from mirix.schemas.openai.chat_completion_response import ChatCompletionResponse from mirix.services.cloud_file_mapping_manager import CloudFileMappingManager from mirix.services.file_manager import FileManager +from mirix.llm_api.llm_debug_logger import get_llm_debug_logger class LLMClientBase: """ @@ -38,19 +40,72 @@ def send_llm_request( """ Issues a request to the downstream model endpoint and parses response. """ + # 获取调试日志记录器 + debug_logger = get_llm_debug_logger() + request_data = self.build_request_data(messages, self.llm_config, tools, force_tool_call, existing_file_uris=existing_file_uris) if get_input_data_for_debugging: return request_data + # 记录请求 + request_id = debug_logger.log_request( + model_name=self.llm_config.model, + endpoint=self.llm_config.model_endpoint, + request_data=request_data, + additional_info={ + "tools_count": len(tools) if tools else 0, + "messages_count": len(messages), + "stream": stream, + "force_tool_call": force_tool_call + } + ) + + start_time = time.time() + try: response_data = self.request(request_data) + response_time_ms = (time.time() - start_time) * 1000 + + # 记录成功响应 + debug_logger.log_response( + request_id=request_id, + response_data=response_data, + response_time_ms=response_time_ms, + additional_info={ + "success": True + } + ) + except Exception as e: + response_time_ms = (time.time() - start_time) * 1000 + + # 记录错误 + debug_logger.log_error( + request_id=request_id, + error=e, + error_context={ + "response_time_ms": response_time_ms, + "model_name": self.llm_config.model, + "endpoint": self.llm_config.model_endpoint + } + ) raise self.handle_llm_error(e) - chat_completion_data = self.convert_response_to_chat_completion(response_data, messages) - - return chat_completion_data + try: + chat_completion_data = self.convert_response_to_chat_completion(response_data, messages) + return chat_completion_data + except Exception as e: + # 记录响应转换错误 + debug_logger.log_error( + request_id=request_id, + error=e, + error_context={ + "stage": "response_conversion", + "response_data_keys": list(response_data.keys()) if isinstance(response_data, dict) else "not_dict" + } + ) + raise @abstractmethod def build_request_data( diff --git a/mirix/llm_api/llm_debug_logger.py b/mirix/llm_api/llm_debug_logger.py new file mode 100644 index 00000000..db00c0bc --- /dev/null +++ b/mirix/llm_api/llm_debug_logger.py @@ -0,0 +1,336 @@ +""" +LLM Debug Logger - 用于详细记录 LLM 调用和响应的调试工具 + +这个模块提供了详细的日志记录功能,用于跟踪: +1. 发送给 LLM 的请求内容 +2. LLM 返回的响应内容 +3. 请求和响应的元数据 +4. 错误和异常信息 +""" + +import json +import logging +import os +import time +from datetime import datetime +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from mirix.log import get_logger + +class LLMDebugLogger: + """LLM 调试日志记录器""" + + def __init__(self, log_dir: Optional[str] = None, enable_file_logging: bool = True): + """ + 初始化 LLM 调试日志记录器 + + Args: + log_dir: 日志文件保存目录,默认为 ./logs/llm_debug + enable_file_logging: 是否启用文件日志记录 + """ + self.logger = get_logger("Mirix.LLMDebug") + self.enable_file_logging = enable_file_logging + + if enable_file_logging: + if log_dir is None: + log_dir = "./logs/llm_debug" + + self.log_dir = Path(log_dir) + self.log_dir.mkdir(parents=True, exist_ok=True) + + # 创建文件处理器 + self._setup_file_handlers() + + def _setup_file_handlers(self): + """设置文件处理器""" + # 请求日志文件 + request_log_file = self.log_dir / f"llm_requests_{datetime.now().strftime('%Y%m%d')}.log" + self.request_handler = logging.FileHandler(request_log_file, encoding='utf-8') + self.request_handler.setLevel(logging.INFO) + request_formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + self.request_handler.setFormatter(request_formatter) + + # 响应日志文件 + response_log_file = self.log_dir / f"llm_responses_{datetime.now().strftime('%Y%m%d')}.log" + self.response_handler = logging.FileHandler(response_log_file, encoding='utf-8') + self.response_handler.setLevel(logging.INFO) + response_formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + self.response_handler.setFormatter(response_formatter) + + # 错误日志文件 + error_log_file = self.log_dir / f"llm_errors_{datetime.now().strftime('%Y%m%d')}.log" + self.error_handler = logging.FileHandler(error_log_file, encoding='utf-8') + self.error_handler.setLevel(logging.ERROR) + error_formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + self.error_handler.setFormatter(error_formatter) + + def log_request( + self, + model_name: str, + endpoint: str, + request_data: Dict[str, Any], + request_id: Optional[str] = None, + additional_info: Optional[Dict[str, Any]] = None + ): + """ + 记录 LLM 请求 + + Args: + model_name: 模型名称 + endpoint: API 端点 + request_data: 请求数据 + request_id: 请求 ID(用于关联请求和响应) + additional_info: 额外信息 + """ + if not request_id: + request_id = f"req_{int(time.time() * 1000)}" + + log_data = { + "timestamp": datetime.now().isoformat(), + "request_id": request_id, + "model_name": model_name, + "endpoint": endpoint, + "request_data": request_data, + "additional_info": additional_info or {} + } + + # 控制台输出 + self.logger.info(f"🚀 LLM Request [{request_id}]") + self.logger.info(f" Model: {model_name}") + self.logger.info(f" Endpoint: {endpoint}") + self.logger.info(f" Messages Count: {len(request_data.get('messages', []))}") + + if 'messages' in request_data: + for i, msg in enumerate(request_data['messages']): + self.logger.info(f" Message {i+1}: {msg.get('role', 'unknown')} - {str(msg.get('content', ''))[:100]}...") + + if 'tools' in request_data and request_data['tools']: + self.logger.info(f" Tools: {len(request_data['tools'])} tools available") + for tool in request_data['tools']: + if isinstance(tool, dict) and 'function' in tool: + self.logger.info(f" - {tool['function'].get('name', 'unknown')}") + + # 文件输出 + if self.enable_file_logging: + request_logger = logging.getLogger(f"Mirix.LLMDebug.Requests") + request_logger.addHandler(self.request_handler) + request_logger.setLevel(logging.INFO) + request_logger.info(json.dumps(log_data, ensure_ascii=False, indent=2)) + request_logger.removeHandler(self.request_handler) + + return request_id + + def log_response( + self, + request_id: str, + response_data: Dict[str, Any], + response_time_ms: Optional[float] = None, + additional_info: Optional[Dict[str, Any]] = None + ): + """ + 记录 LLM 响应 + + Args: + request_id: 关联的请求 ID + response_data: 响应数据 + response_time_ms: 响应时间(毫秒) + additional_info: 额外信息 + """ + log_data = { + "timestamp": datetime.now().isoformat(), + "request_id": request_id, + "response_data": response_data, + "response_time_ms": response_time_ms, + "additional_info": additional_info or {} + } + + # 控制台输出 + self.logger.info(f"📥 LLM Response [{request_id}]") + if response_time_ms: + self.logger.info(f" Response Time: {response_time_ms:.2f}ms") + + if 'choices' in response_data: + choices = response_data['choices'] + self.logger.info(f" Choices Count: {len(choices)}") + + for i, choice in enumerate(choices): + if 'message' in choice: + msg = choice['message'] + self.logger.info(f" Choice {i+1}: {msg.get('role', 'unknown')}") + + if 'content' in msg and msg['content']: + content = str(msg['content']) + self.logger.info(f" Content: {content[:200]}{'...' if len(content) > 200 else ''}") + + if 'tool_calls' in msg and msg['tool_calls']: + self.logger.info(f" Tool Calls: {len(msg['tool_calls'])}") + for j, tool_call in enumerate(msg['tool_calls']): + if isinstance(tool_call, dict): + func_name = tool_call.get('function', {}).get('name', 'unknown') + func_args = tool_call.get('function', {}).get('arguments', '') + self.logger.info(f" Tool {j+1}: {func_name}") + self.logger.info(f" Args: {func_args[:100]}{'...' if len(func_args) > 100 else ''}") + + if 'usage' in response_data: + usage = response_data['usage'] + self.logger.info(f" Usage: {usage.get('prompt_tokens', 0)} prompt + {usage.get('completion_tokens', 0)} completion = {usage.get('total_tokens', 0)} total") + + # 文件输出 + if self.enable_file_logging: + response_logger = logging.getLogger(f"Mirix.LLMDebug.Responses") + response_logger.addHandler(self.response_handler) + response_logger.setLevel(logging.INFO) + response_logger.info(json.dumps(log_data, ensure_ascii=False, indent=2)) + response_logger.removeHandler(self.response_handler) + + def log_error( + self, + request_id: str, + error: Exception, + error_context: Optional[Dict[str, Any]] = None + ): + """ + 记录 LLM 错误 + + Args: + request_id: 关联的请求 ID + error: 错误对象 + error_context: 错误上下文 + """ + log_data = { + "timestamp": datetime.now().isoformat(), + "request_id": request_id, + "error_type": type(error).__name__, + "error_message": str(error), + "error_context": error_context or {} + } + + # 控制台输出 + self.logger.error(f"❌ LLM Error [{request_id}]") + self.logger.error(f" Error Type: {type(error).__name__}") + self.logger.error(f" Error Message: {str(error)}") + + # 文件输出 + if self.enable_file_logging: + error_logger = logging.getLogger(f"Mirix.LLMDebug.Errors") + error_logger.addHandler(self.error_handler) + error_logger.setLevel(logging.ERROR) + error_logger.error(json.dumps(log_data, ensure_ascii=False, indent=2)) + error_logger.removeHandler(self.error_handler) + + def log_json_parse_error( + self, + request_id: str, + json_string: str, + error: Exception, + context: str = "unknown" + ): + """ + 记录 JSON 解析错误 + + Args: + request_id: 关联的请求 ID + json_string: 解析失败的 JSON 字符串 + error: JSON 解析错误 + context: 错误上下文 + """ + log_data = { + "timestamp": datetime.now().isoformat(), + "request_id": request_id, + "context": context, + "error_type": type(error).__name__, + "error_message": str(error), + "json_string": json_string, + "json_length": len(json_string) + } + + # 控制台输出 + self.logger.error(f"🔧 JSON Parse Error [{request_id}] - {context}") + self.logger.error(f" Error: {str(error)}") + self.logger.error(f" JSON Length: {len(json_string)}") + self.logger.error(f" JSON Preview: {json_string[:200]}{'...' if len(json_string) > 200 else ''}") + + # 文件输出 + if self.enable_file_logging: + error_logger = logging.getLogger(f"Mirix.LLMDebug.Errors") + error_logger.addHandler(self.error_handler) + error_logger.setLevel(logging.ERROR) + error_logger.error(json.dumps(log_data, ensure_ascii=False, indent=2)) + error_logger.removeHandler(self.error_handler) + + def save_debug_session( + self, + session_id: str, + requests: List[Dict[str, Any]], + responses: List[Dict[str, Any]], + errors: List[Dict[str, Any]] + ): + """ + 保存完整的调试会话 + + Args: + session_id: 会话 ID + requests: 请求列表 + responses: 响应列表 + errors: 错误列表 + """ + if not self.enable_file_logging: + return + + session_data = { + "session_id": session_id, + "timestamp": datetime.now().isoformat(), + "requests": requests, + "responses": responses, + "errors": errors, + "summary": { + "total_requests": len(requests), + "total_responses": len(responses), + "total_errors": len(errors) + } + } + + session_file = self.log_dir / f"session_{session_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + + with open(session_file, 'w', encoding='utf-8') as f: + json.dump(session_data, f, ensure_ascii=False, indent=2) + + self.logger.info(f"💾 Debug session saved: {session_file}") + + +# 全局调试日志记录器实例 +_debug_logger = None + +def get_llm_debug_logger() -> LLMDebugLogger: + """获取全局 LLM 调试日志记录器实例""" + global _debug_logger + if _debug_logger is None: + # 从环境变量读取配置 + log_dir = os.environ.get('LLM_DEBUG_LOG_DIR', './logs/llm_debug') + enable_file_logging = os.environ.get('LLM_DEBUG_ENABLE_FILE', 'true').lower() == 'true' + _debug_logger = LLMDebugLogger(log_dir=log_dir, enable_file_logging=enable_file_logging) + return _debug_logger + +def enable_llm_debug_logging(log_dir: Optional[str] = None, enable_file_logging: bool = True): + """ + 启用 LLM 调试日志记录 + + Args: + log_dir: 日志文件保存目录 + enable_file_logging: 是否启用文件日志记录 + """ + global _debug_logger + _debug_logger = LLMDebugLogger(log_dir=log_dir, enable_file_logging=enable_file_logging) + _debug_logger.logger.info("🔍 LLM Debug Logging Enabled") + +def disable_llm_debug_logging(): + """禁用 LLM 调试日志记录""" + global _debug_logger + _debug_logger = None diff --git a/tests/MEMORY_TEST_USAGE.md b/tests/MEMORY_TEST_USAGE.md index 9cbb3b99..891bf557 100644 --- a/tests/MEMORY_TEST_USAGE.md +++ b/tests/MEMORY_TEST_USAGE.md @@ -6,25 +6,27 @@ ## 新增功能 -### 1. `run_specific_memory_test(test_name, agent=None, delete_after_test=True)` +### 1. `run_specific_memory_test(test_name, agent=None, config_path=None, delete_after_test=True)` 运行指定的单个内存测试函数。 **参数:** - `test_name` (str): 要运行的测试名称 - `agent` (AgentWrapper, optional): AgentWrapper实例,如果为None则自动创建 +- `config_path` (str, optional): 配置文件路径,如果为None则使用默认配置 - `delete_after_test` (bool): 是否在测试后清理测试数据,默认为True **返回值:** - `bool`: 测试是否成功完成 -### 2. `run_multiple_memory_tests(test_names, agent=None, delete_after_test=True)` +### 2. `run_multiple_memory_tests(test_names, agent=None, config_path=None, delete_after_test=True)` 运行多个指定的内存测试函数。 **参数:** - `test_names` (list): 要运行的测试名称列表 - `agent` (AgentWrapper, optional): AgentWrapper实例,如果为None则自动创建 +- `config_path` (str, optional): 配置文件路径,如果为None则使用默认配置 - `delete_after_test` (bool): 是否在测试后清理测试数据,默认为True **返回值:** @@ -37,17 +39,25 @@ ```python from tests.test_memory import run_specific_memory_test, run_multiple_memory_tests -# 运行单个测试(默认清理测试数据) +# 运行单个测试(使用默认配置,默认清理测试数据) success = run_specific_memory_test('episodic_memory_direct') if success: print("测试通过!") else: print("测试失败!") +# 运行单个测试(使用自定义配置文件) +success = run_specific_memory_test('episodic_memory_direct', config_path='mirix/configs/mirix_gpt4.yaml') + # 运行单个测试(保留测试数据) success = run_specific_memory_test('episodic_memory_direct', delete_after_test=False) -# 运行多个测试(默认清理测试数据) +# 运行单个测试(使用自定义配置并保留数据) +success = run_specific_memory_test('episodic_memory_direct', + config_path='mirix/configs/mirix_azure_example.yaml', + delete_after_test=False) + +# 运行多个测试(使用默认配置,默认清理测试数据) results = run_multiple_memory_tests([ 'episodic_memory_direct', 'procedural_memory_direct', @@ -68,12 +78,21 @@ for test_name, success in results.items(): ### 方法2: 使用独立的测试脚本 ```bash -# 从项目根目录运行 +# 基本用法 - 使用默认配置 python tests/run_specific_memory_test.py episodic_memory_direct -# 运行多个测试 +# 使用自定义配置文件 +python tests/run_specific_memory_test.py episodic_memory_direct --config mirix/configs/mirix_gpt4.yaml + +# 使用短参数名指定配置 +python tests/run_specific_memory_test.py episodic_memory_direct -c mirix/configs/mirix_azure_example.yaml + +# 运行多个测试(使用默认配置) python tests/run_specific_memory_test.py episodic_memory_direct procedural_memory_direct resource_memory_direct +# 运行多个测试(使用自定义配置) +python tests/run_specific_memory_test.py episodic_memory_direct procedural_memory_direct --config mirix/configs/mirix_gpt4.yaml + # 运行所有直接内存操作测试 python tests/run_specific_memory_test.py all_direct_memory_operations @@ -83,12 +102,15 @@ python tests/run_specific_memory_test.py search_methods fts5_comprehensive # 保留测试数据(不清理) python tests/run_specific_memory_test.py episodic_memory_direct --keep-data -# 查看帮助 -python -m tests.run_specific_memory_test --help +# 使用自定义配置并保留数据 +python tests/run_specific_memory_test.py episodic_memory_direct --config mirix/configs/mirix_gpt4.yaml --keep-data + +# 查看帮助信息 +python tests/run_specific_memory_test.py --help # 或者进入tests目录运行 cd tests -python -m testsrun_specific_memory_test episodic_memory_direct +python run_specific_memory_test.py episodic_memory_direct ``` ### 方法3: 修改 test_memory.py 主函数 @@ -97,15 +119,24 @@ python -m testsrun_specific_memory_test episodic_memory_direct ```python if __name__ == "__main__": - # 运行单个测试 + # 运行单个测试(使用默认配置) run_specific_memory_test('episodic_memory_direct') - # 或者运行多个测试 + # 运行单个测试(使用自定义配置) + # run_specific_memory_test('episodic_memory_direct', config_path='mirix/configs/mirix_gpt4.yaml') + + # 运行多个测试(使用默认配置) # run_multiple_memory_tests([ # 'episodic_memory_direct', # 'procedural_memory_direct', # 'resource_memory_direct' # ]) + + # 运行多个测试(使用自定义配置) + # run_multiple_memory_tests([ + # 'episodic_memory_direct', + # 'procedural_memory_direct' + # ], config_path='mirix/configs/mirix_azure_example.yaml') ``` ## 可用的测试名称 @@ -183,8 +214,72 @@ run_specific_memory_test('resource_memory_direct') 1. **快速反馈**: 只运行需要的测试,节省时间 2. **精确调试**: 可以针对特定功能进行测试 3. **灵活组合**: 可以自由组合不同的测试 -4. **易于集成**: 可以轻松集成到CI/CD流程中 -5. **详细报告**: 提供详细的测试结果和摘要 +4. **配置灵活**: 支持多种配置文件,适应不同环境和需求 +5. **易于集成**: 可以轻松集成到CI/CD流程中 +6. **详细报告**: 提供详细的测试结果和摘要 +7. **环境隔离**: 不同配置文件可以用于不同的测试环境 + +## 配置文件使用 + +### 可用的配置文件 + +MIRIX 提供了多个预配置的配置文件,您可以根据需要选择: + +```bash +# 默认配置(推荐用于测试) +mirix/configs/mirix.yaml + +# GPT-4 配置 +mirix/configs/mirix_gpt4.yaml + +# GPT-4o-mini 配置 +mirix/configs/mirix_gpt4o-mini.yaml + +# Azure OpenAI 配置 +mirix/configs/mirix_azure_example.yaml + +# 自定义模型配置 +mirix/configs/mirix_custom_model.yaml + +# 监控配置 +mirix/configs/mirix_monitor.yaml +``` + +### 配置文件选择建议 + +#### **测试环境** +```bash +# 推荐:使用默认配置进行基本测试 +python tests/run_specific_memory_test.py episodic_memory_direct + +# 或者使用GPT-4o-mini(成本较低) +python tests/run_specific_memory_test.py episodic_memory_direct --config mirix/configs/mirix_gpt4o-mini.yaml +``` + +#### **生产环境** +```bash +# 使用GPT-4配置 +python tests/run_specific_memory_test.py episodic_memory_direct --config mirix/configs/mirix_gpt4.yaml + +# 使用Azure OpenAI配置 +python tests/run_specific_memory_test.py episodic_memory_direct --config mirix/configs/mirix_azure_example.yaml +``` + +#### **自定义配置** +```bash +# 使用自定义模型配置 +python tests/run_specific_memory_test.py episodic_memory_direct --config mirix/configs/mirix_custom_model.yaml +``` + +### 配置文件验证 + +脚本会自动验证配置文件是否存在: + +```bash +# 如果配置文件不存在,会显示错误信息 +python tests/run_specific_memory_test.py episodic_memory_direct --config nonexistent.yaml +# 输出: ❌ 配置文件不存在: nonexistent.yaml +``` ## 测试数据清理机制 @@ -288,3 +383,6 @@ python -m tests.run_specific_memory_test episodic_memory_direct 5. **测试数据清理**: 默认情况下会清理测试数据,使用 `--keep-data` 或 `delete_after_test=False` 可以保留数据 6. **间接操作测试**: 通常保留数据以测试记忆累积效果,这是正常行为 7. **嵌入模型配置**: 严格按照环境变量 `BUILD_EMBEDDINGS_FOR_MEMORY` 执行,不支持自动降级 +8. **配置文件路径**: 确保配置文件路径正确,脚本会自动验证配置文件是否存在 +9. **配置文件兼容性**: 不同的配置文件可能使用不同的AI模型,请确保相应的API密钥已正确配置 +10. **环境变量**: 某些配置可能需要特定的环境变量(如API密钥),请确保在运行测试前正确设置 diff --git a/tests/run_specific_memory_test.py b/tests/run_specific_memory_test.py index 94976882..62a64090 100644 --- a/tests/run_specific_memory_test.py +++ b/tests/run_specific_memory_test.py @@ -16,9 +16,12 @@ python tests/run_specific_memory_test.py ... 示例: - # 运行情节记忆直接操作测试 + # 运行情节记忆直接操作测试(使用默认配置) python tests/run_specific_memory_test.py episodic_memory_direct + # 使用自定义配置文件运行测试 + python tests/run_specific_memory_test.py episodic_memory_direct --config mirix/configs/mirix_gpt4.yaml + # 运行多个直接操作测试 python tests/run_specific_memory_test.py episodic_memory_direct procedural_memory_direct resource_memory_direct @@ -30,6 +33,9 @@ # 保留测试数据(不清理) python tests/run_specific_memory_test.py episodic_memory_direct --keep-data + + # 使用自定义配置并保留数据 + python tests/run_specific_memory_test.py episodic_memory_direct --config mirix/configs/mirix_azure_example.yaml --keep-data 可用的测试名称: # 直接内存操作 (manager methods) @@ -73,6 +79,8 @@ import sys import os +import argparse +from pathlib import Path # 添加项目根目录到Python路径 project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) @@ -85,37 +93,68 @@ def print_usage(): """打印使用说明""" print(__doc__) -def main(): - """主函数""" - if len(sys.argv) < 2: - print("❌ 请提供至少一个测试名称") - print_usage() +def parse_arguments(): + """解析命令行参数""" + parser = argparse.ArgumentParser( + description="MIRIX 内存系统指定测试运行器", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__ + ) + + parser.add_argument( + 'test_names', + nargs='+', + help='要运行的测试名称列表' + ) + + parser.add_argument( + '--config', '-c', + type=str, + default='mirix/configs/mirix.yaml', + help='指定配置文件路径 (默认: mirix/configs/mirix.yaml)' + ) + + parser.add_argument( + '--keep-data', + action='store_true', + help='保留测试数据(不进行清理)' + ) + + return parser.parse_args() + +def validate_config_file(config_path): + """验证配置文件是否存在""" + if not os.path.exists(config_path): + print(f"❌ 配置文件不存在: {config_path}") + print("请检查配置文件路径是否正确") sys.exit(1) - # 获取测试名称列表 - test_names = sys.argv[1:] + print(f"📁 使用配置文件: {config_path}") + +def main(): + """主函数""" + # 解析命令行参数 + args = parse_arguments() - # 检查是否是帮助请求 - if any(arg in ['-h', '--help', 'help'] for arg in test_names): - print_usage() - sys.exit(0) + # 验证配置文件 + validate_config_file(args.config) - print(f"🎯 准备运行 {len(test_names)} 个测试") - print(f"测试列表: {', '.join(test_names)}") + print(f"🎯 准备运行 {len(args.test_names)} 个测试") + print(f"测试列表: {', '.join(args.test_names)}") print("="*80) - # 检查是否要保留测试数据 - delete_after_test = True - if '--keep-data' in test_names: - delete_after_test = False - test_names.remove('--keep-data') + if args.keep_data: print("⚠️ 将保留测试数据(不进行清理)") # 运行测试 - if len(test_names) == 1: + if len(args.test_names) == 1: # 单个测试 - test_name = test_names[0] - success = run_specific_memory_test(test_name, delete_after_test=delete_after_test) + test_name = args.test_names[0] + success = run_specific_memory_test( + test_name, + config_path=args.config, + delete_after_test=not args.keep_data + ) if success: print(f"\n🎉 测试 '{test_name}' 成功完成!") @@ -125,13 +164,17 @@ def main(): sys.exit(1) else: # 多个测试 - results = run_multiple_memory_tests(test_names, delete_after_test=delete_after_test) + results = run_multiple_memory_tests( + args.test_names, + config_path=args.config, + delete_after_test=not args.keep_data + ) # 检查是否有失败的测试 failed_tests = [name for name, success in results.items() if not success] if not failed_tests: - print(f"\n🎉 所有 {len(test_names)} 个测试都成功完成!") + print(f"\n🎉 所有 {len(args.test_names)} 个测试都成功完成!") sys.exit(0) else: print(f"\n💥 {len(failed_tests)} 个测试失败: {', '.join(failed_tests)}") diff --git a/tests/test_memory.py b/tests/test_memory.py index 15573d9d..eeb0b36e 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -2101,13 +2101,14 @@ def cleanup_test_data(agent, test_name): except Exception as e: print(f" 清理过程中出现错误: {e}") -def run_specific_memory_test(test_name, agent=None, delete_after_test=True): +def run_specific_memory_test(test_name, agent=None, config_path=None, delete_after_test=True): """ 运行指定的内存测试函数 Args: test_name (str): 要运行的测试名称 agent (AgentWrapper, optional): AgentWrapper实例,如果为None则自动创建 + config_path (str, optional): 配置文件路径,如果为None则使用默认配置 delete_after_test (bool): 是否在测试后清理测试数据,默认为True Returns: @@ -2207,15 +2208,21 @@ def run_specific_memory_test(test_name, agent=None, delete_after_test=True): import sys from pathlib import Path - if getattr(sys, 'frozen', False): - # Running in PyInstaller bundle - bundle_dir = Path(sys._MEIPASS) - config_path = bundle_dir / 'mirix' / 'configs' / 'mirix_monitor.yaml' + # 确定配置文件路径 + if config_path: + # 使用指定的配置文件 + final_config_path = config_path else: - # Running in development - config_path = Path('mirix/configs/mirix_monitor.yaml') - - agent = AgentWrapper(str(config_path)) + # 使用默认配置文件 + if getattr(sys, 'frozen', False): + # Running in PyInstaller bundle + bundle_dir = Path(sys._MEIPASS) + final_config_path = str(bundle_dir / 'mirix' / 'configs' / 'mirix_monitor.yaml') + else: + # Running in development + final_config_path = 'mirix/configs/mirix_monitor.yaml' + + agent = AgentWrapper(final_config_path) print(f"✅ AgentWrapper初始化完成") # 获取测试函数 @@ -2269,13 +2276,14 @@ def run_specific_memory_test(test_name, agent=None, delete_after_test=True): traceback.print_exc() return False -def run_multiple_memory_tests(test_names, agent=None, delete_after_test=True): +def run_multiple_memory_tests(test_names, agent=None, config_path=None, delete_after_test=True): """ 运行多个指定的内存测试函数 Args: test_names (list): 要运行的测试名称列表 agent (AgentWrapper, optional): AgentWrapper实例,如果为None则自动创建 + config_path (str, optional): 配置文件路径,如果为None则使用默认配置 delete_after_test (bool): 是否在测试后清理测试数据,默认为True Returns: @@ -2288,7 +2296,7 @@ def run_multiple_memory_tests(test_names, agent=None, delete_after_test=True): print(f"🔄 运行测试 {test_names.index(test_name) + 1}/{len(test_names)}: {test_name}") print(f"{'='*80}") - success = run_specific_memory_test(test_name, agent, delete_after_test) + success = run_specific_memory_test(test_name, agent, config_path, delete_after_test) results[test_name] = success if success: From ce16d39be2b0a7abe4705c8d0e85f63648447b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=AE=81=E7=A4=BE?= <524395609@qq.com> Date: Tue, 9 Sep 2025 11:19:48 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E7=A7=BB=E9=99=A4LLM=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E8=B7=9F=E8=B8=AA=E5=9C=A8=E7=BB=88=E7=AB=AF=E4=B8=8A=E7=9A=84?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mirix/llm_api/llm_debug_logger.py | 162 +++++++++++++++++++----------- 1 file changed, 103 insertions(+), 59 deletions(-) diff --git a/mirix/llm_api/llm_debug_logger.py b/mirix/llm_api/llm_debug_logger.py index db00c0bc..c41ea0da 100644 --- a/mirix/llm_api/llm_debug_logger.py +++ b/mirix/llm_api/llm_debug_logger.py @@ -29,7 +29,16 @@ def __init__(self, log_dir: Optional[str] = None, enable_file_logging: bool = Tr log_dir: 日志文件保存目录,默认为 ./logs/llm_debug enable_file_logging: 是否启用文件日志记录 """ - self.logger = get_logger("Mirix.LLMDebug") + # 创建独立的日志记录器,避免与主日志系统冲突 + self.logger = logging.getLogger("Mirix.LLMDebug") + self.logger.setLevel(logging.INFO) + + # 清除所有现有的处理器,确保只输出到文件 + self.logger.handlers.clear() + + # 防止日志传播到父日志记录器(避免控制台输出) + self.logger.propagate = False + self.enable_file_logging = enable_file_logging if enable_file_logging: @@ -70,6 +79,11 @@ def _setup_file_handlers(self): '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) self.error_handler.setFormatter(error_formatter) + + # 将处理器添加到日志记录器 + self.logger.addHandler(self.request_handler) + self.logger.addHandler(self.response_handler) + self.logger.addHandler(self.error_handler) def log_request( self, @@ -101,29 +115,38 @@ def log_request( "additional_info": additional_info or {} } - # 控制台输出 - self.logger.info(f"🚀 LLM Request [{request_id}]") - self.logger.info(f" Model: {model_name}") - self.logger.info(f" Endpoint: {endpoint}") - self.logger.info(f" Messages Count: {len(request_data.get('messages', []))}") - - if 'messages' in request_data: - for i, msg in enumerate(request_data['messages']): - self.logger.info(f" Message {i+1}: {msg.get('role', 'unknown')} - {str(msg.get('content', ''))[:100]}...") - - if 'tools' in request_data and request_data['tools']: - self.logger.info(f" Tools: {len(request_data['tools'])} tools available") - for tool in request_data['tools']: - if isinstance(tool, dict) and 'function' in tool: - self.logger.info(f" - {tool['function'].get('name', 'unknown')}") + # 只输出到文件,不在控制台显示 + # 注释掉控制台输出,避免混淆终端输出 # 文件输出 if self.enable_file_logging: - request_logger = logging.getLogger(f"Mirix.LLMDebug.Requests") - request_logger.addHandler(self.request_handler) + request_logger = logging.getLogger(f"Mirix.LLMDebug.Requests.{request_id}") request_logger.setLevel(logging.INFO) + request_logger.propagate = False + + # 清除现有处理器,只添加请求处理器 + request_logger.handlers.clear() + request_logger.addHandler(self.request_handler) + + # 记录请求信息 + request_logger.info(f"🚀 LLM Request [{request_id}]") + request_logger.info(f" Model: {model_name}") + request_logger.info(f" Endpoint: {endpoint}") + request_logger.info(f" Messages Count: {len(request_data.get('messages', []))}") + + if 'messages' in request_data: + for i, msg in enumerate(request_data['messages']): + request_logger.info(f" Message {i+1}: {msg.get('role', 'unknown')} - {str(msg.get('content', ''))[:100]}...") + + if 'tools' in request_data and request_data['tools']: + request_logger.info(f" Tools: {len(request_data['tools'])} tools available") + for tool in request_data['tools']: + if isinstance(tool, dict) and 'function' in tool: + request_logger.info(f" - {tool['function'].get('name', 'unknown')}") + + # 记录完整的请求数据 + request_logger.info("=== FULL REQUEST DATA ===") request_logger.info(json.dumps(log_data, ensure_ascii=False, indent=2)) - request_logger.removeHandler(self.request_handler) return request_id @@ -151,44 +174,53 @@ def log_response( "additional_info": additional_info or {} } - # 控制台输出 - self.logger.info(f"📥 LLM Response [{request_id}]") - if response_time_ms: - self.logger.info(f" Response Time: {response_time_ms:.2f}ms") - - if 'choices' in response_data: - choices = response_data['choices'] - self.logger.info(f" Choices Count: {len(choices)}") - - for i, choice in enumerate(choices): - if 'message' in choice: - msg = choice['message'] - self.logger.info(f" Choice {i+1}: {msg.get('role', 'unknown')}") - - if 'content' in msg and msg['content']: - content = str(msg['content']) - self.logger.info(f" Content: {content[:200]}{'...' if len(content) > 200 else ''}") - - if 'tool_calls' in msg and msg['tool_calls']: - self.logger.info(f" Tool Calls: {len(msg['tool_calls'])}") - for j, tool_call in enumerate(msg['tool_calls']): - if isinstance(tool_call, dict): - func_name = tool_call.get('function', {}).get('name', 'unknown') - func_args = tool_call.get('function', {}).get('arguments', '') - self.logger.info(f" Tool {j+1}: {func_name}") - self.logger.info(f" Args: {func_args[:100]}{'...' if len(func_args) > 100 else ''}") - - if 'usage' in response_data: - usage = response_data['usage'] - self.logger.info(f" Usage: {usage.get('prompt_tokens', 0)} prompt + {usage.get('completion_tokens', 0)} completion = {usage.get('total_tokens', 0)} total") + # 只输出到文件,不在控制台显示 + # 注释掉控制台输出,避免混淆终端输出 # 文件输出 if self.enable_file_logging: - response_logger = logging.getLogger(f"Mirix.LLMDebug.Responses") - response_logger.addHandler(self.response_handler) + response_logger = logging.getLogger(f"Mirix.LLMDebug.Responses.{request_id}") response_logger.setLevel(logging.INFO) + response_logger.propagate = False + + # 清除现有处理器,只添加响应处理器 + response_logger.handlers.clear() + response_logger.addHandler(self.response_handler) + + # 记录响应信息 + response_logger.info(f"📥 LLM Response [{request_id}]") + if response_time_ms: + response_logger.info(f" Response Time: {response_time_ms:.2f}ms") + + if 'choices' in response_data: + choices = response_data['choices'] + response_logger.info(f" Choices Count: {len(choices)}") + + for i, choice in enumerate(choices): + if 'message' in choice: + msg = choice['message'] + response_logger.info(f" Choice {i+1}: {msg.get('role', 'unknown')}") + + if 'content' in msg and msg['content']: + content = str(msg['content']) + response_logger.info(f" Content: {content[:200]}{'...' if len(content) > 200 else ''}") + + if 'tool_calls' in msg and msg['tool_calls']: + response_logger.info(f" Tool Calls: {len(msg['tool_calls'])}") + for j, tool_call in enumerate(msg['tool_calls']): + if isinstance(tool_call, dict): + func_name = tool_call.get('function', {}).get('name', 'unknown') + func_args = tool_call.get('function', {}).get('arguments', '') + response_logger.info(f" Tool {j+1}: {func_name}") + response_logger.info(f" Args: {func_args[:100]}{'...' if len(func_args) > 100 else ''}") + + if 'usage' in response_data: + usage = response_data['usage'] + response_logger.info(f" Usage: {usage.get('prompt_tokens', 0)} prompt + {usage.get('completion_tokens', 0)} completion = {usage.get('total_tokens', 0)} total") + + # 记录完整的响应数据 + response_logger.info("=== FULL RESPONSE DATA ===") response_logger.info(json.dumps(log_data, ensure_ascii=False, indent=2)) - response_logger.removeHandler(self.response_handler) def log_error( self, @@ -212,18 +244,30 @@ def log_error( "error_context": error_context or {} } - # 控制台输出 - self.logger.error(f"❌ LLM Error [{request_id}]") - self.logger.error(f" Error Type: {type(error).__name__}") - self.logger.error(f" Error Message: {str(error)}") + # 只输出到文件,不在控制台显示 + # 注释掉控制台输出,避免混淆终端输出 # 文件输出 if self.enable_file_logging: - error_logger = logging.getLogger(f"Mirix.LLMDebug.Errors") - error_logger.addHandler(self.error_handler) + error_logger = logging.getLogger(f"Mirix.LLMDebug.Errors.{request_id}") error_logger.setLevel(logging.ERROR) + error_logger.propagate = False + + # 清除现有处理器,只添加错误处理器 + error_logger.handlers.clear() + error_logger.addHandler(self.error_handler) + + # 记录错误信息 + error_logger.error(f"❌ LLM Error [{request_id}]") + error_logger.error(f" Error Type: {type(error).__name__}") + error_logger.error(f" Error Message: {str(error)}") + + if error_context: + error_logger.error(f" Error Context: {error_context}") + + # 记录完整的错误数据 + error_logger.error("=== FULL ERROR DATA ===") error_logger.error(json.dumps(log_data, ensure_ascii=False, indent=2)) - error_logger.removeHandler(self.error_handler) def log_json_parse_error( self, From 110f31c45f8490bd9613001b2e2a621c60311a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=AE=81=E7=A4=BE?= <524395609@qq.com> Date: Fri, 12 Sep 2025 20:52:09 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E7=B4=AF=E7=A7=AF=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=95=B0=E9=87=8F=EF=BC=8C=E5=8F=AF=E4=BB=A5=E9=80=9A=E8=BF=87?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E8=AE=BE=E7=BD=AE=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mirix/agent/app_constants.py | 5 ++++- set_low_memory_limit.sh | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100755 set_low_memory_limit.sh diff --git a/mirix/agent/app_constants.py b/mirix/agent/app_constants.py index cf9f7659..8642a90e 100644 --- a/mirix/agent/app_constants.py +++ b/mirix/agent/app_constants.py @@ -1,4 +1,7 @@ -TEMPORARY_MESSAGE_LIMIT = 20 +import os + +# 可以通过环境变量 TEMPORARY_MESSAGE_LIMIT 设置,默认为 20 +TEMPORARY_MESSAGE_LIMIT = int(os.environ.get('TEMPORARY_MESSAGE_LIMIT', '20')) MAXIMUM_NUM_IMAGES_IN_CLOUD = 600 GEMINI_MODELS = ['gemini-2.0-flash', 'gemini-2.5-flash-lite', 'gemini-1.5-pro', 'gemini-2.0-flash-lite', 'gemini-2.5-flash'] diff --git a/set_low_memory_limit.sh b/set_low_memory_limit.sh new file mode 100755 index 00000000..3383c716 --- /dev/null +++ b/set_low_memory_limit.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# 设置较低的 TEMPORARY_MESSAGE_LIMIT 用于测试记忆功能 + +echo "🔧 设置较低的 TEMPORARY_MESSAGE_LIMIT 用于测试记忆功能..." + +# 设置环境变量 +export TEMPORARY_MESSAGE_LIMIT=1 + +echo "✅ 环境变量已设置:" +echo " TEMPORARY_MESSAGE_LIMIT=$TEMPORARY_MESSAGE_LIMIT" +echo "" + +echo "🚀 现在可以运行测试了:" +echo " python test_memory_with_low_limit.py" +echo "" + +echo "📝 或者运行原始测试:" +echo " python -m tests.run_specific_memory_test episodic_memory_indirect --config mirix/configs/mirix_qwen3.yaml" +echo "" + +echo "💡 提示: 现在只需要发送 1 条消息就会触发记忆保存,而不是默认的 20 条" From 8e76f894779261b33a2021729f33edac391861a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=AE=81=E7=A4=BE?= <524395609@qq.com> Date: Tue, 16 Sep 2025 15:55:44 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E6=96=87=E6=9C=AC=E5=AF=B9=E8=AF=9D=E7=9A=84=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_memory.py | 113 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/tests/test_memory.py b/tests/test_memory.py index eeb0b36e..ea6ef3bd 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -1318,6 +1318,110 @@ def test_file_with_memory(): print("\nFile memory test completed!") +def test_memory_based_conversation(agent): + """ + 基于记忆的会话测试 + + 这个测试函数接受外部提供的agent参数,测试基于记忆的会话功能。 + 测试流程: + 1. 先保存一些记忆内容 + 2. 然后基于这些记忆进行会话 + 3. 验证AI能够基于之前的记忆回答问题 + + Args: + agent: 外部提供的AgentWrapper实例 + """ + print("=== 基于记忆的会话测试 ===") + + try: + # 步骤1: 保存一些记忆内容 + print("\n--- 步骤1: 保存记忆内容 ---") + + # 保存个人信息到核心记忆 + personal_info_message = "我的名字是张三,我是一名软件工程师,住在北京,喜欢编程和阅读。" + response1 = agent.send_message( + message=personal_info_message, + memorizing=True + ) + print(f"个人信息保存响应: {response1}") + + # 保存工作相关信息到情节记忆 + work_info_message = "我今天完成了一个重要的项目,使用Python开发了一个机器学习模型,准确率达到了95%。" + response2 = agent.send_message( + message=work_info_message, + memorizing=True + ) + print(f"工作信息保存响应: {response2}") + + # 保存兴趣爱好到语义记忆 + hobby_info_message = "我对人工智能和深度学习很感兴趣,经常阅读相关论文和参加技术会议。" + response3 = agent.send_message( + message=hobby_info_message, + memorizing=True + ) + print(f"兴趣爱好保存响应: {response3}") + + # 步骤2: 基于记忆进行会话(不保存新记忆) + print("\n--- 步骤2: 基于记忆的会话测试 ---") + + # 测试1: 询问个人信息 + print("\n测试1: 询问个人信息") + personal_question = "请告诉我的名字和职业是什么?" + personal_response = agent.send_message( + message=personal_question, + memorizing=False # 不保存新记忆,只基于已有记忆回答 + ) + print(f"个人信息问题: {personal_question}") + print(f"基于记忆的回答: {personal_response}") + + # 测试2: 询问工作相关 + print("\n测试2: 询问工作相关") + work_question = "我今天做了什么工作?有什么成果吗?" + work_response = agent.send_message( + message=work_question, + memorizing=False + ) + print(f"工作问题: {work_question}") + print(f"基于记忆的回答: {work_response}") + + # 测试3: 询问兴趣爱好 + print("\n测试3: 询问兴趣爱好") + hobby_question = "我对什么技术领域比较感兴趣?" + hobby_response = agent.send_message( + message=hobby_question, + memorizing=False + ) + print(f"兴趣爱好问题: {hobby_question}") + print(f"基于记忆的回答: {hobby_response}") + + # 测试4: 综合记忆查询 + print("\n测试4: 综合记忆查询") + comprehensive_question = "请总结一下你了解到的关于我的所有信息。" + comprehensive_response = agent.send_message( + message=comprehensive_question, + memorizing=False + ) + print(f"综合问题: {comprehensive_question}") + print(f"基于记忆的综合回答: {comprehensive_response}") + + # 测试5: 记忆关联测试 + print("\n测试5: 记忆关联测试") + connection_question = "我的职业和兴趣爱好之间有什么联系吗?" + connection_response = agent.send_message( + message=connection_question, + memorizing=False + ) + print(f"关联问题: {connection_question}") + print(f"基于记忆的关联分析: {connection_response}") + + print("\n✅ 基于记忆的会话测试完成!") + return True + + except Exception as e: + print(f"❌ 基于记忆的会话测试失败: {e}") + traceback.print_exc() + return False + def run_file_tests(): """Run all file-related tests""" test_tracker.start_test("File Handling Tests", "Testing file uploading and processing with multiple AI providers") @@ -2142,6 +2246,9 @@ def run_specific_memory_test(test_name, agent=None, config_path=None, delete_aft # Core memory tests - 'core_memory_update_using_chat_agent': 测试使用聊天代理更新核心记忆 + # Memory-based conversation tests + - 'memory_based_conversation': 测试基于记忆的会话功能 + # File handling tests - 'greeting_with_files': 测试文件处理功能 - 'file_types': 测试不同文件类型 @@ -2183,6 +2290,9 @@ def run_specific_memory_test(test_name, agent=None, config_path=None, delete_aft # Core memory tests 'core_memory_update_using_chat_agent': test_core_memory_update_using_chat_agent, + # Memory-based conversation tests + 'memory_based_conversation': test_memory_based_conversation, + # File handling tests 'greeting_with_files': test_greeting_with_files, 'file_types': test_file_types, @@ -2346,6 +2456,9 @@ def run_multiple_memory_tests(test_names, agent=None, config_path=None, delete_a # 'fts5_performance_comparison' # ]) + # 示例5: 运行基于记忆的会话测试 + # run_specific_memory_test('memory_based_conversation') + # 默认运行所有测试 test_all_memories() From 4cbed044fd1af4d6464c3051f4e50f2719f32cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=AE=81=E7=A4=BE?= <524395609@qq.com> Date: Wed, 17 Sep 2025 11:26:28 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E4=BC=9A=E8=AF=9Dprompt=E6=94=B9=E5=8F=91?= =?UTF-8?q?=E9=80=81send=5Fmessage=E4=B8=BA=E6=98=8E=E7=A1=AE=E8=A6=81?= =?UTF-8?q?=E6=B1=82=E8=B0=83=E7=94=A8send=5Fmessage=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mirix/prompts/system/base/chat_agent.txt | 12 ++++++++++-- .../prompts/system/screen_monitor/chat_agent.txt | 16 ++++++++++++---- .../screen_monitor/chat_agent_monitor_on.txt | 14 +++++++++++--- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/mirix/prompts/system/base/chat_agent.txt b/mirix/prompts/system/base/chat_agent.txt index 4c919231..7bc4eeb7 100755 --- a/mirix/prompts/system/base/chat_agent.txt +++ b/mirix/prompts/system/base/chat_agent.txt @@ -53,7 +53,7 @@ When processing user messages, adhere to this structured approach: (1) **Optional Reasoning Phase**: Analyze the user's query internally. Messages without function calls are treated as reasoning messages and remain invisible to users. During this phase, you may utilize `search_in_memory` and `list_memory_within_timerange` to gather necessary information. -(2) **Mandatory Response Transmission**: Execute `send_message` to deliver responses to users. Only content within the `send_message(msg)` function is visible to users. Failure to execute `send_message` will result in system loop errors. +(2) **Mandatory Tool Call**: You MUST call the `send_message` tool to deliver responses to users. This is a required function call, not just a text response. The tool call format should be: `send_message(message="your response here")`. Only content within the `send_message` tool call is visible to users. Failure to call the `send_message` tool will result in system loop errors. Memory Search Parameters: `search_in_memory` supports queries across `episodic`, `procedural`, `resource`, `knowledge vault`, and `semantic` memory categories. Core memory search is not required as this information is fully accessible. @@ -63,4 +63,12 @@ For queries requiring temporal memory retrieval, utilize `list_memory_within_tim System Behavior Notes: Messages without function calls serve as internal reasoning and are not displayed to users. To share reasoning processes with users, utilize `send_intermediate_message` for intermediate communications that do not terminate the processing chain. -Critical: Complete all reasoning processes with `send_message` to prevent infinite processing loops. \ No newline at end of file +**CRITICAL TOOL CALL REQUIREMENTS:** +- You MUST call the `send_message` tool for every user interaction +- This is a function call, not a text response +- Tool call format: `send_message(message="your response content")` +- Do NOT just write text responses without calling the tool +- Do NOT put function calls in the content field - use the proper tool call format +- Failure to call the `send_message` tool will cause infinite processing loops + +Critical: Complete all reasoning processes with a `send_message` tool call to prevent infinite processing loops. \ No newline at end of file diff --git a/mirix/prompts/system/screen_monitor/chat_agent.txt b/mirix/prompts/system/screen_monitor/chat_agent.txt index 9845fd4c..6efbd3ee 100755 --- a/mirix/prompts/system/screen_monitor/chat_agent.txt +++ b/mirix/prompts/system/screen_monitor/chat_agent.txt @@ -21,16 +21,24 @@ You are the Chat Agent, responsible for user communication and proactive memory **User Interaction Protocol:** 1. **Reasoning Phase** (optional): Analyze queries internally using memory search tools -2. **Response Transmission** (mandatory): Use `send_message` to respond to users -3. **`send_message` only for final responses**: Terminates chaining. Use `send_intermediate_message` for status updates. NEVER use `send_message` to return something like "I will...", "I am doing...". These should be sent using `send_intermediate_message`. +2. **Response Transmission** (mandatory): You MUST call the `send_message` tool to respond to users +3. **`send_message` tool call only for final responses**: Terminates chaining. Use `send_intermediate_message` for status updates. NEVER use `send_message` to return something like "I will...", "I am doing...". These should be sent using `send_intermediate_message`. **CRITICAL: Conversation Flow Rules:** - `send_intermediate_message` is ONLY for brief status updates during long operations -- EVERY user query MUST end with a `send_message` call containing your final response +- EVERY user query MUST end with a `send_message` tool call containing your final response - Do NOT use multiple consecutive `send_intermediate_message` calls without substantial work between them -- If you have completed your task or answered the question, use `send_message` immediately +- If you have completed your task or answered the question, call the `send_message` tool immediately - `send_intermediate_message` does NOT end the conversation - you must continue processing +**CRITICAL TOOL CALL REQUIREMENTS:** +- You MUST call the `send_message` tool for every user interaction +- This is a function call, not a text response +- Tool call format: `send_message(message="your response content")` +- Do NOT just write text responses without calling the tool +- Do NOT put function calls in the content field - use the proper tool call format +- Failure to call the `send_message` tool will cause infinite processing loops + **Key Guidelines:** - Maintain concise internal monologue (max 50 words) - Monitor user sentiment; update Persona Block if self-improvement needed diff --git a/mirix/prompts/system/screen_monitor/chat_agent_monitor_on.txt b/mirix/prompts/system/screen_monitor/chat_agent_monitor_on.txt index bc33edd4..7e3e3b9f 100755 --- a/mirix/prompts/system/screen_monitor/chat_agent_monitor_on.txt +++ b/mirix/prompts/system/screen_monitor/chat_agent_monitor_on.txt @@ -23,12 +23,20 @@ When users are upset/frustrated: Message Processing: 1. **Reasoning Phase**: Analyze queries internally using `search_in_memory`/`list_memory_within_timerange` (invisible to users) -2. **Response**: Use `send_message` only when finished - this terminates processing -3. **`send_message` only for final responses**: Terminates chaining. Use `send_intermediate_message` for status updates. NEVER use `send_message` to return something like "I will...", "I am doing...". These should be sent using `send_intermediate_message`. +2. **Response**: You MUST call the `send_message` tool only when finished - this terminates processing +3. **`send_message` tool call only for final responses**: Terminates chaining. Use `send_intermediate_message` for status updates. NEVER use `send_message` to return something like "I will...", "I am doing...". These should be sent using `send_intermediate_message`. + +**CRITICAL TOOL CALL REQUIREMENTS:** +- You MUST call the `send_message` tool for every user interaction +- This is a function call, not a text response +- Tool call format: `send_message(message="your response content")` +- Do NOT just write text responses without calling the tool +- Do NOT put function calls in the content field - use the proper tool call format +- Failure to call the `send_message` tool will cause infinite processing loops Memory Search: - `search_in_memory`: Query `episodic`, `procedural`, `resource`, `knowledge vault`, `semantic` memories - `list_memory_within_timerange`: For temporal queries (infer time ranges independently) - Core memory is fully accessible without search -Critical: Always complete with `send_message` to prevent infinite loops. \ No newline at end of file +Critical: Always complete with a `send_message` tool call to prevent infinite loops. \ No newline at end of file From 24a502c18cb4cf836fafb2125a0d7ca5a08742c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=AE=81=E7=A4=BE?= <524395609@qq.com> Date: Wed, 17 Sep 2025 14:38:18 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E5=8F=98=E9=87=8F=EF=BC=8C=E5=A2=9E=E5=8A=A0=E4=BA=86=E5=8E=BB?= =?UTF-8?q?=E9=99=A4embedding=E7=9A=84=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- set_low_memory_limit.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/set_low_memory_limit.sh b/set_low_memory_limit.sh index 3383c716..931e4d32 100755 --- a/set_low_memory_limit.sh +++ b/set_low_memory_limit.sh @@ -5,6 +5,7 @@ echo "🔧 设置较低的 TEMPORARY_MESSAGE_LIMIT 用于测试记忆功能..." # 设置环境变量 export TEMPORARY_MESSAGE_LIMIT=1 +export LLM_DEBUG_ENABLE_FILE=true echo "✅ 环境变量已设置:" echo " TEMPORARY_MESSAGE_LIMIT=$TEMPORARY_MESSAGE_LIMIT" From a914599ad7d6ef9b5a4654d496e541d9f831dd44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=AE=81=E7=A4=BE?= <524395609@qq.com> Date: Wed, 17 Sep 2025 14:47:22 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=94=99=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- set_low_memory_limit.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/set_low_memory_limit.sh b/set_low_memory_limit.sh index 931e4d32..802b2492 100755 --- a/set_low_memory_limit.sh +++ b/set_low_memory_limit.sh @@ -5,7 +5,7 @@ echo "🔧 设置较低的 TEMPORARY_MESSAGE_LIMIT 用于测试记忆功能..." # 设置环境变量 export TEMPORARY_MESSAGE_LIMIT=1 -export LLM_DEBUG_ENABLE_FILE=true +export BUILD_EMBEDDINGS_FOR_MEMORY=false echo "✅ 环境变量已设置:" echo " TEMPORARY_MESSAGE_LIMIT=$TEMPORARY_MESSAGE_LIMIT"