From f841ccc4b956ad77269403cae6fc7399fb535f17 Mon Sep 17 00:00:00 2001 From: "jinli.yl" Date: Thu, 29 Jan 2026 00:12:03 +0800 Subject: [PATCH] feat(memory): enhance memory management with improved tool parameters and agent coordination --- reme/agent/memory/base_memory_agent.py | 9 +- .../memory/default/personal_retriever.yaml | 87 ++++++++++++------- .../memory/default/personal_summarizer.py | 4 +- .../memory/default/personal_summarizer.yaml | 82 ++++------------- reme/agent/memory/default/reme_retriever.yaml | 14 ++- reme/agent/memory/default/reme_summarizer.py | 2 +- .../agent/memory/default/reme_summarizer.yaml | 14 ++- reme/reme.py | 4 +- .../add_draft_and_retrieve_similar_memory.py | 32 +++++-- reme/tool/memory/delegate_task.py | 24 +++-- 10 files changed, 147 insertions(+), 125 deletions(-) diff --git a/reme/agent/memory/base_memory_agent.py b/reme/agent/memory/base_memory_agent.py index 0e945c02..6b97a1c2 100644 --- a/reme/agent/memory/base_memory_agent.py +++ b/reme/agent/memory/base_memory_agent.py @@ -1,5 +1,6 @@ """Base memory agent for handling memory operations with tool-based reasoning.""" +import json from abc import ABCMeta from ...core.enumeration import MemoryType @@ -57,7 +58,11 @@ class BaseMemoryAgent(BaseReact, metaclass=ABCMeta): @property def meta_memory_info(self) -> str: """Get the meta memory info from context.""" - lines = ["Format: - memory_target: memory_type memories about memory_target"] + lines = [] for memory_target, memory_type in self.memory_target_type_mapping.items(): - lines.append(f"- {memory_target}: {memory_type} memories about {memory_target}") + line = { + "agent": f"Agent managing {memory_type} memories for {memory_target}", + "memory_target": memory_target, + } + lines.append(json.dumps(line, ensure_ascii=False)) return "\n".join(lines) diff --git a/reme/agent/memory/default/personal_retriever.yaml b/reme/agent/memory/default/personal_retriever.yaml index 47de7c6d..b1044f9b 100644 --- a/reme/agent/memory/default/personal_retriever.yaml +++ b/reme/agent/memory/default/personal_retriever.yaml @@ -1,5 +1,5 @@ system_prompt: | - You are a memory retrieval Agent responsible for retrieving {memory_type} memories about {memory_target}. + You are a Memory Retrieval Agent specialized in retrieving {memory_type} memories about {memory_target}. ## User Profile {user_profile} @@ -7,39 +7,62 @@ system_prompt: | ## User Question {context} - ## Retrieval Strategy - ### Phase 1 `retrieve_memory` - - Purpose: Search for relevant memories using semantic similarity - - Try at least 3-5 different queries before moving to next phase: - * Direct question - * Direct question reformulation - * Different phrasings and perspectives - * Entity-focused queries (names, places, events) - * Various keyword combinations - - Time filter (optional): - * Format: single date '20200101' or range '20200101,20200102' - * Example: '20200101,20200102' for 20200101 <= time <= 20200102 - * Single-sided: '0,20200102' (before date) or '20200101,99999999' (after date) - - If no results: retry with different time ranges or remove time constraints + ## Multi-Phase Retrieval Strategy + Follow these phases sequentially to gather comprehensive information: - ### Phase 2 `read_history` - - Purpose: Read full original conversation context - - Use this ONLY after completing multiple retrieve_memory attempts - - Extract history_id from context - - Prioritize most relevant or recent history entries - - Read multiple histories if needed for complete understanding + ### Phase 1: Semantic Search (No Time Filter) + **Tool**: `retrieve_memory` (without time constraints) + **Objective**: Cast a wide net to find potentially relevant memories + **Approach**: + - Execute 3-5 diverse search queries using different formulations: + * Original question verbatim + * Rephrased variations (different wording, synonyms) + * Entity-focused queries (extract and search specific names, places, events) + * Keyword-based searches (core concepts, topics) + * Related context queries (broader themes) + - Review all results before proceeding to next phase - ## Response Requirements - - Answer ONLY based on retrieved memories / user profile / history - NO hallucination or inference - - Always cite the source: reference specific memories with their timestamps - - If information conflicts, present all versions with their respective times - - Try multiple search angles before concluding no information exists + ### Phase 2: Temporal Search (Optional) + **Tool**: `retrieve_memory` (with time filter) + **When to use**: Only if the user question contains temporal references (dates, time periods, "when", "recent", "last year", etc.) + **Time Filter Format**: + - Single date: `20200101` + - Date range: `20200101,20200102` (inclusive: 20200101 ≤ time ≤ 20200102) + - Before date: `0,20200102` (up to and including 20200102) + - After date: `20200101,99999999` (from 20200101 onwards) + **Approach**: + - Identify temporal constraints from the user question + - Refine Phase 1 queries with appropriate time filters + - Try multiple time ranges if initial searches yield no results - ### Output Format - 1. When answering, structure your response as follows: - - [timestamp][Relevant retrieved memories / user profile / history from context] - 2. If no relevant information found after thorough search (5+ queries), state: - "No relevant information found after thorough search using multiple query strategies." + ### Phase 3: Deep Dive into History + **Tool**: `read_history` + **When to use**: After exhausting retrieval attempts OR when specific conversation context is needed + **Approach**: + - Extract `history_id` from retrieved memory references + - Prioritize histories that are most relevant or recent + - Read multiple histories if necessary for complete context + - Use this to understand the full conversation surrounding a memory + + ## Response Guidelines + **Critical Rules**: + - Base your answer EXCLUSIVELY on retrieved memories, user profile, and history data + - Never infer, assume, or hallucinate information + - Always cite sources with timestamps: `[timestamp] Memory content` + - Present conflicting information transparently with respective timestamps + - Exhaust all search strategies before concluding information doesn't exist + + **Output Format**: + When information is found: + ``` + [timestamp] Relevant memory/profile/history content + [timestamp] Additional relevant content + ``` + + When no information is found after thorough search (5+ queries across phases): + ``` + No relevant information found after exhaustive search using multiple query strategies and retrieval phases. + ``` user_message: | - Answer the question following the retrieval strategy and response requirements above. \ No newline at end of file + Retrieve relevant memories following the multi-phase strategy outlined above. \ No newline at end of file diff --git a/reme/agent/memory/default/personal_summarizer.py b/reme/agent/memory/default/personal_summarizer.py index 11b26d81..624cbcce 100644 --- a/reme/agent/memory/default/personal_summarizer.py +++ b/reme/agent/memory/default/personal_summarizer.py @@ -99,6 +99,7 @@ class PersonalSummarizer(BaseMemoryAgent): else: tools_s2, messages_s2, success_s2 = [], [], True + answer = (messages_s1[-1].content if success_s1 else "") + (messages_s2[-1].content if success_s2 else "") success = success_s1 and success_s2 messages = messages_s1 + messages_s2 tools = tools_s1 + tools_s2 @@ -108,8 +109,9 @@ class PersonalSummarizer(BaseMemoryAgent): memory_nodes.extend(tool.memory_nodes) return { - "answer": memory_nodes, + "answer": answer, "success": success, "messages": messages, "tools": tools, + "memory_nodes": memory_nodes, } diff --git a/reme/agent/memory/default/personal_summarizer.yaml b/reme/agent/memory/default/personal_summarizer.yaml index 014da6e5..4867409e 100644 --- a/reme/agent/memory/default/personal_summarizer.yaml +++ b/reme/agent/memory/default/personal_summarizer.yaml @@ -1,51 +1,5 @@ -system_prompt_s1_zh: | - 你是一个记忆Agent,负责管理关于 {memory_target} 的 {memory_type} 类型记忆。 - - ## 最新对话 - Format: round [] : - {context} - - ## 任务 - ### 步骤1 - 根据`最新对话`的内容,在 `add_draft_and_retrieve_similar_memory` 中创建记忆草稿 `memory_draft`。 - 工具会根据memory_draft的内容行向量检索,返回历史相似记忆,确保在第二步的时候更好的管理记忆库记忆。 - - ### 步骤2 - 使用`update_memory`更新向量库记忆。 - 通过`memory_ids_to_delete`删除历史记忆,`memories_to_add`添加新记忆,包括message_time和memory_content。 - 要求: - - 原样提取最新对话中的内容,不得推断、假设或编造。 - - 最后记忆库包含所有的历史记忆和新的记忆,例如记录在同一个主题下用户不同时间的变化。 - - 最后记忆库有比较好的组织,同一主题的记忆放到同一条中,不要有重复/多余的记忆。 - -user_message_s1_zh: | - 严格按照步骤1和步骤2完成任务 - -system_prompt_s2_zh: | - 你是一个Profile Agent,负责管理关于 {memory_target} 的 Profile。 - - ## 最新对话 - Format: round [] : - {context} - - ## 任务 - ### 步骤1 - 根据`最新对话`的内容,在 `add_draft_and_read_all_profiles` 中创建记忆草稿 `profile_draft`。 - 工具会直接返回所有的Profile,确保在第二步的时候更好的管理Profile。 - - ### 步骤2 - 使用`update_profile`更新profile库。 - 通过`profile_ids_to_delete`删除历史Profile,`profiles_to_add`添加新Profile,包括message_time、profile_key和profile_value。 - 要求: - - 原样提取最新对话中的内容,不得推断、假设或编造。 - - 最后Profile库只保留用户最新的状态。例如用户开始喜欢吃苹果,后来只吃喜欢香蕉,可以记录:水果偏好:香蕉 - - 最后Profile库有比较好的组织,同一主题的Profile放到同一条中,不要有重复/多余的Profile。 - -user_message_s2_zh: | - 严格按照步骤1和步骤2完成任务 - system_prompt_s1: | - You are a Memory Agent responsible for managing {memory_type} type memories about {memory_target}. + You are a Memory Agent responsible for managing {memory_type} memories about {memory_target}. ## Latest Conversation Format: round [] : @@ -53,22 +7,23 @@ system_prompt_s1: | ## Task ### Step 1 - Based on the content of `Latest Conversation`, create a memory draft `memory_draft` in `add_draft_and_retrieve_similar_memory`. - The tool will perform vector retrieval based on the content of memory_draft and return historically similar memories to better manage the memory store in Step 2. + Create a memory draft in `add_draft_and_retrieve_similar_memory` based on the latest conversation. + Use actual names from the conversation (e.g., "Bob likes apples") instead of generic references (e.g., "user likes apples"). Always record memories with real names. + The tool will retrieve similar historical memories via vector search to help you consolidate the memory store in Step 2. ### Step 2 - Use `update_memory` to update the vector store memories. - Delete historical memories through `memory_ids_to_delete`, add new memories through `memories_to_add`, including message_time and memory_content. + Update the vector store using `update_memory`. + Remove outdated memories via `memory_ids_to_delete` and add new ones via `memories_to_add` with their message_time and memory_content. Requirements: - - Extract content from the latest conversation as-is, without inference, assumption, or fabrication. - - The final memory store should contain all historical memories and new memories, for example, recording user changes at different times under the same topic. - - The final memory store should be well-organized, with memories on the same topic placed in one entry, without duplicate/redundant memories. + - Extract only what's explicitly stated in the conversation—no inferences, assumptions, or fabrications. + - Preserve all relevant historical and new memories, capturing how things change over time within the same topic. + - Keep the memory store well-organized: group related memories together and eliminate redundancy. user_message_s1: | - Strictly complete the task following Step 1 and Step 2 + Complete the task by following Step 1 and Step 2 in order system_prompt_s2: | - You are a Profile Agent responsible for managing the Profile about {memory_target}. + You are a Profile Agent responsible for managing profiles about {memory_target}. ## Latest Conversation Format: round [] : @@ -76,16 +31,15 @@ system_prompt_s2: | ## Task ### Step 1 - Based on the content of `Latest Conversation`, create a profile draft `profile_draft` in `add_draft_and_read_all_profiles`. - The tool will directly return all Profiles to better manage the Profile store in Step 2. + Create a profile draft in `add_draft_and_read_all_profiles` based on the latest conversation. + The tool will return all existing profiles to help you maintain the profile store in Step 2. ### Step 2 - Use `update_profile` to update the profile store. - Delete historical Profiles through `profile_ids_to_delete`, add new Profiles through `profiles_to_add`, including message_time, profile_key, and profile_value. + Update the profile store using `update_profile`. + Remove conflicting or redundant entries profiles via `profile_ids_to_delete` and add new ones via `profiles_to_add` with their message_time, profile_key, and profile_value. Requirements: - - Extract content from the latest conversation as-is, without inference, assumption, or fabrication. - - The final Profile store should only keep the user's latest state. For example, if the user initially liked apples but later only likes bananas, record: Fruit preference: banana - - The final Profile store should be well-organized, with Profiles on the same topic placed in one entry, without duplicate/redundant Profiles. + - Extract only what's explicitly stated in the conversation—no inferences, assumptions, or fabrications. + - Keep the profile store well-organized: group related profiles together and eliminate duplicates. user_message_s2: | - Strictly complete the task following Step 1 and Step 2 + Complete the task by following Step 1 and Step 2 in order diff --git a/reme/agent/memory/default/reme_retriever.yaml b/reme/agent/memory/default/reme_retriever.yaml index 8db310db..84fb6b4e 100644 --- a/reme/agent/memory/default/reme_retriever.yaml +++ b/reme/agent/memory/default/reme_retriever.yaml @@ -5,15 +5,21 @@ system_prompt: | {context} ## Available Memory Agents - Each line indicates a specialized Memory Agent that is an expert for retrieving memories about a specific memory_target. + Each line below is a JSON object representing a specialized Memory Agent: + - `agent`: Description of what this agent specializes in + - `memory_target`: The unique identifier for this agent (THIS IS WHAT YOU MUST USE) + {meta_memory_info} ## Your Task Analyze the context and delegate retrieval tasks to appropriate specialized agents: - 1. Examine the context content and identify which memory_target(s) are relevant for retrieving information + 1. Examine the context content and identify which memory_target(s) from the "Available Memory Agents" list above should be queried for relevant information 2. For each relevant memory_target, delegate the retrieval task to its corresponding specialized agent - - The memory_target must **exactly match** existing entries in the "Available Memory Agents" listed above - - Do NOT delegate to agents that don't exist above + - **CRITICAL**: The memory_target must be EXACTLY one of the `memory_target` field values from the JSON objects listed in "Available Memory Agents" above + - **DO NOT** extract or create new memory_target names from the context content + - **DO NOT** use topic names, entity names, descriptions, or any other identifiers from the context as memory_targets + - **DO NOT** use the agent description text as memory_target + - **ONLY** use the exact string values from the `memory_target` fields in the JSON objects above - Each memory_target should be assigned **only once** - do not duplicate assignments 3. Use the `delegate_task` tool **once** with all relevant memory_target(s) to enable parallel processing by specialized agents diff --git a/reme/agent/memory/default/reme_summarizer.py b/reme/agent/memory/default/reme_summarizer.py index 5473e71e..934863bb 100644 --- a/reme/agent/memory/default/reme_summarizer.py +++ b/reme/agent/memory/default/reme_summarizer.py @@ -85,7 +85,7 @@ class ReMeSummarizer(BaseMemoryAgent): success = success and agent.response.success messages.extend(agent.response.metadata["messages"]) tools.extend(agent.response.metadata["tools"]) - memory_nodes.extend(agent.response.answer) + memory_nodes.extend(agent.response.metadata["memory_nodes"]) return { "answer": memory_nodes, diff --git a/reme/agent/memory/default/reme_summarizer.yaml b/reme/agent/memory/default/reme_summarizer.yaml index 01a2f4bc..7d235060 100644 --- a/reme/agent/memory/default/reme_summarizer.yaml +++ b/reme/agent/memory/default/reme_summarizer.yaml @@ -5,15 +5,21 @@ system_prompt: | {context} ## Available Memory Agents - Each line indicates a specialized Memory Agent that is an expert for summarizing memories about a specific memory_target. + Each line below is a JSON object representing a specialized Memory Agent: + - `agent`: Description of what this agent specializes in + - `memory_target`: The unique identifier for this agent (THIS IS WHAT YOU MUST USE) + {meta_memory_info} ## Your Task Analyze the context and delegate summarization tasks to appropriate specialized agents: - 1. Examine the context content and identify which memory_target(s) are relevant for storing information + 1. Examine the context content and identify which memory_target(s) from the "Available Memory Agents" list above should receive this information 2. For each relevant memory_target, delegate the summarization task to its corresponding specialized agent - - The memory_target must **exactly match** existing entries in the "Available Memory Agents" listed above - - Do NOT delegate to agents that don't exist above + - **CRITICAL**: The memory_target must be EXACTLY one of the `memory_target` field values from the JSON objects listed in "Available Memory Agents" above + - **DO NOT** extract or create new memory_target names from the context content + - **DO NOT** use topic names, preference categories, descriptions, or any other identifiers from the context as memory_targets + - **DO NOT** use the agent description text as memory_target + - **ONLY** use the exact string values from the `memory_target` fields in the JSON objects above - Each memory_target should be assigned **only once** - do not duplicate assignments 3. Use the `delegate_task` tool **once** with all relevant memory_target(s) to enable parallel processing by specialized agents diff --git a/reme/reme.py b/reme/reme.py index 30d97bf0..d23daf6e 100644 --- a/reme/reme.py +++ b/reme/reme.py @@ -101,7 +101,7 @@ class ReMe(Application): user_name: str | list[str] = "", task_name: str | list[str] = "", tool_name: str | list[str] = "", - enable_thinking_params: bool = False, + enable_thinking_params: bool = True, version: str = "default", retrieve_top_k: int = 20, return_dict: bool = False, @@ -211,7 +211,7 @@ class ReMe(Application): user_name: str | list[str] = "", task_name: str | list[str] = "", tool_name: str | list[str] = "", - enable_thinking_params: bool = False, + enable_thinking_params: bool = True, version: str = "default", retrieve_top_k: int = 20, enable_time_filter: bool = True, diff --git a/reme/tool/memory/add_draft_and_retrieve_similar_memory.py b/reme/tool/memory/add_draft_and_retrieve_similar_memory.py index cb537fc4..fd3d10c8 100644 --- a/reme/tool/memory/add_draft_and_retrieve_similar_memory.py +++ b/reme/tool/memory/add_draft_and_retrieve_similar_memory.py @@ -11,25 +11,43 @@ from ...core.utils import deduplicate_memories class AddDraftAndRetrieveSimilarMemory(BaseMemoryTool): """Tool to add draft memory and retrieve similar memories""" - def __init__(self, top_k: int = 20, enable_memory_target: bool = False, **kwargs): + def __init__( + self, + top_k: int = 20, + enable_memory_target: bool = False, + enable_when_to_use: bool = False, + **kwargs, + ): super().__init__(**kwargs) self.top_k: int = top_k self.enable_memory_target: bool = enable_memory_target + self.enable_when_to_use: bool = enable_when_to_use def _build_query_parameters(self) -> dict: """Build the query parameters schema""" properties = { - "memory_draft": { + "message_time": { "type": "string", - "description": "memory_draft", + "description": "message time, e.g. '2020-01-01 00:00:00'", + }, + "memory_content": { + "type": "string", + "description": "content of the memory.", }, } - required = ["memory_draft"] + required = ["message_time", "memory_content"] + + if self.enable_when_to_use: + properties["when_to_use"] = { + "type": "string", + "description": "description of when to use this memory.", + } + required.append("when_to_use") if self.enable_memory_target: properties["memory_target"] = { "type": "string", - "description": "memory_target", + "description": "target memory type for this memory.", } required.append("memory_target") @@ -56,7 +74,7 @@ class AddDraftAndRetrieveSimilarMemory(BaseMemoryTool): "properties": { "draft_items": { "type": "array", - "description": "List of draft memory items.", + "description": "draft_items", "items": self._build_query_parameters(), }, }, @@ -82,7 +100,7 @@ class AddDraftAndRetrieveSimilarMemory(BaseMemoryTool): queries_by_target[target].append( { - "query": item["memory_draft"], + "query": item["memory_content"], "limit": self.top_k, "filters": {}, }, diff --git a/reme/tool/memory/delegate_task.py b/reme/tool/memory/delegate_task.py index 2914d4d5..d27f4cd4 100644 --- a/reme/tool/memory/delegate_task.py +++ b/reme/tool/memory/delegate_task.py @@ -31,24 +31,32 @@ class DelegateTask(BaseMemoryTool): "parameters": { "type": "object", "properties": { - "memory_target_tasks": { + "tasks": { "type": "array", - "description": "List of memory_target tasks to delegate to specific memory agents", + "description": "List of tasks to delegate to specific memory agents", "items": { - "type": "string", - "description": "A memory_target identifier to delegate to the corresponding agent", + "type": "object", + "description": "A task item", + "properties": { + "memory_target": { + "type": "string", + "description": "The memory_target identifier to " + "delegate to the corresponding agent", + }, + }, + "required": ["memory_target"], }, }, }, - "required": ["memory_target_tasks"], + "required": ["tasks"], }, }, ) async def execute(self): - # Deduplicate and validate memory_target_tasks - memory_target_tasks = self.context.get("memory_target_tasks", []) - memory_target_tasks = sorted(set(memory_target_tasks)) + # Deduplicate and validate tasks + tasks = self.context.get("tasks", []) + memory_target_tasks = sorted(set(task["memory_target"] for task in tasks)) # Submit memory_target_tasks to agents agent_list: list[BaseMemoryAgent] = []