From 5e08aa48b81b586ccfd2f6edd5a4a9953e6cbc3a Mon Sep 17 00:00:00 2001 From: jinliyl <6469360+jinliyl@users.noreply.github.com> Date: Sat, 7 Mar 2026 15:22:56 +0800 Subject: [PATCH] refactor(memory): restructure file-based memory components and enhance message handling (#145) --- README.md | 35 +++++++++---------- README_ZH.md | 32 ++++++++--------- reme/core/schema/as_msg_stat.py | 35 ++++++++++++------- reme/memory/file_based/__init__.py | 30 ++++------------ reme/memory/file_based/component/__init__.py | 0 reme/memory/file_based/components/__init__.py | 13 +++++++ .../{component => components}/compactor.py | 5 ++- .../{component => components}/compactor.yaml | 0 .../context_checker.py | 15 ++++---- .../{component => components}/summarizer.py | 5 ++- .../{component => components}/summarizer.yaml | 0 .../tool_result_compactor.py | 0 .../file_based/reme_in_memory_memory.py | 2 +- reme/memory/file_based/utils/__init__.py | 7 ++++ .../file_based/{ => utils}/as_msg_handler.py | 33 ++++++++++++++--- reme/reme_light.py | 18 +++++----- tests/light/test_compactor.py | 4 +-- tests/light/test_context_check.py | 4 +-- tests/light/test_format_msgs_to_str.py | 4 +-- tests/light/test_summarizer.py | 4 +-- tests/light/test_tool_result_compactor.py | 3 +- tests/light/test_tools.py | 2 +- tests/light/test_utils.py | 2 +- 23 files changed, 148 insertions(+), 105 deletions(-) delete mode 100644 reme/memory/file_based/component/__init__.py create mode 100644 reme/memory/file_based/components/__init__.py rename reme/memory/file_based/{component => components}/compactor.py (89%) rename reme/memory/file_based/{component => components}/compactor.yaml (100%) rename reme/memory/file_based/{component => components}/context_checker.py (92%) rename reme/memory/file_based/{component => components}/summarizer.py (88%) rename reme/memory/file_based/{component => components}/summarizer.yaml (100%) rename reme/memory/file_based/{component => components}/tool_result_compactor.py (100%) create mode 100644 reme/memory/file_based/utils/__init__.py rename reme/memory/file_based/{ => utils}/as_msg_handler.py (92%) diff --git a/README.md b/README.md index 9d18e6ce..7bc1ef86 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ A memory management toolkit for AI agents — Remember Me, Refine Me.

-> For the older version, please refer to the [0.2.x documentation](docs/README_0_2_x_ZH.md). +> For the older version, please refer to the [0.2.x documentation](docs/README_0_2_x.md). --- @@ -64,17 +64,17 @@ working_dir/ [ReMeLight](reme/reme_light.py) is the core class of the file-based memory system. It provides full memory management capabilities for AI agents: -| Method | Function | Key components | -|------------------------|--------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `check_context` | 📊 Check context size | [ContextChecker](reme/memory/file_based/component/context_checker.py) — checks whether context exceeds thresholds and splits messages | -| `compact_memory` | 📦 Compact history into summary | [Compactor](reme/memory/file_based/component/compactor.py) — ReActAgent that generates structured context summaries | -| `summary_memory` | 📝 Persist important memory to files | [Summarizer](reme/memory/file_based/component/summarizer.py) — ReActAgent + file tools (`read` / `write` / `edit`) | -| `compact_tool_result` | ✂️ Compact long tool outputs | [ToolResultCompactor](reme/memory/file_based/component/tool_result_compactor.py) — truncates long tool outputs and stores them in `tool_result/` while keeping file references in messages | -| `memory_search` | 🔍 Semantic memory search | [MemorySearch](reme/memory/file_based/tools/memory_search.py) — hybrid retrieval with vectors + BM25 | -| `ReMeInMemoryMemory` | 🗂️ In-session memory class | [ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) — token-aware memory management with summary compression and state serialization | -| `pre_reasoning_hook` | 🔄 Pre-reasoning hook | `compact_tool_result` + `check_context` + `compact_memory` + `summary_memory` (async) | -| `start` | 🚀 Start memory system | Initialize file storage, file watcher, and embedding cache; clean up expired tool result files | -| `close` | 📕 Shutdown and cleanup | Clean up tool result files, stop file watcher, and persist embedding cache | +| Method | Function | Key components | +|-----------------------|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `check_context` | 📊 Check context size | [ContextChecker](reme/memory/file_based/components/context_checker.py) — checks whether context exceeds thresholds and splits messages | +| `compact_memory` | 📦 Compact history into summary | [Compactor](reme/memory/file_based/components/compactor.py) — ReActAgent that generates structured context summaries | +| `summary_memory` | 📝 Persist important memory to files | [Summarizer](reme/memory/file_based/components/summarizer.py) — ReActAgent + file tools (`read` / `write` / `edit`) | +| `compact_tool_result` | ✂️ Compact long tool outputs | [ToolResultCompactor](reme/memory/file_based/components/tool_result_compactor.py) — truncates long tool outputs and stores them in `tool_result/` while keeping file references in messages | +| `memory_search` | 🔍 Semantic memory search | [MemorySearch](reme/memory/file_based/tools/memory_search.py) — hybrid retrieval with vectors + BM25 | +| `ReMeInMemoryMemory` | 🗂️ In-session memory class | [ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) — token-aware memory management with summary compression and state serialization | +| `pre_reasoning_hook` | 🔄 Pre-reasoning hook | `compact_tool_result` + `check_context` + `compact_memory` + `summary_memory` (async) | +| `start` | 🚀 Start memory system | Initialize file storage, file watcher, and embedding cache; clean up expired tool result files | +| `close` | 📕 Shutdown and cleanup | Clean up tool result files, stop file watcher, and persist embedding cache | --- @@ -186,7 +186,7 @@ graph LR CC -->|Exceeds limit| SM[summary_memory
Async persistence] SM -->|ReAct + FileIO| Files[memory/*.md] Agent -->|Explicit call| Search[memory_search
Vector+BM25] - Agent -->|In-session| InMem[ReMeInMemoryMemory
Token-aware memory] + Agent -->|In - session| InMem[ReMeInMemoryMemory
Token-aware memory] Files -.->|FileWatcher| Store[(FileStore
Vector+FTS index)] Search --> Store ``` @@ -195,7 +195,7 @@ graph LR #### 1. `check_context` — context checking -[ContextChecker](reme/memory/file_based/component/context_checker.py) uses token counting to determine whether the +[ContextChecker](reme/memory/file_based/components/context_checker.py) uses token counting to determine whether the context exceeds thresholds and automatically splits messages into a "to compact" group and a "to keep" group. ```mermaid @@ -217,7 +217,7 @@ graph LR #### 2. `compact_memory` — conversation compaction -[Compactor](reme/memory/file_based/component/compactor.py) uses a ReActAgent to compact conversation history into a * +[Compactor](reme/memory/file_based/components/compactor.py) uses a ReActAgent to compact conversation history into a * *structured context summary**. ```mermaid @@ -245,7 +245,7 @@ graph LR #### 3. `summary_memory` — persistent memory -[Summarizer](reme/memory/file_based/component/summarizer.py) uses a **ReAct + file tools** pattern so that the AI can +[Summarizer](reme/memory/file_based/components/summarizer.py) uses a **ReAct + file tools** pattern so that the AI can decide what to write and where to write it. ```mermaid @@ -271,7 +271,7 @@ graph LR #### 4. `compact_tool_result` — tool result compaction -[ToolResultCompactor](reme/memory/file_based/component/tool_result_compactor.py) addresses the problem of long tool +[ToolResultCompactor](reme/memory/file_based/components/tool_result_compactor.py) addresses the problem of long tool outputs bloating the context. ```mermaid @@ -534,7 +534,6 @@ We evaluate ReMe on the BFCL-V3 multi-turn-base task (random split 50 train / 15 For more details on how to reproduce the experiments, see [quickstart.md](benchmark/bfcl/quickstart.md). - ## ⭐ Community & support - **Star & Watch**: Starring helps more agent developers discover ReMe; Watching keeps you up to date with new releases diff --git a/README_ZH.md b/README_ZH.md index 8723177c..bd038f67 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -60,17 +60,18 @@ working_dir/ [ReMeLight](reme/reme_light.py) 是该记忆系统的核心类,为 AI Agent 提供完整的记忆管理能力: -| 方法 | 功能 | 关键组件 | -|------------------------|--------------|-----------------------------------------------------------------------------------------------------------------------------| -| `check_context` | 📊 检查上下文大小 | [ContextChecker](reme/memory/file_based/component/context_checker.py) — 检查上下文是否超出阈值并拆分Message | -| `compact_memory` | 📦 压缩历史对话为摘要 | [Compactor](reme/memory/file_based/component/compactor.py) — ReActAgent 生成结构化上下文摘要 | -| `summary_memory` | 📝 将重要记忆写入文件 | [Summarizer](reme/memory/file_based/component/summarizer.py) — ReActAgent + 文件工具(read / write / edit) | -| `compact_tool_result` | ✂️ 压缩超长工具输出 | [ToolResultCompactor](reme/memory/file_based/component/tool_result_compactor.py) — 截断超长的工具调用结果并转存到 `tool_result/`,消息中保留文件引用 | -| `memory_search` | 🔍 语义搜索记忆 | [MemorySearch](reme/memory/file_based/tools/memory_search.py) — 向量 + BM25 混合检索 | -| `ReMeInMemoryMemory` | 🗂️ 会话内存类 | [ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) — Token 感知的内存管理,支持压缩摘要和状态序列化 | -| `pre_reasoning_hook` | 🔄 推理前预处理钩子 | compact_tool_result + check_context + compact_memory + summary_memory(async) | -| `start` | 🚀 启动记忆系统 | 初始化文件存储、文件监控、Embedding 缓存;清理过期工具结果文件 | -| `close` | 📕 关闭并清理 | 清理工具结果文件、停止文件监控、保存 Embedding 缓存 |· +| 方法 | 功能 | 关键组件 | +|-----------------------|--------------|------------------------------------------------------------------------------------------------------------------------------| +| `check_context` | 📊 检查上下文大小 | [ContextChecker](reme/memory/file_based/components/context_checker.py) — 检查上下文是否超出阈值并拆分Message | +| `compact_memory` | 📦 压缩历史对话为摘要 | [Compactor](reme/memory/file_based/components/compactor.py) — ReActAgent 生成结构化上下文摘要 | +| `summary_memory` | 📝 将重要记忆写入文件 | [Summarizer](reme/memory/file_based/components/summarizer.py) — ReActAgent + 文件工具(read / write / edit) | +| `compact_tool_result` | ✂️ 压缩超长工具输出 | [ToolResultCompactor](reme/memory/file_based/components/tool_result_compactor.py) — 截断超长的工具调用结果并转存到 `tool_result/`,消息中保留文件引用 | +| `memory_search` | 🔍 语义搜索记忆 | [MemorySearch](reme/memory/file_based/tools/memory_search.py) — 向量 + BM25 混合检索 | +| `ReMeInMemoryMemory` | 🗂️ 会话内存类 | [ReMeInMemoryMemory](reme/memory/file_based/reme_in_memory_memory.py) — Token 感知的内存管理,支持压缩摘要和状态序列化 | +| `pre_reasoning_hook` | 🔄 推理前预处理钩子 | compact_tool_result + check_context + compact_memory + summary_memory(async) | +| `start` | 🚀 启动记忆系统 | 初始化文件存储、文件监控、Embedding 缓存;清理过期工具结果文件 | +| `close` | 📕 关闭并清理 | 清理工具结果文件、停止文件监控、保存 Embedding 缓存 |· + --- ### 🚀 快速开始 @@ -188,7 +189,7 @@ graph LR #### 1. check_context — 上下文检查 -[ContextChecker](reme/memory/file_based/component/context_checker.py) 基于 Token 计数判断上下文是否超限,自动拆分为「待压缩」和「保留」两组消息。 +[ContextChecker](reme/memory/file_based/components/context_checker.py) 基于 Token 计数判断上下文是否超限,自动拆分为「待压缩」和「保留」两组消息。 ```mermaid graph LR @@ -208,7 +209,7 @@ graph LR #### 2. compact_memory — 对话压缩 -[Compactor](reme/memory/file_based/component/compactor.py) 使用 ReActAgent 将历史对话压缩为**结构化上下文摘要**。 +[Compactor](reme/memory/file_based/components/compactor.py) 使用 ReActAgent 将历史对话压缩为**结构化上下文摘要**。 ```mermaid graph LR @@ -235,7 +236,7 @@ graph LR #### 3. summary_memory — 记忆持久化 -[Summarizer](reme/memory/file_based/component/summarizer.py) 采用 **ReAct + 文件工具** 模式,让 AI 自主决定写什么、写到哪。 +[Summarizer](reme/memory/file_based/components/summarizer.py) 采用 **ReAct + 文件工具** 模式,让 AI 自主决定写什么、写到哪。 ```mermaid graph LR @@ -260,7 +261,7 @@ graph LR #### 4. compact_tool_result — 工具结果压缩 -[ToolResultCompactor](reme/memory/file_based/component/tool_result_compactor.py) 解决工具输出过长导致上下文膨胀的问题。 +[ToolResultCompactor](reme/memory/file_based/components/tool_result_compactor.py) 解决工具输出过长导致上下文膨胀的问题。 ```mermaid graph LR @@ -518,7 +519,6 @@ Pass@K 衡量在生成 K 个候选中,至少一个成功完成任务(score=1 关于如何复现实验的更多细节,见 [quickstart.md](benchmark/bfcl/quickstart.md) - ## ⭐ 社区与支持 - **Star 与 Watch**:Star 可让更多智能体开发者发现 ReMe;Watch 可助你第一时间获知新版本与特性。 diff --git a/reme/core/schema/as_msg_stat.py b/reme/core/schema/as_msg_stat.py index 2e861863..b4ef02d4 100644 --- a/reme/core/schema/as_msg_stat.py +++ b/reme/core/schema/as_msg_stat.py @@ -26,6 +26,12 @@ class AsBlockStat(BaseModel): """Return a short preview of the block content.""" return self.format(_DEFAULT_MAX_BLOCK_TEXT_PREVIEW_LENGTH) + def _truncate(self, text: str, max_length: int) -> str: + """Simple truncation with ellipsis.""" + if len(text) <= max_length: + return text + return text[:max_length] + "..." + # pylint: disable=too-many-return-statements def format(self, max_length: int = _DEFAULT_MAX_FORMATTER_TEXT_LENGTH, include_thinking: bool = True) -> str: """Format block content to string representation. @@ -37,22 +43,25 @@ class AsBlockStat(BaseModel): Returns: Formatted string representation of the block. """ - from ..utils import truncate_text - if self.block_type == "text": - return truncate_text(self.text, max_length) if self.text else "" + if not self.text: + return "" + return f"{self._truncate(self.text, max_length)}" if self.block_type == "thinking": - if include_thinking and self.text: - return f"\n{truncate_text(self.text, max_length)}\n" - return "" + if not include_thinking or not self.text: + return "" + return f"{self._truncate(self.text, max_length)}" if self.block_type in ("image", "audio", "video"): - return f"[{self.block_type}] {self.media_url}" if self.media_url else f"[{self.block_type}]" - if self.block_type in ("tool_use", "tool_result"): - if self.block_type == "tool_use": - return f" - tool_call={self.tool_name} params={truncate_text(self.tool_input, max_length)}" - else: - output = truncate_text(self.tool_output, max_length) - return f" - tool_result={self.tool_name} output={output}" if output else "" + content = self.media_url if self.media_url else "" + return f"<{self.block_type}>{content}" + if self.block_type == "tool_use": + content = f"{self.tool_name} params={self._truncate(self.tool_input, max_length)}" + return f"{content}" + if self.block_type == "tool_result": + if not self.tool_output: + return "" + content = f"{self.tool_name} output={self._truncate(self.tool_output, max_length)}" + return f"{content}" return "" diff --git a/reme/memory/file_based/__init__.py b/reme/memory/file_based/__init__.py index 766f9b3e..e4c392e5 100644 --- a/reme/memory/file_based/__init__.py +++ b/reme/memory/file_based/__init__.py @@ -1,29 +1,13 @@ -"""File-based 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: - - ReMeInMemoryMemory: Extended InMemoryMemory with bugfixes and summary support - - AsMsgHandler: Handles AgentScope message statistics, formatting, and context checking - - 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 - - ContextChecker: Checks context size and splits messages for compaction -""" - -from .as_msg_handler import AsMsgHandler -from .component.compactor import Compactor -from .component.context_checker import ContextChecker -from .component.summarizer import Summarizer -from .component.tool_result_compactor import ToolResultCompactor +from . import components +from . import tools +from . import utils from .reme_in_memory_memory import ReMeInMemoryMemory __all__ = [ - "AsMsgHandler", + "tools", + "utils", + "components", "ReMeInMemoryMemory", - "Summarizer", - "Compactor", - "ContextChecker", - "ToolResultCompactor", ] diff --git a/reme/memory/file_based/component/__init__.py b/reme/memory/file_based/component/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/reme/memory/file_based/components/__init__.py b/reme/memory/file_based/components/__init__.py new file mode 100644 index 00000000..42ab2f6b --- /dev/null +++ b/reme/memory/file_based/components/__init__.py @@ -0,0 +1,13 @@ +"""components""" + +from .compactor import Compactor +from .context_checker import ContextChecker +from .summarizer import Summarizer +from .tool_result_compactor import ToolResultCompactor + +__all__ = [ + "Compactor", + "Summarizer", + "ContextChecker", + "ToolResultCompactor", +] diff --git a/reme/memory/file_based/component/compactor.py b/reme/memory/file_based/components/compactor.py similarity index 89% rename from reme/memory/file_based/component/compactor.py rename to reme/memory/file_based/components/compactor.py index 3292c874..e7b37b30 100644 --- a/reme/memory/file_based/component/compactor.py +++ b/reme/memory/file_based/components/compactor.py @@ -4,7 +4,7 @@ from agentscope.agent import ReActAgent from agentscope.message import Msg from agentscope.token import HuggingFaceTokenCounter -from ..as_msg_handler import AsMsgHandler +from ..utils import AsMsgHandler from ....core.op import BaseOp from ....core.utils import get_std_logger @@ -32,10 +32,13 @@ class Compactor(BaseOp): if not messages: return "" + before_token_count = self.msg_handler.count_msgs_token(messages) history_formatted_str: str = self.msg_handler.format_msgs_to_str( messages=messages, memory_compact_threshold=self.memory_compact_threshold, ) + after_token_count = self.msg_handler.count_str_token(history_formatted_str) + logger.info(f"Compactor before_token_count={before_token_count} after_token_count={after_token_count}") if not history_formatted_str: logger.warning(f"No history to compact. messages={messages}") diff --git a/reme/memory/file_based/component/compactor.yaml b/reme/memory/file_based/components/compactor.yaml similarity index 100% rename from reme/memory/file_based/component/compactor.yaml rename to reme/memory/file_based/components/compactor.yaml diff --git a/reme/memory/file_based/component/context_checker.py b/reme/memory/file_based/components/context_checker.py similarity index 92% rename from reme/memory/file_based/component/context_checker.py rename to reme/memory/file_based/components/context_checker.py index 82bb381e..18ac4bf0 100644 --- a/reme/memory/file_based/component/context_checker.py +++ b/reme/memory/file_based/components/context_checker.py @@ -3,7 +3,7 @@ from agentscope.message import Msg from agentscope.token import HuggingFaceTokenCounter -from ..as_msg_handler import AsMsgHandler +from ..utils import AsMsgHandler from ....core.op import BaseOp from ....core.utils import get_std_logger @@ -87,11 +87,12 @@ class ContextChecker(BaseOp): memory_compact_reserve=self.memory_compact_reserve, ) - logger.info( - f"ContextChecker Result: " - f"to_compact={len(messages_to_compact)}, " - f"to_keep={len(messages_to_keep)}, " - f"is_valid={is_valid}", - ) + if messages_to_compact: + logger.info( + f"ContextChecker Result: " + f"to_compact={len(messages_to_compact)}, " + f"to_keep={len(messages_to_keep)}, " + f"is_valid={is_valid}", + ) return messages_to_compact, messages_to_keep, is_valid diff --git a/reme/memory/file_based/component/summarizer.py b/reme/memory/file_based/components/summarizer.py similarity index 88% rename from reme/memory/file_based/component/summarizer.py rename to reme/memory/file_based/components/summarizer.py index db3522da..d4e057be 100644 --- a/reme/memory/file_based/component/summarizer.py +++ b/reme/memory/file_based/components/summarizer.py @@ -7,7 +7,7 @@ from agentscope.message import Msg from agentscope.token import HuggingFaceTokenCounter from agentscope.tool import Toolkit -from ..as_msg_handler import AsMsgHandler +from ..utils import AsMsgHandler from ....core.op import BaseOp from ....core.utils import get_std_logger @@ -40,10 +40,13 @@ class Summarizer(BaseOp): if not messages: return "" + before_token_count = self.msg_handler.count_msgs_token(messages) history_formatted_str: str = self.msg_handler.format_msgs_to_str( messages=messages, memory_compact_threshold=self.memory_compact_threshold, ) + after_token_count = self.msg_handler.count_str_token(history_formatted_str) + logger.info(f"Summarizer before_token_count={before_token_count} after_token_count={after_token_count}") if not history_formatted_str: logger.warning(f"No history to summarize. messages={messages}") diff --git a/reme/memory/file_based/component/summarizer.yaml b/reme/memory/file_based/components/summarizer.yaml similarity index 100% rename from reme/memory/file_based/component/summarizer.yaml rename to reme/memory/file_based/components/summarizer.yaml diff --git a/reme/memory/file_based/component/tool_result_compactor.py b/reme/memory/file_based/components/tool_result_compactor.py similarity index 100% rename from reme/memory/file_based/component/tool_result_compactor.py rename to reme/memory/file_based/components/tool_result_compactor.py diff --git a/reme/memory/file_based/reme_in_memory_memory.py b/reme/memory/file_based/reme_in_memory_memory.py index 16f18726..da2118e8 100644 --- a/reme/memory/file_based/reme_in_memory_memory.py +++ b/reme/memory/file_based/reme_in_memory_memory.py @@ -5,7 +5,7 @@ from agentscope.memory import InMemoryMemory from agentscope.message import Msg from agentscope.token import HuggingFaceTokenCounter -from .as_msg_handler import AsMsgHandler +from .utils import AsMsgHandler from ...core.utils import get_std_logger logger = get_std_logger() diff --git a/reme/memory/file_based/utils/__init__.py b/reme/memory/file_based/utils/__init__.py new file mode 100644 index 00000000..6249ab15 --- /dev/null +++ b/reme/memory/file_based/utils/__init__.py @@ -0,0 +1,7 @@ +"""utils""" + +from .as_msg_handler import AsMsgHandler + +__all__ = [ + "AsMsgHandler", +] diff --git a/reme/memory/file_based/as_msg_handler.py b/reme/memory/file_based/utils/as_msg_handler.py similarity index 92% rename from reme/memory/file_based/as_msg_handler.py rename to reme/memory/file_based/utils/as_msg_handler.py index 9db6cac2..30facedc 100644 --- a/reme/memory/file_based/as_msg_handler.py +++ b/reme/memory/file_based/utils/as_msg_handler.py @@ -5,8 +5,8 @@ import json from agentscope.message import Msg from agentscope.token import HuggingFaceTokenCounter -from ...core.schema import AsMsgStat, AsBlockStat -from ...core.utils import get_std_logger +from ....core.schema import AsMsgStat, AsBlockStat +from ....core.utils import get_std_logger logger = get_std_logger() @@ -111,6 +111,19 @@ class AsMsgHandler: metadata=message.metadata or {}, ) + if not isinstance(message.content, list): + logger.warning( + "Unexpected message.content type %s, expected str or list, returning empty stat.", + type(message.content), + ) + return AsMsgStat( + name=message.name or message.role, + role=message.role, + content=blocks, + timestamp=message.timestamp or "", + metadata=message.metadata or {}, + ) + for block in message.content: block_type = block.get("type", "unknown") @@ -155,7 +168,7 @@ class AsMsgHandler: elif block_type == "tool_use": tool_name = block.get("name", "") - tool_input = block.get("raw_input", "") + tool_input = block.get("input", "") try: input_str = json.dumps(tool_input, ensure_ascii=False) except (TypeError, ValueError): @@ -227,7 +240,8 @@ class AsMsgHandler: formatted_content = stat.format(include_thinking=include_thinking) content_token_count = self.count_str_token(formatted_content) - if total_token_count + content_token_count > memory_compact_threshold: + is_latest = i == len(messages) - 1 + if not is_latest and total_token_count + content_token_count > memory_compact_threshold: logger.info( "Skipping older messages: adding %d tokens would exceed threshold %d (current: %d)", content_token_count, @@ -236,6 +250,13 @@ class AsMsgHandler: ) break + if is_latest and content_token_count > memory_compact_threshold: + logger.warning( + "Latest message alone (%d tokens) exceeds threshold %d, including it anyway.", + content_token_count, + memory_compact_threshold, + ) + formatted_parts.append(formatted_content) total_token_count += content_token_count @@ -324,6 +345,10 @@ class AsMsgHandler: accumulated_tokens = 0 for i in range(len(msg_stats) - 1, -1, -1): + # Skip messages already added as tool_use dependencies to avoid double-counting tokens + if i in keep_indices: + continue + msg, stat = msg_stats[i] # Check if adding this message would exceed reserve limit diff --git a/reme/reme_light.py b/reme/reme_light.py index 922b846b..94309eb0 100644 --- a/reme/reme_light.py +++ b/reme/reme_light.py @@ -26,16 +26,15 @@ from agentscope.tool import Toolkit, ToolResponse from .config import ReMeConfigParser from .core import Application from .core.utils import get_hf_token_counter, get_std_logger -from .memory.file_based import ( +from .memory.file_based import ReMeInMemoryMemory +from .memory.file_based.components import ( Compactor, ContextChecker, Summarizer, ToolResultCompactor, - ReMeInMemoryMemory, - AsMsgHandler, ) -from .memory.file_based import MemorySearch -from .memory.file_based.tools import FileIO +from .memory.file_based.tools import FileIO, MemorySearch +from .memory.file_based.utils import AsMsgHandler logger = get_std_logger() @@ -487,7 +486,7 @@ class ReMeLight(Application): - compact_ratio: Compaction threshold ratio Note: - - Completed/failed/cancelled tasks are cleaned up before adding new ones + - Completed/failed/canceled tasks are cleaned up before adding new ones - Task results and errors are logged automatically - Use await_summary_tasks() to wait for all pending tasks to complete """ @@ -561,7 +560,7 @@ class ReMeLight(Application): Returns: tuple[list[Msg], str]: A tuple containing: - - list[Msg]: Messages to keep in context (may be reduced) + - list[Msg]: Messages to keep in context (maybe reduced) - str: Updated compressed summary incorporating compacted messages Note: @@ -584,10 +583,11 @@ class ReMeLight(Application): compact_msgs = messages[:-tool_result_compact_keep_n] await self.compact_tool_result(compact_msgs) - messages_to_compact, messages_to_keep, is_valid = msg_handler.context_check( + messages_to_compact, messages_to_keep, is_valid = await self.check_context( messages=messages, memory_compact_threshold=left_compact_threshold, memory_compact_reserve=memory_compact_reserve, + token_counter=token_counter, ) if not messages_to_compact: @@ -626,7 +626,7 @@ class ReMeLight(Application): Wait for all background summary tasks to complete and collect results. Blocks until all pending summary tasks in the task list have completed, - cancelled, or failed. Collects status information from each task and + canceled, or failed. Collects status information from each task and clears the task list after processing. Returns: diff --git a/tests/light/test_compactor.py b/tests/light/test_compactor.py index 8ae2a051..cb3c9dee 100644 --- a/tests/light/test_compactor.py +++ b/tests/light/test_compactor.py @@ -3,7 +3,6 @@ import asyncio from agentscope.message import Msg - from test_utils import ( get_dash_chat_model, get_formatter, @@ -11,8 +10,7 @@ from test_utils import ( ) from reme.core.utils import get_std_logger -from reme.memory.file_based import Compactor - +from reme.memory.file_based.components import Compactor logger = get_std_logger() diff --git a/tests/light/test_context_check.py b/tests/light/test_context_check.py index 872d6e2d..934a43f2 100644 --- a/tests/light/test_context_check.py +++ b/tests/light/test_context_check.py @@ -1,10 +1,10 @@ """Tests for AsMsgHandler.context_check method.""" from agentscope.message import Msg - from test_utils import get_token_counter + from reme.core.utils import get_std_logger -from reme.memory.file_based.as_msg_handler import AsMsgHandler +from reme.memory.file_based.utils import AsMsgHandler logger = get_std_logger() diff --git a/tests/light/test_format_msgs_to_str.py b/tests/light/test_format_msgs_to_str.py index 93dd7a2f..64e97330 100644 --- a/tests/light/test_format_msgs_to_str.py +++ b/tests/light/test_format_msgs_to_str.py @@ -5,10 +5,10 @@ import sys from agentscope.message import Msg - from test_utils import get_token_counter + from reme.core.utils import get_std_logger -from reme.memory.file_based.as_msg_handler import AsMsgHandler +from reme.memory.file_based.utils import AsMsgHandler logger = get_std_logger() diff --git a/tests/light/test_summarizer.py b/tests/light/test_summarizer.py index 9718cd2a..2789bca5 100644 --- a/tests/light/test_summarizer.py +++ b/tests/light/test_summarizer.py @@ -7,16 +7,16 @@ from pathlib import Path from agentscope.message import Msg from agentscope.tool import Toolkit - from test_utils import ( get_dash_chat_model, get_formatter, get_token_counter, ) from reme.core.utils import get_std_logger -from reme.memory.file_based import Summarizer +from reme.memory.file_based.components import Summarizer from reme.memory.file_based.tools import FileIO + logger = get_std_logger() diff --git a/tests/light/test_tool_result_compactor.py b/tests/light/test_tool_result_compactor.py index b6cb7c69..1697911d 100644 --- a/tests/light/test_tool_result_compactor.py +++ b/tests/light/test_tool_result_compactor.py @@ -6,8 +6,9 @@ from datetime import datetime, timedelta from pathlib import Path from agentscope.message import Msg -from reme.memory.file_based import ToolResultCompactor + from reme.core.utils import is_truncated +from reme.memory.file_based.components import ToolResultCompactor def create_tool_result_msg(output: str | list, tool_name: str = "test_tool") -> Msg: diff --git a/tests/light/test_tools.py b/tests/light/test_tools.py index 933d506d..891fa534 100644 --- a/tests/light/test_tools.py +++ b/tests/light/test_tools.py @@ -9,8 +9,8 @@ import tempfile import pytest -from reme.memory.file_based.tools.shell import Shell from reme.memory.file_based.tools.file_io import FileIO +from reme.memory.file_based.tools.shell import Shell from reme.memory.file_based.tools.utils import DEFAULT_MAX_LINES, DEFAULT_MAX_BYTES diff --git a/tests/light/test_utils.py b/tests/light/test_utils.py index 19740fe6..27583948 100644 --- a/tests/light/test_utils.py +++ b/tests/light/test_utils.py @@ -4,7 +4,7 @@ import os from agentscope.message import Msg, ThinkingBlock, TextBlock, ToolUseBlock, ToolResultBlock -from reme.memory.file_based import AsMsgHandler +from reme.memory.file_based.utils import AsMsgHandler def get_token_counter():