From 416206d3328b3c7100aad995aa2a9fae7e0788ef Mon Sep 17 00:00:00 2001 From: "jinli.yl" Date: Wed, 7 Jan 2026 18:07:49 +0800 Subject: [PATCH] feat(mem_agent): implement memory agent architecture with specialized summarizers and retrievers --- reme_ai/core/op/base_op.py | 12 +- reme_ai/core/vector_store/es_vector_store.py | 2 +- reme_ai/mem_agent/__init__.py | 8 +- reme_ai/mem_agent/base_memory_agent.py | 48 +++--- reme_ai/mem_agent/retriever/__init__.py | 9 ++ reme_ai/mem_agent/retriever/reme_retriever.py | 42 +++++ .../mem_agent/retriever/reme_retriever.yaml | 50 ++++++ reme_ai/mem_agent/retriever/remy_agent.py | 51 ++++++ reme_ai/mem_agent/retriever/remy_agent.yaml | 36 +++++ reme_ai/mem_agent/simple_chat.py | 2 +- reme_ai/mem_agent/stream_chat.py | 2 +- reme_ai/mem_agent/summarizer/__init__.py | 15 ++ .../summarizer/identity_summarizer.py | 33 ++++ .../summarizer/identity_summarizer.yaml | 28 ++++ .../summarizer/personal_summarizer.py | 41 +++++ .../summarizer/personal_summarizer.yaml | 54 +++++++ .../summarizer/procedural_summarizer.py | 42 +++++ .../summarizer/procedural_summarizer.yaml | 70 ++++++++ .../mem_agent/summarizer/reme_summarizer.py | 105 ++++++++++++ .../mem_agent/summarizer/reme_summarizer.yaml | 52 ++++++ .../mem_agent/summarizer/tool_summarizer.py | 42 +++++ .../mem_agent/summarizer/tool_summarizer.yaml | 55 +++++++ reme_ai/mem_tool/hands_off_tool.py | 149 ++++++++++++++++++ reme_ai/mem_tool/hands_off_tool.yaml | 19 +++ .../mem_tool/history/add_history_memory.py | 1 - reme_ai/mem_tool/vector/add_memory.py | 37 ++++- reme_ai/mem_tool/vector/add_memory.yaml | 7 - reme_ai/mem_tool/vector/add_summary_memory.py | 20 ++- .../mem_tool/vector/add_summary_memory.yaml | 8 - reme_ai/mem_tool/vector/update_memory.py | 27 +++- reme_ai/mem_tool/vector/update_memory.yaml | 12 -- .../mem_tool/vector/vector_retrieve_memory.py | 38 +++++ .../vector/vector_retrieve_memory.yaml | 1 + 33 files changed, 1039 insertions(+), 79 deletions(-) create mode 100644 reme_ai/mem_agent/retriever/__init__.py create mode 100644 reme_ai/mem_agent/retriever/reme_retriever.py create mode 100644 reme_ai/mem_agent/retriever/reme_retriever.yaml create mode 100644 reme_ai/mem_agent/retriever/remy_agent.py create mode 100644 reme_ai/mem_agent/retriever/remy_agent.yaml create mode 100644 reme_ai/mem_agent/summarizer/__init__.py create mode 100644 reme_ai/mem_agent/summarizer/identity_summarizer.py create mode 100644 reme_ai/mem_agent/summarizer/identity_summarizer.yaml create mode 100644 reme_ai/mem_agent/summarizer/personal_summarizer.py create mode 100644 reme_ai/mem_agent/summarizer/personal_summarizer.yaml create mode 100644 reme_ai/mem_agent/summarizer/procedural_summarizer.py create mode 100644 reme_ai/mem_agent/summarizer/procedural_summarizer.yaml create mode 100644 reme_ai/mem_agent/summarizer/reme_summarizer.py create mode 100644 reme_ai/mem_agent/summarizer/reme_summarizer.yaml create mode 100644 reme_ai/mem_agent/summarizer/tool_summarizer.py create mode 100644 reme_ai/mem_agent/summarizer/tool_summarizer.yaml create mode 100644 reme_ai/mem_tool/hands_off_tool.py create mode 100644 reme_ai/mem_tool/hands_off_tool.yaml diff --git a/reme_ai/core/op/base_op.py b/reme_ai/core/op/base_op.py index 5cb5a0a2..4f96ec32 100644 --- a/reme_ai/core/op/base_op.py +++ b/reme_ai/core/op/base_op.py @@ -4,7 +4,7 @@ import asyncio import copy import inspect from pathlib import Path -from typing import Callable, Any, Optional +from typing import Callable, Optional from loguru import logger from tqdm import tqdm @@ -136,7 +136,7 @@ class BaseOp: return {k: self.context[k] for k in parameters.properties.keys() if (k in required_keys or k in self.context)} @property - def output(self) -> Any: + def output(self): """Get the single output value from context.""" output_properties = self.tool_call.output.properties if not output_properties: @@ -149,7 +149,7 @@ class BaseOp: return None @output.setter - def output(self, value: Any): + def output(self, value): """Set the single output value into context.""" output_properties = self.tool_call.output.properties if not output_properties: @@ -322,15 +322,21 @@ class BaseOp: for name, op in sub_ops.items(): assert self.async_mode == op.async_mode, "Async mode mismatch!" op.name = name + if self.language: + op.language = self.language self.sub_ops.append(op) elif isinstance(sub_ops, list): for op in sub_ops: assert self.async_mode == op.async_mode, "Async mode mismatch!" + if self.language: + op.language = self.language self.sub_ops.append(op) else: assert self.async_mode == sub_ops.async_mode, "Async mode mismatch!" + if self.language: + sub_ops.language = self.language self.sub_ops.append(sub_ops) def add_sub_op(self, sub_op: "BaseOp"): diff --git a/reme_ai/core/vector_store/es_vector_store.py b/reme_ai/core/vector_store/es_vector_store.py index 06dcafa5..d68f283e 100644 --- a/reme_ai/core/vector_store/es_vector_store.py +++ b/reme_ai/core/vector_store/es_vector_store.py @@ -341,7 +341,7 @@ class ESVectorStore(BaseVectorStore): actions = [] for node in nodes_to_update: - doc = { + doc: dict = { "vector_id": node.vector_id, "content": node.content, "metadata": node.metadata, diff --git a/reme_ai/mem_agent/__init__.py b/reme_ai/mem_agent/__init__.py index 5ecdd48b..69495a12 100644 --- a/reme_ai/mem_agent/__init__.py +++ b/reme_ai/mem_agent/__init__.py @@ -1,9 +1,15 @@ -"""Agent module providing chat operations.""" +"""memory agent""" +from . import retriever +from . import summarizer +from .base_memory_agent import BaseMemoryAgent from .simple_chat import SimpleChat from .stream_chat import StreamChat __all__ = [ + "retriever", + "summarizer", + "BaseMemoryAgent", "StreamChat", "SimpleChat", ] diff --git a/reme_ai/mem_agent/base_memory_agent.py b/reme_ai/mem_agent/base_memory_agent.py index e4466fc3..7b6bdba0 100644 --- a/reme_ai/mem_agent/base_memory_agent.py +++ b/reme_ai/mem_agent/base_memory_agent.py @@ -18,19 +18,25 @@ class BaseMemoryAgent(BaseOp, metaclass=ABCMeta): def __init__( self, - max_steps: int = 20, - tool_call_interval: float = 0, + tools: list[BaseMemoryTool], add_think_tool: bool = False, # only for instruct model - tools: list[BaseMemoryTool] | None = None, + force_tool_language: bool = True, + tool_call_interval: float = 0, + max_steps: int = 20, **kwargs, ): super().__init__(**kwargs) - self.max_steps: int = max_steps + self.tools: list[BaseMemoryTool] = tools or [] + if add_think_tool: + self.tools.append(ThinkTool()) + if force_tool_language and self.language: + for tool in self.tools: + tool.language = self.language self.tool_call_interval: float = tool_call_interval - self.add_think_tool: bool = add_think_tool - assert not self.sub_ops, "sub_ops must be empty, use `tools`~" - if tools: - self.sub_ops.extend([t.set_language(self.language) for t in tools]) + self.max_steps: int = max_steps + + self.messages: list[Message] = [] + self.success: bool = True def _build_tool_call(self) -> ToolCall: return ToolCall( @@ -66,19 +72,6 @@ class BaseMemoryAgent(BaseOp, metaclass=ABCMeta): }, ) - @property - def tools(self) -> list[BaseMemoryTool]: - """Returns the list of memory tools available to the agent.""" - tools: list[BaseMemoryTool] = [o for o in self.sub_ops if isinstance(o, BaseMemoryTool)] - if self.add_think_tool: - tools.append(ThinkTool(language=self.language)) - return tools - - @tools.setter - def tools(self, tools: list[BaseMemoryTool] | BaseMemoryTool): - """Sets the memory tools for the agent.""" - self.sub_ops = tools - def get_messages(self) -> list[Message]: """Extracts and returns messages from the context query or messages.""" if self.context.get("query"): @@ -141,26 +134,29 @@ class BaseMemoryAgent(BaseOp, metaclass=ABCMeta): async def react(self, messages: list[Message]): """Performs reasoning and acting steps until completion or max steps reached.""" + success: bool = False for step in range(self.max_steps): assistant_message, should_act = await self._reasoning_step(messages, step) if not should_act: + success = True break tool_result_messages = await self._acting_step(assistant_message, step) messages.extend(tool_result_messages) - return messages + return messages, success async def execute(self): messages = await self.build_messages() for i, message in enumerate(messages): logger.info(f"step0.{i} {message.role} {message.name or ''} {message.simple_dump()}") - messages = await self.react(messages) - self.output = [ - m.simple_dump(add_name=True, add_reasoning=True, add_time_created=True, add_metadata=True) for m in messages - ] + self.messages, self.success = await self.react(messages) + if self.success and self.messages: + self.output = self.messages[-1].content + else: + self.output = "" @property def memory_target(self) -> str: diff --git a/reme_ai/mem_agent/retriever/__init__.py b/reme_ai/mem_agent/retriever/__init__.py new file mode 100644 index 00000000..83f1266f --- /dev/null +++ b/reme_ai/mem_agent/retriever/__init__.py @@ -0,0 +1,9 @@ +"""memory retriever""" + +from .reme_retriever import ReMeRetriever +from .remy_agent import ReMyAgent + +__all__ = [ + "ReMeRetriever", + "ReMyAgent", +] diff --git a/reme_ai/mem_agent/retriever/reme_retriever.py b/reme_ai/mem_agent/retriever/reme_retriever.py new file mode 100644 index 00000000..4a7a3fa9 --- /dev/null +++ b/reme_ai/mem_agent/retriever/reme_retriever.py @@ -0,0 +1,42 @@ +"""ReMe retriever that builds messages with meta memories.""" + +from typing import List + +from ..base_memory_agent import BaseMemoryAgent +from ...core.context import C +from ...core.enumeration import Role +from ...core.schema import Message +from ...core.utils import get_now_time, format_messages + + +@C.register_op() +class ReMeRetriever(BaseMemoryAgent): + """Memory agent that retrieves and builds messages with meta memory context.""" + + def __init__(self, enable_tool_memory: bool = True, **kwargs): + """Initialize retriever with tool memory option.""" + super().__init__(**kwargs) + self.enable_tool_memory = enable_tool_memory + + async def _read_meta_memories(self) -> str: + """Read and return meta memories as string.""" + from ...mem_tool import ReadMetaMemory + + op = ReadMetaMemory(enable_tool_memory=self.enable_tool_memory, enable_identity_memory=False) + await op.call() + return str(op.output) + + async def build_messages(self) -> List[Message]: + """Build messages with system prompt and user message.""" + system_prompt = self.prompt_format( + prompt_name="system_prompt", + now_time=get_now_time(), + meta_memory_info=await self._read_meta_memories(), + context=format_messages(self.get_messages()), + ) + + messages = [ + Message(role=Role.SYSTEM, content=system_prompt), + Message(role=Role.USER, content=self.get_prompt("user_message")), + ] + return messages diff --git a/reme_ai/mem_agent/retriever/reme_retriever.yaml b/reme_ai/mem_agent/retriever/reme_retriever.yaml new file mode 100644 index 00000000..c818edd3 --- /dev/null +++ b/reme_ai/mem_agent/retriever/reme_retriever.yaml @@ -0,0 +1,50 @@ +tool: | + Retrieve relevant memories from the memory bank to assist in answering questions. + Use this tool when you need to search for historical information, user preferences, + procedural knowledge, or any other stored memories that may help answer the current query. + The agent will analyze the context, determine what information is needed, and perform + semantic searches across different memory types to find the most relevant memories. + +system_prompt: | + You are a memory agent. Please analyze the context, retrieve relevant information from the memory bank when needed, and return a summary of the retrieved memories to assist in answering the user's question. + + ## Context + {context} + + ## Current Time + {now_time} + + ## Available Meta Memories + Format: "- (): " + {meta_memory_info} + + ## Your Tasks + + 1. **Analyze** the context to determine whether retrieval is necessary: + - If the question can be directly answered using the existing context, output `` and stop. + - If additional information is required, proceed to retrieval. + - Consider which types of meta memory from the "Available Meta Memories" list are most relevant. + + 2. **Retrieve** relevant memories using `vector_retrieve_memory`: + - Select the appropriate `memory_type` and `memory_target` from the "Available Meta Memories" list. + - Clearly define the needed information and construct suitable queries. + - Design queries flexibly based on actual needs: + * Generate different queries for different `memory_type`/`memory_target` combinations. + * For the same combination, create multiple queries using different phrasings or perspectives. + * Choose the optimal combination strategy based on the retrieval scenario. + - **Important**: When retrieving tool-related memories (`memory_type` is "tool"), the query must use the tool’s exact name (not a description or paraphrase of the problem). + - If retrieval results include a `ref_memory_id` and more details are needed—or if vector retrieval proves insufficient—use `read_history_memory` with the `ref_memory_id` as the `memory_id` parameter. + + 3. **Iterate if necessary**: + - If the initial retrieval fails, try alternative phrasings or perspectives. + - If multiple memory types exist, attempt retrievals across different types. + - Before concluding that no relevant memory exists, perform at least 2–3 retrieval attempts using varied phrasings or viewpoints. + - If repeated vector retrievals still fail to yield sufficient information, use `read_history_memory` to fetch the original message content. + + 4. **Output** the result: + - If no retrieval is needed, output ``. + - If relevant memories are found, clearly summarize the retrieved information. + - If multiple attempts still yield no relevant memory, output ``. + +user_message: | + Please analyze the context, retrieve relevant information from the memory bank when needed, and return a summary of the retrieved memories to assist in answering the user's question. diff --git a/reme_ai/mem_agent/retriever/remy_agent.py b/reme_ai/mem_agent/retriever/remy_agent.py new file mode 100644 index 00000000..1eaa9ac7 --- /dev/null +++ b/reme_ai/mem_agent/retriever/remy_agent.py @@ -0,0 +1,51 @@ +"""ReMy agent with identity and meta memory capabilities.""" + +from typing import List + +from ..base_memory_agent import BaseMemoryAgent +from ...core.context import C +from ...core.enumeration import Role +from ...core.schema import Message +from ...core.utils import get_now_time + + +@C.register_op() +class ReMyAgent(BaseMemoryAgent): + """Memory agent with identity awareness and meta memory retrieval.""" + + def __init__(self, enable_tool_memory: bool = True, enable_identity_memory: bool = True, **kwargs): + """Initialize ReMy agent with memory options.""" + super().__init__(**kwargs) + self.enable_tool_memory = enable_tool_memory + self.enable_identity_memory = enable_identity_memory + + @staticmethod + async def _read_identity_memory() -> str: + """Read and return identity memory as string.""" + from ...mem_tool import ReadIdentityMemory + + op = ReadIdentityMemory() + await op.call() + return str(op.output) + + async def _read_meta_memories(self) -> str: + """Read and return meta memories as string.""" + from ...mem_tool import ReadMetaMemory + + op = ReadMetaMemory( + enable_tool_memory=self.enable_tool_memory, + enable_identity_memory=self.enable_identity_memory, + ) + await op.call() + return str(op.output) + + async def build_messages(self) -> List[Message]: + """Build messages with system prompt and user messages.""" + system_prompt = self.prompt_format( + prompt_name="system_prompt", + now_time=get_now_time(), + identity_memory=await self._read_identity_memory(), + meta_memory_info=await self._read_meta_memories(), + ) + + return [Message(role=Role.SYSTEM, content=system_prompt)] + self.get_messages() diff --git a/reme_ai/mem_agent/retriever/remy_agent.yaml b/reme_ai/mem_agent/retriever/remy_agent.yaml new file mode 100644 index 00000000..5498b3cc --- /dev/null +++ b/reme_ai/mem_agent/retriever/remy_agent.yaml @@ -0,0 +1,36 @@ +tool: | + Conversational AI assistant with integrated memory capabilities. + Use this tool to engage in natural conversations with users while leveraging + stored identity and memory context. The agent can access historical information, + user preferences, and procedural knowledge through its memory system, and can + use various tools to accomplish tasks and answer questions. + +system_prompt: | + You are ReMy, an intelligent AI assistant with memory capabilities. + + ## Current Time + {now_time} + + ## Self-Awareness + {identity_memory} + + ## Available Meta Memories + Format: "- (): " + {meta_memory_info} + + ## Guiding Principles + 1. **Be Helpful and Accurate**: Provide clear and correct information. + 2. **Use Memory Wisely**: Retrieve relevant memories when they can improve your response. + 3. **Use Tools Appropriately**: Select the right tool for each task. + 4. **Stay Conversational**: Maintain a natural and friendly tone. + 5. **Seek Clarification**: Ask questions if the user’s intent is unclear. + 6. **Acknowledge Limitations**: Be honest about what you can and cannot do. + + ## How to Use the Memory Retrieval Tool + When using `vector_retrieve_memory` to search memories: + - Choose an appropriate `memory_type` and `memory_target` from the "Available Meta Memories" list above. + - Formulate a clear and specific query based on the information you need. + - **Important**: When retrieving tool-related memories (`memory_type` is "tool"), the query must use the tool’s exact name (not a description or a question). + - If retrieval results include a `ref_memory_id` and you need more details, use `read_history_memory` with the `ref_memory_id` as the `memory_id` parameter. + - If the initial retrieval yields no results, try rephrasing your query or using a different memory type. + - You may generate multiple queries with different phrasings or perspectives for the same memory type/target. diff --git a/reme_ai/mem_agent/simple_chat.py b/reme_ai/mem_agent/simple_chat.py index 0ba33c97..fc97b7a4 100644 --- a/reme_ai/mem_agent/simple_chat.py +++ b/reme_ai/mem_agent/simple_chat.py @@ -1,4 +1,4 @@ -"""Simple chat agent for non-streaming conversations.""" +"""Simple chat for test.""" from loguru import logger diff --git a/reme_ai/mem_agent/stream_chat.py b/reme_ai/mem_agent/stream_chat.py index 95ee6bb5..bb410cf6 100644 --- a/reme_ai/mem_agent/stream_chat.py +++ b/reme_ai/mem_agent/stream_chat.py @@ -1,4 +1,4 @@ -"""Streaming chat agent for real-time conversation streaming.""" +"""Streaming chat for test.""" from loguru import logger diff --git a/reme_ai/mem_agent/summarizer/__init__.py b/reme_ai/mem_agent/summarizer/__init__.py new file mode 100644 index 00000000..a8f17536 --- /dev/null +++ b/reme_ai/mem_agent/summarizer/__init__.py @@ -0,0 +1,15 @@ +"""memory summarizer""" + +from .identity_summarizer import IdentitySummarizer +from .personal_summarizer import PersonalSummarizer +from .procedural_summarizer import ProceduralSummarizer +from .reme_summarizer import ReMeSummarizer +from .tool_summarizer import ToolSummarizer + +__all__ = [ + "IdentitySummarizer", + "PersonalSummarizer", + "ProceduralSummarizer", + "ReMeSummarizer", + "ToolSummarizer", +] diff --git a/reme_ai/mem_agent/summarizer/identity_summarizer.py b/reme_ai/mem_agent/summarizer/identity_summarizer.py new file mode 100644 index 00000000..be571cdc --- /dev/null +++ b/reme_ai/mem_agent/summarizer/identity_summarizer.py @@ -0,0 +1,33 @@ +"""Specialized agent for extracting and updating agent self-cognition memories.""" + +from ..base_memory_agent import BaseMemoryAgent +from ...core.context import C +from ...core.enumeration import Role, MemoryType +from ...core.schema import Message +from ...core.utils import get_now_time, format_messages + + +@C.register_op() +class IdentitySummarizer(BaseMemoryAgent): + """Analyzes conversations to extract and update agent's self-perception.""" + + memory_type: MemoryType = MemoryType.IDENTITY + + async def build_messages(self) -> list[Message]: + """Construct system and user messages with formatted context and timestamp.""" + system_prompt = self.prompt_format( + prompt_name="system_prompt", + now_time=get_now_time(), + context=format_messages(self.get_messages()), + memory_type=self.memory_type.value, + ) + + messages = [ + Message(role=Role.SYSTEM, content=system_prompt), + Message(role=Role.USER, content=self.get_prompt("user_message")), + ] + return messages + + async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]: + """Execute tool calls with workspace_id and author context.""" + return await super()._acting_step(assistant_message, step, author=self.author, **kwargs) diff --git a/reme_ai/mem_agent/summarizer/identity_summarizer.yaml b/reme_ai/mem_agent/summarizer/identity_summarizer.yaml new file mode 100644 index 00000000..b19f8f48 --- /dev/null +++ b/reme_ai/mem_agent/summarizer/identity_summarizer.yaml @@ -0,0 +1,28 @@ +tool: | + Update agent self-cognition based on conversation context. + First read existing self-cognition using `read_identity_memory`, then analyze the context to determine if updates are needed, and use `update_identity_memory` to update when necessary. + +system_prompt: | + You are a specialized memory agent in the domain of self-awareness. Your task is to update the main agent's self-perception based on the provided context. + + ## Context: + {context} + + ## Current Time: + {now_time} + + ## Your Responsibilities: + + 1. **Read the main agent's current self-perception**: Retrieve it using `read_identity_memory`. + + 2. **Analyze the context** to determine whether an update to self-perception is needed: + - Extract any self-perception–related information from the dialogue (e.g., self-awareness, personality traits, current state, etc.). + - If no relevant self-perception information is found, output `` and halt further processing. + + 3. **Update if necessary**: Use `update_identity_memory` to perform the update: + - Compare the extracted information with the existing self-perception. + - If an update is required (due to new information, corrections, or additions), invoke `update_identity_memory`. + - If no update is needed, output ``. + +user_message: | + Please analyze the context and update the main agent's self-perception if necessary. diff --git a/reme_ai/mem_agent/summarizer/personal_summarizer.py b/reme_ai/mem_agent/summarizer/personal_summarizer.py new file mode 100644 index 00000000..357e7b9f --- /dev/null +++ b/reme_ai/mem_agent/summarizer/personal_summarizer.py @@ -0,0 +1,41 @@ +"""Specialized agent for extracting and managing personal memories about specific individuals.""" + +from ..base_memory_agent import BaseMemoryAgent +from ...core.context import C +from ...core.enumeration import Role, MemoryType +from ...core.schema import Message +from ...core.utils import get_now_time, format_messages + + +@C.register_op() +class PersonalSummarizer(BaseMemoryAgent): + """Extracts and stores personal information about individuals from conversations.""" + + memory_type: MemoryType = MemoryType.PERSONAL + + async def build_messages(self) -> list[Message]: + """Construct messages with context, memory_target, and memory_type information.""" + system_prompt = self.prompt_format( + prompt_name="system_prompt", + now_time=get_now_time(), + context=format_messages(self.get_messages()), + memory_type=self.memory_type.value, + memory_target=self.memory_target, + ) + + messages = [ + Message(role=Role.SYSTEM, content=system_prompt), + Message(role=Role.USER, content=self.get_prompt("user_message")), + ] + return messages + + async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]: + """Execute tool calls with memory_target, memory_type, and author context.""" + return await super()._acting_step( + assistant_message, + step, + memory_target=self.memory_target, + memory_type=self.memory_type.value, + author=self.author, + **kwargs, + ) diff --git a/reme_ai/mem_agent/summarizer/personal_summarizer.yaml b/reme_ai/mem_agent/summarizer/personal_summarizer.yaml new file mode 100644 index 00000000..eafe1da5 --- /dev/null +++ b/reme_ai/mem_agent/summarizer/personal_summarizer.yaml @@ -0,0 +1,54 @@ +tool: | + Extract and store personal memories from conversation context. + Use this tool to analyze dialogues and extract important personal information about users, + such as preferences, habits, personal background, relationships, and significant facts. + The agent will determine whether the information is worth remembering, check for duplicates + or conflicts with existing memories, and perform add, update, or delete operations as needed. + +system_prompt: | + You are a professional memory agent specializing in the domain of **{memory_target}**. Your task is to update the main agent's {memory_type} memory regarding {memory_target} based on the context. + + ## Context: + {context} + + ## Current Time: + {now_time} + + ## Memory Objective: + You are managing **{memory_type}** memories about **{memory_target}** for the main agent. Focus on extracting and storing information directly related to this person’s preferences, habits, personal background, and significant facts. + + ## Your Tasks: + + 1. **Analyze and Extract** potential memories from the dialogue context: + - Determine whether the conversation contains important, memorable information, including but not limited to: user preferences, habits, or personal details; key facts, decisions, or conclusions; relationships or contextual background related to people or topics. + - If the dialogue is casual chatter or contains no valuable information, output `` and stop. + - Extract key information using clear and concise phrasing. + - Each memory entry must be self-contained and understandable without additional context. + - Avoid storing trivial or temporary information. + - Before proceeding, list all extracted memories in your response. + + 2. **Retrieve similar historical memories** using `vector_retrieve_memory`: + - Perform a semantic similarity search based on the extracted memories to find existing, potentially relevant memories. + - Retrieve related memories for comparison to check for duplication or associations. + + 3. **Compare and Decide** on memory operations: + - Compare the newly extracted memories with historical ones to ensure the final memory store contains no duplicates or contradictions. + - Choose the appropriate operation based on the situation: + - If the information already exists and is consistent: skip—no action needed. + - If existing memory needs supplementation or correction: use `update_memory` to update it. + - If existing memory is outdated or incorrect: use `delete_memory` to remove it. + - If the information is entirely new: use `add_memory` to add it to the memory store. + + 4. **Output** the result: + - If no memory operation is required, output ``. + - If memories were added, updated, or deleted, summarize the operations performed. + + ## Guidelines: + - Be selective: store only truly important information. + - Stay concise: each memory should be clear and atomic. + - Be accurate: ensure extracted content faithfully reflects the original context. + - Avoid redundancy: always check for similar existing memories before adding new ones. + - Include relevant metadata (e.g., timestamps) when appropriate. + +user_message: | + Please analyze the context to determine whether important information should be extracted and stored as memory, and perform memory addition, deletion, or update operations when necessary. diff --git a/reme_ai/mem_agent/summarizer/procedural_summarizer.py b/reme_ai/mem_agent/summarizer/procedural_summarizer.py new file mode 100644 index 00000000..e31422e4 --- /dev/null +++ b/reme_ai/mem_agent/summarizer/procedural_summarizer.py @@ -0,0 +1,42 @@ +"""Specialized agent for extracting and managing procedural knowledge and workflows.""" + +from ..base_memory_agent import BaseMemoryAgent +from ...core.context import C +from ...core.enumeration import Role, MemoryType +from ...core.schema import Message +from ...core.utils import get_now_time, format_messages + + +@C.register_op() +class ProceduralSummarizer(BaseMemoryAgent): + """Extracts step-by-step procedures, best practices, and task-completion strategies.""" + + memory_type: MemoryType = MemoryType.PROCEDURAL + + async def build_messages(self) -> list[Message]: + """Construct messages with context, memory_target, and memory_type information.""" + system_prompt = self.prompt_format( + prompt_name="system_prompt", + now_time=get_now_time(), + context=format_messages(self.get_messages()), + memory_type=self.memory_type.value, + memory_target=self.memory_target, + ) + + messages = [ + Message(role=Role.SYSTEM, content=system_prompt), + Message(role=Role.USER, content=self.get_prompt("user_message")), + ] + return messages + + async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]: + """Execute tool calls with ref_memory_id, memory_target, memory_type, and author context.""" + return await super()._acting_step( + assistant_message, + step, + ref_memory_id=self.ref_memory_id, + memory_target=self.memory_target, + memory_type=self.memory_type.value, + author=self.author, + **kwargs, + ) diff --git a/reme_ai/mem_agent/summarizer/procedural_summarizer.yaml b/reme_ai/mem_agent/summarizer/procedural_summarizer.yaml new file mode 100644 index 00000000..83a8867d --- /dev/null +++ b/reme_ai/mem_agent/summarizer/procedural_summarizer.yaml @@ -0,0 +1,70 @@ +tool: | + Extract and store procedural memories from conversation context. + Use this tool to analyze dialogues and extract important procedural knowledge, + such as step-by-step workflows, how-to guides, best practices, problem-solving methods, + debugging techniques, and task completion strategies. + The agent will also reflect on task outcomes - extracting lessons from failures + and successful strategies from successes to improve future performance. + +system_prompt: | + You are a professional memory Agent specializing in the domain of **{memory_target}**. Your task is to update the main agent's {memory_type} memory regarding {memory_target} based on the context. + + ## Context: + {context} + + ## Current Time: + {now_time} + + ## Memory Objective: + You are managing **{memory_type}** memories about **{memory_target}** for the main Agent. Focus on extracting and storing procedural knowledge, such as: + - Step-by-step procedures and workflows + - Operational guides and instructions + - Best practices and methodologies + - Established routines and processes + - Problem-solving techniques and troubleshooting tips + - Task-completion strategies + + ## Your Tasks: + + 1. **Analyze and Extract** potential memories from the conversation context: + - Determine whether the dialogue contains procedural knowledge worth remembering, including but not limited to: + - Multi-step procedures or workflows + - Instructions for completing specific tasks + - Best practices or recommended approaches + - Problem-solving methods or debugging tips + - Configuration or setup processes + - If the context includes task outcome information: + - **Successful tasks**: Extract and reflect on successful experiences; summarize key success factors, effective methods, and reusable strategies. + - **Failed tasks**: Extract and reflect on lessons learned; analyze root causes of failure, pitfalls to avoid, and improvement suggestions. + - **Both success and failure**: Conduct comparative reflection; identify critical differences and distill key decision factors and best practices. + - If the conversation is casual chat or contains no valuable information, output `` and stop. + - Express extracted information clearly and concisely. + - Each memory entry should be self-contained and understandable without additional context. + - Avoid storing trivial or transient information. + - Before proceeding, list all extracted memories in your response. + + 2. **Retrieve similar historical memories** using `vector_retrieve_memory`: + - Perform a semantic similarity search based on the extracted memories. + - Retrieve potentially relevant existing memories for comparison to check for duplicates or associations. + + 3. **Compare and Decide** on memory operations: + - Compare extracted memories against historical ones to ensure no duplicates or conflicts exist in the final memory repository. + - Choose the appropriate operation based on the situation: + - If the information already exists and is consistent: skip (no action needed). + - If existing memory needs supplementation or correction: use `update_memory` to revise it. + - If existing memory is outdated or incorrect: use `delete_memory` to remove it. + - If the information is entirely new: use `add_memory` to add it to the memory repository. + + 4. **Output** the result: + - If no memory operation is needed, output ``. + - If memories were added, updated, or deleted, summarize the performed operations. + + ## Guidelines: + - **Be selective**: Store only truly important information. + - **Stay concise**: Each memory should be clear and atomic. + - **Be precise and accurate**: Ensure extracted content faithfully reflects the original context. + - **Avoid redundancy**: Always check for similar existing memories before adding new ones. + - **Include relevant metadata when appropriate** (e.g., timestamp, preconditions, expected outcomes). + +user_message: | + Please analyze the context to determine whether important procedural knowledge should be extracted and stored as memory, and perform memory addition, deletion, or update operations when necessary. diff --git a/reme_ai/mem_agent/summarizer/reme_summarizer.py b/reme_ai/mem_agent/summarizer/reme_summarizer.py new file mode 100644 index 00000000..e5222e7b --- /dev/null +++ b/reme_ai/mem_agent/summarizer/reme_summarizer.py @@ -0,0 +1,105 @@ +"""Orchestrator for complete memory summarization workflow across all memory types.""" + +import re +from typing import List + +from loguru import logger + +from ..base_memory_agent import BaseMemoryAgent +from ...core.context import C +from ...core.enumeration import Role +from ...core.schema import Message, MemoryNode +from ...core.utils import get_now_time, format_messages + + +@C.register_op() +class ReMeSummarizer(BaseMemoryAgent): + """Coordinates memory updates by delegating to specialized memory agents.""" + + def __init__(self, enable_tool_memory: bool = True, enable_identity_memory: bool = True, **kwargs): + """Initialize with flags to enable/disable tool and identity memory processing.""" + super().__init__(**kwargs) + self.enable_tool_memory = enable_tool_memory + self.enable_identity_memory = enable_identity_memory + + async def _add_history_memory(self) -> MemoryNode: + """Store conversation history and return the memory node.""" + from ...mem_tool import AddHistoryMemory + + op = AddHistoryMemory() + await op.call(messages=self.get_messages()) + return op.output + + @staticmethod + async def _read_identity_memory() -> str: + """Retrieve agent's self-perception memory.""" + from ...mem_tool import ReadIdentityMemory + + op = ReadIdentityMemory() + await op.call() + return op.output + + async def _read_meta_memories(self) -> str: + """Fetch all meta-memory entries that define specialized memory agents.""" + from ...mem_tool import ReadMetaMemory + + op = ReadMetaMemory( + enable_tool_memory=self.enable_tool_memory, + enable_identity_memory=self.enable_identity_memory, + ) + await op.call() + return str(op.output) + + async def build_messages(self) -> List[Message]: + """Construct initial messages with context, identity, and meta-memory information.""" + memory_node: MemoryNode = await self._add_history_memory() + self.context["ref_memory_id"] = memory_node.memory_id + now_time = get_now_time() + identity_memory = await self._read_identity_memory() + meta_memory_info = await self._read_meta_memories() + context = format_messages(self.get_messages()) + logger.info( + f"now_time={now_time} " + f"memory_node={memory_node} " + f"identity_memory={identity_memory} " + f"meta_memory_info={meta_memory_info} " + f"context={context}", + ) + + system_prompt = self.prompt_format( + prompt_name="system_prompt", + now_time=now_time, + identity_memory=identity_memory, + meta_memory_info=meta_memory_info, + context=context, + ) + + user_message = self.get_prompt("user_message") + messages = [ + Message(role=Role.SYSTEM, content=system_prompt), + Message(role=Role.USER, content=user_message), + ] + + return messages + + async def _reasoning_step(self, messages: list[Message], step: int, **kwargs) -> tuple[Message, bool]: + """Refresh meta-memory info in system prompt before each reasoning step.""" + meta_memory_info = await self._read_meta_memories() + system_messages = [message for message in messages if message.role is Role.SYSTEM] + if system_messages: + system_message = system_messages[0] + pattern = r'("- \(\): "\n)(.*?)(\n\n)' + replacement = rf"\g<1>{meta_memory_info}\g<3>" + system_message.content = re.sub(pattern, replacement, system_message.content, flags=re.DOTALL) + + return await super()._reasoning_step(messages, step, **kwargs) + + async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]: + """Execute tool calls with ref_memory_id and author context.""" + return await super()._acting_step( + assistant_message, + step, + ref_memory_id=self.context["ref_memory_id"], + author=self.author, + **kwargs, + ) diff --git a/reme_ai/mem_agent/summarizer/reme_summarizer.yaml b/reme_ai/mem_agent/summarizer/reme_summarizer.yaml new file mode 100644 index 00000000..baa6a7d6 --- /dev/null +++ b/reme_ai/mem_agent/summarizer/reme_summarizer.yaml @@ -0,0 +1,52 @@ +tool: | + Orchestrate the complete memory summarization workflow for the agent. + This tool receives conversation context and performs necessary memory updates including: + 1. Creating new meta-memory entries if needed + 2. Adding summary memory for quick future recall + 3. Delegating to specialized memory agents for detailed memory extraction and update + +system_prompt: | + # Context + {context} + + You are a Memory Agent responsible for performing necessary updates and summaries of the main Agent's memories based on the **context**. + + ## Current Time + {now_time} + + ## Main Agent's Self-Perception + {identity_memory} + + ## Main Agent's Meta Memory + Each line of meta memory indicates the existence of a specialized Memory Agent dedicated to deep summarization and updating of memories within a specific dimension (memory_type + memory_target). + Format: "- (): " + {meta_memory_info} + + ## Your Tasks + + ### 1. Create New Meta Memory (if needed) + When the context contains significant personal or procedural information not yet covered by existing meta memories: + - Use `add_meta_memory` to create one or more new meta memory entries. + - For personal memories: specify `memory_type="personal"` and `memory_target=`. + - For procedural memories: specify `memory_type="procedural"` and `memory_target=`. + - Each meta memory entry will instantiate a dedicated specialized Memory Agent for that dimension. + + ### 2. Add Summary Memory (if valuable) + When the context includes information worth remembering for quick future recall: + - Use `add_summary_memory` to store a concise summary. + - The summary should capture key points, decisions, or important facts to aid later recollection of the original conversation. + + ### 3. Delegate to Specialized Memory Agents (Core Task) + You do not need to summarize or update memories yourself. Instead, analyze the context, identify which memory dimensions (memory_type + memory_target) from the existing meta memory require updates, and delegate using `hands_off`: + - The parameters of `hands_off` (`memory_type` and `memory_target`) must exactly match an existing entry in the "Main Agent's Meta Memory" listed above. + - You may delegate concurrently to multiple specialized agents to enable parallel memory processing. + - Each specialized agent will perform detailed memory extraction, addition, updating, or deletion within its assigned dimension. + + ## Output Requirements + - If the context contains no memorable information (e.g., simple greetings or meaningless small talk), output ``. + - If any memory operations were performed, briefly summarize what was done. + +user_message: | + Please perform your task based on the context. + + diff --git a/reme_ai/mem_agent/summarizer/tool_summarizer.py b/reme_ai/mem_agent/summarizer/tool_summarizer.py new file mode 100644 index 00000000..50399bba --- /dev/null +++ b/reme_ai/mem_agent/summarizer/tool_summarizer.py @@ -0,0 +1,42 @@ +"""Specialized agent for extracting and managing tool usage guidelines and best practices.""" + +from ..base_memory_agent import BaseMemoryAgent +from ...core.context import C +from ...core.enumeration import Role, MemoryType +from ...core.schema import Message +from ...core.utils import get_now_time, format_messages + + +@C.register_op() +class ToolSummarizer(BaseMemoryAgent): + """Analyzes tool executions to extract effective usage patterns and optimization tips.""" + + memory_type: MemoryType = MemoryType.TOOL + + async def build_messages(self) -> list[Message]: + """Construct messages with context, memory_target, and memory_type information.""" + system_prompt = self.prompt_format( + prompt_name="system_prompt", + now_time=get_now_time(), + context=format_messages(self.get_messages()), + memory_type=self.memory_type.value, + memory_target=self.memory_target, + ) + + messages = [ + Message(role=Role.SYSTEM, content=system_prompt), + Message(role=Role.USER, content=self.get_prompt("user_message")), + ] + return messages + + async def _acting_step(self, assistant_message: Message, step: int, **kwargs) -> list[Message]: + """Execute tool calls with ref_memory_id, memory_target, memory_type, and author context.""" + return await super()._acting_step( + assistant_message, + step, + ref_memory_id=self.ref_memory_id, + memory_target=self.memory_target, + memory_type=self.memory_type.value, + author=self.author, + **kwargs, + ) diff --git a/reme_ai/mem_agent/summarizer/tool_summarizer.yaml b/reme_ai/mem_agent/summarizer/tool_summarizer.yaml new file mode 100644 index 00000000..859764e4 --- /dev/null +++ b/reme_ai/mem_agent/summarizer/tool_summarizer.yaml @@ -0,0 +1,55 @@ +tool: | + Extract and store tool usage guidelines from tool call execution context. + Use this tool to analyze tool calls and their results, extracting valuable insights + about how to use tools more effectively, including best practices, common patterns, + error handling strategies, and optimization tips. + The agent will determine whether the information is worth remembering, check for duplicates + or conflicts with existing tool guidelines, and perform add, update, or delete operations as needed. + +system_prompt: | + You are a professional memory Agent specializing in the domain of **{memory_target}**. Please analyze the tool execution context, and your task is to update the main Agent's **{memory_type}** memory regarding **{memory_target}** based on this context. + + ## Context: + {context} + + ## Current Time: + {now_time} + + ## Memory Target: + You are managing the main Agent’s **{memory_type}** memory about **{memory_target}**. Focus on extracting and storing guidelines, best practices, and insights on how to effectively use this tool. + + ## Your Tasks: + + 1. **Analyze and Extract** tool usage guidelines from the execution context: + - Determine whether the tool invocation and its results contain valuable insights worth remembering, including but not limited to: successful usage patterns and best practices; common errors and how to avoid them; effective parameter combinations; performance optimization tips; edge cases and special handling requirements. + - If the tool execution represents a routine operation with no new insights, output `` and stop. + - Extract key guidelines in a clear and actionable manner. + - Each guideline should be self-contained and directly applicable. + - Avoid storing trivial or obvious information. + - Before proceeding, list all extracted guidelines in your response. + + 2. **Retrieve historical guidelines** for this tool by calling `vector_retrieve_memory`, using the `tool_name` as the query parameter to fetch any existing guidelines. + + 3. **Compare and Decide** on the appropriate memory operation: + - Compare the newly extracted guidelines with the historical ones to ensure the final memory store contains no duplicates or contradictions. + - Normally, `vector_retrieve_memory` should return at most one guideline per tool. If multiple guidelines exist for the same tool, use `delete_memory` to remove the redundant entries and merge all useful information into a single, comprehensive guideline. + - Choose the appropriate action based on the situation: + - If the guideline already exists and is consistent: skip—no action needed. + - If the existing guideline needs supplementation or refinement: use `update_memory` to enhance it. + - If the existing guideline is outdated or incorrect: use `update_memory` to replace it with the correct version. + - If multiple guidelines exist for the same tool: use `delete_memory` to remove duplicates, then use `update_memory` on the remaining entry to consolidate all useful information. + - If the guideline is entirely new: use `add_memory` to add it to the memory store. + + 4. **Output** the result: + - If no memory operation is required, output ``. + - If you added, updated, or deleted any guidelines, summarize the operations performed. + + ## Guidelines: + - **Be selective**: Only retain insights that genuinely improve tool usage efficiency. + - **Keep it actionable**: Each guideline should offer clear, practical advice. + - **Ensure accuracy**: Verify that extracted guidelines are supported by actual tool execution results. + - **Avoid redundancy**: Always check for similar existing guidelines before adding new ones. + - **Include relevant context when appropriate** (e.g., parameter values, error messages). + +user_message: | + Please analyze the tool execution context to determine whether important usage guidelines should be extracted and stored as memory, and perform memory addition, deletion, or update operations when necessary. diff --git a/reme_ai/mem_tool/hands_off_tool.py b/reme_ai/mem_tool/hands_off_tool.py new file mode 100644 index 00000000..e0b63ecb --- /dev/null +++ b/reme_ai/mem_tool/hands_off_tool.py @@ -0,0 +1,149 @@ +"""Hands-off tool for distributing memory tasks to appropriate agents.""" + +import json +from typing import TYPE_CHECKING + +from loguru import logger + +from .base_memory_tool import BaseMemoryTool +from ..core.context import C +from ..core.enumeration import MemoryType + +if TYPE_CHECKING: + from ..mem_agent import BaseMemoryAgent + + +@C.register_op() +class HandsOffTool(BaseMemoryTool): + """Distribute memory tasks to appropriate agents based on memory_type.""" + + def __init__(self, memory_agents: list["BaseMemoryAgent"], force_agent_language: bool = True, **kwargs): + super().__init__(**kwargs) + self.memory_agent_dict: dict[MemoryType, "BaseMemoryAgent"] = {} + if memory_agents: + for agent in memory_agents: + if agent.memory_type is None: + continue + + self.memory_agent_dict[agent.memory_type] = agent + if force_agent_language and self.language: + agent.language = self.language + + def _build_item_schema(self) -> tuple[dict, list[str]]: + """Build shared schema properties and required fields for memory tasks.""" + properties = { + "memory_type": { + "type": "string", + "description": self.get_prompt("memory_type"), + "enum": [ + MemoryType.IDENTITY.value, + MemoryType.PERSONAL.value, + MemoryType.PROCEDURAL.value, + MemoryType.TOOL.value, + ], + }, + "memory_target": { + "type": "string", + "description": self.get_prompt("memory_target"), + }, + } + required = ["memory_type", "memory_target"] + return properties, required + + def _build_parameters(self) -> dict: + """Build input schema for single memory task distribution.""" + properties, required = self._build_item_schema() + return { + "type": "object", + "properties": properties, + "required": required, + } + + def _build_multiple_parameters(self) -> dict: + """Build input schema for multiple memory task distribution.""" + item_properties, required_fields = self._build_item_schema() + return { + "type": "object", + "properties": { + "memory_tasks": { + "type": "array", + "description": self.get_prompt("memory_tasks"), + "items": { + "type": "object", + "properties": item_properties, + "required": required_fields, + }, + }, + }, + "required": ["memory_tasks"], + } + + @staticmethod + def _parse_memory_type_target(task: dict): + memory_type = task.get("memory_type", "") + memory_target = task.get("memory_target", "") + return { + "memory_type": MemoryType(memory_type), + "memory_target": memory_target, + } + + def _collect_tasks(self) -> list[dict]: + """Collect memory tasks from context based on enable_multiple flag.""" + tasks: list[dict] = [] + if self.enable_multiple: + memory_tasks: list[dict] = self.context.get("memory_tasks", []) + for task in memory_tasks: + tasks.append(self._parse_memory_type_target(task)) + else: + tasks.append(self._parse_memory_type_target(self.context)) + return tasks + + async def execute(self): + """Execute memory tasks by distributing to appropriate agents in parallel.""" + tasks = self._collect_tasks() + + if not tasks: + self.output = "No valid memory tasks to execute." + return + + # Submit tasks to corresponding agents + agent_list = [] + for i, task in enumerate(tasks): + memory_type: MemoryType = task["memory_type"] + memory_target: str = task["memory_target"] + + if memory_type not in self.memory_agent_dict: + logger.warning(f"No agent found for memory_type={memory_type}") + continue + + agent_copy = self.memory_agent_dict[memory_type].copy() + agent_list.append({ + "agent": agent_copy, + "memory_type": memory_type, + "memory_target": memory_target, + }) + + logger.info(f"Task {i}: Submitting {memory_type.value} agent for target={memory_target}") + self.submit_async_task( + agent_copy.call, + query=self.context.get("query", ""), + messages=self.context.get("messages", []), + memory_target=memory_target, + ref_memory_id=self.context.get("ref_memory_id", ""), + ) + + await self.join_async_tasks() + + # Collect results + results = [] + for i, (agent, memory_type, memory_target) in enumerate(agent_list): + result_str = str(agent.output) + results.append({ + "memory_type": memory_type.value, + "memory_target": memory_target, + "result": result_str[:200] + ("..." if len(result_str) > 200 else ""), + }) + logger.info(f"Task {i}: Completed {memory_type.value} agent for target={memory_target}") + + results_str = json.dumps(results, ensure_ascii=False, indent=2) + self.set_output(f"Successfully executed {len(results)} memory tasks:\n{results_str}") diff --git a/reme_ai/mem_tool/hands_off_tool.yaml b/reme_ai/mem_tool/hands_off_tool.yaml new file mode 100644 index 00000000..06a443d7 --- /dev/null +++ b/reme_ai/mem_tool/hands_off_tool.yaml @@ -0,0 +1,19 @@ +tool: | + Distribute a memory task to the appropriate agent based on memory_type. + Use this tool to hand off memory summarization to specialized agents. + Examples: summarizing user preferences, extracting procedural knowledge, or analyzing tool usage patterns. + +tool_multiple: | + Distribute multiple memory tasks to appropriate agents in parallel. + Use this tool to hand off multiple memory summarization tasks efficiently. + Each task will be processed by its corresponding specialized agent based on memory_type. + +memory_type: | + The type of memory to process. Determines which specialized agent handles the task. + +memory_target: | + The target entity for this memory. + This helps the agent focus on the specific subject of the memory task. + +memory_tasks: | + A list of memory tasks to distribute, each with memory_type and memory_target. diff --git a/reme_ai/mem_tool/history/add_history_memory.py b/reme_ai/mem_tool/history/add_history_memory.py index 1701582d..65065a2c 100644 --- a/reme_ai/mem_tool/history/add_history_memory.py +++ b/reme_ai/mem_tool/history/add_history_memory.py @@ -38,7 +38,6 @@ class AddHistoryMemory(BaseMemoryTool): return messages = [Message(**m) if isinstance(m, dict) else m for m in messages] - memory_content = format_messages(messages) memory_node = self._build_memory_node(memory_content=memory_content, memory_type=MemoryType.HISTORY) diff --git a/reme_ai/mem_tool/vector/add_memory.py b/reme_ai/mem_tool/vector/add_memory.py index 87e54169..c4a1a9c2 100644 --- a/reme_ai/mem_tool/vector/add_memory.py +++ b/reme_ai/mem_tool/vector/add_memory.py @@ -9,22 +9,31 @@ from ...core.schema import MemoryNode @C.register_op() class AddMemory(BaseMemoryTool): - """Add memories to vector store with optional when_to_use and metadata. + """Add memories to vector store with optional when_to_use and custom metadata fields. Supports single/multiple addition modes via `enable_multiple` parameter. + Metadata fields can be customized via `metadata_desc` parameter. """ - def __init__(self, add_when_to_use: bool = False, add_metadata: bool = True, **kwargs): + def __init__(self, add_when_to_use: bool = False, metadata_desc: dict[str, str] | None = None, **kwargs): """Initialize AddMemory. Args: add_when_to_use: Include when_to_use field for better retrieval. - add_metadata: Include metadata field for additional info. + metadata_desc: Dictionary defining metadata fields and their descriptions. + Example: + { + "year": "The `year` information associated with the memory(Optional)", + "month": "The `month` information associated with the memory(Optional)", + "day": "The `day` information associated with the memory(Optional)", + "hour": "The `hour` information associated with the memory(Optional)", + } + If None or empty dict, metadata field will not be included. **kwargs: Additional arguments for BaseMemoryTool. """ super().__init__(**kwargs) self.add_when_to_use: bool = add_when_to_use - self.add_metadata: bool = add_metadata + self.metadata_desc: dict[str, str] = metadata_desc or {} def _build_item_schema(self) -> tuple[dict, list[str]]: """Build shared schema properties and required fields for memory items. @@ -48,10 +57,19 @@ class AddMemory(BaseMemoryTool): } required.append("memory_content") - if self.add_metadata: + # Add metadata field if metadata_desc is provided and not empty + if self.metadata_desc: + metadata_properties = { + key: {"type": "string", "description": desc} for key, desc in self.metadata_desc.items() + } + # Generate dynamic description based on metadata_desc fields + field_descriptions = "\n".join([f" - {key}: {desc}" for key, desc in self.metadata_desc.items()]) + metadata_description = f"Optional metadata for the memory. Available fields:\n{field_descriptions}" + properties["metadata"] = { "type": "object", - "description": self.get_prompt("metadata"), + "description": metadata_description, + "properties": metadata_properties, } return properties, required @@ -95,7 +113,12 @@ class AddMemory(BaseMemoryTool): """ memory_content = mem_dict.get("memory_content", "") when_to_use = mem_dict.get("when_to_use", "") if self.add_when_to_use else "" - metadata = mem_dict.get("metadata", {}) if self.add_metadata else {} + # Only extract metadata if metadata_desc is configured + # Convert all metadata values to strings + metadata = {} + if self.metadata_desc: + raw_metadata = mem_dict.get("metadata", {}) + metadata = {key: str(value).strip() for key, value in raw_metadata.items() if value} return memory_content, when_to_use, metadata async def execute(self): diff --git a/reme_ai/mem_tool/vector/add_memory.yaml b/reme_ai/mem_tool/vector/add_memory.yaml index d484f349..30df9476 100644 --- a/reme_ai/mem_tool/vector/add_memory.yaml +++ b/reme_ai/mem_tool/vector/add_memory.yaml @@ -26,12 +26,5 @@ memory_content: | Should be a clear, concise statement that captures the information to remember. Keep it focused on a single piece of information for better retrieval accuracy. -metadata: | - Optional metadata for the memory, providing additional context. Can include: - - time: The timestamp or date associated with the memory (e.g., "2025-01-06 10:30:00") - - source: Where this information came from (e.g., "user_input", "documentation", "observation") - - tags: List of tags for categorization (e.g., ["authentication", "security"]) - - Any other custom key-value pairs relevant to the memory - memories: | A list of memory objects to store. diff --git a/reme_ai/mem_tool/vector/add_summary_memory.py b/reme_ai/mem_tool/vector/add_summary_memory.py index eaedf01e..4eb63d34 100644 --- a/reme_ai/mem_tool/vector/add_summary_memory.py +++ b/reme_ai/mem_tool/vector/add_summary_memory.py @@ -14,19 +14,20 @@ class AddSummaryMemory(AddMemory): - Single memory mode only (enable_multiple=False) - Uses 'summary_memory' parameter instead of 'memory_content' - No when_to_use field (add_when_to_use=False) + - Metadata fields can be customized via `metadata_desc` parameter """ - def __init__(self, add_metadata: bool = True, **kwargs): + def __init__(self, metadata_desc: dict[str, str] | None = None, **kwargs): """Initialize AddSummaryMemory. Args: - add_metadata: Include metadata field for additional info. + metadata_desc: Dictionary defining metadata fields and their descriptions. **kwargs: Additional arguments for AddMemory. """ # Force single mode and disable when_to_use kwargs["enable_multiple"] = False kwargs["add_when_to_use"] = False - super().__init__(add_metadata=add_metadata, **kwargs) + super().__init__(metadata_desc=metadata_desc, **kwargs) def _build_parameters(self) -> dict: """Build input schema for summary memory addition.""" @@ -38,10 +39,19 @@ class AddSummaryMemory(AddMemory): } required = ["summary_memory"] - if self.add_metadata: + # Add metadata field if metadata_desc is provided and not empty + if self.metadata_desc: + metadata_properties = { + key: {"type": "string", "description": desc} for key, desc in self.metadata_desc.items() + } + # Generate dynamic description based on metadata_desc fields + field_descriptions = "\n".join([f" - {key}: {desc}" for key, desc in self.metadata_desc.items()]) + metadata_description = f"Optional metadata for the memory. Available fields:\n{field_descriptions}" + properties["metadata"] = { "type": "object", - "description": self.get_prompt("metadata"), + "description": metadata_description, + "properties": metadata_properties, } return { diff --git a/reme_ai/mem_tool/vector/add_summary_memory.yaml b/reme_ai/mem_tool/vector/add_summary_memory.yaml index e9e469c4..20c6a1f1 100644 --- a/reme_ai/mem_tool/vector/add_summary_memory.yaml +++ b/reme_ai/mem_tool/vector/add_summary_memory.yaml @@ -17,11 +17,3 @@ summary_memory: | - "User prefers Python for backend development and has experience with FastAPI framework" - "Project deadline is January 15th, requires authentication, payment integration, and admin dashboard" - "Bug in user registration was caused by missing email validation, fixed by adding regex check" - -metadata: | - Optional metadata for the memory, providing additional context. Can include: - - time: The timestamp or date associated with the memory (e.g., "2025-01-06 10:30:00") - - source: Where this information came from (e.g., "conversation", "meeting", "observation") - - tags: List of tags for categorization (e.g., ["project", "decision"]) - - summary_type: Type of summary (e.g., "conversation", "decision", "event", "task") - - Any other custom key-value pairs relevant to the memory diff --git a/reme_ai/mem_tool/vector/update_memory.py b/reme_ai/mem_tool/vector/update_memory.py index e6c64981..3a92722a 100644 --- a/reme_ai/mem_tool/vector/update_memory.py +++ b/reme_ai/mem_tool/vector/update_memory.py @@ -12,24 +12,25 @@ class UpdateMemory(BaseMemoryTool): """Update memories by deleting old ones and inserting new ones. Supports single/multiple update modes via `enable_multiple` parameter. + Metadata fields can be customized via `metadata_desc` parameter. """ def __init__( self, add_when_to_use: bool = False, - add_metadata: bool = True, + metadata_desc: dict[str, str] | None = None, **kwargs, ): """Initialize UpdateMemory. Args: add_when_to_use: Include when_to_use field for better retrieval. - add_metadata: Include metadata field for additional info. + metadata_desc: Dictionary defining metadata fields and their descriptions. **kwargs: Additional arguments for BaseMemoryTool. """ super().__init__(**kwargs) self.add_when_to_use: bool = add_when_to_use - self.add_metadata: bool = add_metadata + self.metadata_desc: dict[str, str] = metadata_desc or {} def _build_item_schema(self) -> tuple[dict, list[str]]: """Build shared schema properties and required fields for memory items. @@ -57,10 +58,19 @@ class UpdateMemory(BaseMemoryTool): } required.append("memory_content") - if self.add_metadata: + # Add metadata field if metadata_desc is provided and not empty + if self.metadata_desc: + metadata_properties = { + key: {"type": "string", "description": desc} for key, desc in self.metadata_desc.items() + } + # Generate dynamic description based on metadata_desc fields + field_descriptions = "\n".join([f" - {key}: {desc}" for key, desc in self.metadata_desc.items()]) + metadata_description = f"Optional metadata for the memory. Available fields:\n{field_descriptions}" + properties["metadata"] = { "type": "object", - "description": self.get_prompt("metadata"), + "description": metadata_description, + "properties": metadata_properties, } return properties, required @@ -105,7 +115,12 @@ class UpdateMemory(BaseMemoryTool): memory_id = mem_dict.get("memory_id", "") memory_content = mem_dict.get("memory_content", "") when_to_use = mem_dict.get("when_to_use", "") if self.add_when_to_use else "" - metadata = mem_dict.get("metadata", {}) if self.add_metadata else {} + # Only extract metadata if metadata_desc is configured + # Convert all metadata values to strings + metadata = {} + if self.metadata_desc: + raw_metadata = mem_dict.get("metadata", {}) + metadata = {key: str(value).strip() for key, value in raw_metadata.items() if value} return memory_id, memory_content, when_to_use, metadata async def execute(self): diff --git a/reme_ai/mem_tool/vector/update_memory.yaml b/reme_ai/mem_tool/vector/update_memory.yaml index 16e14c57..9f01c42e 100644 --- a/reme_ai/mem_tool/vector/update_memory.yaml +++ b/reme_ai/mem_tool/vector/update_memory.yaml @@ -23,23 +23,11 @@ memory_id: | when_to_use: | Optional condition description for when to retrieve this memory. This field is used for vector embedding to improve retrieval accuracy by providing contextual information. - Examples: - - "when user asks about authentication" - - "when deploying to production" - - "when using search_tool" - - "when handling error cases" memory_content: | The new content of the memory to store. Should be a clear, concise statement that captures the updated information to remember. Keep it focused on a single piece of information for better retrieval accuracy. -metadata: | - Optional metadata for the new memory, providing additional context. Can include: - - time: The timestamp or date associated with the memory (e.g., "2025-01-06 10:30:00") - - source: Where this information came from (e.g., "user_input", "documentation", "observation") - - tags: List of tags for categorization (e.g., ["authentication", "security"]) - - Any other custom key-value pairs relevant to the memory - memories: | A list of memory update objects. diff --git a/reme_ai/mem_tool/vector/vector_retrieve_memory.py b/reme_ai/mem_tool/vector/vector_retrieve_memory.py index b0eb8ba7..06350b46 100644 --- a/reme_ai/mem_tool/vector/vector_retrieve_memory.py +++ b/reme_ai/mem_tool/vector/vector_retrieve_memory.py @@ -15,12 +15,14 @@ class VectorRetrieveMemory(BaseMemoryTool): Supports single/multiple query modes via `enable_multiple` parameter. When `add_memory_type_target` is False, memory_type/memory_target are from context. + Metadata filters can be customized via `metadata_desc` parameter for pre-retrieval filtering. """ def __init__( self, enable_summary_memory: bool = False, add_memory_type_target: bool = False, + metadata_desc: dict[str, str] | None = None, top_k: int = 10, **kwargs, ): @@ -29,12 +31,20 @@ class VectorRetrieveMemory(BaseMemoryTool): Args: enable_summary_memory: Include summary memories in results. add_memory_type_target: Include memory_type/memory_target in schema (else from context). + metadata_desc: Dictionary defining metadata filter fields and their descriptions. + These fields will be used as filters in vector search before similarity matching. + Example: + { + "year": "The year to filter memories(Optional)", + "month": "The month to filter memories(Optional)", + } top_k: Max memories to retrieve per query. **kwargs: Additional args for BaseMemoryTool. """ super().__init__(**kwargs) self.enable_summary_memory: bool = enable_summary_memory self.add_memory_type_target: bool = add_memory_type_target + self.metadata_desc: dict[str, str] = metadata_desc or {} self.top_k: int = top_k def _build_query_schema(self) -> tuple[dict, list[str]]: @@ -69,6 +79,23 @@ class VectorRetrieveMemory(BaseMemoryTool): } required.append("query") + # Add metadata filter fields if metadata_desc is provided and not empty + if self.metadata_desc: + metadata_properties = { + key: {"type": "string", "description": desc} for key, desc in self.metadata_desc.items() + } + # Generate dynamic description based on metadata_desc fields + field_descriptions = "\n".join([f" - {key}: {desc}" for key, desc in self.metadata_desc.items()]) + metadata_description = ( + f"Optional metadata filters for narrowing search results. Available fields:\n{field_descriptions}" + ) + + properties["metadata_filters"] = { + "type": "object", + "description": metadata_description, + "properties": metadata_properties, + } + return properties, required def _build_parameters(self) -> dict: @@ -113,6 +140,7 @@ class VectorRetrieveMemory(BaseMemoryTool): memory_type: str, memory_target: str, query: str, + metadata_filters: dict | None = None, ) -> list[MemoryNode]: """Retrieve memories by query using vector similarity search. @@ -120,6 +148,7 @@ class VectorRetrieveMemory(BaseMemoryTool): memory_type: Memory type to search. memory_target: Memory target to search. query: Query string for similarity search. + metadata_filters: Optional metadata filters to narrow search results. Returns: List of matching memories. @@ -133,6 +162,13 @@ class VectorRetrieveMemory(BaseMemoryTool): "memory_target": [memory_target], } + # Add metadata filters if provided + if metadata_filters: + for key, value in metadata_filters.items(): + if value: # Only add non-empty filter values + value = str(value).strip() + filter_dict[key] = [value] if not isinstance(value, list) else value + nodes: list[VectorNode] = await self.vector_store.search( query=query, top_k=self.top_k, @@ -189,6 +225,7 @@ class VectorRetrieveMemory(BaseMemoryTool): for item in query_items: memory_type = item.get("memory_type") or default_memory_type memory_target = item.get("memory_target") or default_memory_target + metadata_filters = item.get("metadata_filters", {}) if self.metadata_desc else {} if not memory_type or not memory_target: logger.warning(f"Skipping query with missing memory_type or memory_target: {item}") @@ -198,6 +235,7 @@ class VectorRetrieveMemory(BaseMemoryTool): memory_type=memory_type, memory_target=memory_target, query=item["query"], + metadata_filters=metadata_filters, ) memories.extend(retrieved) diff --git a/reme_ai/mem_tool/vector/vector_retrieve_memory.yaml b/reme_ai/mem_tool/vector/vector_retrieve_memory.yaml index c30a6efe..4ab56f9f 100644 --- a/reme_ai/mem_tool/vector/vector_retrieve_memory.yaml +++ b/reme_ai/mem_tool/vector/vector_retrieve_memory.yaml @@ -29,3 +29,4 @@ query: | query_items: | A list of query items for vector similarity search. + Each item can include metadata_filters to narrow down search results.