mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-10-06 02:48:22 +00:00
feat(mem_agent): implement memory agent architecture with specialized summarizers and retrievers
This commit is contained in:
parent
f493ba2f3a
commit
416206d332
33 changed files with 1039 additions and 79 deletions
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
9
reme_ai/mem_agent/retriever/__init__.py
Normal file
9
reme_ai/mem_agent/retriever/__init__.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
"""memory retriever"""
|
||||
|
||||
from .reme_retriever import ReMeRetriever
|
||||
from .remy_agent import ReMyAgent
|
||||
|
||||
__all__ = [
|
||||
"ReMeRetriever",
|
||||
"ReMyAgent",
|
||||
]
|
||||
42
reme_ai/mem_agent/retriever/reme_retriever.py
Normal file
42
reme_ai/mem_agent/retriever/reme_retriever.py
Normal file
|
|
@ -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
|
||||
50
reme_ai/mem_agent/retriever/reme_retriever.yaml
Normal file
50
reme_ai/mem_agent/retriever/reme_retriever.yaml
Normal file
|
|
@ -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: "- <memory_type>(<memory_target>): <description>"
|
||||
{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 `<NO_RETRIEVAL_NEEDED>` 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 `<NO_RETRIEVAL_NEEDED>`.
|
||||
- If relevant memories are found, clearly summarize the retrieved information.
|
||||
- If multiple attempts still yield no relevant memory, output `<NO_RELEVANT_MEMORY>`.
|
||||
|
||||
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.
|
||||
51
reme_ai/mem_agent/retriever/remy_agent.py
Normal file
51
reme_ai/mem_agent/retriever/remy_agent.py
Normal file
|
|
@ -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()
|
||||
36
reme_ai/mem_agent/retriever/remy_agent.yaml
Normal file
36
reme_ai/mem_agent/retriever/remy_agent.yaml
Normal file
|
|
@ -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: "- <memory_type>(<memory_target>): <description>"
|
||||
{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.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
"""Simple chat agent for non-streaming conversations."""
|
||||
"""Simple chat for test."""
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Streaming chat agent for real-time conversation streaming."""
|
||||
"""Streaming chat for test."""
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
|
|
|||
15
reme_ai/mem_agent/summarizer/__init__.py
Normal file
15
reme_ai/mem_agent/summarizer/__init__.py
Normal file
|
|
@ -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",
|
||||
]
|
||||
33
reme_ai/mem_agent/summarizer/identity_summarizer.py
Normal file
33
reme_ai/mem_agent/summarizer/identity_summarizer.py
Normal file
|
|
@ -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)
|
||||
28
reme_ai/mem_agent/summarizer/identity_summarizer.yaml
Normal file
28
reme_ai/mem_agent/summarizer/identity_summarizer.yaml
Normal file
|
|
@ -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 `<NO_MEMORY_NEEDED>` 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 `<NO_MEMORY_NEEDED>`.
|
||||
|
||||
user_message: |
|
||||
Please analyze the context and update the main agent's self-perception if necessary.
|
||||
41
reme_ai/mem_agent/summarizer/personal_summarizer.py
Normal file
41
reme_ai/mem_agent/summarizer/personal_summarizer.py
Normal file
|
|
@ -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,
|
||||
)
|
||||
54
reme_ai/mem_agent/summarizer/personal_summarizer.yaml
Normal file
54
reme_ai/mem_agent/summarizer/personal_summarizer.yaml
Normal file
|
|
@ -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 `<NO_MEMORY_NEEDED>` 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 `<NO_MEMORY_NEEDED>`.
|
||||
- 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.
|
||||
42
reme_ai/mem_agent/summarizer/procedural_summarizer.py
Normal file
42
reme_ai/mem_agent/summarizer/procedural_summarizer.py
Normal file
|
|
@ -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,
|
||||
)
|
||||
70
reme_ai/mem_agent/summarizer/procedural_summarizer.yaml
Normal file
70
reme_ai/mem_agent/summarizer/procedural_summarizer.yaml
Normal file
|
|
@ -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 `<NO_MEMORY_NEEDED>` 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 `<NO_MEMORY_NEEDED>`.
|
||||
- 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.
|
||||
105
reme_ai/mem_agent/summarizer/reme_summarizer.py
Normal file
105
reme_ai/mem_agent/summarizer/reme_summarizer.py
Normal file
|
|
@ -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'("- <memory_type>\(<memory_target>\): <description>"\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,
|
||||
)
|
||||
52
reme_ai/mem_agent/summarizer/reme_summarizer.yaml
Normal file
52
reme_ai/mem_agent/summarizer/reme_summarizer.yaml
Normal file
|
|
@ -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: "- <memory_type>(<memory_target>): <description>"
|
||||
{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=<person's name>`.
|
||||
- For procedural memories: specify `memory_type="procedural"` and `memory_target=<topic or domain>`.
|
||||
- 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 `<NO_MEMORY_NEEDED>`.
|
||||
- If any memory operations were performed, briefly summarize what was done.
|
||||
|
||||
user_message: |
|
||||
Please perform your task based on the context.
|
||||
|
||||
|
||||
42
reme_ai/mem_agent/summarizer/tool_summarizer.py
Normal file
42
reme_ai/mem_agent/summarizer/tool_summarizer.py
Normal file
|
|
@ -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,
|
||||
)
|
||||
55
reme_ai/mem_agent/summarizer/tool_summarizer.yaml
Normal file
55
reme_ai/mem_agent/summarizer/tool_summarizer.yaml
Normal file
|
|
@ -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 `<NO_MEMORY_NEEDED>` 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 `<NO_MEMORY_NEEDED>`.
|
||||
- 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.
|
||||
149
reme_ai/mem_tool/hands_off_tool.py
Normal file
149
reme_ai/mem_tool/hands_off_tool.py
Normal file
|
|
@ -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}")
|
||||
19
reme_ai/mem_tool/hands_off_tool.yaml
Normal file
19
reme_ai/mem_tool/hands_off_tool.yaml
Normal file
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue