refactor(memory): restructure tool and procedural memory agents with enhanced capabilities

This commit is contained in:
jinli.yl 2026-02-27 16:17:22 +08:00
parent 2a1953ac6b
commit c4e6225336
13 changed files with 508 additions and 58 deletions

View file

@ -7,8 +7,8 @@ from .procedural.procedural_retriever import ProceduralRetriever
from .procedural.procedural_summarizer import ProceduralSummarizer
from .reme_retriever import ReMeRetriever
from .reme_summarizer import ReMeSummarizer
from .tool.tool_retriever import ToolRetriever
from .tool.tool_summarizer import ToolSummarizer
from .tool_call.tool_retriever import ToolRetriever
from .tool_call.tool_summarizer import ToolSummarizer
from ...core import R
__all__ = [

View file

@ -1,36 +1,82 @@
"""Procedural memory retriever agent implementation."""
"""Procedural memory retriever agent for retrieving procedural memories through vector search."""
from ..base_memory_agent import BaseMemoryAgent
from ....core.enumeration import Role, MemoryType
from ....core.op import BaseTool
from ....core.schema import Message
from ....core.utils import format_messages
class ProceduralRetriever(BaseMemoryAgent):
"""Agent responsible for retrieving procedural memories."""
"""Retrieve procedural memories through vector search and history reading.
Procedural memories represent "how-to" knowledge including:
- Workflows and step-by-step instructions
- Task execution patterns and best practices
- Success and failure patterns from past experiences
"""
memory_type: MemoryType = MemoryType.PROCEDURAL
def __init__(self, return_memory_nodes: bool = False, **kwargs):
super().__init__(**kwargs)
self.return_memory_nodes: bool = return_memory_nodes
async def build_messages(self) -> list[Message]:
"""Build messages with system prompt and user message."""
"""Build messages with procedural memory retrieval context."""
if self.context.get("query"):
context = self.context.query
elif self.context.get("messages"):
context = format_messages(self.context.messages)
context = self.description + "\n" + format_messages(self.context.messages)
else:
raise ValueError("input must have either `query` or `messages`")
return [
Message(
role=Role.SYSTEM,
role=Role.USER,
content=self.prompt_format(
prompt_name="system_prompt",
meta_memory_info=await self._read_meta_memories(),
context=context,
prompt_name="user_message",
memory_type=self.memory_type.value,
memory_target=self.memory_target,
context=context.strip(),
),
),
Message(
role=Role.USER,
content=self.get_prompt("user_message"),
),
]
async def _acting_step(
self,
assistant_message: Message,
tools: list[BaseTool],
step: int,
stage: str = "",
**kwargs,
) -> tuple[list[BaseTool], list[Message]]:
"""Execute tool calls with memory context."""
return await super()._acting_step(
assistant_message,
tools,
step,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
retrieved_nodes=self.retrieved_nodes,
**kwargs,
)
async def execute(self):
result = await super().execute()
if self.return_memory_nodes:
result["answer"] = "\n".join(
[
n.format(
include_memory_id=False,
include_when_to_use=True,
include_content=True,
include_message_time=False,
ref_memory_id_key="",
)
for n in self.retrieved_nodes
],
)
result["retrieved_nodes"] = self.retrieved_nodes
return result

View file

@ -0,0 +1,43 @@
user_message: |
You are a Memory Retrieval Agent specialized in retrieving {memory_type} memories about {memory_target}.
## User Task/Context
{context}
## Retrieval Strategy
Follow these phases to gather comprehensive procedural knowledge:
### Phase 1: Semantic Search
**Tool**: `retrieve_memory`
**Objective**: Find relevant procedural knowledge (workflows, best practices, lessons learned)
**Approach**:
- Execute 3-5 diverse search queries using different formulations:
* Task-oriented queries (e.g., "how to accomplish X", "steps for Y")
* Experience-based queries (e.g., "successful approach for X", "what worked for Y")
* Problem-focused queries (e.g., "common issues with X", "solutions for Y")
* Pattern-based queries (e.g., "best practices for X", "recommended workflow for Y")
* Context-specific queries (extract specific tools, techniques, or domains mentioned)
### Phase 2: Deep Dive into History
**Tool**: `read_history`
**When to use**: After exhausting retrieval attempts OR when specific execution context is needed
**Important Constraints**:
- Each history is very long and resource-intensive to read
- **Maximum limit: Read no more than 3 histories total**
- Only use this phase when absolutely necessary for understanding the full execution context
**Approach**:
- Extract `history_id` from retrieved memory references
- Prioritize the most relevant histories
- Can read multiple histories at once by passing multiple history_ids
- Be selective: choose only the top 1-3 most promising histories
- Use this to understand the complete task execution flow surrounding a memory
## Response Guidelines
- Base your answer EXCLUSIVELY on retrieved procedural memories and history data
- Focus on actionable "how-to" knowledge: workflows, steps, best practices
- Never infer, assume, or hallucinate procedures not found in memories
- Highlight both success patterns and failure lessons when available
- If you find sufficient information to answer the user's question, you may output directly without exhausting all search phases
- Exhaust all search strategies before concluding information doesn't exist
Output a summary of all retrieved procedural memories and relevant history data.

View file

@ -1,47 +1,84 @@
"""Procedural memory summarizer agent implementation."""
"""Procedural memory summarizer agent for extracting and storing procedural knowledge."""
from loguru import logger
from ..base_memory_agent import BaseMemoryAgent
from ....core.enumeration import Role, MemoryType
from ....core.op import BaseTool
from ....core.schema import Message
from ....core.utils import format_messages
class ProceduralSummarizer(BaseMemoryAgent):
"""Agent responsible for summarizing procedural memories."""
"""Extract and store procedural memories from task execution trajectories.
Procedural memories capture "how-to" knowledge including:
- Successful workflows and step-by-step approaches
- Lessons learned from failures and mistakes
- Best practices and optimization patterns
- Task execution strategies and techniques
"""
memory_type: MemoryType = MemoryType.PROCEDURAL
async def build_messages(self) -> list[Message]:
"""Build messages for procedural memory extraction."""
return [
Message(
role=Role.SYSTEM,
role=Role.USER,
content=self.prompt_format(
prompt_name="system_prompt",
context=self.description + "\n" + format_messages(self.get_messages()),
outcome="successful task completion" if self.success else "task failure",
prompt_name="user_message",
context=self.context.history_node.content,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
),
),
Message(
role=Role.USER,
content=self.get_prompt("user_message"),
),
]
async def _reasoning_step(self, messages: list[Message], step: int, **kwargs) -> tuple[Message, bool]:
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 memory_target, memory_type, and author context."""
messages: list[Message] = await super()._acting_step(
async def _acting_step(
self,
assistant_message: Message,
tools: list[BaseTool],
step: int,
stage: str = "",
**kwargs,
) -> tuple[list[BaseTool], list[Message]]:
"""Execute tool calls with memory context."""
return await super()._acting_step(
assistant_message,
tools,
step,
stage=stage,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
ref_memory_id=self.ref_memory_id,
history_node=self.history_node,
author=self.author,
retrieved_nodes=self.retrieved_nodes,
**kwargs,
)
return messages
async def execute(self):
"""Execute procedural memory extraction."""
# Log available tools
for i, tool in enumerate(self.tools):
logger.info(f"[{self.__class__.__name__}] tool_call[{i}]={tool.tool_call.simple_input_dump(as_dict=False)}")
messages = await self.build_messages()
for i, message in enumerate(messages):
role = message.name or message.role
logger.info(f"[{self.__class__.__name__}] role={role} {message.simple_dump(as_dict=False)}")
tools, messages, success = await self.react(messages, self.tools)
answer = messages[-1].content if success and messages else ""
memory_nodes = []
for tool in tools:
if tool.memory_nodes:
memory_nodes.extend(tool.memory_nodes)
return {
"answer": answer,
"success": success,
"messages": messages,
"tools": tools,
"memory_nodes": memory_nodes,
}

View file

@ -0,0 +1,35 @@
user_message: |
You are a Memory Agent responsible for managing {memory_type} memories about {memory_target}.
## Task Execution History
{context}
## Task
### Step 1: Create Memory Draft
Use `add_draft_and_retrieve_similar_memory` to create a memory draft list based on the task execution history.
- For each memory draft, fill in the required parameters:
* `memory_content`: concise procedural knowledge extracted from the execution
- Focus on extracting actionable "how-to" knowledge:
* Successful approaches: "When doing X, approach Y works well because..."
* Failure patterns: "Avoid doing X when Y because it leads to..."
* Best practices: "Always check X before doing Y to ensure..."
* Workflow patterns: "The optimal sequence for X is: step1 → step2 → step3"
* Problem-solution pairs: "When encountering X issue, the solution is Y"
- Extract all important procedural insights comprehensively—do not miss critical patterns
- The tool will retrieve similar historical memories via vector search to help you in Step 2
### Step 2: Add New Memories
Review each memory draft from Step 1 and compare it with the retrieved historical memories, then use `add_memory` to add new memories:
**Parameters for each memory:**
- `memory_content`: procedural knowledge content
**When to add:**
- Add memories that capture unique procedural insights not already covered
- Add memories that provide more specific/detailed guidance than existing ones
- Add memories that document new success patterns or failure lessons
**When to skip:**
- **Skip** drafts if their content is already fully covered by historical memories (avoid redundancy)
- **Skip** drafts that are too generic or not actionable
- **Skip** drafts that describe facts rather than procedures (e.g., "X happened" vs "When X happens, do Y")

View file

@ -1,10 +0,0 @@
"""Tool memory retriever agent implementation."""
from ..base_memory_agent import BaseMemoryAgent
from ....core.enumeration import MemoryType
class ToolRetriever(BaseMemoryAgent):
"""Agent responsible for retrieving tool-related memories."""
memory_type: MemoryType = MemoryType.TOOL

View file

@ -1,10 +0,0 @@
"""Tool memory summarizer agent implementation."""
from ..base_memory_agent import BaseMemoryAgent
from ....core.enumeration import MemoryType
class ToolSummarizer(BaseMemoryAgent):
"""Agent responsible for summarizing tool-related memories."""
memory_type: MemoryType = MemoryType.TOOL

View file

@ -0,0 +1,83 @@
"""Tool memory retriever agent for retrieving tool usage experiences through vector search."""
from ..base_memory_agent import BaseMemoryAgent
from ....core.enumeration import Role, MemoryType
from ....core.op import BaseTool
from ....core.schema import Message
from ....core.utils import format_messages
class ToolRetriever(BaseMemoryAgent):
"""Retrieve tool memories through vector search and history reading.
Tool memories represent knowledge about tool usage including:
- Successful tool invocations and their parameters
- Failed tool calls and lessons learned
- Tool selection strategies for different scenarios
- Parameter optimization patterns
"""
memory_type: MemoryType = MemoryType.TOOL
def __init__(self, return_memory_nodes: bool = False, **kwargs):
super().__init__(**kwargs)
self.return_memory_nodes: bool = return_memory_nodes
async def build_messages(self) -> list[Message]:
"""Build messages with tool memory retrieval context."""
if self.context.get("query"):
context = self.context.query
elif self.context.get("messages"):
context = self.description + "\n" + format_messages(self.context.messages)
else:
raise ValueError("input must have either `query` or `messages`")
return [
Message(
role=Role.USER,
content=self.prompt_format(
prompt_name="user_message",
memory_type=self.memory_type.value,
memory_target=self.memory_target,
context=context.strip(),
),
),
]
async def _acting_step(
self,
assistant_message: Message,
tools: list[BaseTool],
step: int,
stage: str = "",
**kwargs,
) -> tuple[list[BaseTool], list[Message]]:
"""Execute tool calls with memory context."""
return await super()._acting_step(
assistant_message,
tools,
step,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
retrieved_nodes=self.retrieved_nodes,
**kwargs,
)
async def execute(self):
result = await super().execute()
if self.return_memory_nodes:
result["answer"] = "\n".join(
[
n.format(
include_memory_id=False,
include_when_to_use=True,
include_content=True,
include_message_time=False,
ref_memory_id_key="",
)
for n in self.retrieved_nodes
],
)
result["retrieved_nodes"] = self.retrieved_nodes
return result

View file

@ -0,0 +1,44 @@
user_message: |
You are a Memory Retrieval Agent specialized in retrieving {memory_type} memories about {memory_target}.
## User Task/Context
{context}
## Retrieval Strategy
Follow these phases to gather comprehensive tool usage knowledge:
### Phase 1: Semantic Search
**Tool**: `retrieve_memory`
**Objective**: Find relevant tool usage experiences (successful patterns, failure lessons, parameter insights)
**Approach**:
- Execute 3-5 diverse search queries using different formulations:
* Tool-specific queries (e.g., "how to use tool X", "parameters for tool Y")
* Scenario-based queries (e.g., "which tool for task X", "tool selection for Y")
* Problem-focused queries (e.g., "tool X failed because", "error handling for tool Y")
* Parameter-focused queries (e.g., "optimal parameters for X", "configuration for Y")
* Success pattern queries (e.g., "successful use of tool X", "best results with Y")
### Phase 2: Deep Dive into History
**Tool**: `read_history`
**When to use**: After exhausting retrieval attempts OR when specific tool invocation context is needed
**Important Constraints**:
- Each history is very long and resource-intensive to read
- **Maximum limit: Read no more than 3 histories total**
- Only use this phase when absolutely necessary for understanding the full tool usage context
**Approach**:
- Extract `history_id` from retrieved memory references
- Prioritize the most relevant histories
- Can read multiple histories at once by passing multiple history_ids
- Be selective: choose only the top 1-3 most promising histories
- Use this to understand the complete tool invocation flow surrounding a memory
## Response Guidelines
- Base your answer EXCLUSIVELY on retrieved tool memories and history data
- Focus on actionable tool usage knowledge: when to use, how to configure, what to avoid
- Never infer, assume, or hallucinate tool behaviors not found in memories
- Highlight both successful patterns and failure lessons when available
- Include specific parameter recommendations when available
- If you find sufficient information to answer the user's question, you may output directly without exhausting all search phases
- Exhaust all search strategies before concluding information doesn't exist
Output a summary of all retrieved tool memories and relevant history data.

View file

@ -0,0 +1,84 @@
"""Tool memory summarizer agent for extracting and storing tool usage experiences."""
from loguru import logger
from ..base_memory_agent import BaseMemoryAgent
from ....core.enumeration import Role, MemoryType
from ....core.op import BaseTool
from ....core.schema import Message
class ToolSummarizer(BaseMemoryAgent):
"""Extract and store tool memories from task execution trajectories.
Tool memories capture knowledge about tool usage including:
- Successful tool invocations with effective parameters
- Failed tool calls and why they failed
- Tool selection strategies for different scenarios
- Parameter optimization insights
"""
memory_type: MemoryType = MemoryType.TOOL
async def build_messages(self) -> list[Message]:
"""Build messages for tool memory extraction."""
return [
Message(
role=Role.USER,
content=self.prompt_format(
prompt_name="user_message",
context=self.context.history_node.content,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
),
),
]
async def _acting_step(
self,
assistant_message: Message,
tools: list[BaseTool],
step: int,
stage: str = "",
**kwargs,
) -> tuple[list[BaseTool], list[Message]]:
"""Execute tool calls with memory context."""
return await super()._acting_step(
assistant_message,
tools,
step,
stage=stage,
memory_type=self.memory_type.value,
memory_target=self.memory_target,
history_node=self.history_node,
author=self.author,
retrieved_nodes=self.retrieved_nodes,
**kwargs,
)
async def execute(self):
"""Execute tool memory extraction."""
# Log available tools
for i, tool in enumerate(self.tools):
logger.info(f"[{self.__class__.__name__}] tool_call[{i}]={tool.tool_call.simple_input_dump(as_dict=False)}")
messages = await self.build_messages()
for i, message in enumerate(messages):
role = message.name or message.role
logger.info(f"[{self.__class__.__name__}] role={role} {message.simple_dump(as_dict=False)}")
tools, messages, success = await self.react(messages, self.tools)
answer = messages[-1].content if success and messages else ""
memory_nodes = []
for tool in tools:
if tool.memory_nodes:
memory_nodes.extend(tool.memory_nodes)
return {
"answer": answer,
"success": success,
"messages": messages,
"tools": tools,
"memory_nodes": memory_nodes,
}

View file

@ -0,0 +1,36 @@
user_message: |
You are a Memory Agent responsible for managing {memory_type} memories about {memory_target}.
## Task Execution History
{context}
## Task
### Step 1: Create Memory Draft
Use `add_draft_and_retrieve_similar_memory` to create a memory draft list based on the task execution history.
- For each memory draft, fill in the required parameters:
* `memory_content`: concise tool usage knowledge extracted from the execution
- Focus on extracting actionable tool usage insights:
* Successful patterns: "Tool X works well for task Y with parameters Z"
* Failure lessons: "Tool X fails when Y because Z, use alternative A instead"
* Parameter insights: "For best results with tool X, set parameter Y to Z"
* Selection guidance: "When facing scenario X, prefer tool Y over Z because..."
* Error handling: "If tool X returns error Y, the solution is Z"
- Extract all important tool usage insights comprehensively—do not miss critical patterns
- The tool will retrieve similar historical memories via vector search to help you in Step 2
### Step 2: Add New Memories
Review each memory draft from Step 1 and compare it with the retrieved historical memories, then use `add_memory` to add new memories:
**Parameters for each memory:**
- `memory_content`: tool usage knowledge content
**When to add:**
- Add memories that capture unique tool usage insights not already covered
- Add memories that provide more specific parameter recommendations
- Add memories that document new success patterns or failure lessons
- Add memories that clarify tool selection criteria
**When to skip:**
- **Skip** drafts if their content is already fully covered by historical memories (avoid redundancy)
- **Skip** drafts that are too generic or not actionable
- **Skip** drafts that describe tool invocations without insights (e.g., "Tool X was called" vs "Tool X succeeded because Y")

View file

@ -191,7 +191,7 @@ class ReMe(Application):
enable_multiple=True,
),
ReadAllProfiles(
enable_thinking_params=enable_thinking_params,
enable_thinking_params=False,
enable_memory_target=False,
profile_dir=self.profile_dir,
),
@ -207,8 +207,42 @@ class ReMe(Application):
else:
raise NotImplementedError(f"version={version} is not supported")
procedural_summarizer: BaseMemoryAgent = ProceduralSummarizer(tools=[])
tool_summarizer: BaseMemoryAgent = ToolSummarizer(tools=[])
procedural_summarizer: BaseMemoryAgent = ProceduralSummarizer(
llm=llm_config_name,
tools=[
AddDraftAndRetrieveSimilarMemory(
enable_thinking_params=enable_thinking_params,
enable_memory_target=False,
enable_when_to_use=False,
enable_multiple=True,
top_k=retrieve_top_k,
),
AddMemory(
enable_thinking_params=enable_thinking_params,
enable_memory_target=False,
enable_when_to_use=False,
enable_multiple=True,
),
],
)
tool_summarizer: BaseMemoryAgent = ToolSummarizer(
llm=llm_config_name,
tools=[
AddDraftAndRetrieveSimilarMemory(
enable_thinking_params=enable_thinking_params,
enable_memory_target=False,
enable_when_to_use=False,
enable_multiple=True,
top_k=retrieve_top_k,
),
AddMemory(
enable_thinking_params=enable_thinking_params,
enable_memory_target=False,
enable_when_to_use=False,
enable_multiple=True,
),
],
)
memory_agents = []
memory_targets = []
@ -313,8 +347,36 @@ class ReMe(Application):
else:
raise NotImplementedError(f"version={version} is not supported")
procedural_retriever: BaseMemoryAgent = ProceduralRetriever(tools=[])
tool_retriever: BaseMemoryAgent = ToolRetriever(tools=[])
procedural_retriever: BaseMemoryAgent = ProceduralRetriever(
llm=llm_config_name,
tools=[
RetrieveMemory(
top_k=retrieve_top_k,
enable_thinking_params=enable_thinking_params,
enable_time_filter=False,
enable_multiple=True,
),
ReadHistory(
enable_thinking_params=enable_thinking_params,
enable_multiple=True,
),
],
)
tool_retriever: BaseMemoryAgent = ToolRetriever(
llm=llm_config_name,
tools=[
RetrieveMemory(
top_k=retrieve_top_k,
enable_thinking_params=enable_thinking_params,
enable_time_filter=False,
enable_multiple=True,
),
ReadHistory(
enable_thinking_params=enable_thinking_params,
enable_multiple=True,
),
],
)
memory_agents = []
memory_targets = []