ReMe/reme/memory/file_based/components/compactor.py
jinliyl 7b02c45218
style(memory): update message formatting and improve logging (#175)
* style(memory): update message formatting and improve logging

- Change default include_thinking parameter to True in as_msg_handler.py
- Replace angle brackets with square brackets for block formatting in as_msg_stat.py
- Add newline replacement in text truncation method in as_msg_stat.py
- Add loading duration timing to embedding cache loading in base_embedding_model.py
- Replace XML-style tags with markdown headers in compactor.py conversation format
- Update compactor.yaml prompts to reference markdown-style headers instead of XML tags
- Modify summarizer.py to use markdown-style conversation header format

* refactor(file-watcher): replace scan_on_start with rebuild_index_on_start parameter

- Replace scan_on_start and clear_on_start boolean parameters with single rebuild_index_on_start
- Update BaseFileWatcher constructor to use rebuild_index_on_start instead of two separate flags
- Modify initialization logic to clear and rescan when rebuild_index_on_start is True
- Remove scan_on_start parameter from CLI and light configuration files
- Update documentation to remove scan_on_start from quick start guides
- Rename all test methods and classes from scan_on_start to rebuild_index_on_start
- Add timezone-aware datetime helper method to summarizer component
- Format log message with proper line breaks for readability

* fix(core): resolve file watcher initialization issue and update version

- Fixed file watcher task creation to properly handle rebuild index on start logic
- Moved initialization and watch loop into async function to ensure proper execution order
- Updated package version from 0.3.1.1 to 0.3.1.2
- Added missing comma in embedding model logging statement

* fix(core): reduce max formatter text length limit

- Changed _DEFAULT_MAX_FORMATTER_TEXT_LENGTH from 2000 to 1000
- Updated constant value in as_msg_stat.py schema module

* fix(file-watcher): change default rebuild index behavior on start

- Changed rebuild_index_on_start parameter default from False to True
- This ensures index is rebuilt by default when file watcher starts
- Maintains consistent state initialization for file watching operations

* feat(compactor): add return_dict option and improve summary validation

- Add _is_valid_summary function to validate summary content format
- Introduce return_dict parameter to return structured results with validation
- Update prompt templates with clearer task descriptions and formatting rules
- Refactor update_user_message prompts to combine prefix and suffix logic
- Return dictionary with user_message, history_compact, and is_valid fields when enabled
- Add proper error handling for exception cases in memory compaction
- Maintain backward compatibility with string return when return_dict=False

* feat(memory): add thinking block configuration option

- Add add_thinking_block parameter to compactor component
- Pass include_thinking flag to message formatting in compactor
- Add add_thinking_block parameter to reme_light compact function
- Add add_thinking_block parameter to reme_light summarize function
- Add add_thinking_block parameter to summarizer component
- Pass include_thinking flag to message formatting in summarizer
- Remove previous-summary tags from compressed summary format
2026-03-24 00:20:15 +08:00

110 lines
3.8 KiB
Python

"""Compactor module for memory compaction operations."""
from agentscope.agent import ReActAgent
from agentscope.message import Msg
from ..utils import AsMsgHandler
from ....core.op import BaseOp
from ....core.utils import get_logger
logger = get_logger()
def _is_valid_summary(content: str) -> bool:
"""Check if the summary content is valid.
Args:
content: The summary content to validate.
Returns:
True if valid, False otherwise.
"""
if not content or not content.strip():
return False
if "##" not in content:
return False
return True
class Compactor(BaseOp):
"""Compactor class for compacting memory messages."""
def __init__(
self,
memory_compact_threshold: int,
console_enabled: bool = False,
return_dict: bool = False,
add_thinking_block: bool = True,
**kwargs,
):
super().__init__(**kwargs)
self.memory_compact_threshold: int = memory_compact_threshold
self.console_enabled: bool = console_enabled
self.return_dict: bool = return_dict
self.add_thinking_block: bool = add_thinking_block
# pylint: disable=too-many-return-statements
async def execute(self):
messages: list[Msg] = self.context.get("messages", [])
previous_summary: str = self.context.get("previous_summary", "")
if not messages:
if self.return_dict:
return {"user_message": "", "history_compact": "", "is_valid": False}
return ""
msg_handler = AsMsgHandler(self.as_token_counter)
before_token_count = await msg_handler.count_msgs_token(messages)
history_formatted_str: str = await msg_handler.format_msgs_to_str(
messages=messages,
memory_compact_threshold=self.memory_compact_threshold,
include_thinking=self.add_thinking_block,
)
after_token_count = await 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}")
if self.return_dict:
return {"user_message": "", "history_compact": "", "is_valid": False}
return ""
agent = ReActAgent(
name="reme_compactor",
model=self.as_llm,
sys_prompt=self.get_prompt("system_prompt"),
formatter=self.as_llm_formatter,
)
agent.set_console_output_enabled(self.console_enabled)
if previous_summary:
user_message: str = (
f"# conversation\n{history_formatted_str}\n\n"
f"# previous-summary\n{previous_summary}\n\n" + self.get_prompt("update_user_message")
)
else:
user_message: str = f"# conversation\n{history_formatted_str}\n\n" + self.get_prompt("initial_user_message")
logger.info(f"Compactor sys_prompt={agent.sys_prompt} user_message={user_message}")
compact_msg: Msg = await agent.reply(
Msg(
name="reme",
role="user",
content=user_message,
),
)
history_compact: str = compact_msg.get_text_content()
is_valid: bool = _is_valid_summary(history_compact)
if not is_valid:
logger.warning(f"Invalid summary result: {history_compact[:200]}...")
if self.return_dict:
return {"user_message": user_message, "history_compact": history_compact, "is_valid": False}
return ""
logger.info(f"Compactor Result:\n{history_compact}")
if self.return_dict:
return {"user_message": user_message, "history_compact": history_compact, "is_valid": True}
return history_compact