From 9e2e98ef40a232c519a524de8bda1ca86e85466a Mon Sep 17 00:00:00 2001 From: jinliyl <6469360+jinliyl@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:43:21 +0800 Subject: [PATCH] Dev/readme (#137) * refactor(memory): rename copaw to reme and update module structure * chore(release): bump version to 0.3.0.6b1 * refactor(docs): update README and ReMeLight implementation * refactor(reme): rename ReMeCopaw to ReMeLight and remove CLI module * docs(readme): update Chinese documentation with enhanced structure and content * docs(readme): update Chinese documentation for context compression * docs(readme): update context compression section header * docs(readme): update documentation with installation and usage guide * docs(readme): update Chinese documentation table * chore(deps): update dependency extras configuration * chore(test): remove deprecated test files for message operations * docs(readme): update documentation with ReMeLight implementation changes --- README.md | 476 ++++++---------- README_ZH.md | 539 +++++++----------- example.env | 14 +- pyproject.toml | 6 +- reme/__init__.py | 4 +- reme/config/{copaw.yaml => light.yaml} | 0 reme/core/llm/base_llm.py | 4 +- reme/core/service_context.py | 8 +- reme/{memory => extension}/cli/__init__.py | 0 reme/{memory => extension}/cli/fb_cli.py | 0 reme/{memory => extension}/cli/fb_cli.yaml | 0 .../{memory => extension}/cli/fb_compactor.py | 0 .../cli/fb_compactor.yaml | 0 .../cli/fb_context_checker.py | 0 .../cli/fb_summarizer.py | 0 .../cli/fb_summarizer.yaml | 0 reme/{ => extension}/reme_cli.py | 0 reme/memory/__init__.py | 4 +- .../__init__.py | 10 +- .../compactor.py | 0 .../compactor.yaml | 0 .../file_io.py | 0 .../memory_formatter.py | 0 reme/memory/file_based/reme_chat_formatter.py | 29 + .../reme_in_memory_memory.py} | 2 +- .../summarizer.py | 0 .../summarizer.yaml | 0 .../tool_result_compactor.py | 0 .../{file_based_copaw => file_based}/utils.py | 40 ++ reme/memory/skills/__init__.py | 0 reme/{reme_copaw.py => reme_light.py} | 153 ++--- test/test_agentic_retrieve_op.py | 113 ---- {tests => test}/test_fs_compactor.py | 0 {tests => test}/test_fs_context_checker.py | 0 .../test_fs_file_watch_integration.py | 0 {tests => test}/test_fs_memory_get.py | 0 {tests => test}/test_fs_memory_search.py | 0 {tests => test}/test_fs_summary.py | 0 test/test_message_compact_op.py | 81 --- test/test_message_compress_op.py | 285 --------- test/test_message_offload_op.py | 247 -------- tests/{copaw => light}/test_compactor.py | 2 +- .../{copaw => light}/test_memory_formatter.py | 2 +- tests/light/test_reme_light.py | 198 +++++++ tests/{copaw => light}/test_summarizer.py | 2 +- .../test_tool_result_compactor.py | 4 +- tests/{copaw => light}/test_utils.py | 2 +- 47 files changed, 745 insertions(+), 1480 deletions(-) rename reme/config/{copaw.yaml => light.yaml} (100%) rename reme/{memory => extension}/cli/__init__.py (100%) rename reme/{memory => extension}/cli/fb_cli.py (100%) rename reme/{memory => extension}/cli/fb_cli.yaml (100%) rename reme/{memory => extension}/cli/fb_compactor.py (100%) rename reme/{memory => extension}/cli/fb_compactor.yaml (100%) rename reme/{memory => extension}/cli/fb_context_checker.py (100%) rename reme/{memory => extension}/cli/fb_summarizer.py (100%) rename reme/{memory => extension}/cli/fb_summarizer.yaml (100%) rename reme/{ => extension}/reme_cli.py (100%) rename reme/memory/{file_based_copaw => file_based}/__init__.py (76%) rename reme/memory/{file_based_copaw => file_based}/compactor.py (100%) rename reme/memory/{file_based_copaw => file_based}/compactor.yaml (100%) rename reme/memory/{file_based_copaw => file_based}/file_io.py (100%) rename reme/memory/{file_based_copaw => file_based}/memory_formatter.py (100%) create mode 100644 reme/memory/file_based/reme_chat_formatter.py rename reme/memory/{file_based_copaw/copaw_in_memory_memory.py => file_based/reme_in_memory_memory.py} (99%) rename reme/memory/{file_based_copaw => file_based}/summarizer.py (100%) rename reme/memory/{file_based_copaw => file_based}/summarizer.yaml (100%) rename reme/memory/{file_based_copaw => file_based}/tool_result_compactor.py (100%) rename reme/memory/{file_based_copaw => file_based}/utils.py (84%) delete mode 100644 reme/memory/skills/__init__.py rename reme/{reme_copaw.py => reme_light.py} (88%) delete mode 100644 test/test_agentic_retrieve_op.py rename {tests => test}/test_fs_compactor.py (100%) rename {tests => test}/test_fs_context_checker.py (100%) rename {tests => test}/test_fs_file_watch_integration.py (100%) rename {tests => test}/test_fs_memory_get.py (100%) rename {tests => test}/test_fs_memory_search.py (100%) rename {tests => test}/test_fs_summary.py (100%) delete mode 100644 test/test_message_compact_op.py delete mode 100644 test/test_message_compress_op.py delete mode 100644 test/test_message_offload_op.py rename tests/{copaw => light}/test_compactor.py (99%) rename tests/{copaw => light}/test_memory_formatter.py (99%) create mode 100644 tests/light/test_reme_light.py rename tests/{copaw => light}/test_summarizer.py (99%) rename tests/{copaw => light}/test_tool_result_compactor.py (97%) rename tests/{copaw => light}/test_utils.py (97%) diff --git a/README.md b/README.md index 98c5dd8c..c2c17069 100644 --- a/README.md +++ b/README.md @@ -17,144 +17,59 @@

- A memory management toolkit for AI agents — Remember Me, Refine Me.
+ Memory Management Toolkit for AI Agents, Remember Me, Refine Me.

-> For legacy versions, see [0.2.x Documentation](docs/README_0_2_x.md) +> For legacy versions, please refer to [0.2.x Documentation](docs/README_0_2_x.md) --- -🧠 ReMe is a **memory management framework** built for **AI agents**, offering both **file-based** and **vector-based** -memory systems. +🧠 ReMe is a memory management framework built specifically for **AI Agents**, offering both file-based and vector-based memory systems. -It addresses two core problems of agent memory: **limited context windows** (early information gets truncated or lost -during -long conversations) and **stateless sessions** (new conversations cannot inherit history and always start from scratch). +It addresses two core memory challenges for agents: **Limited context window** (early information gets truncated or lost in long conversations), and **Stateless sessions** (new conversations cannot inherit history, starting from scratch every time). -ReMe gives agents **real memory** — old conversations are automatically condensed, important information is persisted, -and the next conversation can recall it automatically. +ReMe gives agents **true memory capability** — old conversations are automatically condensed, important information is persistently stored, and relevant context is automatically recalled in future conversations. --- -## 📁 File-Based CoPaw Memory System +## 📁 File-Based Memory System -> Memory as files, files as memory +> Memory as Files, Files as Memory -Treat **memory as files** — readable, editable, and portable. [CoPaw](https://github.com/agentscope-ai/CoPaw) -integrates this memory system -through [MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py), -which inherits `ReMeCopaw` and exposes memory management capabilities. +Treat **memory as files** — readable, editable, and copyable. [CoPaw](https://github.com/agentscope-ai/CoPaw)'s memory system inherits from `ReMeLight`, implementing memory management capabilities. -| Traditional Memory Systems | File-Based ReMe | -|----------------------------|--------------------| -| 🗄️ Database storage | 📝 Markdown files | -| 🔒 Opaque | 👀 Read anytime | -| ❌ Hard to modify | ✏️ Edit directly | +| Traditional Memory System | File Based ReMe | +|---------------------------|-------------------| +| 🗄️ Database storage | 📝 Markdown files | +| 🔒 Invisible | 👀 Always readable | +| ❌ Hard to modify | ✏️ Direct editing | | 🚫 Hard to migrate | 📦 Copy to migrate | ``` working_dir/ -├── MEMORY.md # Long-term memory: user preferences, project config, etc. +├── MEMORY.md # Long-term memory: user preferences, project configs, etc. ├── memory/ -│ └── YYYY-MM-DD.md # Daily summary logs: written automatically after conversation ends -└── tool_result/ # Cache for oversized tool outputs (auto-managed, auto-cleaned when expired) +│ └── YYYY-MM-DD.md # Daily summary logs: auto-written after conversations +└── tool_result/ # Long tool output cache (auto-managed, auto-cleanup on expiry) └── .txt ``` ### Core Capabilities -[ReMeCopaw](reme/reme_copaw.py) is the core class of this memory system, providing complete memory management -capabilities for AI Agents: +[ReMeLight](reme/reme_light.py) is the core class of this memory system, providing complete memory management capabilities for AI Agents: -| Method | Function | Key Components | -|--------------------------|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `start` | 🚀 Start memory system | Initialize file store, file watcher, Embedding cache; clean up expired tool result files | -| `close` | 📕 Close and clean up | Clean tool result files, stop file watcher, save Embedding cache | -| `compact_memory` | 📦 Compact history to summary | [Compactor](reme/memory/file_based_copaw/compactor.py) — ReActAgent generates structured context checkpoint | -| `summary_memory` | 📝 Write important memory to files | [Summarizer](reme/memory/file_based_copaw/summarizer.py) — ReActAgent + file tools (read / write / edit) | -| `compact_tool_result` | ✂️ Compact oversized tool output | [ToolResultCompactor](reme/memory/file_based_copaw/tool_result_compactor.py) — Truncate and save to `tool_result/`, keep file reference in message | -| `add_async_summary_task` | ⚡ Submit background summary task | `asyncio.create_task`, summary doesn't block main conversation flow | -| `await_summary_tasks` | ⏳ Wait for background tasks | Collect results from all background summary tasks, call before closing to ensure writes complete | -| `memory_search` | 🔍 Semantic memory search | [MemorySearch](reme/memory/tools/chunk/memory_search.py) — Vector + BM25 hybrid retrieval | -| `get_in_memory_memory` | 🗂️ Create in-memory instance | [CoPawInMemoryMemory](reme/memory/file_based_copaw/copaw_in_memory_memory.py) — Token-aware memory management, supports compression summary and state serialization | -| `update_params` | ⚙️ Update runtime parameters | Adjust `max_input_length`, `memory_compact_ratio`, `language` at runtime | - ---- - -## 🗃️ Vector-Based ReMe - -[ReMe Vector Based](reme/reme.py) is the core class for the vector-based memory system, supporting unified management of -three memory types: - -| Memory Type | Purpose | Usage Context | -|------------------------------|-----------------------------------------------------|---------------| -| **Personal memory** | User preferences, habits | `user_name` | -| **Task / procedural memory** | Task execution experience, success/failure patterns | `task_name` | -| **Tool memory** | Tool usage experience, parameter tuning | `tool_name` | - -### Core Capabilities - -| Method | Function | Description | -|--------------------|---------------------|-----------------------------------------------------------| -| `summarize_memory` | 🧠 Summarize memory | Automatically extract and store memory from conversations | -| `retrieve_memory` | 🔍 Retrieve memory | Retrieve relevant memory by query | -| `add_memory` | ➕ Add memory | Manually add memory to vector store | -| `get_memory` | 📖 Get memory | Fetch a single memory by ID | -| `update_memory` | ✏️ Update memory | Update content or metadata of existing memory | -| `delete_memory` | 🗑️ Delete memory | Delete specified memory | -| `list_memory` | 📋 List memory | List memories with filtering and sorting | - ---- - -## 💻 ReMeCli: Terminal Assistant with File-Based Memory - - - - - - - -
-


-
- - -


-
- -### When Is Memory Written? - -| Scenario | Written to | Trigger | -|---------------------------------------------|------------------------|------------------------------------| -| Auto-compact when context is too long | `memory/YYYY-MM-DD.md` | Automatic in background | -| User runs `/compact` | `memory/YYYY-MM-DD.md` | Manual compact + background save | -| User runs `/new` | `memory/YYYY-MM-DD.md` | New conversation + background save | -| User says "remember this" | `MEMORY.md` or log | Agent writes via `write` tool | -| Agent finds important decisions/preferences | `MEMORY.md` | Agent writes proactively | - -### Memory Retrieval Tools - -| Method | Tool | When to use | Example | -|-----------------|-----------------|----------------------------------|---------------------------------------| -| Semantic search | `memory_search` | Unsure where it is, fuzzy lookup | "Earlier discussion about deployment" | -| Direct read | `read` | Know the date or file | Read `memory/2025-02-13.md` | - -Search uses **vector + BM25 hybrid retrieval** (vector weight 0.7, BM25 weight 0.3), so queries using both natural -language and exact -keywords can match. - -### Built-in Tools - -| Tool | Function | Details | -|-----------------|----------------|------------------------------------------------------------| -| `memory_search` | Search memory | Vector + BM25 hybrid search over MEMORY.md and memory/*.md | -| `bash` | Run commands | Execute bash commands with timeout and output truncation | -| `ls` | List directory | Show directory structure | -| `read` | Read file | Text and images supported, with segmented reading | -| `edit` | Edit file | Replace after exact text match | -| `write` | Write file | Create or overwrite, auto-create directories | -| `execute_code` | Run Python | Execute code snippets | -| `web_search` | Web search | Search via Tavily | +| Method | Function | Key Components | +|---------------------------|-----------------------------|---------------------------------------------------------------------------------------------------------------------| +| `start` | 🚀 Start memory system | Initialize file store, file watcher, embedding cache; cleanup expired tool result files | +| `close` | 📕 Close and cleanup | Cleanup tool result files, stop file watcher, save embedding cache | +| `compact_memory` | 📦 Compress history to summary | [Compactor](reme/memory/file_based/compactor.py) — ReActAgent generates structured context checkpoints | +| `summary_memory` | 📝 Write important memories to files | [Summarizer](reme/memory/file_based/summarizer.py) — ReActAgent + file tools (read / write / edit) | +| `compact_tool_result` | ✂️ Compress long tool outputs | [ToolResultCompactor](reme/memory/file_based/tool_result_compactor.py) — Truncate and save to `tool_result/`, keep file reference in message | +| `add_async_summary_task` | ⚡ Submit background summary task | `asyncio.create_task`, summary doesn't block main conversation flow | +| `await_summary_tasks` | ⏳ Wait for background tasks | Collect results from all background summary tasks, call before closing to ensure writes complete | +| `memory_search` | 🔍 Semantic memory search | [MemorySearch](reme/memory/tools/chunk/memory_search.py) — Vector + BM25 hybrid retrieval | +| `get_in_memory_memory` | 🗂️ Create in-memory instance | [ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) — Token-aware memory management, supports compression summaries and state serialization | +| `update_params` | ⚙️ Dynamically update runtime params | Runtime adjustment of `max_input_length`, `memory_compact_ratio`, `language` | --- @@ -163,131 +78,66 @@ keywords can match. ### Installation ```bash -pip install -U reme-ai +pip install -U reme-ai[as] ``` ### Environment Variables -API keys are set via environment variables; you can put them in a `.env` file in the project root: +`ReMeLight` environment variables configure embedding and storage backends -| Variable | Description | Example | -|---------------------------|----------------------------------|-----------------------------------------------------| -| `REME_LLM_API_KEY` | LLM API key | `sk-xxx` | -| `REME_LLM_BASE_URL` | LLM base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | -| `REME_EMBEDDING_API_KEY` | Embedding API key | `sk-xxx` | -| `REME_EMBEDDING_BASE_URL` | Embedding base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | -| `TAVILY_API_KEY` | Tavily search API key (optional) | `tvly-xxx` | - -### Using ReMeCli - -#### Start ReMeCli - -```bash -remecli config=cli -``` - -#### ReMeCli System Commands - -> Year of the Horse easter egg: `/horse` — fireworks, galloping animation, and random horse-year blessings. - -Commands starting with `/` control session state: - -| Command | Description | Waits for response | -|------------|--------------------------------------------------------------------|--------------------| -| `/compact` | Manually compact current conversation and save to long-term memory | Yes | -| `/new` | Start new conversation; history saved to long-term memory | No | -| `/clear` | Clear everything, **without saving** | No | -| `/history` | View uncompressed messages in current conversation | No | -| `/help` | Show command list | No | -| `/exit` | Exit | No | - -**Difference between the three commands** - -| Command | Compact summary | Long-term memory | Message history | -|------------|-----------------|------------------|-----------------| -| `/compact` | New summary | Saved | Keep recent | -| `/new` | Cleared | Saved | Cleared | -| `/clear` | Cleared | Not saved | Cleared | - -> `/clear` permanently deletes; nothing is persisted anywhere. - -### Using the ReMe Package - -#### File-Based ReMe (CoPaw Memory System) - -`ReMeCopaw` receives AgentScope components like `ChatModelBase`, `Formatter`, `Toolkit`, and configures Embedding and -storage backend via environment variables: - -| Environment Variable | Description | Default | -|----------------------------|-----------------------------------------------|-----------------------------------------------------| -| `EMBEDDING_API_KEY` | Embedding service API Key | `""` (vector search disabled if not configured) | -| `EMBEDDING_BASE_URL` | Embedding service Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | -| `EMBEDDING_MODEL_NAME` | Embedding model name | `""` | -| `EMBEDDING_DIMENSIONS` | Vector dimensions | `1024` | -| `EMBEDDING_CACHE_ENABLED` | Whether to enable Embedding cache | `true` | -| `EMBEDDING_MAX_CACHE_SIZE` | Maximum cache entries | `2000` | -| `FTS_ENABLED` | Whether to enable full-text search (BM25) | `true` | -| `MEMORY_STORE_BACKEND` | Storage backend (`auto` / `chroma` / `local`) | `auto` (local on Windows, chroma on others) | +| Environment Variable | Description | Default | +|-----------------------------|--------------------------------------|-----------------------------------------------------| +| `EMBEDDING_API_KEY` | Embedding service API Key | `""` | +| `EMBEDDING_BASE_URL` | Embedding service Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | +| `EMBEDDING_MODEL_NAME` | Embedding model name | `""` | +| `EMBEDDING_DIMENSIONS` | Vector dimensions | `1024` | +| `EMBEDDING_CACHE_ENABLED` | Enable embedding cache | `true` | +| `EMBEDDING_MAX_CACHE_SIZE` | Maximum cache entries | `2000` | +| `FTS_ENABLED` | Enable full-text search (BM25) | `true` | +| `MEMORY_STORE_BACKEND` | Storage backend (`auto` / `chroma` / `local`) | `auto` (local on Windows, chroma otherwise) | ```python import asyncio -from agentscope.formatter import ClaudeFormatter -from agentscope.model import get_model -from agentscope.token import HuggingFaceTokenCounter -from agentscope.tool import Toolkit - -from reme.reme_copaw import ReMeCopaw - +from agentscope.message import Msg +from reme.reme_light import ReMeLight async def main(): - # Prepare AgentScope core components - chat_model = get_model(config={"backend": "openai", "model_name": "qwen3.5-plus"}) - formatter = ClaudeFormatter() - token_counter = HuggingFaceTokenCounter() - toolkit = Toolkit() # Can register additional tools - - # Initialize ReMeCopaw - reme = ReMeCopaw( + reme = ReMeLight( working_dir=".reme", # Memory file storage directory - chat_model=chat_model, - formatter=formatter, - token_counter=token_counter, - toolkit=toolkit, max_input_length=128000, # Model context window (tokens) - memory_compact_ratio=0.7, # Trigger compaction when reaching max_input_length * 0.7 + memory_compact_ratio=0.7, # Trigger compression at max_input_length * 0.7 language="zh", # Summary language (zh / "") tool_result_threshold=1000, # Auto-save tool outputs exceeding this character count retention_days=7, # tool_result/ file retention days ) await reme.start() - messages = [...] # list[Msg], conversation history + messages = [...] - # 1. Compact oversized tool outputs (prevent tool results from overflowing context) + # 1. Compress long tool outputs (prevent tool results from bloating context) messages = await reme.compact_tool_result(messages) - # 2. Compact history to structured summary (trigger: context approaching limit) - summary = await reme.compact_memory( - messages=messages, - previous_summary="", # Can pass previous summary for incremental update - ) - print(f"Compact summary:\n{summary}") + # 2. Compress conversation history to structured summary (triggered when context approaches limit), pass previous summary for incremental updates + summary = await reme.compact_memory(messages=messages, previous_summary="") # 3. Submit async summary task in background (non-blocking, writes to memory/YYYY-MM-DD.md) reme.add_async_summary_task(messages=messages) # 4. Semantic memory search (Vector + BM25 hybrid retrieval) result = await reme.memory_search(query="Python version preference", max_results=5) - print(f"Search results: {result}") - # 5. Get in-memory instance (CoPawInMemoryMemory, manages single conversation context) + # 5. Get in-memory instance (ReMeInMemoryMemory, manages single conversation context) AgentScope InMemoryMemory memory = reme.get_in_memory_memory() token_stats = await memory.estimate_tokens() print(f"Current context usage: {token_stats['context_usage_ratio']:.1f}%") + print(f"Message tokens: {token_stats['messages_tokens']}") + print(f"Estimated total tokens: {token_stats['estimated_tokens']}") # 6. Wait for background tasks before closing - await reme.await_summary_tasks() + summary_result = await reme.await_summary_tasks() + + # Close ReMeLight await reme.close() @@ -297,6 +147,28 @@ if __name__ == "__main__": #### Vector-Based ReMe +## 🗃️ Vector-Based ReMe + +[ReMe Vector Based](reme/reme.py) is the core class of the vector-based memory system, supporting unified management of three memory types: + +| Memory Type | Purpose | Use Case | +|----------------------|--------------------------------------|--------------| +| **Personal Memory** | Record user preferences, habits | `user_name` | +| **Task/Procedural Memory** | Record task execution experience, success/failure patterns | `task_name` | +| **Tool Memory** | Record tool usage experience, parameter optimization | `tool_name` | + +### Core Capabilities + +| Method | Function | Description | +|---------------------|------------------|------------------------------------| +| `summarize_memory` | 🧠 Memory Summary | Auto-extract and store memories from conversations | +| `retrieve_memory` | 🔍 Memory Retrieval | Retrieve relevant memories based on query | +| `add_memory` | ➕ Add Memory | Manually add memory to vector store | +| `get_memory` | 📖 Get Memory | Get single memory by ID | +| `update_memory` | ✏️ Update Memory | Update existing memory content or metadata | +| `delete_memory` | 🗑️ Delete Memory | Delete specified memory | +| `list_memory` | 📋 List Memories | List memories by type, supports filtering and sorting | + ```python import asyncio from reme import ReMe @@ -323,24 +195,24 @@ async def main(): messages = [ {"role": "user", "content": "Help me write a Python script", "time_created": "2026-02-28 10:00:00"}, - {"role": "assistant", "content": "Sure, I'll help you write it", "time_created": "2026-02-28 10:00:05"}, + {"role": "assistant", "content": "OK, let me help you", "time_created": "2026-02-28 10:00:05"}, ] - # 1. Summarize memory from conversation (auto-extract user preferences, task experience, etc.) + # 1. Summarize memories from conversation (auto-extract user preferences, task experience, etc.) result = await reme.summarize_memory( messages=messages, user_name="alice", # Personal memory # task_name="code_writing", # Task memory ) - print(f"Summarize result: {result}") + print(f"Summary result: {result}") - # 2. Retrieve relevant memory + # 2. Retrieve relevant memories memories = await reme.retrieve_memory( query="Python programming", user_name="alice", # task_name="code_writing", ) - print(f"Retrieve result: {memories}") + print(f"Retrieval result: {memories}") # 3. Manually add memory memory_node = await reme.add_memory( @@ -358,11 +230,11 @@ async def main(): updated_memory = await reme.update_memory( memory_id=memory_id, user_name="alice", - memory_content="User prefers concise, well-commented code style", + memory_content="User prefers concise code style with comments", ) print(f"Updated memory: {updated_memory}") - # 6. List all memories for user (with filtering and sorting) + # 6. List all user memories (supports filtering and sorting) all_memories = await reme.list_memory( user_name="alice", limit=10, @@ -385,91 +257,81 @@ if __name__ == "__main__": asyncio.run(main()) ``` ---- - ## 🏛️ Technical Architecture -### File-Based CoPaw Memory System Architecture +### File-Based ReMeLight Memory System Architecture -[CoPaw MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py) -inherits -`ReMeCopaw` and integrates memory capabilities into the Agent reasoning flow: +[CoPaw MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py) inherits from `ReMeLight`, integrating memory capabilities into the Agent reasoning flow: ```mermaid graph TB - CoPaw["CoPaw MemoryManager\n(inherits ReMeCopaw)"] -->|pre_reasoning hook| Hook[MemoryCompactionHook] - CoPaw --> ReMeCopaw[ReMeCopaw] - Hook -->|exceeds threshold| ReMeCopaw - ReMeCopaw --> CompactMemory[compact_memory\nHistory compaction] - ReMeCopaw --> SummaryMemory[summary_memory\nWrite memory to files] - ReMeCopaw --> CompactToolResult[compact_tool_result\nOversized tool output compaction] - ReMeCopaw --> MemSearch[memory_search\nSemantic search] - ReMeCopaw --> InMemory[get_in_memory_memory\nCoPawInMemoryMemory] + CoPaw["CoPaw MemoryManager\n(inherits ReMeLight)"] -->|pre_reasoning hook| Hook[MemoryCompactionHook] + CoPaw --> ReMeLight[ReMeLight] + Hook -->|exceeds threshold| ReMeLight + ReMeLight --> CompactMemory[compact_memory\nHistory Compression] + ReMeLight --> SummaryMemory[summary_memory\nWrite Memory to Files] + ReMeLight --> CompactToolResult[compact_tool_result\nLong Tool Output Compression] + ReMeLight --> MemSearch[memory_search\nSemantic Search] + ReMeLight --> InMemory[get_in_memory_memory\nReMeInMemoryMemory] CompactMemory --> Compactor[Compactor\nReActAgent] - SummaryMemory --> Summarizer[Summarizer\nReActAgent + file tools] - CompactToolResult --> ToolResultCompactor[ToolResultCompactor\nTruncate + save to file] + SummaryMemory --> Summarizer[Summarizer\nReActAgent + File Tools] + CompactToolResult --> ToolResultCompactor[ToolResultCompactor\nTruncate + Save to File] Summarizer --> FileIO[FileIO\nread / write / edit] FileIO --> MemoryFiles[memory/YYYY-MM-DD.md] ToolResultCompactor --> ToolResultFiles[tool_result/*.txt] - MemoryFiles -.->|File change| FileWatcher[Async File Watcher] - FileWatcher -->|Update index| FileStore[Local DB] + MemoryFiles -.->|file changes| FileWatcher[Async File Watcher] + FileWatcher -->|update index| FileStore[Local Database] MemSearch --> FileStore ``` -#### Auto-Compaction Trigger Flow +#### Auto-Compression Trigger Flow -`MemoryCompactionHook` checks context token usage before each reasoning step, automatically triggering compaction when -threshold is exceeded: +`MemoryCompactionHook` checks context token usage before each reasoning step, automatically triggering compression when threshold is exceeded: ```mermaid graph LR A[pre_reasoning] --> B{Token exceeds threshold?} B -->|No| Z[Continue reasoning] - B -->|Yes| C[compact_tool_result\nCompact oversized tool outputs in recent messages] + B -->|Yes| C[compact_tool_result\nCompress long tool outputs in recent messages] C --> D[compact_memory\nGenerate structured context checkpoint] D --> E[Mark old messages as COMPRESSED] - E --> F[add_async_summary_task\nBackground write to memory file] + E --> F[add_async_summary_task\nBackground write to memory files] F --> Z ``` -#### Context Compaction Summary Format +#### Context Compression Summary Format -[Compactor](reme/memory/file_based_copaw/compactor.py) uses ReActAgent to compact history into structured **context -checkpoints**: +[Compactor](reme/memory/file_based/compactor.py) uses ReActAgent to compress conversation history into structured **context checkpoints**: -| Field | Description | -|-----------------------|--------------------------------------------------| -| `## Goal` | 🎯 User's objectives (can be multiple) | -| `## Constraints` | ⚙️ Constraints and preferences mentioned by user | -| `## Progress` | 📈 Completed / in progress / blocked tasks | -| `## Key Decisions` | 🔑 Decisions made with brief reasons | -| `## Next Steps` | 🗺️ Next action plan (ordered list) | -| `## Critical Context` | 📌 File paths, function names, error messages | +| Field | Description | +|------------------------|----------------------------------------------| +| `## Goal` | 🎯 Goals the user wants to accomplish (can be multiple) | +| `## Constraints` | ⚙️ Constraints and preferences mentioned by user | +| `## Progress` | 📈 Completed / In-progress / Blocked tasks | +| `## Key Decisions` | 🔑 Decisions made with brief rationale | +| `## Next Steps` | 🗺️ Next action plan (ordered list) | +| `## Critical Context` | 📌 Key data like file paths, function names, error messages | -Supports **incremental updates**: when `previous_summary` is passed, automatically merges new conversation with old -summary, preserving historical progress. +Supports **incremental updates**: When `previous_summary` is provided, new conversation is automatically merged with old summary, preserving historical progress. -#### Tool Result Compaction +#### Tool Result Compression -[ToolResultCompactor](reme/memory/file_based_copaw/tool_result_compactor.py) solves context overflow caused by oversized -tool outputs: +[ToolResultCompactor](reme/memory/file_based/tool_result_compactor.py) solves the problem of context bloat caused by overly long tool outputs: ```mermaid graph LR A[tool_result message] --> B{Content length > threshold?} - B -->|No| C[Keep as-is] + B -->|No| C[Keep as is] B -->|Yes| D[Truncate to threshold characters] D --> E[Write full content to tool_result/uuid.txt] E --> F[Append file reference path to message] ``` -Expired files (exceeding `retention_days`) are automatically cleaned up during `start` / `close` / -`compact_tool_result`. +Expired files (exceeding `retention_days`) are automatically cleaned up during `start` / `close` / `compact_tool_result`. -#### Memory Summary: ReAct + File Tools +#### Memory Summarization: ReAct + File Tools -[Summarizer](reme/memory/file_based_copaw/summarizer.py) uses the **ReAct + file tools** pattern, letting AI -autonomously decide what to write and where: +[Summarizer](reme/memory/file_based/summarizer.py) uses the **ReAct + File Tools** pattern, letting AI autonomously decide what to write and where: ```mermaid graph LR @@ -477,68 +339,86 @@ graph LR B --> C[Act: read memory/YYYY-MM-DD.md] C --> D{Think: How to merge with existing content?} D --> E[Act: edit to update file] - E --> F{Think: Anything missing?} + E --> F{Think: Anything missed?} F -->|Yes| B - F -->|No| G[Done] + F -->|No| G[Complete] ``` -[FileIO](reme/memory/file_based_copaw/file_io.py) provides file operation tools: +[FileIO](reme/memory/file_based/file_io.py) provides file operation tools: -| Tool | Function | Use case | -|---------|--------------------------------|-----------------------------------------| -| `read` | Read file content (line range) | View existing memory, avoid duplicates | -| `write` | Overwrite file | Create new memory file or major rewrite | -| `edit` | Replace after exact match | Append or modify specific sections | +| Tool | Function | Use Case | +|---------|-------------------------------|-----------------------------------| +| `read` | Read file content (supports line ranges) | View existing memories, avoid duplicate writes | +| `write` | Overwrite file | Create new memory files or major restructuring | +| `edit` | Replace after exact match | Append new content or modify specific sections | -#### In-Memory Session Management +#### In-Memory Management -[CoPawInMemoryMemory](reme/memory/file_based_copaw/copaw_in_memory_memory.py) extends AgentScope's `InMemoryMemory`: +[ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) extends AgentScope's `InMemoryMemory`: -| Feature | Description | -|----------------------------------|---------------------------------------------------------------------| -| `get_memory` | Filter messages by mark, auto-prepend compression summary | -| `estimate_tokens` | Precisely estimate current context token usage and ratio | -| `get_history_str` | Generate human-readable conversation summary (with token stats) | -| `state_dict` / `load_state_dict` | Support state serialization / deserialization (session persistence) | +| Feature | Description | +|-----------------------------------|------------------------------------------| +| `get_memory` | Filter messages by tag, auto-prepend compression summary at head | +| `estimate_tokens` | Precisely estimate current context token usage and utilization | +| `get_history_str` | Generate human-readable conversation history summary (with token stats) | +| `state_dict` / `load_state_dict` | Support state serialization / deserialization (session persistence) | +| `mark_messages_compressed` | Mark messages as compressed state | +| `get_compressed_summary` | Get compressed summary content | #### Memory Retrieval -[MemorySearch](reme/memory/tools/chunk/memory_search.py) provides **vector + BM25 hybrid retrieval**: +[MemorySearch](reme/memory/tools/chunk/memory_search.py) provides **Vector + BM25 hybrid retrieval** capabilities: -| Retrieval | Strength | Weakness | -|---------------------|-------------------------------------------------|----------------------------------------| -| **Vector semantic** | Captures similar meaning with different wording | Weaker on exact token match | -| **BM25 full-text** | Strong exact token match | No synonym or paraphrase understanding | +| Retrieval Method | Advantage | Disadvantage | +|------------------|----------------------------------------|----------------------------------| +| **Vector Semantic** | Captures semantically similar but differently worded content | Weak on exact token matching | +| **BM25 Full-text** | Excellent for exact token hits | Cannot understand synonyms and paraphrases | -**Fusion**: Both retrieval paths are used; results are combined by weighted sum (vector 0.7 + BM25 0.3), so both -natural-language queries and exact lookups get reliable results. +**Fusion Mechanism**: After dual-path recall, weighted sum is applied (Vector 0.7 + BM25 0.3), enabling both natural language and exact lookups to hit. ```mermaid graph LR - Q[Search query] --> V[Vector search × 0.7] -Q --> B[BM25 × 0.3] -V --> M[Dedupe + weighted merge] -B --> M -M --> R[Top-N results] + Q[Search Query] --> V[Vector Search × 0.7] + Q --> B[BM25 × 0.3] + V --> M[Dedupe + Weighted Fusion] + B --> M + M --> R[Top-N Results] ``` --- ### Vector-Based ReMe Core Architecture +### Installation + +```bash +pip install -U reme-ai +``` + +### Environment Variables + +API keys are set via environment variables, can be written in `.env` file in project root: + +| Environment Variable | Description | Example | +|----------------------------|--------------------------|-----------------------------------------------------| +| `REME_LLM_API_KEY` | LLM API Key | `sk-xxx` | +| `REME_LLM_BASE_URL` | LLM Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | +| `REME_EMBEDDING_API_KEY` | Embedding API Key | `sk-xxx` | +| `REME_EMBEDDING_BASE_URL` | Embedding Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | + ```mermaid graph TB User[User / Agent] --> ReMe[Vector Based ReMe] - ReMe --> Summarize[Memory Summarize] - ReMe --> Retrieve[Memory Retrieve] - ReMe --> CRUD[CRUD] + ReMe --> Summarize[Memory Summarization] + ReMe --> Retrieve[Memory Retrieval] + ReMe --> CRUD[CRUD Operations] Summarize --> PersonalSum[PersonalSummarizer] Summarize --> ProceduralSum[ProceduralSummarizer] Summarize --> ToolSum[ToolSummarizer] Retrieve --> PersonalRet[PersonalRetriever] Retrieve --> ProceduralRet[ProceduralRetriever] Retrieve --> ToolRet[ToolRetriever] - PersonalSum --> VectorStore[Vector DB] + PersonalSum --> VectorStore[Vector Database] ProceduralSum --> VectorStore ToolSum --> VectorStore PersonalRet --> VectorStore @@ -546,19 +426,13 @@ graph TB ToolRet --> VectorStore ``` ---- - ## ⭐ Community & Support -- **Star & Watch**: Star helps more agent developers discover ReMe; Watch keeps you updated on new releases and - features. -- **Share your work**: In Issues or Discussions, share what ReMe unlocks for your agents — we’re happy to highlight - great community examples. -- **Need a new feature?** Open a Feature Request; we’ll iterate with the community. -- **Code contributions**: All forms of code contribution are welcome. See - the [Contribution Guide](docs/contribution.md). -- **Acknowledgments**: Thanks to OpenClaw, Mem0, MemU, CoPaw, and other open-source projects for inspiration and - support. +- **Star & Watch**: Star helps more agent developers discover ReMe; Watch keeps you informed about new releases and features. +- **Share Your Work**: Share what ReMe unlocked for your agent in Issues or Discussions — we'd love to showcase community achievements. +- **Need a Feature?** Submit a Feature Request, and we'll work with the community to improve. +- **Code Contributions**: All forms of code contributions are welcome, please see the [Contribution Guide](docs/contribution.md). +- **Acknowledgments**: Thanks to OpenClaw, Mem0, MemU, CoPaw, and other excellent open-source projects for their inspiration and help. --- @@ -577,7 +451,7 @@ graph TB ## ⚖️ License -This project is open source under the Apache License 2.0. See the [LICENSE](./LICENSE) file for details. +This project is open-sourced under the Apache License 2.0, see the [LICENSE](./LICENSE) file for details. --- diff --git a/README_ZH.md b/README_ZH.md index d5b8231b..99e6b1e1 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -33,13 +33,12 @@ ReMe 让智能体拥有**真正的记忆力**——旧对话自动浓缩,重 --- -## 📁 基于文件的 CoPaw 记忆系统 +## 📁 基于文件的记忆系统 > 记忆即文件,文件即记忆 -将**记忆视为文件**——可读、可编辑、可复制。[CoPaw](https://github.com/agentscope-ai/CoPaw) -通过 [MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py) -集成此记忆系统,继承 `ReMeCopaw` 并对外暴露记忆管理能力。 +将**记忆视为文件**——可读、可编辑、可复制。 +[CoPaw](https://github.com/agentscope-ai/CoPaw)通过继承 `ReMeLight` 实现了长期记忆和上下文的管理。 | 传统记忆系统 | File Based ReMe | |-----------|-----------------| @@ -59,22 +58,209 @@ working_dir/ ### 核心能力 -[ReMeCopaw](reme/reme_copaw.py) 是该记忆系统的核心类,为 AI Agent 提供完整的记忆管理能力: +[ReMeLight](reme/reme_light.py) 是该记忆系统的核心类,为 AI Agent 提供完整的记忆管理能力: -| 方法 | 功能 | 关键组件 | -|--------------------------|--------------|----------------------------------------------------------------------------------------------------------------| -| `start` | 🚀 启动记忆系统 | 初始化文件存储、文件监控、Embedding 缓存;清理过期工具结果文件 | -| `close` | 📕 关闭并清理 | 清理工具结果文件、停止文件监控、保存 Embedding 缓存 | -| `compact_memory` | 📦 压缩历史对话为摘要 | [Compactor](reme/memory/file_based_copaw/compactor.py) — ReActAgent 生成结构化上下文检查点 | -| `summary_memory` | 📝 将重要记忆写入文件 | [Summarizer](reme/memory/file_based_copaw/summarizer.py) — ReActAgent + 文件工具(read / write / edit) | -| `compact_tool_result` | ✂️ 压缩超长工具输出 | [ToolResultCompactor](reme/memory/file_based_copaw/tool_result_compactor.py) — 截断并转存到 `tool_result/`,消息中保留文件引用 | -| `add_async_summary_task` | ⚡ 提交后台摘要任务 | `asyncio.create_task`,摘要不阻塞主对话流程 | -| `await_summary_tasks` | ⏳ 等待后台任务完成 | 收集所有后台摘要任务的结果,关闭前调用确保写入完成 | -| `memory_search` | 🔍 语义搜索记忆 | [MemorySearch](reme/memory/tools/chunk/memory_search.py) — 向量 + BM25 混合检索 | -| `get_in_memory_memory` | 🗂️ 创建会话内存实例 | [CoPawInMemoryMemory](reme/memory/file_based_copaw/copaw_in_memory_memory.py) — Token 感知的内存管理,支持压缩摘要和状态序列化 | -| `update_params` | ⚙️ 动态更新运行时参数 | 运行时调整 `max_input_length`、`memory_compact_ratio`、`language` | +| 方法 | 功能 | 关键组件 | +|--------------------------|--------------|----------------------------------------------------------------------------------------------------------| +| `start` | 🚀 启动记忆系统 | 初始化文件存储、文件监控、Embedding 缓存;清理过期工具结果文件 | +| `close` | 📕 关闭并清理 | 清理工具结果文件、停止文件监控、保存 Embedding 缓存 | +| `compact_memory` | 📦 压缩历史对话为摘要 | [Compactor](reme/memory/file_based/compactor.py) — ReActAgent 生成结构化上下文检查点 | +| `summary_memory` | 📝 将重要记忆写入文件 | [Summarizer](reme/memory/file_based/summarizer.py) — ReActAgent + 文件工具(read / write / edit) | +| `compact_tool_result` | ✂️ 压缩超长工具输出 | [ToolResultCompactor](reme/memory/file_based/tool_result_compactor.py) — 截断并转存到 `tool_result/`,消息中保留文件引用 | | +| `memory_search` | 🔍 语义搜索记忆 | [MemorySearch](reme/memory/tools/chunk/memory_search.py) — 向量 + BM25 混合检索 | +| `get_in_memory_memory` | 🗂️ 创建会话内存实例 | [ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) — Token 感知的内存管理,支持压缩摘要和状态序列化 | -## 🗃️ 基于向量库的 ReMe +--- + +### 🚀 快速开始 + +#### 安装 + +```bash +pip install -U reme-ai[light] +``` + +#### 环境变量 + +`ReMeLight` 环境变量配置 Embedding 和存储后端 + +| Variable | Description | Example | +|----------------------|--------------------|-----------------------------------------------------| +| `LLM_API_KEY` | LLM API key | `sk-xxx` | +| `LLM_BASE_URL` | LLM base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | +| `EMBEDDING_API_KEY` | Embedding API key | `sk-xxx` | +| `EMBEDDING_BASE_URL` | Embedding base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | +| `LLM_MODEL_NAME` | LLM model name | `qwen3.5-plus` | + +#### Python使用 + +```python +import asyncio + +from agentscope.message import Msg +from reme.reme_light import ReMeLight + + +async def main(): + reme = ReMeLight( + working_dir=".reme", # 记忆文件存储目录 + max_input_length=128000, # 模型上下文窗口(tokens) + memory_compact_ratio=0.7, # 达到 max_input_length * 0.7 时触发压缩 + language="zh", # 摘要语言(zh / "") + tool_result_threshold=1000, # 超过此字符数的工具输出自动转存 + retention_days=7, # tool_result/ 文件保留天数 + ) + await reme.start() + + messages = [...] + + # 1. 压缩超长工具输出(防止工具结果撑爆上下文) + messages = await reme.compact_tool_result(messages) + + # 2. 将历史对话压缩为结构化摘要(触发时机:上下文接近上限),可传入上轮摘要,实现增量更新 + summary = await reme.compact_memory(messages=messages, previous_summary="") + + # 3. 后台异步提交摘要任务(不阻塞对话,摘要写入 memory/YYYY-MM-DD.md) + reme.add_async_summary_task(messages=messages) + + # 4. 语义搜索记忆(向量 + BM25 混合检索) + result = await reme.memory_search(query="Python 版本偏好", max_results=5) + + # 5. 获取会话内存实例(ReMeInMemoryMemory,管理单次对话的上下文)AgentScope InMemoryMemory + memory = reme.get_in_memory_memory() + token_stats = await memory.estimate_tokens() + print(f"当前上下文使用率: {token_stats['context_usage_ratio']:.1f}%") + print(f"消息 Token 数: {token_stats['messages_tokens']}") + print(f"预估总 Token 数: {token_stats['estimated_tokens']}") + + # 6. 关闭前等待后台任务完成 + summary_result = await reme.await_summary_tasks() + + # 关闭 ReMeLight + await reme.close() + + +if __name__ == "__main__": + asyncio.run(main()) +``` + +### 基于文件的 ReMeLight 记忆系统架构 + +[CoPaw MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py) 继承 +`ReMeLight`,将记忆能力集成到 Agent 推理流程中: + +```mermaid +graph TB + CoPaw["CoPaw MemoryManager\n(继承 ReMeLight)"] -->|pre_reasoning hook| Hook[MemoryCompactionHook] + CoPaw --> ReMeLight[ReMeLight] + Hook -->|超出阈值| ReMeLight + ReMeLight --> CompactMemory[compact_memory\n历史对话压缩] + ReMeLight --> SummaryMemory[summary_memory\n记忆写入文件] + ReMeLight --> CompactToolResult[compact_tool_result\n超长工具输出压缩] + ReMeLight --> MemSearch[memory_search\n语义搜索] + ReMeLight --> InMemory[get_in_memory_memory\nReMeInMemoryMemory] + CompactMemory --> Compactor[Compactor\nReActAgent] + SummaryMemory --> Summarizer[Summarizer\nReActAgent + 文件工具] + CompactToolResult --> ToolResultCompactor[ToolResultCompactor\n截断 + 转存文件] + Summarizer --> FileIO[FileIO\nread / write / edit] + FileIO --> MemoryFiles[memory/YYYY-MM-DD.md] + ToolResultCompactor --> ToolResultFiles[tool_result/*.txt] + MemoryFiles -.->|文件变更| FileWatcher[异步文件监控] + FileWatcher -->|更新索引| FileStore[本地数据库] + MemSearch --> FileStore +``` + +### 上下文压缩机制 + +#### 上下文压缩 + +[Compactor](reme/memory/file_based/compactor.py) 使用 ReActAgent 将历史对话压缩为结构化的**上下文检查点**: + +| 字段 | 说明 | +|-----------------------|-----------------------| +| `## Goal` | 🎯 用户要完成的目标(可多项) | +| `## Constraints` | ⚙️ 用户提到的约束和偏好 | +| `## Progress` | 📈 已完成 / 进行中 / 阻塞的任务 | +| `## Key Decisions` | 🔑 做出的决策及简短理由 | +| `## Next Steps` | 🗺️ 下一步行动计划(有序列表) | +| `## Critical Context` | 📌 文件路径、函数名、错误信息等关键数据 | + +支持**增量更新**:传入 `previous_summary` 时,自动将新对话与旧摘要合并,保留历史进展。 + +#### 工具结果压缩 + +[ToolResultCompactor](reme/memory/file_based/tool_result_compactor.py) 解决工具输出过长(比如 browser use)导致上下文膨胀的问题: + +```mermaid +graph LR + A[tool_result 消息] --> B{内容长度 > threshold?} + B -->|否| C[保留原样] + B -->|是| D[截断到 threshold 字符] + D --> E[完整内容写入 tool_result/uuid.txt] + E --> F[消息中追加文件引用路径] +``` + +过期文件(超过 `retention_days`)在 `start` / `close` / `compact_tool_result` 时自动清理。 + +### 记忆总结:ReAct + 文件工具 + +[Summarizer](reme/memory/file_based/summarizer.py) 采用 **ReAct + 文件工具** 模式,让 AI 自主决定写什么、写到哪: + +```mermaid +graph LR + A[接收对话] --> B{思考: 有什么值得记录?} + B --> C[行动: read memory/YYYY-MM-DD.md] + C --> D{思考: 如何与现有内容合并?} + D --> E[行动: edit 更新文件] + E --> F{思考: 还有遗漏吗?} + F -->|是| B + F -->|否| G[完成] +``` + +[FileIO](reme/memory/file_based/file_io.py) 提供文件操作工具集: + +| 工具 | 功能 | 使用场景 | +|---------|---------------|---------------| +| `read` | 读取文件内容(支持行范围) | 查看现有记忆,避免重复写入 | +| `write` | 覆盖写入文件 | 创建新记忆文件或大幅重构 | +| `edit` | 精确匹配后替换 | 追加新内容或修改特定段落 | + +### 会话内存管理 + +[ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) 扩展了 AgentScope 的 `InMemoryMemory`: + +| 功能 | 说明 | +|----------------------------------|---------------------------| +| `get_memory` | 按标记过滤消息,自动在头部追加压缩摘要 | +| `estimate_tokens` | 精确估算当前上下文 Token 用量及使用率 | +| `get_history_str` | 生成人类可读的对话历史摘要(含 Token 统计) | +| `state_dict` / `load_state_dict` | 支持状态序列化 / 反序列化(会话持久化) | +| `mark_messages_compressed` | 标记消息为已压缩状态 | +| `get_compressed_summary` | 获取已压缩的摘要内容 | + +### 记忆检索 + +[MemorySearch](reme/memory/tools/chunk/memory_search.py) 提供**向量 + BM25 混合检索**能力: + +| 检索方式 | 优势 | 劣势 | +|-------------|-----------------|----------------| +| **向量语义** | 捕捉意义相近但措辞不同的内容 | 对精确 token 匹配较弱 | +| **BM25 全文** | 精确 token 命中效果极佳 | 无法理解同义词和改写 | + +**融合机制**:两路召回后按权重加权求和(向量 0.7 + BM25 0.3),自然语言与精确查找均可命中。 + +```mermaid +graph LR + Q[搜索查询] --> V[向量搜索 × 0.7] +Q --> B[BM25 × 0.3] +V --> M[去重 + 加权融合] +B --> M +M --> R[Top-N 结果] +``` + +--- + +## 🗃️ 基于向量库的记忆系统 [ReMe Vector Based](reme/reme.py) 是基于向量库的记忆系统核心类,支持三种记忆类型的统一管理: @@ -96,60 +282,6 @@ working_dir/ | `delete_memory` | 🗑️ 删除记忆 | 删除指定记忆 | | `list_memory` | 📋 列出记忆 | 列出某类记忆,支持过滤和排序 | ---- - -## 💻 ReMeCli:基于文件记忆的终端助手 - - - - - - - -
-


-
- - -


-
- -### 什么时候会写记忆? - -| 场景 | 写到哪 | 怎么触发 | -|------------------|------------------------|----------------------| -| 上下文超长自动压缩 | `memory/YYYY-MM-DD.md` | 后台自动 | -| 用户执行 `/compact` | `memory/YYYY-MM-DD.md` | 手动压缩 + 后台保存 | -| 用户执行 `/new` | `memory/YYYY-MM-DD.md` | 新对话 + 后台保存 | -| 用户说"记住这个" | `MEMORY.md` 或日志 | Agent 用 `write` 工具写入 | -| Agent 发现了重要决策/偏好 | `MEMORY.md` | Agent 主动写 | - -### 记忆检索工具 - -| 方式 | 工具 | 什么时候用 | 举例 | -|------|-----------------|------------|--------------------------| -| 语义搜索 | `memory_search` | 不确定记在哪,模糊找 | "之前关于部署的讨论" | -| 直接读 | `read` | 知道是哪天、哪个文件 | 读 `memory/2025-02-13.md` | - -搜索用的是**向量 + BM25 混合检索**(向量权重 0.7,BM25 权重 0.3),无论自然语言还是精确关键词都能命中。 - -### 内置工具 - -| 工具 | 功能 | 细节 | -|-----------------|----------|----------------------------------------| -| `memory_search` | 搜记忆 | MEMORY.md 和 memory/*.md 里做向量+BM25 混合检索 | -| `bash` | 跑命令 | 执行 bash 命令,有超时和输出截断 | -| `ls` | 看目录 | 列目录结构 | -| `read` | 读文件 | 文本和图片都行,支持分段读 | -| `edit` | 改文件 | 精确匹配文本后替换 | -| `write` | 写文件 | 创建或覆盖,自动建目录 | -| `execute_code` | 跑 Python | 运行代码片段 | -| `web_search` | 联网搜索 | 通过 Tavily | - ---- - -## 🚀 快速开始 - ### 安装 ```bash @@ -160,134 +292,17 @@ pip install -U reme-ai API 密钥通过环境变量设置,可写在项目根目录的 `.env` 文件中: -| 环境变量 | 说明 | 示例 | -|---------------------------|-----------------------|-----------------------------------------------------| -| `REME_LLM_API_KEY` | LLM 的 API Key | `sk-xxx` | -| `REME_LLM_BASE_URL` | LLM 的 Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | -| `REME_EMBEDDING_API_KEY` | Embedding 的 API Key | `sk-xxx` | -| `REME_EMBEDDING_BASE_URL` | Embedding 的 Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | -| `TAVILY_API_KEY` | Tavily 搜索 API Key(可选) | `tvly-xxx` | - -### 使用 ReMeCli - -#### 启动 ReMeCli - -```bash -remecli config=cli -``` - -#### ReMeCli 系统命令 - -> 马年彩蛋:`/horse` 触发——烟花、奔马动画和随机马年祝福。 - -对话里输入 `/` 开头的命令控制状态: - -| 命令 | 说明 | 需等待响应 | -|------------|---------------------|-------| -| `/compact` | 手动压缩当前对话,同时后台存到长期记忆 | 是 | -| `/new` | 开始新对话,历史后台保存到长期记忆 | 否 | -| `/clear` | 清空一切,**不保存** | 否 | -| `/history` | 看当前对话里未压缩的消息 | 否 | -| `/help` | 看命令列表 | 否 | -| `/exit` | 退出 | 否 | - -**三个命令的区别** - -| 命令 | 压缩摘要 | 长期记忆 | 消息历史 | -|------------|-------|------|-------| -| `/compact` | 生成新摘要 | 保存 | 保留最近的 | -| `/new` | 清空 | 保存 | 清空 | -| `/clear` | 清空 | 不保存 | 清空 | - -> `/clear` 是真删,删了就没了,不会存到任何地方。 - -### 使用 ReMe Package - -#### 基于文件的 ReMe(CoPaw的记忆系统) - -`ReMeCopaw` 接收 AgentScope 的 `ChatModelBase`、`Formatter`、`Toolkit` 等组件,通过环境变量配置 Embedding 和存储后端: - -| 环境变量 | 说明 | 默认值 | -|----------------------------|-----------------------------------|-----------------------------------------------------| -| `EMBEDDING_API_KEY` | Embedding 服务 API Key | `""`(未配置则禁用向量搜索) | -| `EMBEDDING_BASE_URL` | Embedding 服务 Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | -| `EMBEDDING_MODEL_NAME` | Embedding 模型名称 | `""` | -| `EMBEDDING_DIMENSIONS` | 向量维度 | `1024` | -| `EMBEDDING_CACHE_ENABLED` | 是否启用 Embedding 缓存 | `true` | -| `EMBEDDING_MAX_CACHE_SIZE` | 最大缓存条数 | `2000` | -| `FTS_ENABLED` | 是否启用全文搜索(BM25) | `true` | -| `MEMORY_STORE_BACKEND` | 存储后端(`auto` / `chroma` / `local`) | `auto`(Windows 用 local,其他用 chroma) | +| 环境变量 | 说明 | 示例 | +|-----------------|----------------------|-----------------------------------------------------| +| `LLM_API_KEY` | LLM 的 API Key | `sk-xxx` | +| `LLM_BASE_URL` | LLM 的 Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | +| `EMBEDDING_API_KEY` | Embedding 的 API Key | `sk-xxx` | +| `EMBEDDING_BASE_URL` | Embedding 的 Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | +### Python使用 ```python import asyncio -from agentscope.formatter import ClaudeFormatter -from agentscope.model import get_model -from agentscope.token import HuggingFaceTokenCounter -from agentscope.tool import Toolkit - -from reme.reme_copaw import ReMeCopaw - - -async def main(): - # 准备 AgentScope 核心组件 - chat_model = get_model(config={"backend": "openai", "model_name": "qwen3.5-plus"}) - formatter = ClaudeFormatter() - token_counter = HuggingFaceTokenCounter() - toolkit = Toolkit() # 可注册额外工具 - - # 初始化 ReMeCopaw - reme = ReMeCopaw( - working_dir=".reme", # 记忆文件存储目录 - chat_model=chat_model, - formatter=formatter, - token_counter=token_counter, - toolkit=toolkit, - max_input_length=128000, # 模型上下文窗口(tokens) - memory_compact_ratio=0.7, # 达到 max_input_length * 0.7 时触发压缩 - language="zh", # 摘要语言(zh / "") - tool_result_threshold=1000, # 超过此字符数的工具输出自动转存 - retention_days=7, # tool_result/ 文件保留天数 - ) - await reme.start() - - messages = [...] # list[Msg],对话历史 - - # 1. 压缩超长工具输出(防止工具结果撑爆上下文) - messages = await reme.compact_tool_result(messages) - - # 2. 将历史对话压缩为结构化摘要(触发时机:上下文接近上限) - summary = await reme.compact_memory( - messages=messages, - previous_summary="", # 可传入上轮摘要,实现增量更新 - ) - print(f"压缩摘要:\n{summary}") - - # 3. 后台异步提交摘要任务(不阻塞对话,摘要写入 memory/YYYY-MM-DD.md) - reme.add_async_summary_task(messages=messages) - - # 4. 语义搜索记忆(向量 + BM25 混合检索) - result = await reme.memory_search(query="Python 版本偏好", max_results=5) - print(f"搜索结果: {result}") - - # 5. 获取会话内存实例(CoPawInMemoryMemory,管理单次对话的上下文) - memory = reme.get_in_memory_memory() - token_stats = await memory.estimate_tokens() - print(f"当前上下文使用率: {token_stats['context_usage_ratio']:.1f}%") - - # 6. 关闭前等待后台任务完成 - await reme.await_summary_tasks() - await reme.close() - - -if __name__ == "__main__": - asyncio.run(main()) -``` - -#### 基于向量库的 ReMe - -```python -import asyncio from reme import ReMe @@ -374,137 +389,7 @@ if __name__ == "__main__": asyncio.run(main()) ``` -## 🏛️ 技术架构 - -### 基于文件的 CoPaw 记忆系统架构 - -[CoPaw MemoryManager](https://github.com/agentscope-ai/CoPaw/blob/main/src/copaw/agents/memory/memory_manager.py) 继承 -`ReMeCopaw`,将记忆能力集成到 Agent 推理流程中: - -```mermaid -graph TB - CoPaw["CoPaw MemoryManager\n(继承 ReMeCopaw)"] -->|pre_reasoning hook| Hook[MemoryCompactionHook] - CoPaw --> ReMeCopaw[ReMeCopaw] - Hook -->|超出阈值| ReMeCopaw - ReMeCopaw --> CompactMemory[compact_memory\n历史对话压缩] - ReMeCopaw --> SummaryMemory[summary_memory\n记忆写入文件] - ReMeCopaw --> CompactToolResult[compact_tool_result\n超长工具输出压缩] - ReMeCopaw --> MemSearch[memory_search\n语义搜索] - ReMeCopaw --> InMemory[get_in_memory_memory\nCoPawInMemoryMemory] - CompactMemory --> Compactor[Compactor\nReActAgent] - SummaryMemory --> Summarizer[Summarizer\nReActAgent + 文件工具] - CompactToolResult --> ToolResultCompactor[ToolResultCompactor\n截断 + 转存文件] - Summarizer --> FileIO[FileIO\nread / write / edit] - FileIO --> MemoryFiles[memory/YYYY-MM-DD.md] - ToolResultCompactor --> ToolResultFiles[tool_result/*.txt] - MemoryFiles -.->|文件变更| FileWatcher[异步文件监控] - FileWatcher -->|更新索引| FileStore[本地数据库] - MemSearch --> FileStore -``` - -#### 自动压缩触发流程 - -`MemoryCompactionHook` 在每次推理前检查上下文 Token 用量,超过阈值时自动触发压缩: - -```mermaid -graph LR - A[pre_reasoning] --> B{Token 超过阈值?} - B -->|否| Z[继续推理] - B -->|是| C[compact_tool_result\n压缩最近消息中的超长工具输出] - C --> D[compact_memory\n生成结构化上下文检查点] - D --> E[标记旧消息为 COMPRESSED] - E --> F[add_async_summary_task\n后台写入 memory 文件] - F --> Z -``` - -#### 上下文压缩摘要格式 - -[Compactor](reme/memory/file_based_copaw/compactor.py) 使用 ReActAgent 将历史对话压缩为结构化的**上下文检查点**: - -| 字段 | 说明 | -|-----------------------|-----------------------| -| `## Goal` | 🎯 用户要完成的目标(可多项) | -| `## Constraints` | ⚙️ 用户提到的约束和偏好 | -| `## Progress` | 📈 已完成 / 进行中 / 阻塞的任务 | -| `## Key Decisions` | 🔑 做出的决策及简短理由 | -| `## Next Steps` | 🗺️ 下一步行动计划(有序列表) | -| `## Critical Context` | 📌 文件路径、函数名、错误信息等关键数据 | - -支持**增量更新**:传入 `previous_summary` 时,自动将新对话与旧摘要合并,保留历史进展。 - -#### 工具结果压缩 - -[ToolResultCompactor](reme/memory/file_based_copaw/tool_result_compactor.py) 解决工具输出过长导致上下文膨胀的问题: - -```mermaid -graph LR - A[tool_result 消息] --> B{内容长度 > threshold?} - B -->|否| C[保留原样] - B -->|是| D[截断到 threshold 字符] - D --> E[完整内容写入 tool_result/uuid.txt] - E --> F[消息中追加文件引用路径] -``` - -过期文件(超过 `retention_days`)在 `start` / `close` / `compact_tool_result` 时自动清理。 - -#### 记忆总结:ReAct + 文件工具 - -[Summarizer](reme/memory/file_based_copaw/summarizer.py) 采用 **ReAct + 文件工具** 模式,让 AI 自主决定写什么、写到哪: - -```mermaid -graph LR - A[接收对话] --> B{思考: 有什么值得记录?} - B --> C[行动: read memory/YYYY-MM-DD.md] - C --> D{思考: 如何与现有内容合并?} - D --> E[行动: edit 更新文件] - E --> F{思考: 还有遗漏吗?} - F -->|是| B - F -->|否| G[完成] -``` - -[FileIO](reme/memory/file_based_copaw/file_io.py) 提供文件操作工具集: - -| 工具 | 功能 | 使用场景 | -|---------|---------------|---------------| -| `read` | 读取文件内容(支持行范围) | 查看现有记忆,避免重复写入 | -| `write` | 覆盖写入文件 | 创建新记忆文件或大幅重构 | -| `edit` | 精确匹配后替换 | 追加新内容或修改特定段落 | - -#### 会话内存管理 - -[CoPawInMemoryMemory](reme/memory/file_based_copaw/copaw_in_memory_memory.py) 扩展了 AgentScope 的 `InMemoryMemory`: - -| 功能 | 说明 | -|----------------------------------|---------------------------| -| `get_memory` | 按标记过滤消息,自动在头部追加压缩摘要 | -| `estimate_tokens` | 精确估算当前上下文 Token 用量及使用率 | -| `get_history_str` | 生成人类可读的对话历史摘要(含 Token 统计) | -| `state_dict` / `load_state_dict` | 支持状态序列化 / 反序列化(会话持久化) | - -#### 记忆检索 - -[MemorySearch](reme/memory/tools/chunk/memory_search.py) 提供**向量 + BM25 混合检索**能力: - -| 检索方式 | 优势 | 劣势 | -|-------------|-----------------|----------------| -| **向量语义** | 捕捉意义相近但措辞不同的内容 | 对精确 token 匹配较弱 | -| **BM25 全文** | 精确 token 命中效果极佳 | 无法理解同义词和改写 | - -**融合机制**:两路召回后按权重加权求和(向量 0.7 + BM25 0.3),自然语言与精确查找均可命中。 - -```mermaid -graph LR - Q[搜索查询] --> V[向量搜索 × 0.7] -Q --> B[BM25 × 0.3] -V --> M[去重 + 加权融合] -B --> M -M --> R[Top-N 结果] -``` - ---- - -### 基于向量库的 ReMe 核心架构 - +### 技术架构 ```mermaid graph TB User[用户 / Agent] --> ReMe[Vector Based ReMe] diff --git a/example.env b/example.env index b988f2b2..7bcdbf6a 100644 --- a/example.env +++ b/example.env @@ -1,11 +1,7 @@ -FLOW_EMBEDDING_API_KEY=sk-xxxx -FLOW_EMBEDDING_BASE_URL=https://xxxx/v1 -FLOW_LLM_API_KEY=sk-xxxx -FLOW_LLM_BASE_URL=https://xxxx/v1 - -REME_LLM_API_KEY=sk-xxxx -REME_LLM_BASE_URL=https://xxxx/v1 -REME_EMBEDDING_API_KEY=sk-xxxx -REME_EMBEDDING_BASE_URL=https://xxxx/v1 +LLM_API_KEY=sk-xxxx +LLM_BASE_URL=https://xxxx/v1 +EMBEDDING_API_KEY=sk-xxxx +EMBEDDING_BASE_URL=https://xxxx/v1 +LLM_MODEL_NAME=qwen3.5-plus TAVILY_API_KEY=xxxx diff --git a/pyproject.toml b/pyproject.toml index a3f99fbc..43da64dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,11 +77,11 @@ dev = [ ] full = [ - "reme_ai[dev,ray]", + "reme_ai[dev,ray,light]", ] -as = [ - "agentscope", +light = [ + "agentscope==1.0.16.dev0", ] [tool.setuptools.packages.find] diff --git a/reme/__init__.py b/reme/__init__.py index add74af3..9842ce1a 100644 --- a/reme/__init__.py +++ b/reme/__init__.py @@ -5,9 +5,8 @@ from . import core from . import extension from . import memory from .reme import ReMe -from .reme_cli import ReMeCli -__version__ = "0.3.0.5" +__version__ = "0.3.0.6b1" __all__ = [ "config", @@ -15,7 +14,6 @@ __all__ = [ "extension", "memory", "ReMe", - "ReMeCli", ] """ diff --git a/reme/config/copaw.yaml b/reme/config/light.yaml similarity index 100% rename from reme/config/copaw.yaml rename to reme/config/light.yaml diff --git a/reme/core/llm/base_llm.py b/reme/core/llm/base_llm.py index 856c7f3b..ba13ff10 100644 --- a/reme/core/llm/base_llm.py +++ b/reme/core/llm/base_llm.py @@ -50,12 +50,12 @@ class BaseLLM(ABC): @property def api_key(self) -> str | None: """Get API key from environment variable.""" - return os.getenv("REME_LLM_API_KEY") or self._api_key + return os.getenv("LLM_API_KEY") or self._api_key @property def base_url(self) -> str | None: """Get base URL from environment variable.""" - return os.getenv("REME_LLM_BASE_URL") or self._base_url + return os.getenv("LLM_BASE_URL") or self._base_url @staticmethod def _accumulate_tool_call_chunk(tool_call, ret_tools: list[ToolCall]): diff --git a/reme/core/service_context.py b/reme/core/service_context.py index 0b9c5a1b..ecb6c3a3 100644 --- a/reme/core/service_context.py +++ b/reme/core/service_context.py @@ -50,10 +50,10 @@ class ServiceContext(BaseDict): load_env() # Update common environment variables for LLM and embedding services. - self.update_env("REME_LLM_API_KEY", llm_api_key) - self.update_env("REME_LLM_BASE_URL", llm_base_url) - self.update_env("REME_EMBEDDING_API_KEY", embedding_api_key) - self.update_env("REME_EMBEDDING_BASE_URL", embedding_base_url) + self.update_env("LLM_API_KEY", llm_api_key) + self.update_env("LLM_BASE_URL", llm_base_url) + self.update_env("EMBEDDING_API_KEY", embedding_api_key) + self.update_env("EMBEDDING_BASE_URL", embedding_base_url) if service_config is None: parser_class = parser if parser is not None else PydanticConfigParser diff --git a/reme/memory/cli/__init__.py b/reme/extension/cli/__init__.py similarity index 100% rename from reme/memory/cli/__init__.py rename to reme/extension/cli/__init__.py diff --git a/reme/memory/cli/fb_cli.py b/reme/extension/cli/fb_cli.py similarity index 100% rename from reme/memory/cli/fb_cli.py rename to reme/extension/cli/fb_cli.py diff --git a/reme/memory/cli/fb_cli.yaml b/reme/extension/cli/fb_cli.yaml similarity index 100% rename from reme/memory/cli/fb_cli.yaml rename to reme/extension/cli/fb_cli.yaml diff --git a/reme/memory/cli/fb_compactor.py b/reme/extension/cli/fb_compactor.py similarity index 100% rename from reme/memory/cli/fb_compactor.py rename to reme/extension/cli/fb_compactor.py diff --git a/reme/memory/cli/fb_compactor.yaml b/reme/extension/cli/fb_compactor.yaml similarity index 100% rename from reme/memory/cli/fb_compactor.yaml rename to reme/extension/cli/fb_compactor.yaml diff --git a/reme/memory/cli/fb_context_checker.py b/reme/extension/cli/fb_context_checker.py similarity index 100% rename from reme/memory/cli/fb_context_checker.py rename to reme/extension/cli/fb_context_checker.py diff --git a/reme/memory/cli/fb_summarizer.py b/reme/extension/cli/fb_summarizer.py similarity index 100% rename from reme/memory/cli/fb_summarizer.py rename to reme/extension/cli/fb_summarizer.py diff --git a/reme/memory/cli/fb_summarizer.yaml b/reme/extension/cli/fb_summarizer.yaml similarity index 100% rename from reme/memory/cli/fb_summarizer.yaml rename to reme/extension/cli/fb_summarizer.yaml diff --git a/reme/reme_cli.py b/reme/extension/reme_cli.py similarity index 100% rename from reme/reme_cli.py rename to reme/extension/reme_cli.py diff --git a/reme/memory/__init__.py b/reme/memory/__init__.py index a7df138a..ac825bc6 100644 --- a/reme/memory/__init__.py +++ b/reme/memory/__init__.py @@ -1,11 +1,11 @@ """memory""" -from . import cli +from . import file_based from . import tools from . import vector_based __all__ = [ - "cli", + "file_based", "tools", "vector_based", ] diff --git a/reme/memory/file_based_copaw/__init__.py b/reme/memory/file_based/__init__.py similarity index 76% rename from reme/memory/file_based_copaw/__init__.py rename to reme/memory/file_based/__init__.py index 5b0405f1..29f33749 100644 --- a/reme/memory/file_based_copaw/__init__.py +++ b/reme/memory/file_based/__init__.py @@ -1,11 +1,11 @@ -"""File-based CoPaw Memory Module. +"""File-based Memory Module. This module provides memory management components for CoPaw (Cooperative Paw) agents, including memory formatting, compaction, summarization, and file I/O operations. Components: - MemoryFormatter: Converts message lists to formatted strings with token limiting - - CoPawInMemoryMemory: Extended InMemoryMemory with bugfixes and summary support + - ReMeInMemoryMemory: Extended InMemoryMemory with bugfixes and summary support - Summarizer: Generates memory summaries using LLM - Compactor: Compacts memory content to reduce token usage - ToolResultCompactor: Truncates large tool results and saves full content to files @@ -14,18 +14,20 @@ Components: from . import utils from .compactor import Compactor -from .copaw_in_memory_memory import CoPawInMemoryMemory from .file_io import FileIO from .memory_formatter import MemoryFormatter +from .reme_chat_formatter import ReMeChatFormatter +from .reme_in_memory_memory import ReMeInMemoryMemory from .summarizer import Summarizer from .tool_result_compactor import ToolResultCompactor __all__ = [ "MemoryFormatter", - "CoPawInMemoryMemory", + "ReMeInMemoryMemory", "Summarizer", "Compactor", "ToolResultCompactor", "FileIO", "utils", + "ReMeChatFormatter", ] diff --git a/reme/memory/file_based_copaw/compactor.py b/reme/memory/file_based/compactor.py similarity index 100% rename from reme/memory/file_based_copaw/compactor.py rename to reme/memory/file_based/compactor.py diff --git a/reme/memory/file_based_copaw/compactor.yaml b/reme/memory/file_based/compactor.yaml similarity index 100% rename from reme/memory/file_based_copaw/compactor.yaml rename to reme/memory/file_based/compactor.yaml diff --git a/reme/memory/file_based_copaw/file_io.py b/reme/memory/file_based/file_io.py similarity index 100% rename from reme/memory/file_based_copaw/file_io.py rename to reme/memory/file_based/file_io.py diff --git a/reme/memory/file_based_copaw/memory_formatter.py b/reme/memory/file_based/memory_formatter.py similarity index 100% rename from reme/memory/file_based_copaw/memory_formatter.py rename to reme/memory/file_based/memory_formatter.py diff --git a/reme/memory/file_based/reme_chat_formatter.py b/reme/memory/file_based/reme_chat_formatter.py new file mode 100644 index 00000000..b70e700c --- /dev/null +++ b/reme/memory/file_based/reme_chat_formatter.py @@ -0,0 +1,29 @@ +"""ReMe chat formatter.""" + +from typing import Any + +from agentscope.formatter import OpenAIChatFormatter +from agentscope.token import HuggingFaceTokenCounter + +from .utils import _extract_text_from_messages + + +class ReMeChatFormatter(OpenAIChatFormatter): + """ReMe chat formatter class.""" + + async def _count(self, msgs: list[dict[str, Any]]) -> int | None: + """Count the number of tokens in the input messages. If token counter + is not provided, `None` will be returned. + + Args: + msgs (`list[Msg]`): + The input messages to count tokens for. + """ + if self.token_counter is None: + return None + + assert isinstance(self.token_counter, HuggingFaceTokenCounter) + text = _extract_text_from_messages(msgs) + token_ids = self.token_counter.tokenizer.encode(text) + token_count = len(token_ids) + return token_count diff --git a/reme/memory/file_based_copaw/copaw_in_memory_memory.py b/reme/memory/file_based/reme_in_memory_memory.py similarity index 99% rename from reme/memory/file_based_copaw/copaw_in_memory_memory.py rename to reme/memory/file_based/reme_in_memory_memory.py index 02dc77a4..0e915382 100644 --- a/reme/memory/file_based_copaw/copaw_in_memory_memory.py +++ b/reme/memory/file_based/reme_in_memory_memory.py @@ -13,7 +13,7 @@ from .utils import safe_count_message_tokens, safe_count_str_tokens, _get_block_ logger = logging.getLogger(__name__) -class CoPawInMemoryMemory(InMemoryMemory): +class ReMeInMemoryMemory(InMemoryMemory): """Extended InMemoryMemory with bugfixes and summary support.""" def __init__( diff --git a/reme/memory/file_based_copaw/summarizer.py b/reme/memory/file_based/summarizer.py similarity index 100% rename from reme/memory/file_based_copaw/summarizer.py rename to reme/memory/file_based/summarizer.py diff --git a/reme/memory/file_based_copaw/summarizer.yaml b/reme/memory/file_based/summarizer.yaml similarity index 100% rename from reme/memory/file_based_copaw/summarizer.yaml rename to reme/memory/file_based/summarizer.yaml diff --git a/reme/memory/file_based_copaw/tool_result_compactor.py b/reme/memory/file_based/tool_result_compactor.py similarity index 100% rename from reme/memory/file_based_copaw/tool_result_compactor.py rename to reme/memory/file_based/tool_result_compactor.py diff --git a/reme/memory/file_based_copaw/utils.py b/reme/memory/file_based/utils.py similarity index 84% rename from reme/memory/file_based_copaw/utils.py rename to reme/memory/file_based/utils.py index a5f8f967..f460f96f 100644 --- a/reme/memory/file_based_copaw/utils.py +++ b/reme/memory/file_based/utils.py @@ -1,6 +1,7 @@ """Utility functions for working with text.""" import logging +from pathlib import Path from agentscope.token import HuggingFaceTokenCounter @@ -229,3 +230,42 @@ def _get_block_tokens( # pylint: disable=too-many-return-statements return 0, "" return 0, "" + + +_token_counter = None + + +def get_token_counter(): + """Get or initialize the global token counter instance. + + Returns: + TokenCounterBase: The token counter instance for Qwen models. + + Raises: + RuntimeError: If token counter initialization fails. + """ + global _token_counter + if _token_counter is None: + # Use Qwen tokenizer for DashScope models + # Qwen3 series uses the same tokenizer as Qwen2.5 + + # Try local tokenizer first, fall back to online if not found + local_tokenizer_path = Path(__file__).parent.parent.parent / "tokenizer" + + if local_tokenizer_path.exists() and (local_tokenizer_path / "tokenizer.json").exists(): + tokenizer_path = str(local_tokenizer_path) + logger.info(f"Using local Qwen tokenizer from {tokenizer_path}") + else: + tokenizer_path = "Qwen/Qwen2.5-7B-Instruct" + logger.info( + "Local tokenizer not found, downloading from HuggingFace", + ) + + _token_counter = HuggingFaceTokenCounter( + pretrained_model_name_or_path=tokenizer_path, + use_mirror=True, # Use HF mirror for users in China + use_fast=True, + trust_remote_code=True, + ) + logger.debug("Token counter initialized with Qwen tokenizer") + return _token_counter diff --git a/reme/memory/skills/__init__.py b/reme/memory/skills/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/reme/reme_copaw.py b/reme/reme_light.py similarity index 88% rename from reme/reme_copaw.py rename to reme/reme_light.py index 2c76bf5e..4396c16f 100644 --- a/reme/reme_copaw.py +++ b/reme/reme_light.py @@ -1,7 +1,7 @@ """ -ReMe Copaw Application Module +ReMe Light Application Module -This module provides the ReMeCopaw class, a specialized application built on top of +This module provides the ReMeLight class, a specialized application built on top of ReMe's core Application framework. It integrates memory management capabilities including memory compaction, summarization, tool result management, and semantic memory search functionality. @@ -22,22 +22,23 @@ from pathlib import Path from agentscope.formatter import FormatterBase from agentscope.message import Msg, TextBlock -from agentscope.model import ChatModelBase +from agentscope.model import ChatModelBase, OpenAIChatModel from agentscope.token import HuggingFaceTokenCounter from agentscope.tool import Toolkit, ToolResponse from .config import ReMeConfigParser from .core import Application -from .memory.file_based_copaw import Compactor, Summarizer, ToolResultCompactor, CoPawInMemoryMemory +from .memory.file_based import Compactor, Summarizer, ToolResultCompactor, ReMeInMemoryMemory, ReMeChatFormatter +from .memory.file_based.utils import get_token_counter from .memory.tools import MemorySearch +from .core.utils import load_env -# Module-level logger for tracking application events and errors logger = logging.getLogger(__name__) -class ReMeCopaw(Application): +class ReMeLight(Application): """ - ReMe Copaw Application Class + ReMe Light Application Class A specialized application class that extends ReMe's core Application framework with advanced memory management capabilities. This class is designed to handle @@ -64,13 +65,17 @@ class ReMeCopaw(Application): def __init__( self, - working_dir: str, - chat_model: ChatModelBase, - formatter: FormatterBase, - token_counter: HuggingFaceTokenCounter, - toolkit: Toolkit, - max_input_length: int, - memory_compact_ratio: float, + working_dir: str = ".reme", + llm_api_key: str | None = None, + llm_base_url: str | None = None, + embedding_api_key: str | None = None, + embedding_base_url: str | None = None, + chat_model: ChatModelBase | None = None, + formatter: FormatterBase | None = None, + token_counter: HuggingFaceTokenCounter | None = None, + toolkit: Toolkit | None = None, + max_input_length: int = 128000, + memory_compact_ratio: float = 0.7, language: str = "zh", vector_weight: float = 0.7, candidate_multiplier: float = 3.0, @@ -90,23 +95,11 @@ class ReMeCopaw(Application): self.tool_result_path = self.working_path / "tool_result" self.tool_result_path.mkdir(parents=True, exist_ok=True) - # Store references to core components - self.chat_model: ChatModelBase = chat_model - self.formatter: FormatterBase = formatter - self.token_counter: HuggingFaceTokenCounter = token_counter - self.toolkit: Toolkit = toolkit - # Initialize runtime parameters (will be updated via update_params) self.max_input_length: int = 0 self.memory_compact_threshold: int = 0 self.language: str = "" - # Store configuration parameters - self.vector_weight: float = vector_weight - self.candidate_multiplier: float = candidate_multiplier - self.tool_result_threshold: int = tool_result_threshold - self.retention_days: int = retention_days - # Apply initial parameter configuration self.update_params( max_input_length=max_input_length, @@ -114,18 +107,21 @@ class ReMeCopaw(Application): language=language, ) - # Retrieve embedding configuration from environment variables - # These settings control the vector search capabilities - ( - embedding_api_key, - embedding_base_url, - embedding_model_name, - embedding_dimensions, - embedding_cache_enabled, - embedding_max_cache_size, - embedding_max_input_length, - embedding_max_batch_size, - ) = self.get_emb_envs() + # Store configuration parameters + self.vector_weight: float = vector_weight + self.candidate_multiplier: float = candidate_multiplier + self.tool_result_threshold: int = tool_result_threshold + self.retention_days: int = retention_days + + load_env() + + llm_model_name = self._safe_str("LLM_MODEL_NAME", "") + embedding_model_name = self._safe_str("EMBEDDING_MODEL_NAME", "") + embedding_dimensions = self._safe_int("EMBEDDING_DIMENSIONS", 1024) + embedding_cache_enabled = self._safe_str("EMBEDDING_CACHE_ENABLED", "true").lower() == "true" + embedding_max_cache_size = self._safe_int("EMBEDDING_MAX_CACHE_SIZE", 2000) + embedding_max_input_length = self._safe_int("EMBEDDING_MAX_INPUT_LENGTH", 8192) + embedding_max_batch_size = self._safe_int("EMBEDDING_MAX_BATCH_SIZE", 10) # Determine if vector search should be enabled based on configuration # Vector search requires either an API key or a local model name @@ -149,13 +145,14 @@ class ReMeCopaw(Application): else: memory_backend = memory_store_backend - # Initialize the parent Application class with configuration # Initialize the parent Application class with comprehensive configuration super().__init__( + llm_api_key=llm_api_key, + llm_base_url=llm_base_url, embedding_api_key=embedding_api_key, embedding_base_url=embedding_base_url, working_dir=str(self.working_path), - config_path="copaw", + config_path="light", enable_logo=False, log_to_console=False, parser=ReMeConfigParser, @@ -183,6 +180,27 @@ class ReMeCopaw(Application): }, ) + if chat_model is not None: + self.chat_model: ChatModelBase = chat_model + else: + # add more params later + self.chat_model = OpenAIChatModel( + api_key=os.environ["LLM_API_KEY"], + client_kwargs={"base_url": os.environ["LLM_BASE_URL"]}, + model_name=llm_model_name, + ) + + if token_counter is not None: + self.token_counter: HuggingFaceTokenCounter = token_counter + else: + self.token_counter = get_token_counter() + + if formatter is not None: + self.formatter: FormatterBase = formatter + else: + self.formatter = ReMeChatFormatter(token_counter=self.token_counter) + self.toolkit: Toolkit | None = toolkit + # Initialize list to track background summarization tasks self.summary_tasks: list[asyncio.Task] = [] @@ -264,55 +282,6 @@ class ReMeCopaw(Application): logger.warning(f"Invalid int value '{value}' for key '{key}', using default {default}") return default - def get_emb_envs(self): - """ - Retrieve all embedding-related configuration from environment variables. - - This method collects all settings needed for the embedding service, - including API credentials, model configuration, and caching parameters. - - Environment Variables: - EMBEDDING_API_KEY: API key for the embedding service - EMBEDDING_BASE_URL: Base URL for the embedding API (default: dashscope) - EMBEDDING_MODEL_NAME: Name of the embedding model to use - EMBEDDING_DIMENSIONS: Vector dimensions (default: 1024) - EMBEDDING_CACHE_ENABLED: Whether to enable caching (default: true) - EMBEDDING_MAX_CACHE_SIZE: Maximum cache entries (default: 2000) - EMBEDDING_MAX_INPUT_LENGTH: Max input text length (default: 8192) - EMBEDDING_MAX_BATCH_SIZE: Max batch size for requests (default: 10) - - Returns: - tuple: A tuple containing all embedding configuration values in order: - (api_key, base_url, model_name, dimensions, cache_enabled, - max_cache_size, max_input_length, max_batch_size) - """ - # API authentication and endpoint configuration - embedding_api_key = self._safe_str("EMBEDDING_API_KEY", "") - embedding_base_url = self._safe_str("EMBEDDING_BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1") - embedding_model_name = self._safe_str("EMBEDDING_MODEL_NAME", "") - - # Model and vector configuration - embedding_dimensions = self._safe_int("EMBEDDING_DIMENSIONS", 1024) - - # Caching configuration for performance optimization - embedding_cache_enabled = self._safe_str("EMBEDDING_CACHE_ENABLED", "true").lower() == "true" - embedding_max_cache_size = self._safe_int("EMBEDDING_MAX_CACHE_SIZE", 2000) - - # Input processing limits - embedding_max_input_length = self._safe_int("EMBEDDING_MAX_INPUT_LENGTH", 8192) - embedding_max_batch_size = self._safe_int("EMBEDDING_MAX_BATCH_SIZE", 10) - - return ( - embedding_api_key, - embedding_base_url, - embedding_model_name, - embedding_dimensions, - embedding_cache_enabled, - embedding_max_cache_size, - embedding_max_input_length, - embedding_max_batch_size, - ) - def _cleanup_tool_results(self) -> int: """ Clean up expired tool result files from the tool result directory. @@ -681,13 +650,13 @@ class ReMeCopaw(Application): """ Create and return an in-memory memory instance. - This method instantiates a CoPawInMemoryMemory object configured with + This method instantiates a ReMeInMemoryMemory object configured with the current application's token counter, formatter, and input length limits. The in-memory memory provides fast, temporary storage for conversation context without persistence. Returns: - CoPawInMemoryMemory: A configured in-memory memory instance ready + ReMeInMemoryMemory: A configured in-memory memory instance ready for storing and retrieving conversation messages Note: @@ -695,7 +664,7 @@ class ReMeCopaw(Application): - Useful for managing conversation context within a single session - Shares the same token counter and formatter as the main application """ - return CoPawInMemoryMemory( + return ReMeInMemoryMemory( token_counter=self.token_counter, formatter=self.formatter, max_input_length=self.max_input_length, diff --git a/test/test_agentic_retrieve_op.py b/test/test_agentic_retrieve_op.py deleted file mode 100644 index 3cb9d851..00000000 --- a/test/test_agentic_retrieve_op.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Test script for AgenticRetrieveOp. - -This script provides a simple end-to-end test case for AgenticRetrieveOp. -It can be run directly with: python test_agentic_retrieve_op.py -""" - -import asyncio -import json - -from flowllm.core.enumeration import Role -from flowllm.core.schema import Message, ToolCall -from loguru import logger - -from reme_ai.agent.react.agentic_retrieve_op import AgenticRetrieveOp -from reme_ai.main import ReMeApp - - -async def test_agentic_retrieve_basic(): - """Basic test for AgenticRetrieveOp with a short conversation history.""" - logger.info("\n" + "=" * 60) - logger.info("Test: AgenticRetrieveOp basic behavior") - logger.info("=" * 60) - - tool_call_id = "call_6596dafa2a6a46f7a217da" - f = open("README.md", encoding="utf-8") - readme_content = f.read() - f.close() - - messages = [ - Message( - role=Role.SYSTEM, - content=( - "You are a helpful assistant. " - "请先使用`Grep`匹配关键词或者正则表达式所在行数,然后通过`ReadFile`读取位置附近的代码。" - "如果没有找到匹配项,永远不要放弃尝试,尝试其他的参数,比如只搜索部分关键词。" - "`Grep`之后通过 `ReadFile` 命令,你可以从指定偏移位置`offset`+长度`limit`开始查看内容,不要超过100行。" - "如果当前内容不足,`ReadFile` 命令也可以不断尝试不同的`offset`和`limit`参数" - ), - ), - Message( - role=Role.USER, - content="搜索下reme项目的的README内容", - ), - Message( - role=Role.ASSISTANT, - content="", - tool_calls=[ - ToolCall( - **{ - "index": 0, - "id": tool_call_id, - "function": { - "arguments": '{"query": "readme"}', - "name": "web_search", - }, - "type": "function", - }, - ), - ], - ), - Message( - role=Role.TOOL, - content=readme_content * 4, - tool_call_id=tool_call_id, - ), - Message( - role=Role.USER, - content="根据readme回答task memory在appworld的效果是多少,需要具体的数值", - ), - ] - - # llm = "qwen3_coder_plus" - llm = "qwen3_30b_instruct" - # llm = "qwen3_30b_thinking" - # llm = "qwen3_coder_30b_instruct" - # llm = "qwen3_max_instruct" - op = AgenticRetrieveOp(llm=llm) - - await op.async_call( - messages=[m.model_dump() for m in messages], - working_summary_mode="auto", - compact_ratio_threshold=0.75, - max_total_tokens=20000, - max_tool_message_tokens=2000, - group_token_threshold=None, - keep_recent_count=1, - store_dir="./test_working_memory", - chat_id="c123", - ) - - answer = op.context.response.answer - messages = op.context.response.metadata["messages"] - logger.info(f"✓ AgenticRetrieveOp result answer: {answer}") - logger.info(f"✓ AgenticRetrieveOp result messages: {json.dumps(messages, ensure_ascii=False, indent=2)}") - logger.info(f" Success: {op.context.response.success}") - - -async def async_main(): - """Entry point for running AgenticRetrieveOp test.""" - async with ReMeApp(): - logger.info("=" * 80) - logger.info("Testing AgenticRetrieveOp - ReAct Retrieval Workflow") - logger.info("=" * 80) - - await test_agentic_retrieve_basic() - - logger.info("\n" + "=" * 80) - logger.info("All AgenticRetrieveOp tests completed!") - logger.info("=" * 80) - - -if __name__ == "__main__": - asyncio.run(async_main()) diff --git a/tests/test_fs_compactor.py b/test/test_fs_compactor.py similarity index 100% rename from tests/test_fs_compactor.py rename to test/test_fs_compactor.py diff --git a/tests/test_fs_context_checker.py b/test/test_fs_context_checker.py similarity index 100% rename from tests/test_fs_context_checker.py rename to test/test_fs_context_checker.py diff --git a/tests/test_fs_file_watch_integration.py b/test/test_fs_file_watch_integration.py similarity index 100% rename from tests/test_fs_file_watch_integration.py rename to test/test_fs_file_watch_integration.py diff --git a/tests/test_fs_memory_get.py b/test/test_fs_memory_get.py similarity index 100% rename from tests/test_fs_memory_get.py rename to test/test_fs_memory_get.py diff --git a/tests/test_fs_memory_search.py b/test/test_fs_memory_search.py similarity index 100% rename from tests/test_fs_memory_search.py rename to test/test_fs_memory_search.py diff --git a/tests/test_fs_summary.py b/test/test_fs_summary.py similarity index 100% rename from tests/test_fs_summary.py rename to test/test_fs_summary.py diff --git a/test/test_message_compact_op.py b/test/test_message_compact_op.py deleted file mode 100644 index cd8e4edc..00000000 --- a/test/test_message_compact_op.py +++ /dev/null @@ -1,81 +0,0 @@ -"""Test script for MessageCompactOp. - -This script provides test cases for MessageCompactOp class. -It can be run directly with: python test_context_compact_op.py -""" - -import asyncio - -from flowllm.core.enumeration import Role -from flowllm.core.schema import Message - -from reme_ai.main import ReMeApp -from reme_ai.retrieve.working import BatchWriteFileOp -from reme_ai.summary.working import MessageCompactOp - - -async def async_main(): - """Test function for MessageCompactOp.""" - async with ReMeApp(): - # Create test messages with system, user, assistant, tool sequence - messages = [ - Message(role=Role.SYSTEM, content="You are a helpful assistant."), - Message(role=Role.USER, content="What is the weather today?"), - Message( - role=Role.ASSISTANT, - content="I'll check the weather for you.", - ), - Message( - role=Role.TOOL, - content="A" * 5000, # Large tool message that should be compacted - tool_call_id="call_001", - ), - Message( - role=Role.ASSISTANT, - content="Let me also check the forecast.", - ), - Message( - role=Role.TOOL, - content="B" * 5000, # Another large tool message - tool_call_id="call_002", - ), - Message( - role=Role.USER, - content="What about tomorrow?", - ), - Message( - role=Role.ASSISTANT, - content="I'll check tomorrow's weather.", - ), - Message( - role=Role.TOOL, - content="C" * 5000, # Third large tool message - tool_call_id="call_003", - ), - Message( - role=Role.TOOL, - content="Recent result", # Recent tool message (should be kept) - tool_call_id="call_004", - ), - ] - - # Create op with lower thresholds for testing - op = MessageCompactOp() >> BatchWriteFileOp() - - # Execute the compaction - await op.async_call( - messages=[m.model_dump() for m in messages], - max_total_tokens=1000, # Low threshold to trigger compaction - max_tool_message_tokens=100, # Low threshold to compact tool messages - preview_char_length=50, # Keep 50 chars in preview - keep_recent_count=1, # Keep 1 recent tool message - store_dir="./test_compact_storage", - ) - - # Print results - result = op.context.response.answer - print(f"Context compaction result: {result}") - - -if __name__ == "__main__": - asyncio.run(async_main()) diff --git a/test/test_message_compress_op.py b/test/test_message_compress_op.py deleted file mode 100644 index 51423c3a..00000000 --- a/test/test_message_compress_op.py +++ /dev/null @@ -1,285 +0,0 @@ -""" -Test script for MessageCompressOp. - -This script demonstrates how to use the message compression operation to reduce -token usage in conversation histories using language models. -""" - -import asyncio - -from loguru import logger - -from reme_ai.main import ReMeApp -from reme_ai.summary.working import MessageCompressOp - - -async def main(): - """Main function to test MessageCompressOp.""" - - async with ReMeApp(): - logger.info("=" * 80) - logger.info("Testing MessageCompressOp - LLM-based Context Compression") - logger.info("=" * 80) - - # Create a mock conversation with multiple messages - messages = [ - { - "role": "system", - "content": "You are a helpful AI assistant specialized in software development.", - }, - { - "role": "user", - "content": "I need help building a REST API in Python. I want to use FastAPI.", - }, - { - "role": "assistant", - "content": "Great choice! FastAPI is an excellent framework for building REST APIs. " - "It's fast, modern, and has automatic API documentation. To get started, you'll need " - "to install FastAPI and uvicorn. Would you like me to guide you through setting up " - "your first endpoint?", - }, - { - "role": "user", - "content": "Yes please. I want to create a user management API with CRUD operations.", - }, - { - "role": "assistant", - "content": "Perfect! For a user management API, I recommend this structure:\n" - "1. Define a User model using Pydantic\n" - "2. Create POST /users endpoint for creating users\n" - "3. Create GET /users and GET /users/{id} for reading\n" - "4. Create PUT /users/{id} for updates\n" - "5. Create DELETE /users/{id} for deletion\n" - "We'll also need a database. Would you prefer SQLite, PostgreSQL, or MongoDB?", - }, - { - "role": "user", - "content": "Let's use PostgreSQL. Also, I need JWT authentication.", - }, - { - "role": "assistant", - "content": "Excellent. PostgreSQL is a robust choice. For JWT authentication, we'll use " - "python-jose library. Here's what we'll implement:\n" - "1. User registration endpoint\n" - "2. Login endpoint that returns JWT token\n" - "3. Protected endpoints that require valid JWT\n" - "4. Password hashing using bcrypt\n" - "Let me show you the code for the User model first.", - }, - { - "role": "user", - "content": "Before we proceed, I also need rate limiting and input validation.", - }, - { - "role": "assistant", - "content": "Good thinking! For rate limiting, we can use slowapi library which integrates " - "well with FastAPI. For input validation, Pydantic (which FastAPI uses) handles most of it, " - "but we can add custom validators. I'll also add request validation middleware. " - "Let's start implementing all of this step by step.", - }, - { - "role": "user", - "content": "I need to build a distributed task queue system in Python that can handle millions of tasks" - " per day. It needs to be horizontally scalable and fault-tolerant.", - }, - { - "role": "assistant", - "content": "That's a challenging but exciting project! For a high-throughput distributed task queue, " - "I recommend a architecture with:\n\n1. **Message Broker**: Redis or RabbitMQ for task " - "distribution\n2. **Task Workers**: Multiple worker processes across multiple machines\n3." - " **Result Backend**: Redis or PostgreSQL for storing task results\n4. **Monitoring**:" - " Prometheus + Grafana for metrics\n5. **API Layer**: FastAPI for task submission and " - "status queries\n\nFor the core library, we can build on top of Celery or create a custo" - "m solution. Would you like me to design the system architecture first, or do you have pr" - "eferences for specific technologies?", - }, - { - "role": "user", - "content": "I want to build this from scratch without Celery. I need custom features like task" - " priorities, retry policies with exponential backoff, and task dependencies. Also," - " I need it to support both synchronous and asynchronous task execution patterns.", - }, - { - "role": "assistant", - "content": "Excellent! Building from scratch gives us full control. Let me design a comprehensive " - "system architecture:\n\n**Core Components:**\n1. **Task Router**: Distributes tasks based" - " on priority queues\n2. **Worker Manager**: Handles worker lifecycle and load balancing\n3." - " **Retry Engine**: Implements exponential backoff with jitter\n4. **Dependency Graph**:" - " Manages task dependencies using topological sorting\n5. **State Manager**: Tracks task " - "states (pending, running, completed, failed)\n6. **Metrics Collector**: Real-time " - "performance metrics\n\n**Data Structures:**\n- Priority queues using Redis Sorted Sets\n-" - " Task metadata in PostgreSQL with JSONB columns\n- Distributed locks with Redis Redlock" - " algorithm\n\n**Features:**\n- At-least-once delivery guarantee\n- Dead letter queue for " - "failed tasks\n- Rate limiting per task type\n- Circuit breaker pattern for failing " - "services\n\nWould you like me to start with the core task model and priority queue " - "implementation?", - }, - { - "role": "user", - "content": "That sounds good. But I also need WebSocket support for real-time task status updates, a " - "web dashboard for monitoring, and the ability to dynamically scale workers based on queue" - " depth. Plus, I need task deduplication and exactly-once processing semantics.", - }, - { - "role": "assistant", - "content": "Perfect! Let me enhance the architecture with these advanced requirements:\n\n**Real-time " - "Features:**\n1. **WebSocket Manager**: Using Python's websocket-server with Redis pub/sub " - "for multi-instance communication\n2. **Event Stream**: Task state changes published to Re" - "dis streams\n3. **Client SDK**: JavaScript/TypeScript library for real-time subscriptio" - "ns\n\n**Auto-scaling Architecture:**\n1. **Metrics Aggregator**: Collects queue depth, " - "processing latency, error rates\n2. **Scaler Service**: Kubernetes HPA integration or cu" - "stom Docker Swarm scaling\n3. **Predictive Scaling**: ML model to predict load based on hi" - "storical patterns\n\n**Exactly-once Processing:**\n1. **Idempotency Keys**: Each task has " - "UUID-based idempotency key\n2. **Deduplication Store**: Redis with TTL for processed task " - "IDs\n3. **Transactional Outbox**: PostgreSQL outbox pattern for reliable event publishing" - "\n4. **Two-phase Commit**: For distributed task processing\n\n**Dashboard Features:**\n- " - "Real-time queue metrics with D3.js visualizations\n- Task timeline view with dependency g" - "raphs\n- Worker health monitoring with automatic restart\n- Alert system for SLA violati" - "ons\n\n**Additional Components:**\n- Task scheduler for delayed tasks using Redis keys w" - "ith TTL\n- Batch processing support for bulk operations\n- Multi-tenant isolation with na" - "mespace support\n\nThis is getting quite comprehensive! Should I start with the core tas" - "k model and database schema, or would you prefer to begin with the WebSocket real-time up" - "date system?", - }, - { - "role": "user", - "content": "This is getting complex. I also need multi-region support with eventual consistency, the " - "ability to pause/resume task processing by task type, and A/B testing capabilities for " - "different worker implementations. Also, I need comprehensive audit logging and GDPR comp" - "liance features.", - }, - { - "role": "assistant", - "content": "Absolutely! This is evolving into an enterprise-grade system. Let me architect the compl" - "ete solution:\n\n**Multi-region Architecture:**\n1. **Global Load Balancer**: GeoDNS with" - " health checks\n2. **Cross-region Replication**: Redis Cluster with active-active setup " - "using CRDTs\n3. **Conflict Resolution**: Vector clocks for task ordering, last-writer-win" - "s for metadata\n4. **Region-aware Routing**: Route tasks to workers in same region when p" - "ossible\n5. **Failover Mechanism**: Automatic region failover with 30-second RTO\n\n**Adv" - "anced Control Features:**\n1. **Task Type Governance**: \n - Pause/resume via Redis fe" - "ature flags with immediate propagation\n - Rate limits per task type with burst capaci" - "ty\n - Resource quotas (CPU/memory) per task category\n2. **A/B Testing Framework**:\n" - " - Task routing based on consistent hashing of task ID\n - Variant assignment with s" - "tickiness\n - Statistical significance tracking for performance metrics\n - Automati" - "c winner selection based on success rate and latency\n\n**Compliance & Audit:**\n1. **A" - "udit Trail**:\n - Immutable task history in PostgreSQL with row-level security\n -" - " Change data capture (CDC) using Debezium\n - Cryptographic signing of audit logs\n " - " - 7-year retention policy with automated archival to S3\n2. **GDPR Compliance**:\n " - " - Right to be forgotten: Cascade delete with verification\n - Data portability: JSO" - "N export of all user tasks\n - Consent management: Task processing consent tracking" - "\n - Data anonymization: PII encryption with rotating keys\n\n**Enhanced Monitoring:*" - "*\n1. **Distributed Tracing**: OpenTelemetry integration with Jaeger\n2. **Performance " - "Profiling**: Py-spy continuous profiling\n3. **Cost Analytics**: Per-task-type resource" - " consumption tracking\n4. **SLA Monitoring**: 99.9% task processing latency targets\n\n" - "**Security Features:**\n1. **Zero-trust Architecture**: mTLS between all services\n2. **" - "Secrets Management**: HashiCorp Vault integration\n3. **Network Segmentation**: VPC is" - "olation with private subnets\n4. **Encryption**: AES-256 at rest, TLS 1.3 in transit\n" - "\n**Operational Excellence:**\n1. **Blue-green Deployments**: Zero-downtime updates\n2." - " **Canary Releases**: Gradual rollout of worker changes\n3. **Chaos Engineering**: Rand" - "om worker termination testing\n4. **Capacity Planning**: Automated infrastructure provi" - "sioning\n\nThis is becoming a mission-critical system! Would you like me to start with " - "the database schema and core task lifecycle management, or should we begin with the mul" - "ti-region replication strategy? I can also create a detailed implementation roadmap wi" - "th phases if you prefer.", - }, - { - "role": "user", - "content": "How do I reverse a string in Python?", - }, - { - "role": "assistant", - "content": "You can reverse a string in Python using slicing: `reversed_string = original_string[::" - "-1]`. The `[::-1]` slice notation means start at the end of the string and end at posit" - "ion 0, moving with the step -1.", - }, - { - "role": "user", - "content": "What's the difference between a list and a tuple?", - }, - { - "role": "assistant", - "content": "Lists are mutable (you can change their content after creation) and use square bracket" - "s `[]`. Tuples are immutable and use parentheses `()`. Lists are better for collectio" - "ns that need to change, while tuples are faster and can be used as dictionary keys.", - }, - { - "role": "user", - "content": "How do I handle exceptions in Python?", - }, - { - "role": "assistant", - "content": "Use try-except blocks: `try:` followed by your risky code, then `except SpecificError" - ":` to catch particular exceptions. Always catch specific exceptions rather than the g" - "eneric `Exception` when possible. You can also use `finally:` for cleanup code that ru" - "ns regardless.", - }, - ] - - # Test 1: Messages below threshold (should not compress) - logger.info("\n" + "=" * 60) - logger.info("Test 1: Messages below threshold (should skip compression)") - logger.info("=" * 60) - - compress_op1 = MessageCompressOp() - - await compress_op1.async_call( - messages=messages, - max_total_tokens=50000, # High threshold, won't trigger - keep_recent_count=2, - ) - - result_messages1 = compress_op1.context.response.answer - logger.info(f"✓ Result: {len(result_messages1)} messages (unchanged)") - - # Test 2: Messages above threshold (should compress) - logger.info("\n" + "=" * 60) - logger.info("Test 2: Messages above threshold (should compress)") - logger.info("=" * 60) - - compress_op2 = MessageCompressOp() - - await compress_op2.async_call( - messages=messages, - max_total_tokens=2000, # Low threshold, will trigger - keep_recent_count=2, - compress_system_message=False, - ) - - result_messages2 = compress_op2.context.response.answer - logger.info(f"✓ Result: {len(result_messages2)} messages (compressed)") - - # Display compression results - logger.info("\n" + "=" * 60) - logger.info("Compression Result Details:") - logger.info("=" * 60) - logger.info(f"Original messages: {len(messages)}") - logger.info(f"Compressed messages: {len(result_messages2)}") - - # Test 3: Messages above threshold (should compress) - logger.info("\n" + "=!" * 30) - logger.info("Test 3: Messages above micro threshold (should compress)") - logger.info("=!" * 30) - - compress_op2 = MessageCompressOp() - - await compress_op2.async_call( - messages=messages, - max_total_tokens=2000, # Low threshold, will trigger - keep_recent_count=2, - compress_system_message=False, # Don't compress system messages - group_token_threshold=1500, - ) - - result_messages2 = compress_op2.context.response.answer - logger.info(f"✓ Result: {len(result_messages2)} messages (compressed)") - - # Display compression results - logger.info("\n" + "=" * 60) - logger.info("Compression Result Details:") - logger.info("=" * 60) - logger.info(f"Original messages: {len(messages)}") - logger.info(f"Compressed messages: {len(result_messages2)}") - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/test/test_message_offload_op.py b/test/test_message_offload_op.py deleted file mode 100644 index 403e89fd..00000000 --- a/test/test_message_offload_op.py +++ /dev/null @@ -1,247 +0,0 @@ -"""Test script for MessageOffloadOp. - -This script provides test cases for MessageOffloadOp class. -It can be run directly with: python test_context_offload_op.py -""" - -import asyncio - -from flowllm.core.enumeration import Role -from flowllm.core.schema import Message -from loguru import logger - -from reme_ai.enumeration import WorkingSummaryMode -from reme_ai.main import ReMeApp -from reme_ai.retrieve.working import BatchWriteFileOp -from reme_ai.summary.working import MessageOffloadOp - - -async def test_compact_mode(): - """Test COMPACT mode - Only apply compaction with MessageOffloadOp.""" - logger.info("\n" + "=" * 60) - logger.info("Test: COMPACT mode - Only apply compaction") - logger.info("=" * 60) - - # Create test messages with system, user, assistant, tool sequence - messages = [ - Message(role=Role.SYSTEM, content="You are a helpful assistant."), - Message(role=Role.USER, content="What is the weather today?"), - Message( - role=Role.ASSISTANT, - content="I'll check the weather for you.", - ), - Message( - role=Role.TOOL, - content="A" * 5000, # Large tool message that should be compacted - tool_call_id="call_001", - ), - Message( - role=Role.ASSISTANT, - content="Let me also check the forecast.", - ), - Message( - role=Role.TOOL, - content="B" * 5000, # Another large tool message - tool_call_id="call_002", - ), - Message( - role=Role.USER, - content="What about tomorrow?", - ), - Message( - role=Role.ASSISTANT, - content="I'll check tomorrow's weather.", - ), - Message( - role=Role.TOOL, - content="C" * 5000, # Third large tool message - tool_call_id="call_003", - ), - Message( - role=Role.TOOL, - content="Recent result", # Recent tool message (should be kept) - tool_call_id="call_004", - ), - ] - - op = MessageOffloadOp() >> BatchWriteFileOp() - - await op.async_call( - messages=[m.model_dump() for m in messages], - context_manage_mode=WorkingSummaryMode.COMPACT, - max_total_tokens=1000, # Low threshold to trigger compaction - max_tool_message_tokens=100, # Low threshold to compact tool messages - preview_char_length=50, # Keep 50 chars in preview - keep_recent_count=1, # Keep 1 recent tool message - store_dir="./test_compact_storage", - ) - - result = op.context.response.answer - logger.info(f"✓ COMPACT mode result: {len(result)} messages") - logger.info(f" Success: {op.context.response.success}") - - -async def test_compress_mode(): - """Test COMPRESS mode - Only apply compression with MessageOffloadOp.""" - logger.info("\n" + "=" * 60) - logger.info("Test: COMPRESS mode - Only apply compression") - logger.info("=" * 60) - - # Create test messages with system, user, assistant, tool sequence - messages = [ - Message(role=Role.SYSTEM, content="You are a helpful assistant."), - Message(role=Role.USER, content="What is the weather today?"), - Message( - role=Role.ASSISTANT, - content="I'll check the weather for you.", - ), - Message( - role=Role.TOOL, - content="A" * 5000, # Large tool message that should be compacted - tool_call_id="call_001", - ), - Message( - role=Role.ASSISTANT, - content="Let me also check the forecast.", - ), - Message( - role=Role.TOOL, - content="B" * 5000, # Another large tool message - tool_call_id="call_002", - ), - Message( - role=Role.USER, - content="What about tomorrow?", - ), - Message( - role=Role.ASSISTANT, - content="I'll check tomorrow's weather.", - ), - Message( - role=Role.TOOL, - content="C" * 5000, # Third large tool message - tool_call_id="call_003", - ), - Message( - role=Role.TOOL, - content="Recent result", # Recent tool message (should be kept) - tool_call_id="call_004", - ), - ] - - op = MessageOffloadOp() >> BatchWriteFileOp() - - await op.async_call( - messages=[m.model_dump() for m in messages], - context_manage_mode=WorkingSummaryMode.COMPRESS, - max_total_tokens=2000, # Low threshold to trigger compression - keep_recent_count=2, - store_dir="./test_compact_storage", - ) - - result = op.context.response.answer - logger.info(f"✓ COMPRESS mode result: {len(result)} messages") - logger.info(f" Success: {op.context.response.success}") - - -async def test_auto_mode(): - """Test AUTO mode - Apply compaction first, then compression if needed using MessageOffloadOp.""" - logger.info("\n" + "=" * 60) - logger.info("Test: AUTO mode - Apply compaction first, then compression if needed") - logger.info("=" * 60) - - # Create messages with extensive user content to ensure compact ratio exceeds threshold - auto_messages = [ - Message(role=Role.SYSTEM, content="You are a helpful assistant."), - Message(role=Role.USER, content="What is the weather today?"), - Message( - role=Role.ASSISTANT, - content="I'll check the weather for you.", - ), - Message( - role=Role.TOOL, - content="A" * 5000, # Large tool message that should be compacted - tool_call_id="call_001", - ), - Message( - role=Role.USER, - content="I need detailed information about the weather forecast for the next week. " - "Please provide temperature, humidity, wind speed, and precipitation chances for each day. " - "Also, I want to know about any weather warnings or advisories. " - "This is very important for my travel planning." * 50, # Long user message - ), - Message( - role=Role.ASSISTANT, - content="I'll gather comprehensive weather information for you. Let me check multiple sources." * 3, - ), - Message( - role=Role.TOOL, - content="B" * 5000, # Another large tool message - tool_call_id="call_002", - ), - Message( - role=Role.USER, - content="Can you also provide information about air quality, UV index, and sunrise/sunset times? " - "I'm planning outdoor activities and need to know the best times to be outside. " - "Also, please include historical weather data for comparison." * 4, # More long user content - ), - Message( - role=Role.ASSISTANT, - content="Absolutely! I'll get all that information for you including air quality metrics and UV data." * 2, - ), - Message( - role=Role.TOOL, - content="C" * 5000, # Third large tool message - tool_call_id="call_003", - ), - Message( - role=Role.USER, - content="What about tomorrow?", - ), - Message( - role=Role.ASSISTANT, - content="I'll check tomorrow's weather.", - ), - Message( - role=Role.TOOL, - content="Recent result", # Recent tool message (should be kept) - tool_call_id="call_004", - ), - ] - - op = MessageOffloadOp() >> BatchWriteFileOp() - - await op.async_call( - messages=[m.model_dump() for m in auto_messages], - context_manage_mode=WorkingSummaryMode.AUTO, - compact_ratio_threshold=0.2, # Low threshold, should trigger compression after compact - max_total_tokens=1000, - max_tool_message_tokens=100, - preview_char_length=50, - keep_recent_count=1, - store_dir="./test_compact_storage", - ) - - result = op.context.response.answer - logger.info(f"✓ AUTO mode result: {len(result)} messages") - logger.info(f" Success: {op.context.response.success}") - - -async def async_main(): - """Test function for MessageOffloadOp.""" - async with ReMeApp(): - logger.info("=" * 80) - logger.info("Testing MessageOffloadOp - Context Management Orchestration") - logger.info("=" * 80) - - await test_compact_mode() - await test_compress_mode() - await test_auto_mode() - - logger.info("\n" + "=" * 80) - logger.info("All tests completed!") - logger.info("=" * 80) - - -if __name__ == "__main__": - asyncio.run(async_main()) diff --git a/tests/copaw/test_compactor.py b/tests/light/test_compactor.py similarity index 99% rename from tests/copaw/test_compactor.py rename to tests/light/test_compactor.py index d044f376..32dfd9d3 100644 --- a/tests/copaw/test_compactor.py +++ b/tests/light/test_compactor.py @@ -10,7 +10,7 @@ from test_utils import ( get_formatter, get_token_counter, ) -from reme.memory.file_based_copaw import Compactor +from reme.memory.file_based import Compactor # 配置日志输出到控制台 logging.basicConfig( diff --git a/tests/copaw/test_memory_formatter.py b/tests/light/test_memory_formatter.py similarity index 99% rename from tests/copaw/test_memory_formatter.py rename to tests/light/test_memory_formatter.py index 7da3db2d..00f45bb1 100644 --- a/tests/copaw/test_memory_formatter.py +++ b/tests/light/test_memory_formatter.py @@ -7,7 +7,7 @@ import logging from agentscope.message import Msg from test_utils import get_token_counter -from reme.memory.file_based_copaw import MemoryFormatter +from reme.memory.file_based import MemoryFormatter # 配置日志输出到控制台 logging.basicConfig( diff --git a/tests/light/test_reme_light.py b/tests/light/test_reme_light.py new file mode 100644 index 00000000..9e2ac70a --- /dev/null +++ b/tests/light/test_reme_light.py @@ -0,0 +1,198 @@ +"""测试 ReMeLight""" + +import asyncio + +from agentscope.message import Msg +from reme.reme_light import ReMeLight + + +# ==================== 消息创建辅助函数 ==================== +def create_user_msg(content: str) -> Msg: + """创建用户消息""" + return Msg(name="user", role="user", content=content) + + +def create_assistant_msg(content: str) -> Msg: + """创建助手消息""" + return Msg(name="assistant", role="assistant", content=content) + + +def create_tool_use_msg(tool_id: str, tool_name: str, tool_input: dict) -> Msg: + """创建工具调用消息""" + return Msg( + name="assistant", + role="assistant", + content=[ + { + "type": "tool_use", + "id": tool_id, + "name": tool_name, + "input": tool_input, + }, + ], + ) + + +def create_tool_result_msg(tool_id: str, tool_name: str, output: str) -> Msg: + """创建工具结果消息""" + return Msg( + name="tool", + role="user", + content=[ + { + "type": "tool_result", + "id": tool_id, + "name": tool_name, + "output": output, + }, + ], + ) + + +def create_thinking_msg(thinking_content: str) -> Msg: + """创建思考消息""" + return Msg( + name="assistant", + role="assistant", + content=[ + { + "type": "thinking", + "text": thinking_content, + }, + ], + ) + + +# ==================== 构建模拟对话历史 ==================== +def build_sample_messages() -> list[Msg]: + """构建一段包含多种消息类型的模拟对话""" + messages = [ + # 用户询问 Python 版本 + create_user_msg("我想设置一个 Python 开发环境,你有什么建议?"), + # 助手思考 + create_thinking_msg("用户想要搭建 Python 开发环境,我需要了解他的需求和偏好..."), + # 助手回复 + create_assistant_msg( + "好的!我建议使用 Python 3.11 或 3.12 版本,它们性能更好且功能丰富。" + "你希望用于什么类型的开发?Web、数据科学还是其他?", + ), + # 用户提供更多信息 + create_user_msg("主要是做 Web 开发,使用 FastAPI 框架。另外我喜欢用 pyenv 管理版本。"), + # 助手调用工具查询 + create_tool_use_msg( + tool_id="call_001", + tool_name="search_web", + tool_input={"query": "FastAPI Python version compatibility 2024"}, + ), + # 工具返回结果(模拟较长的输出) + create_tool_result_msg( + tool_id="call_001", + tool_name="search_web", + output=( + "FastAPI 官方推荐使用 Python 3.8+ 版本,但 3.11/3.12 性能最佳。\n" + "主要依赖:\n" + "- Starlette: ASGI 框架\n" + "- Pydantic v2: 数据验证\n" + "- Uvicorn: ASGI 服务器\n" + "最新版本 FastAPI 0.109+ 完全支持 Python 3.12。\n" + "建议搭配 uv 或 pip-tools 进行依赖管理。" + ), + ), + # 助手总结建议 + create_assistant_msg( + "根据查询结果,我的建议是:\n" + "1. **Python 版本**: 使用 Python 3.11 或 3.12(通过 pyenv 安装)\n" + "2. **框架**: FastAPI 0.109+ 完全兼容这些版本\n" + "3. **依赖管理**: 推荐使用 uv(更快)或 pip-tools\n" + "4. **ASGI 服务器**: Uvicorn 配合 gunicorn 用于生产环境\n\n" + "需要我帮你生成一个项目模板吗?", + ), + # 用户确认偏好 + create_user_msg("好的,我决定用 Python 3.12 + FastAPI + uv。请记住我的这些偏好。"), + # 助手确认 + create_assistant_msg( + "已记录你的开发偏好:\n" + "- Python 版本: 3.12 (通过 pyenv 管理)\n" + "- Web 框架: FastAPI\n" + "- 包管理器: uv\n" + "以后有相关问题我会参考这些偏好给你建议!", + ), + ] + return messages + + +# ==================== 主测试流程 ==================== +async def main(): + """ReMeLight 主测试流程,演示完整的记忆管理功能。""" + # 初始化 ReMeLight + reme = ReMeLight( + working_dir=".reme", # 记忆文件存储目录 + max_input_length=128000, # 模型上下文窗口(tokens) + memory_compact_ratio=0.7, # 达到 max_input_length * 0.7 时触发压缩 + language="zh", # 摘要语言(zh / "") + tool_result_threshold=1000, # 超过此字符数的工具输出自动转存 + retention_days=7, # tool_result/ 文件保留天数 + ) + await reme.start() + print("=" * 60) + print("ReMeLight 已启动") + print("=" * 60) + + # 构建模拟对话历史 + messages = build_sample_messages() + print(f"\n[原始消息数量]: {len(messages)} 条") + + # 1. 压缩超长工具输出(防止工具结果撑爆上下文) + print("\n" + "-" * 40) + print("[步骤 1] 压缩超长工具输出...") + messages = await reme.compact_tool_result(messages) + print(f"处理后消息数量: {len(messages)} 条") + + # 2. 将历史对话压缩为结构化摘要(触发时机:上下文接近上限) + print("\n" + "-" * 40) + print("[步骤 2] 生成结构化压缩摘要...") + summary = await reme.compact_memory( + messages=messages, + previous_summary="", # 可传入上轮摘要,实现增量更新 + ) + print(f"压缩摘要:\n{summary[:500]}..." if len(summary) > 500 else f"压缩摘要:\n{summary}") + + # 3. 后台异步提交摘要任务(不阻塞对话,摘要写入 memory/YYYY-MM-DD.md) + print("\n" + "-" * 40) + print("[步骤 3] 提交后台异步摘要任务...") + reme.add_async_summary_task(messages=messages) + print("异步任务已提交") + + # 4. 语义搜索记忆(向量 + BM25 混合检索) + print("\n" + "-" * 40) + print("[步骤 4] 语义搜索记忆...") + result = await reme.memory_search(query="Python 版本偏好", max_results=5) + print(f"搜索结果: {result}") + + # 5. 获取会话内存实例(ReMeInMemoryMemory,管理单次对话的上下文) + print("\n" + "-" * 40) + print("[步骤 5] 获取会话内存实例并估算 Token 使用...") + memory = reme.get_in_memory_memory() + # 将消息添加到内存中以便估算 + for msg in messages: + await memory.add(msg) + token_stats = await memory.estimate_tokens() + print(f"当前上下文使用率: {token_stats['context_usage_ratio']:.1f}%") + print(f"消息 Token 数: {token_stats['messages_tokens']}") + print(f"预估总 Token 数: {token_stats['estimated_tokens']}") + + # 6. 关闭前等待后台任务完成 + print("\n" + "-" * 40) + print("[步骤 6] 等待后台任务完成...") + summary_result = await reme.await_summary_tasks() + print(f"后台摘要任务完成,结果长度: {len(summary_result)} 字符") + + # 关闭 ReMeLight + await reme.close() + print("\n" + "=" * 60) + print("ReMeLight 已关闭") + print("=" * 60) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/copaw/test_summarizer.py b/tests/light/test_summarizer.py similarity index 99% rename from tests/copaw/test_summarizer.py rename to tests/light/test_summarizer.py index d0f7eb5f..560efabd 100644 --- a/tests/copaw/test_summarizer.py +++ b/tests/light/test_summarizer.py @@ -13,7 +13,7 @@ from test_utils import ( get_formatter, get_token_counter, ) -from reme.memory.file_based_copaw import Summarizer +from reme.memory.file_based import Summarizer # 配置日志输出到控制台 logging.basicConfig( diff --git a/tests/copaw/test_tool_result_compactor.py b/tests/light/test_tool_result_compactor.py similarity index 97% rename from tests/copaw/test_tool_result_compactor.py rename to tests/light/test_tool_result_compactor.py index ba225255..059e626e 100644 --- a/tests/copaw/test_tool_result_compactor.py +++ b/tests/light/test_tool_result_compactor.py @@ -7,8 +7,8 @@ from pathlib import Path from agentscope.message import Msg -from reme.memory.file_based_copaw.tool_result_compactor import ToolResultCompactor -from reme.memory.file_based_copaw.utils import TRUNCATION_MARKER_START +from reme.memory.file_based.tool_result_compactor import ToolResultCompactor +from reme.memory.file_based.utils import TRUNCATION_MARKER_START def create_tool_result_msg(output: str | list, tool_name: str = "test_tool") -> Msg: diff --git a/tests/copaw/test_utils.py b/tests/light/test_utils.py similarity index 97% rename from tests/copaw/test_utils.py rename to tests/light/test_utils.py index f5da46e2..f5cae021 100644 --- a/tests/copaw/test_utils.py +++ b/tests/light/test_utils.py @@ -64,7 +64,7 @@ def get_formatter(): """Get formatter instance.""" from agentscope.formatter import OpenAIChatFormatter from agentscope.token import HuggingFaceTokenCounter - from reme.memory.file_based_copaw.utils import _extract_text_from_messages + from reme.memory.file_based.utils import _extract_text_from_messages class ReMeChatFormatter(OpenAIChatFormatter): """ReMe chat formatter class."""