diff --git a/memoryscope/memoryscope/contrib/example_query_worker.py b/memoryscope/memoryscope/contrib/example_query_worker.py
index 90821522..7337534a 100644
--- a/memoryscope/memoryscope/contrib/example_query_worker.py
+++ b/memoryscope/memoryscope/contrib/example_query_worker.py
@@ -8,7 +8,7 @@ from memoryscope.enumeration.message_role_enum import MessageRoleEnum
class ExampleQueryWorker(MemoryBaseWorker):
# NOTE: If you want to utilize the capabilities of the prompt handler, please be sure to include this sentence.
- FILE_PATH: str = __file__
+ file_path: str = __file__
def _parse_params(self, **kwargs):
self.rewrite_history_count: int = kwargs.get("rewrite_history_count", 2)
diff --git a/memoryscope/memoryscope/core/worker/backend/contra_repeat_worker.py b/memoryscope/memoryscope/core/worker/backend/contra_repeat_worker.py
index 9507b23b..7e271c5d 100644
--- a/memoryscope/memoryscope/core/worker/backend/contra_repeat_worker.py
+++ b/memoryscope/memoryscope/core/worker/backend/contra_repeat_worker.py
@@ -20,7 +20,7 @@ class ContraRepeatWorker(MemoryBaseWorker):
- Adjusts the status of memory nodes based on the analysis.
- Persists the updated node statuses back into memory.
"""
- FILE_PATH: str = __file__
+ file_path: str = __file__
def _parse_params(self, **kwargs):
self.generation_model_kwargs: dict = kwargs.get("generation_model_kwargs", {})
diff --git a/memoryscope/memoryscope/core/worker/backend/get_observation_with_time_worker.py b/memoryscope/memoryscope/core/worker/backend/get_observation_with_time_worker.py
index 4bea129a..fa74f99b 100644
--- a/memoryscope/memoryscope/core/worker/backend/get_observation_with_time_worker.py
+++ b/memoryscope/memoryscope/core/worker/backend/get_observation_with_time_worker.py
@@ -12,7 +12,7 @@ class GetObservationWithTimeWorker(GetObservationWorker):
A specialized worker class that extends GetObservationWorker functionality to handle
retrieval of observations which include associated timestamp information from chat messages.
"""
- FILE_PATH: str = __file__
+ file_path: str = __file__
OBS_STORE_KEY: str = NEW_OBS_WITH_TIME_NODES
def filter_messages(self) -> List[Message]:
diff --git a/memoryscope/memoryscope/core/worker/backend/get_observation_worker.py b/memoryscope/memoryscope/core/worker/backend/get_observation_worker.py
index b43f8fca..9d38a112 100644
--- a/memoryscope/memoryscope/core/worker/backend/get_observation_worker.py
+++ b/memoryscope/memoryscope/core/worker/backend/get_observation_worker.py
@@ -15,7 +15,7 @@ class GetObservationWorker(MemoryBaseWorker):
"""
A specialized worker class to generate the observations from the original chat histories.
"""
- FILE_PATH: str = __file__
+ file_path: str = __file__
OBS_STORE_KEY: str = NEW_OBS_NODES
def _parse_params(self, **kwargs):
diff --git a/memoryscope/memoryscope/core/worker/backend/get_reflection_subject_worker.py b/memoryscope/memoryscope/core/worker/backend/get_reflection_subject_worker.py
index aa24a833..bcbcb74e 100644
--- a/memoryscope/memoryscope/core/worker/backend/get_reflection_subject_worker.py
+++ b/memoryscope/memoryscope/core/worker/backend/get_reflection_subject_worker.py
@@ -16,7 +16,7 @@ class GetReflectionSubjectWorker(MemoryBaseWorker):
generating reflection prompts with current insights, invoking an LLM for fresh insights,
parsing the LLM responses, forming new insight nodes, and updating memory statuses accordingly.
"""
- FILE_PATH: str = __file__
+ file_path: str = __file__
def _parse_params(self, **kwargs):
self.reflect_obs_cnt_threshold: int = kwargs.get("reflect_obs_cnt_threshold", 10)
diff --git a/memoryscope/memoryscope/core/worker/backend/info_filter_worker.py b/memoryscope/memoryscope/core/worker/backend/info_filter_worker.py
index 648a45cf..cba420cd 100644
--- a/memoryscope/memoryscope/core/worker/backend/info_filter_worker.py
+++ b/memoryscope/memoryscope/core/worker/backend/info_filter_worker.py
@@ -13,7 +13,7 @@ class InfoFilterWorker(MemoryBaseWorker):
model to process this prompt, parses the AI's generated response to allocate scores, and ultimately retains
messages in `self.chat_messages` based on these assigned scores.
"""
- FILE_PATH: str = __file__
+ file_path: str = __file__
def _parse_params(self, **kwargs):
self.preserved_scores: str = kwargs.get("preserved_scores", "2,3")
diff --git a/memoryscope/memoryscope/core/worker/backend/long_contra_repeat_worker.py b/memoryscope/memoryscope/core/worker/backend/long_contra_repeat_worker.py
index cc2a8556..59f66eaf 100644
--- a/memoryscope/memoryscope/core/worker/backend/long_contra_repeat_worker.py
+++ b/memoryscope/memoryscope/core/worker/backend/long_contra_repeat_worker.py
@@ -17,7 +17,7 @@ class LongContraRepeatWorker(MemoryBaseWorker):
to provide specialized functionality for long conversations with potential
contradictory or repetitive statements.
"""
- FILE_PATH: str = __file__
+ file_path: str = __file__
def _parse_params(self, **kwargs):
self.unit_test_flag = False
diff --git a/memoryscope/memoryscope/core/worker/backend/update_insight_worker.py b/memoryscope/memoryscope/core/worker/backend/update_insight_worker.py
index 7bee8aa0..b4bcbcdc 100644
--- a/memoryscope/memoryscope/core/worker/backend/update_insight_worker.py
+++ b/memoryscope/memoryscope/core/worker/backend/update_insight_worker.py
@@ -18,7 +18,7 @@ class UpdateInsightWorker(MemoryBaseWorker):
generates refreshed insights via an LLM, and manages node statuses and content updates,
incorporating features for concurrent execution and logging.
"""
- FILE_PATH: str = __file__
+ file_path: str = __file__
def _parse_params(self, **kwargs):
self.update_insight_threshold: float = kwargs.get("update_insight_threshold", 0.1)
diff --git a/memoryscope/memoryscope/core/worker/frontend/extract_time_worker.py b/memoryscope/memoryscope/core/worker/frontend/extract_time_worker.py
index 70e1ba00..98d47afd 100644
--- a/memoryscope/memoryscope/core/worker/frontend/extract_time_worker.py
+++ b/memoryscope/memoryscope/core/worker/frontend/extract_time_worker.py
@@ -15,7 +15,7 @@ class ExtractTimeWorker(MemoryBaseWorker):
"""
EXTRACT_TIME_PATTERN = r"-\s*(\S+)[::]\s*(\S+)"
- FILE_PATH: str = __file__
+ file_path: str = __file__
def _parse_params(self, **kwargs):
self.generation_model_kwargs: dict = kwargs.get("generation_model_kwargs", {})
diff --git a/memoryscope/memoryscope/core/worker/frontend/print_memory_worker.py b/memoryscope/memoryscope/core/worker/frontend/print_memory_worker.py
index 7421614d..6843d0cc 100644
--- a/memoryscope/memoryscope/core/worker/frontend/print_memory_worker.py
+++ b/memoryscope/memoryscope/core/worker/frontend/print_memory_worker.py
@@ -12,7 +12,7 @@ class PrintMemoryWorker(MemoryBaseWorker):
"""
Formats the memories to print.
"""
- FILE_PATH: str = __file__
+ file_path: str = __file__
def _run(self):
"""
diff --git a/memoryscope/memoryscope/core/worker/memory_base_worker.py b/memoryscope/memoryscope/core/worker/memory_base_worker.py
index 9572cc53..353f1a1f 100644
--- a/memoryscope/memoryscope/core/worker/memory_base_worker.py
+++ b/memoryscope/memoryscope/core/worker/memory_base_worker.py
@@ -16,7 +16,7 @@ from memoryscope.scheme.message import Message
class MemoryBaseWorker(BaseWorker, metaclass=ABCMeta):
- FILE_PATH: str = __file__
+ file_path: str = __file__
def __init__(self,
embedding_model: str = "",
diff --git a/reme_ai/__init__.py b/reme_ai/__init__.py
index 89b437b5..39d39f5c 100644
--- a/reme_ai/__init__.py
+++ b/reme_ai/__init__.py
@@ -1,6 +1,5 @@
from reme_ai import retrieve
from reme_ai import summary
-from reme_ai import agent
from reme_ai import vector_store
__version__ = "0.1.0"
diff --git a/reme_ai/agent/react/__init__.py b/reme_ai/agent/react/__init__.py
deleted file mode 100644
index f49ce6d5..00000000
--- a/reme_ai/agent/react/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .react_v1_op import ReactV1Op
diff --git a/reme_ai/agent/react/react_v1_op.py b/reme_ai/agent/react/react_v1_op.py
deleted file mode 100644
index 41b612c3..00000000
--- a/reme_ai/agent/react/react_v1_op.py
+++ /dev/null
@@ -1,82 +0,0 @@
-import datetime
-import time
-from typing import List, Dict
-
-from flowllm import C, BaseLLMOp
-from flowllm.flow.base_tool_flow import BaseToolFlow
-from flowllm.flow.gallery import DashscopeSearchToolFlow, CodeToolFlow, TerminateToolFlow
-from loguru import logger
-
-from reme_ai.schema import Message, Role
-
-
-@C.register_op()
-class ReactV1Op(BaseLLMOp):
- file_path: str = __file__
-
- def execute(self):
- query: str = self.context.query
-
- max_steps: int = int(self.op_params.get("max_steps", 10))
- tools: List[BaseToolFlow] = [DashscopeSearchToolFlow(), CodeToolFlow(), TerminateToolFlow()]
- tool_dict: Dict[str, BaseToolFlow] = {x.name: x for x in tools}
- now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
- has_terminate_tool = False
-
- user_prompt = self.prompt_format(prompt_name="role_prompt",
- time=now_time,
- tools=",".join([x.name for x in tools]),
- query=query)
- messages: List[Message] = [Message(role=Role.USER, content=user_prompt)]
- logger.info(f"step.0 user_prompt={user_prompt}")
-
- for i in range(max_steps):
- if has_terminate_tool:
- assistant_message: Message = self.llm.chat(messages)
- else:
- assistant_message: Message = self.llm.chat(messages, tools=[x.tool_call for x in tools])
-
- messages.append(assistant_message)
- logger.info(f"assistant.{i}.reasoning_content={assistant_message.reasoning_content}\n"
- f"content={assistant_message.content}\n"
- f"tool.size={len(assistant_message.tool_calls)}")
-
- if has_terminate_tool:
- break
-
- for tool in assistant_message.tool_calls:
- if tool.name == "terminate":
- has_terminate_tool = True
- logger.info(f"step={i} find terminate tool, break.")
- break
-
- if not has_terminate_tool and not assistant_message.tool_calls:
- logger.warning(f"【bugfix】step={i} no tools, break.")
- has_terminate_tool = True
-
- for j, tool_call in enumerate(assistant_message.tool_calls):
- logger.info(f"submit step={i} tool_calls.name={tool_call.name} argument_dict={tool_call.argument_dict}")
-
- if tool_call.name not in tool_dict:
- continue
-
- self.submit_task(tool_dict[tool_call.name].__call__, **tool_call.argument_dict)
- time.sleep(1)
-
- if not has_terminate_tool:
- user_content_list = []
- for tool_result, tool_call in zip(self.join_task(), assistant_message.tool_calls):
- logger.info(f"submit step={i} tool_calls.name={tool_call.name} tool_result={tool_result}")
- assert isinstance(tool_result, str)
- user_content_list.append(f"\n{tool_result}\n")
- user_content_list.append(self.prompt_format(prompt_name="next_prompt"))
- assistant_message.tool_calls.clear()
- messages.append(Message(role=Role.USER, content="\n".join(user_content_list)))
-
- else:
- assistant_message.tool_calls.clear()
- messages.append(Message(role=Role.USER, content=self.prompt_format(prompt_name="final_prompt")))
-
- # Store results in context instead of response
- self.context.messages = messages
- self.context.answer = messages[-1].content
diff --git a/reme_ai/agent/react/react_v1_prompt.yaml b/reme_ai/agent/react/react_v1_prompt.yaml
deleted file mode 100644
index f2c49725..00000000
--- a/reme_ai/agent/react/react_v1_prompt.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
-role_prompt: |
- You are a helpful assistant.
- The current time is {time}.
- Please proactively choose the most suitable tool or combination of tools based on the user's question, including {tools} etc.
- Please first think about how to break down the problem into subtasks, what tools and parameters should be used for each subtask, and finally provide the tool call name and parameters.
- Try calling the same tool multiple times with different parameters to obtain information from various perspectives.
- Please determine the response language based on the language of the user's question.
-
- {query}
-
-# write a complete and rigorous report to answer user's questions based on the context.
-next_prompt: |
- Think based on the current content and the user's question: Is the current context sufficient to answer the user's question?
-
- - If the current context is not sufficient to answer the user's question, consider what information is missing.
- Re-plan and think about how to break down the missing information into subtasks.
- For each subtask, determine what tools and parameters should be used for the query.
- Please first provide the reasoning process, then give the tool call name and parameters.
-
- - If the current context is sufficient to answer the user's question, use the **terminate** tool.
-
-# Please determine the response language based on the language of the user's question.
-final_prompt: |
- Please integrate the context and provide a complete answer to the user's question.
-
- # User's Question
- {query}
-
diff --git a/reme_ai/config/default.yaml b/reme_ai/config/default.yaml
index 8e50c277..4462cc7b 100644
--- a/reme_ai/config/default.yaml
+++ b/reme_ai/config/default.yaml
@@ -16,6 +16,34 @@ http:
limit_concurrency: 64
flow:
+ retrieve_task_memory:
+ flow_content: build_query_op >> recall_vector_store_op >> rerank_memory_op >> rewrite_memory_op
+ description: "Retrieve the most relevant top_k memory experience from historical memory based on the query to help solve tasks better now"
+ input_schema:
+ query:
+ type: "str"
+ description: "current query"
+ required: true
+
+ summary_task_memory:
+ flow_content: trajectory_preprocess_op >> (success_extraction_op|failure_extraction_op|comparative_extraction_op) >> memory_validation_op >> update_vector_store_op
+ description: "Summarize trajectories or messages into memories"
+ input_schema:
+ trajectories:
+ type: "list"
+ description: "A list of conversation trajectory information, including message content and score. This field does not need to be filled in, the system will complete it automatically."
+ required: false
+
+ vector_store:
+ flow_content: vector_store_action_op
+ description: "directly operate the vector store."
+ input_schema:
+ action:
+ type: "str"
+ description: "vector store operations"
+ required: true
+ enum: [ copy, delete, delete_ids, dump, load ]
+
retrieve_task_memory_simple:
flow_content: build_query_op >> recall_vector_store_op >> merge_memory_op
description: "Retrieve the most relevant top_k memory experience from historical memory based on the query to help solve tasks better now"
@@ -34,44 +62,6 @@ flow:
description: "A list of conversation trajectory information, including message content and score. This field does not need to be filled in, the system will complete it automatically."
required: false
- retrieve_task_memory:
- flow_content: build_query_op >> recall_vector_store_op >> rerank_memory_op >> rewrite_memory_op
- description: "Retrieve the most relevant top_k memory experience from historical memory based on the query to help solve tasks better now"
- input_schema:
- query:
- type: "str"
- description: "current query"
- required: true
-
- summary_task_memory:
- # memory_deduplication_op
- flow_content: trajectory_preprocess_op >> (success_extraction_op|failure_extraction_op|comparative_extraction_op) >> memory_validation_op >> update_vector_store_op
- description: "Summarize trajectories or messages into memories"
- input_schema:
- trajectories:
- type: "list"
- description: "A list of conversation trajectory information, including message content and score. This field does not need to be filled in, the system will complete it automatically."
- required: false
-
- agent_task:
- flow_content: react_op
- description: "A React-capable agent that can utilize web search and code execution tools."
- input_schema:
- query:
- type: "str"
- description: "current query"
- required: true
-
- vector_store:
- flow_content: vector_store_action_op
- description: "directly operate the vector store."
- input_schema:
- action:
- type: "str"
- description: "vector store operations"
- required: true
- enum: [ copy, delete, delete_ids, dump, load ]
-
llm:
default:
backend: openai_compatible
diff --git a/reme_ai/constants/__init__.py b/reme_ai/constants/__init__.py
new file mode 100644
index 00000000..d351b271
--- /dev/null
+++ b/reme_ai/constants/__init__.py
@@ -0,0 +1,7 @@
+from . import common_constants
+from . import language_constants
+
+__all__ = [
+ "common_constants",
+ "language_constants"
+]
diff --git a/reme_ai/constants/common_constants.py b/reme_ai/constants/common_constants.py
new file mode 100644
index 00000000..74645416
--- /dev/null
+++ b/reme_ai/constants/common_constants.py
@@ -0,0 +1,48 @@
+# common_constants.py
+# This module defines constants used as keys throughout the application to maintain a consistent reference
+# for data structures related to workflow management, chat interactions, context storage, memory operations,
+# node processing, and temporal inference functionalities.
+
+WORKFLOW_NAME = "workflow_name"
+
+MEMORYSCOPE_CONTEXT = "memoryscope_context"
+
+RESULT = "result"
+
+MEMORIES = "memories"
+
+CHAT_MESSAGES = "chat_messages"
+
+CHAT_MESSAGES_SCATTER = "chat_messages_scatter"
+
+CHAT_KWARGS = "chat_kwargs"
+
+USER_NAME = "user_name"
+
+TARGET_NAME = "target_name"
+
+MEMORY_MANAGER = "memory_manager"
+
+QUERY_WITH_TS = "query_with_ts"
+
+RETRIEVE_MEMORY_NODES = "retrieve_memory_nodes"
+
+RANKED_MEMORY_NODES = "ranked_memory_nodes"
+
+NOT_REFLECTED_NODES = "not_reflected_nodes"
+
+NOT_UPDATED_NODES = "not_updated_nodes"
+
+EXTRACT_TIME_DICT = "extract_time_dict"
+
+NEW_OBS_NODES = "new_obs_nodes"
+
+NEW_OBS_WITH_TIME_NODES = "new_obs_with_time_nodes"
+
+INSIGHT_NODES = "insight_nodes"
+
+TODAY_NODES = "today_nodes"
+
+MERGE_OBS_NODES = "merge_obs_nodes"
+
+TIME_INFER = "time_infer"
diff --git a/reme_ai/constants/language_constants.py b/reme_ai/constants/language_constants.py
new file mode 100644
index 00000000..e3ccf952
--- /dev/null
+++ b/reme_ai/constants/language_constants.py
@@ -0,0 +1,215 @@
+from memoryscope.enumeration.language_enum import LanguageEnum
+
+# This dictionary maps languages to lists of words related to datetime expressions.
+# It aids in recognizing and processing datetime mentions in text, enhancing the system's ability to understand
+# temporal context across different languages.
+DATATIME_WORD_LIST = {
+ LanguageEnum.CN: [
+ "天",
+ "周",
+ "月",
+ "年",
+ "星期",
+ "点",
+ "分钟",
+ "小时",
+ "秒",
+ "上午",
+ "下午",
+ "早上",
+ "早晨",
+ "晚上",
+ "中午",
+ "日",
+ "夜",
+ "清晨",
+ "傍晚",
+ "凌晨",
+ "岁",
+ ],
+ LanguageEnum.EN: [
+ # Units of Time
+ "year", "yr",
+ "month", "mo",
+ "week", "wk",
+ "day", "d",
+ "hour", "hr",
+ "minute", "min",
+ "second", "sec",
+
+ # Days of the Week
+ "Monday", "Mon",
+ "Tuesday", "Tue", "Tues",
+ "Wednesday", "Wed",
+ "Thursday", "Thu", "Thur", "Thurs",
+ "Friday", "Fri",
+ "Saturday", "Sat",
+ "Sunday", "Sun",
+
+ # Months of the Year
+ "January", "Jan",
+ "February", "Feb",
+ "March", "Mar",
+ "April", "Apr",
+ "May", "May",
+ "June", "Jun",
+ "July", "Jul",
+ "August", "Aug",
+ "September", "Sep", "Sept",
+ "October", "Oct",
+ "November", "Nov",
+ "December", "Dec",
+
+ # Relative Time References
+ "Today",
+ "Tomorrow", "Tmrw",
+ "Yesterday", "Yday",
+ "Now",
+ "Morning", "AM", "a.m.",
+ "Afternoon", "PM", "p.m.",
+ "Evening",
+ "Night",
+ "Midnight",
+ "Noon",
+
+ # Seasonal References
+ "Spring",
+ "Summer",
+ "Autumn", "Fall",
+ "Winter",
+
+ # General Time References
+ "Century", "cent.",
+ "Decade",
+ "Millennium",
+ "Quarter", "Q1", "Q2", "Q3", "Q4",
+ "Semester",
+ "Fortnight",
+ "Weekend"
+ ]
+}
+
+# A mapping of weekdays for each supported language, facilitating calendar-related operations and understanding
+# within the application.
+WEEKDAYS = {
+ LanguageEnum.CN: [
+ "周一",
+ "周二",
+ "周三",
+ "周四",
+ "周五",
+ "周六",
+ "周日"
+ ],
+ LanguageEnum.EN: [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ]
+}
+
+MONTH_DICT = {
+ LanguageEnum.CN: [
+ "1月",
+ "2月",
+ "3月",
+ "4月",
+ "5月",
+ "6月",
+ "7月",
+ "8月",
+ "9月",
+ "10月",
+ "11月",
+ "12月",
+ ],
+ LanguageEnum.EN: [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+ ]
+}
+
+# Constants for the word 'none' in different languages
+NONE_WORD = {
+ LanguageEnum.CN: "无",
+ LanguageEnum.EN: "none"
+}
+
+# Constants for the word 'repeated' in different languages
+REPEATED_WORD = {
+ LanguageEnum.CN: "重复",
+ LanguageEnum.EN: "repeated"
+}
+
+# Constants for the word 'contradictory' in different languages
+CONTRADICTORY_WORD = {
+ LanguageEnum.CN: "矛盾",
+ LanguageEnum.EN: "contradiction"
+}
+
+# Constants for the phrase 'included' in different languages
+CONTAINED_WORD = {
+ LanguageEnum.CN: "被包含",
+ LanguageEnum.EN: "contained"
+}
+
+# Constants for the symbol ':' in different languages' representations
+COLON_WORD = {
+ LanguageEnum.CN: ":",
+ LanguageEnum.EN: ":"
+}
+
+# Constants for the symbol ',' in different languages' representations
+COMMA_WORD = {
+ LanguageEnum.CN: ",",
+ LanguageEnum.EN: ","
+}
+
+# Default human name placeholders for different languages
+DEFAULT_HUMAN_NAME = {
+ LanguageEnum.CN: "用户",
+ LanguageEnum.EN: "user"
+}
+
+# Mapping of datetime terms from natural language to standardized keys for each supported language
+DATATIME_KEY_MAP = {
+ LanguageEnum.CN: {
+ "年": "year",
+ "月": "month",
+ "日": "day",
+ "周": "week",
+ "星期几": "weekday",
+ },
+ LanguageEnum.EN: {
+ "Year": "year",
+ "Month": "month",
+ "Day": "day",
+ "Week": "week",
+ "Weekday": "weekday",
+ }
+}
+
+# Phrase for indicating inferred time in different languages
+TIME_INFER_WORD = {
+ LanguageEnum.CN: "推断时间",
+ LanguageEnum.EN: "Inference time"
+}
+
+USER_NAME_EXPRESSION = {
+ LanguageEnum.CN: "用户姓名是{name}。",
+ LanguageEnum.EN: "User's name is {name}."
+}
diff --git a/reme_ai/agent/__init__.py b/reme_ai/enumeration/__init__.py
similarity index 100%
rename from reme_ai/agent/__init__.py
rename to reme_ai/enumeration/__init__.py
diff --git a/reme_ai/enumeration/language_constants.py b/reme_ai/enumeration/language_constants.py
new file mode 100644
index 00000000..e3ccf952
--- /dev/null
+++ b/reme_ai/enumeration/language_constants.py
@@ -0,0 +1,215 @@
+from memoryscope.enumeration.language_enum import LanguageEnum
+
+# This dictionary maps languages to lists of words related to datetime expressions.
+# It aids in recognizing and processing datetime mentions in text, enhancing the system's ability to understand
+# temporal context across different languages.
+DATATIME_WORD_LIST = {
+ LanguageEnum.CN: [
+ "天",
+ "周",
+ "月",
+ "年",
+ "星期",
+ "点",
+ "分钟",
+ "小时",
+ "秒",
+ "上午",
+ "下午",
+ "早上",
+ "早晨",
+ "晚上",
+ "中午",
+ "日",
+ "夜",
+ "清晨",
+ "傍晚",
+ "凌晨",
+ "岁",
+ ],
+ LanguageEnum.EN: [
+ # Units of Time
+ "year", "yr",
+ "month", "mo",
+ "week", "wk",
+ "day", "d",
+ "hour", "hr",
+ "minute", "min",
+ "second", "sec",
+
+ # Days of the Week
+ "Monday", "Mon",
+ "Tuesday", "Tue", "Tues",
+ "Wednesday", "Wed",
+ "Thursday", "Thu", "Thur", "Thurs",
+ "Friday", "Fri",
+ "Saturday", "Sat",
+ "Sunday", "Sun",
+
+ # Months of the Year
+ "January", "Jan",
+ "February", "Feb",
+ "March", "Mar",
+ "April", "Apr",
+ "May", "May",
+ "June", "Jun",
+ "July", "Jul",
+ "August", "Aug",
+ "September", "Sep", "Sept",
+ "October", "Oct",
+ "November", "Nov",
+ "December", "Dec",
+
+ # Relative Time References
+ "Today",
+ "Tomorrow", "Tmrw",
+ "Yesterday", "Yday",
+ "Now",
+ "Morning", "AM", "a.m.",
+ "Afternoon", "PM", "p.m.",
+ "Evening",
+ "Night",
+ "Midnight",
+ "Noon",
+
+ # Seasonal References
+ "Spring",
+ "Summer",
+ "Autumn", "Fall",
+ "Winter",
+
+ # General Time References
+ "Century", "cent.",
+ "Decade",
+ "Millennium",
+ "Quarter", "Q1", "Q2", "Q3", "Q4",
+ "Semester",
+ "Fortnight",
+ "Weekend"
+ ]
+}
+
+# A mapping of weekdays for each supported language, facilitating calendar-related operations and understanding
+# within the application.
+WEEKDAYS = {
+ LanguageEnum.CN: [
+ "周一",
+ "周二",
+ "周三",
+ "周四",
+ "周五",
+ "周六",
+ "周日"
+ ],
+ LanguageEnum.EN: [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ]
+}
+
+MONTH_DICT = {
+ LanguageEnum.CN: [
+ "1月",
+ "2月",
+ "3月",
+ "4月",
+ "5月",
+ "6月",
+ "7月",
+ "8月",
+ "9月",
+ "10月",
+ "11月",
+ "12月",
+ ],
+ LanguageEnum.EN: [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+ ]
+}
+
+# Constants for the word 'none' in different languages
+NONE_WORD = {
+ LanguageEnum.CN: "无",
+ LanguageEnum.EN: "none"
+}
+
+# Constants for the word 'repeated' in different languages
+REPEATED_WORD = {
+ LanguageEnum.CN: "重复",
+ LanguageEnum.EN: "repeated"
+}
+
+# Constants for the word 'contradictory' in different languages
+CONTRADICTORY_WORD = {
+ LanguageEnum.CN: "矛盾",
+ LanguageEnum.EN: "contradiction"
+}
+
+# Constants for the phrase 'included' in different languages
+CONTAINED_WORD = {
+ LanguageEnum.CN: "被包含",
+ LanguageEnum.EN: "contained"
+}
+
+# Constants for the symbol ':' in different languages' representations
+COLON_WORD = {
+ LanguageEnum.CN: ":",
+ LanguageEnum.EN: ":"
+}
+
+# Constants for the symbol ',' in different languages' representations
+COMMA_WORD = {
+ LanguageEnum.CN: ",",
+ LanguageEnum.EN: ","
+}
+
+# Default human name placeholders for different languages
+DEFAULT_HUMAN_NAME = {
+ LanguageEnum.CN: "用户",
+ LanguageEnum.EN: "user"
+}
+
+# Mapping of datetime terms from natural language to standardized keys for each supported language
+DATATIME_KEY_MAP = {
+ LanguageEnum.CN: {
+ "年": "year",
+ "月": "month",
+ "日": "day",
+ "周": "week",
+ "星期几": "weekday",
+ },
+ LanguageEnum.EN: {
+ "Year": "year",
+ "Month": "month",
+ "Day": "day",
+ "Week": "week",
+ "Weekday": "weekday",
+ }
+}
+
+# Phrase for indicating inferred time in different languages
+TIME_INFER_WORD = {
+ LanguageEnum.CN: "推断时间",
+ LanguageEnum.EN: "Inference time"
+}
+
+USER_NAME_EXPRESSION = {
+ LanguageEnum.CN: "用户姓名是{name}。",
+ LanguageEnum.EN: "User's name is {name}."
+}
diff --git a/reme_ai/retrieve/personal/__init__.py b/reme_ai/retrieve/personal/__init__.py
index 24d17f43..16168138 100644
--- a/reme_ai/retrieve/personal/__init__.py
+++ b/reme_ai/retrieve/personal/__init__.py
@@ -1,17 +1,17 @@
-from .extract_time_worker import ExtractTimeWorker
-from .fuse_rerank_worker import FuseRerankWorker
-from .print_memory_worker import PrintMemoryWorker
-from .read_message_worker import ReadMessageWorker
-from .retrieve_memory_worker import RetrieveMemoryWorker
-from .semantic_rank_worker import SemanticRankWorker
-from .set_query_worker import SetQueryWorker
+from .extract_time_op import ExtractTimeOp
+from .fuse_rerank_op import FuseRerankOp
+from .print_memory_op import PrintMemoryOp
+from .read_message_op import ReadMessageOp
+from .retrieve_memory_op import RetrieveMemoryOp
+from .semantic_rank_op import SemanticRankOp
+from .set_query_op import SetQueryOp
__all__ = [
- "ExtractTimeWorker",
- "FuseRerankWorker",
- "PrintMemoryWorker",
- "ReadMessageWorker",
- "RetrieveMemoryWorker",
- "SemanticRankWorker",
- "SetQueryWorker"
+ "ExtractTimeOp",
+ "FuseRerankOp",
+ "PrintMemoryOp",
+ "ReadMessageOp",
+ "RetrieveMemoryOp",
+ "SemanticRankOp",
+ "SetQueryOp"
]
diff --git a/reme_ai/retrieve/personal/extract_time_op.py b/reme_ai/retrieve/personal/extract_time_op.py
new file mode 100644
index 00000000..3957f47e
--- /dev/null
+++ b/reme_ai/retrieve/personal/extract_time_op.py
@@ -0,0 +1,76 @@
+import re
+from typing import Dict
+
+from flowllm import C, BaseLLMOp
+from flowllm.enumeration.role import Role
+from flowllm.schema.message import Message
+from loguru import logger
+
+from reme_ai.constants.common_constants import QUERY_WITH_TS, EXTRACT_TIME_DICT
+from reme_ai.constants.language_constants import DATATIME_KEY_MAP
+from reme_ai.utils.datetime_handler import DatetimeHandler
+
+
+@C.register_op()
+class ExtractTimeOp(BaseLLMOp):
+ file_path: str = __file__
+ EXTRACT_TIME_PATTERN = r"-\s*(\S+)[::]\s*(\S+)"
+
+ """
+ A specialized worker class designed to identify and extract time-related information
+ from text generated by an LLM, translating date-time keywords based on the set language,
+ and storing this extracted data within a shared context.
+ """
+
+ def get_language_value(self, value_dict: dict):
+
+ return value_dict.get(self.language, value_dict.get("en"))
+
+ def execute(self):
+ """
+ Executes the primary logic of identifying and extracting time data from an LLM's response.
+
+ This method first checks if the input query contains any datetime keywords. If not, it logs and returns.
+ It then constructs a prompt with contextual information including formatted timestamps and calls the LLM.
+ The response is parsed for time-related data using regex, translated via a language-specific key map,
+ and the resulting time data is stored in the shared context.
+ """
+ query, query_timestamp = self.context[QUERY_WITH_TS]
+
+ # Identify if the query contains datetime keywords
+ contain_datetime = DatetimeHandler.has_time_word(query, self.language)
+ if not contain_datetime:
+ logger.info(f"contain_datetime={contain_datetime}")
+ return
+
+ # Prepare the prompt with necessary contextual details
+ time_format = self.prompt_format(prompt_name="time_string_format")
+ query_time_str = DatetimeHandler(dt=query_timestamp).string_format(time_format, self.language)
+
+ # Create message with system and few-shot examples
+ system_prompt = self.prompt_format(prompt_name="extract_time_system")
+ few_shot = self.prompt_format(prompt_name="extract_time_few_shot")
+ user_prompt = self.prompt_format(prompt_name="extract_time_user_query", query=query,
+ query_time_str=query_time_str)
+
+ full_prompt = f"{system_prompt}\n\n{few_shot}\n\n{user_prompt}"
+ logger.info(f"extract_time_prompt={full_prompt}")
+
+ # Invoke the LLM to generate a response
+ response = self.llm.chat([Message(role=Role.USER, content=full_prompt)])
+
+ # Handle empty or unsuccessful responses
+ if not response or not response.content:
+ return
+ response_text = response.content
+
+ # Extract time information from the LLM's response using regex
+ extract_time_dict: Dict[str, str] = {}
+ matches = re.findall(self.EXTRACT_TIME_PATTERN, response_text)
+ key_map: dict = DATATIME_KEY_MAP[DatetimeHandler.language_transform]
+ for key, value in matches:
+ if key in key_map.keys():
+ extract_time_dict[key_map[key]] = value
+
+ logger.info(f"response_text={response_text} matches={matches} filters={extract_time_dict}")
+ self.context[EXTRACT_TIME_DICT] = extract_time_dict
diff --git a/reme_ai/retrieve/personal/extract_time_prompt.yaml b/reme_ai/retrieve/personal/extract_time_prompt.yaml
new file mode 100644
index 00000000..dfaa7945
--- /dev/null
+++ b/reme_ai/retrieve/personal/extract_time_prompt.yaml
@@ -0,0 +1,135 @@
+time_string_format_zh: |
+ {year}年{month}{day}日,{year}年第{week}周,{weekday},{hour}时。
+
+
+time_string_format: |
+ {month} {day}, {year}, {week}th week of {year}, {weekday}, at {hour}.
+
+
+extract_time_system_zh: |
+ 任务:从语句与语句发生的时间,推断并提取语句内容中指向的时间段。
+ 回答尽可能完整的时间段。
+ 回答的格式严格遵照示例中的已有格式规范。
+ 若语句不涉及时间则回答无。
+
+
+extract_time_system: |
+ Task: From the sentences and the time when they occurred, infer and extract the time periods indicated in the content of the sentences.
+ Answer with the most complete time periods possible.
+ The format of the answers must strictly adhere to the specifications in the examples provided.
+ If the sentence does not involve time, respond with "none."
+
+
+extract_time_few_shot_zh: |
+ 示例1:
+ 句子:我记得你前年四月份去了阿联酋,阿联酋有哪些好玩的地方?迪拜和阿布扎比你更喜欢哪个?沙漠的景色壮观吗?
+ 时间:1992年8月20日,1992年第34周,周一,18时。
+ 回答:
+ - 年:1990 - 月:4月
+
+ 示例2:
+ 句子:后天下午三点的会议记得参加。我在日历上仔细标注了这个重要的日子,提醒自己不要错过。会议将在公司会议室举行,这是一个讨论未来发展方向的重要机会。
+ 时间:2024年6月19日,2024年第25周,周二,13时。
+ 回答:
+ - 年:2024 - 月:6月 - 日:21 - 时:15
+
+ 示例3:
+ 句子:下个月第一个周六去杭州玩。
+ 时间:2005年7月15日,2005年第28周,周六,0时。
+ 回答:
+ - 年:2005 - 月:8月 - 周:31 - 星期几:周六
+
+ 示例4:
+ 句子:上周末我们去的那个小镇真是太美了。
+ 时间:1999年12月2日,1999年第48周,周二,8时。
+ 回答:
+ - 年:1999 - 周:47 - 星期几:周六,周日
+
+ 示例5:
+ 句子:再过半小时就要宣讲了,记得准备材料。
+ 时间:2020年6月22日,2020年第25周,周一,9时。
+ 回答:
+ - 年:2020 - 月:6月 - 日:22 - 时:10
+
+ 示例6:
+ 句子:10000米长跑比赛的开始时间是3分47秒前。
+ 时间:1987年2月17日,1987年第7周,周三,19时。
+ 回答:
+ - 年:1987 - 月:2 - 日:17 - 时:19
+
+ 示例7:
+ 句子:上个月的这个时候我们还在筹备音乐会。每天都是忙碌而充实的日子,我们为音乐会的顺利举办而努力奋斗着。彩排、布景、节目安排,每一个细节都需要精心安排和准备。
+ 时间:1995年11月24日,1995年第48周,周二,17时。
+ 回答:
+ - 年:1995 - 月:10 - 日:24
+
+ 示例8:
+ 句子:我的朋友非常喜欢运动,他认为运动有助于增强身体素质。
+ 时间:2015年1月23日,2015年第4周,周四,7时。
+ 回答:
+ 无
+
+
+extract_time_few_shot: |
+ Example 1:
+ Sentence: I remember you went to the UAE in April the year before last. Which places in the UAE are fun? Which do you prefer, Dubai or Abu Dhabi? Are the desert views spectacular?
+ Time: August 20, 1992, 34th week of 1992, Monday, at 18.
+ Answer:
+ - Year: 1990 - Month: 4
+
+ Example 2:
+ Sentence: Remember to attend the meeting at 3 PM the day after tomorrow. I carefully marked this important day on my calendar to remind myself not to miss it. The meeting will be held in the company conference room, and it's an important opportunity to discuss future development directions.
+ Time: June 19, 2024, 25th week of 2024, Tuesday, at 13.
+ Answer:
+ - Year: 2024 - Month: 6 - Day: 21 - Hour: 15
+
+ Example 3:
+ Sentence: Next month on the first Saturday, let's go to Hangzhou.
+ Time: July 15, 2005, 28th week of 2005, Saturday, at 0.
+ Answer:
+ - Year: 2005 - Month: 8 - Week: 31 - Day of Week: 6
+
+ Example 4:
+ Sentence: The small town we visited last weekend was truly beautiful.
+ Time: December 2, 1999, 48th week of 1999, Tuesday, at 8.
+ Answer:
+ - Year: 1999 - Week: 47 - Day of Week: 6, 7
+
+ Example 5:
+ Sentence: The presentation will start in half an hour, remember to prepare the materials.
+ Time: June 22, 2020, 25th week of 2020, Monday, at 9.
+ Answer:
+ - Year: 2020 - Month: 6 - Day: 22 - Hour: 10
+
+ Example 6:
+ Sentence: The start time for the 10,000-meter race was 3 minutes and 47 seconds ago.
+ Time: February 17, 1987, 7th week of 1987, Wednesday, at 19.
+ Answer:
+ - Year: 1987 - Month: 2 - Day: 17 - Hour: 19
+
+ Example 7:
+ Sentence: At this time last month, we were still preparing for the concert. Every day was busy and fulfilling, and we worked hard for the successful holding of the concert. Rehearsals, set design, and program arrangements - every detail needed careful planning and preparation.
+ Time: November 24, 1995, 48th week of 1995, Tuesday, at 17.
+ Answer:
+ - Year: 1995 - Month: 10 - Day: 24
+
+ Example 8:
+ Sentence: My friend loves sports very much and believes that exercise helps improve physical fitness.
+ Time: January 23, 2015, 4th week of 2015, Thursday, at 7.
+ Answer:
+ None
+
+
+extract_time_user_query_zh: |
+ 句子:{query}
+ 时间:{query_time_str}
+ 回答:
+
+
+extract_time_user_query: |
+ Sentence: {query}
+ Time: {query_time_str}
+ Answer:
+
+
+
diff --git a/reme_ai/retrieve/personal/extract_time_worker.py b/reme_ai/retrieve/personal/extract_time_worker.py
deleted file mode 100644
index 70e1ba00..00000000
--- a/reme_ai/retrieve/personal/extract_time_worker.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import re
-from typing import Dict
-
-from memoryscope.constants.common_constants import QUERY_WITH_TS, EXTRACT_TIME_DICT
-from memoryscope.constants.language_constants import DATATIME_KEY_MAP
-from memoryscope.core.utils.datetime_handler import DatetimeHandler
-from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
-
-
-class ExtractTimeWorker(MemoryBaseWorker):
- """
- A specialized worker class designed to identify and extract time-related information
- from text generated by an LLM, translating date-time keywords based on the set language,
- and storing this extracted data within a shared context.
- """
-
- EXTRACT_TIME_PATTERN = r"-\s*(\S+)[::]\s*(\S+)"
- FILE_PATH: str = __file__
-
- def _parse_params(self, **kwargs):
- self.generation_model_kwargs: dict = kwargs.get("generation_model_kwargs", {})
-
- def _run(self):
- """
- Executes the primary logic of identifying and extracting time data from an LLM's response.
-
- This method first checks if the input query contains any datetime keywords. If not, it logs and returns.
- It then constructs a prompt with contextual information including formatted timestamps and calls the LLM.
- The response is parsed for time-related data using regex, translated via a language-specific key map,
- and the resulting time data is stored in the shared context.
- """
- query, query_timestamp = self.get_workflow_context(QUERY_WITH_TS)
-
- # Identify if the query contains datetime keywords
- contain_datetime = DatetimeHandler.has_time_word(query, self.language)
- if not contain_datetime:
- self.logger.info(f"contain_datetime={contain_datetime}")
- return
-
- # Prepare the prompt with necessary contextual details
- query_time_str = DatetimeHandler(dt=query_timestamp).string_format(self.prompt_handler.time_string_format,
- self.language)
- system_prompt = self.prompt_handler.extract_time_system
- few_shot = self.prompt_handler.extract_time_few_shot
- user_query = self.prompt_handler.extract_time_user_query.format(query=query, query_time_str=query_time_str)
- extract_time_message = self.prompt_to_msg(system_prompt=system_prompt, few_shot=few_shot, user_query=user_query)
- self.logger.info(f"extract_time_message={extract_time_message}")
-
- # Invoke the LLM to generate a response
- response = self.generation_model.call(messages=extract_time_message, **self.generation_model_kwargs)
-
- # Handle empty or unsuccessful responses
- if not response.status or not response.message.content:
- return
- response_text = response.message.content
-
- # Extract time information from the LLM's response using regex
- extract_time_dict: Dict[str, str] = {}
- matches = re.findall(self.EXTRACT_TIME_PATTERN, response_text)
- key_map: dict = self.get_language_value(DATATIME_KEY_MAP)
- for key, value in matches:
- if key in key_map.keys():
- extract_time_dict[key_map[key]] = value
- self.logger.info(f"response_text={response_text} matches={matches} filters={extract_time_dict}")
- self.set_workflow_context(EXTRACT_TIME_DICT, extract_time_dict)
diff --git a/reme_ai/retrieve/personal/extract_time_worker.yaml b/reme_ai/retrieve/personal/extract_time_worker.yaml
deleted file mode 100644
index 85a01ee3..00000000
--- a/reme_ai/retrieve/personal/extract_time_worker.yaml
+++ /dev/null
@@ -1,133 +0,0 @@
-time_string_format:
- cn: |
- {year}年{month}{day}日,{year}年第{week}周,{weekday},{hour}时。
- en: |
- {month} {day}, {year}, {week}th week of {year}, {weekday}, at {hour}.
-
-
-extract_time_system:
- cn: |
- 任务:从语句与语句发生的时间,推断并提取语句内容中指向的时间段。
- 回答尽可能完整的时间段。
- 回答的格式严格遵照示例中的已有格式规范。
- 若语句不涉及时间则回答无。
- en: |
- Task: From the sentences and the time when they occurred, infer and extract the time periods indicated in the content of the sentences.
- Answer with the most complete time periods possible.
- The format of the answers must strictly adhere to the specifications in the examples provided.
- If the sentence does not involve time, respond with "none."
-
-
-extract_time_few_shot:
- cn: |
- 示例1:
- 句子:我记得你前年四月份去了阿联酋,阿联酋有哪些好玩的地方?迪拜和阿布扎比你更喜欢哪个?沙漠的景色壮观吗?
- 时间:1992年8月20日,1992年第34周,周一,18时。
- 回答:
- - 年:1990 - 月:4月
-
- 示例2:
- 句子:后天下午三点的会议记得参加。我在日历上仔细标注了这个重要的日子,提醒自己不要错过。会议将在公司会议室举行,这是一个讨论未来发展方向的重要机会。
- 时间:2024年6月19日,2024年第25周,周二,13时。
- 回答:
- - 年:2024 - 月:6月 - 日:21 - 时:15
-
- 示例3:
- 句子:下个月第一个周六去杭州玩。
- 时间:2005年7月15日,2005年第28周,周六,0时。
- 回答:
- - 年:2005 - 月:8月 - 周:31 - 星期几:周六
-
- 示例4:
- 句子:上周末我们去的那个小镇真是太美了。
- 时间:1999年12月2日,1999年第48周,周二,8时。
- 回答:
- - 年:1999 - 周:47 - 星期几:周六,周日
-
- 示例5:
- 句子:再过半小时就要宣讲了,记得准备材料。
- 时间:2020年6月22日,2020年第25周,周一,9时。
- 回答:
- - 年:2020 - 月:6月 - 日:22 - 时:10
-
- 示例6:
- 句子:10000米长跑比赛的开始时间是3分47秒前。
- 时间:1987年2月17日,1987年第7周,周三,19时。
- 回答:
- - 年:1987 - 月:2 - 日:17 - 时:19
-
- 示例7:
- 句子:上个月的这个时候我们还在筹备音乐会。每天都是忙碌而充实的日子,我们为音乐会的顺利举办而努力奋斗着。彩排、布景、节目安排,每一个细节都需要精心安排和准备。
- 时间:1995年11月24日,1995年第48周,周二,17时。
- 回答:
- - 年:1995 - 月:10 - 日:24
-
- 示例8:
- 句子:我的朋友非常喜欢运动,他认为运动有助于增强身体素质。
- 时间:2015年1月23日,2015年第4周,周四,7时。
- 回答:
- 无
-
- en: |
- Example 1:
- Sentence: I remember you went to the UAE in April the year before last. Which places in the UAE are fun? Which do you prefer, Dubai or Abu Dhabi? Are the desert views spectacular?
- Time: August 20, 1992, 34th week of 1992, Monday, at 18.
- Answer:
- - Year: 1990 - Month: 4
-
- Example 2:
- Sentence: Remember to attend the meeting at 3 PM the day after tomorrow. I carefully marked this important day on my calendar to remind myself not to miss it. The meeting will be held in the company conference room, and it's an important opportunity to discuss future development directions.
- Time: June 19, 2024, 25th week of 2024, Tuesday, at 13.
- Answer:
- - Year: 2024 - Month: 6 - Day: 21 - Hour: 15
-
- Example 3:
- Sentence: Next month on the first Saturday, let's go to Hangzhou.
- Time: July 15, 2005, 28th week of 2005, Saturday, at 0.
- Answer:
- - Year: 2005 - Month: 8 - Week: 31 - Day of Week: 6
-
- Example 4:
- Sentence: The small town we visited last weekend was truly beautiful.
- Time: December 2, 1999, 48th week of 1999, Tuesday, at 8.
- Answer:
- - Year: 1999 - Week: 47 - Day of Week: 6, 7
-
- Example 5:
- Sentence: The presentation will start in half an hour, remember to prepare the materials.
- Time: June 22, 2020, 25th week of 2020, Monday, at 9.
- Answer:
- - Year: 2020 - Month: 6 - Day: 22 - Hour: 10
-
- Example 6:
- Sentence: The start time for the 10,000-meter race was 3 minutes and 47 seconds ago.
- Time: February 17, 1987, 7th week of 1987, Wednesday, at 19.
- Answer:
- - Year: 1987 - Month: 2 - Day: 17 - Hour: 19
-
- Example 7:
- Sentence: At this time last month, we were still preparing for the concert. Every day was busy and fulfilling, and we worked hard for the successful holding of the concert. Rehearsals, set design, and program arrangements - every detail needed careful planning and preparation.
- Time: November 24, 1995, 48th week of 1995, Tuesday, at 17.
- Answer:
- - Year: 1995 - Month: 10 - Day: 24
-
- Example 8:
- Sentence: My friend loves sports very much and believes that exercise helps improve physical fitness.
- Time: January 23, 2015, 4th week of 2015, Thursday, at 7.
- Answer:
- None
-
-
-extract_time_user_query:
- cn: |
- 句子:{query}
- 时间:{query_time_str}
- 回答:
-
- en: |
- Sentence: {query}
- Time: {query_time_str}
- Answer:
-
-
-
diff --git a/reme_ai/retrieve/personal/fuse_rerank_op.py b/reme_ai/retrieve/personal/fuse_rerank_op.py
new file mode 100644
index 00000000..fc7353d0
--- /dev/null
+++ b/reme_ai/retrieve/personal/fuse_rerank_op.py
@@ -0,0 +1,128 @@
+from typing import Dict, List
+
+from flowllm import C, BaseLLMOp
+from loguru import logger
+
+from reme_ai.constants.common_constants import EXTRACT_TIME_DICT
+from reme_ai.schema.memory import BaseMemory
+from reme_ai.utils.datetime_handler import DatetimeHandler
+
+
+@C.register_op()
+class FuseRerankOp(BaseLLMOp):
+ """
+ Reranks the memory nodes by scores, types, and temporal relevance. Formats the top-K reranked nodes to print.
+ """
+ file_path: str = __file__
+
+ @staticmethod
+ def match_memory_time(extract_time_dict: Dict[str, str], memory: BaseMemory):
+ """
+ Determines whether the memory is relevant based on time matching.
+ """
+ if extract_time_dict:
+ match_event_flag = True
+ for k, v in extract_time_dict.items():
+ event_value = memory.metadata.get(f"event_{k}", "")
+ if event_value in ["-1", v]:
+ continue
+ else:
+ match_event_flag = False
+ break
+
+ match_msg_flag = True
+ for k, v in extract_time_dict.items():
+ msg_value = memory.metadata.get(f"msg_{k}", "")
+ if msg_value == v:
+ continue
+ else:
+ match_msg_flag = False
+ break
+ else:
+ match_event_flag = False
+ match_msg_flag = False
+
+ memory.metadata["match_event_flag"] = str(int(match_event_flag))
+ memory.metadata["match_msg_flag"] = str(int(match_msg_flag))
+ return match_event_flag, match_msg_flag
+
+ def execute(self):
+ """
+ Executes the reranking process on memories considering their scores, types, and temporal relevance.
+
+ This method performs the following steps:
+ 1. Retrieves extraction time data and a list of memories from the context.
+ 2. Reranks memories based on a combination of their original score, type,
+ and temporal alignment with extracted events/messages.
+ 3. Selects the top-K reranked memories according to the predefined threshold.
+ 4. Optionally infuses inferred time information into the content of selected memories.
+ 5. Logs reranking details and formats the final list of memories for output.
+ """
+ # Get operation parameters
+ fuse_score_threshold = self.op_params.get("fuse_score_threshold", 0.1)
+ fuse_ratio_dict = self.op_params.get("fuse_ratio_dict", {})
+ fuse_time_ratio = self.op_params.get("fuse_time_ratio", 2.0)
+ output_memory_max_count = self.op_params.get("output_memory_max_count", 5)
+
+ # Parse input parameters from the context
+ extract_time_dict: Dict[str, str] = self.context.get(EXTRACT_TIME_DICT, {})
+ memory_list: List[BaseMemory] = self.context.response.metadata.get("memory_list", [])
+
+ # Check if memories are available; warn and return if not
+ if not memory_list:
+ logger.warning("Memory list is empty.")
+ self.context.response.answer = ""
+ return
+
+ logger.info(f"Fuse reranking {len(memory_list)} memories")
+
+ # Perform reranking based on score, type, and time relevance
+ reranked_memories = []
+ for memory in memory_list:
+ # Skip memories below the fuse score threshold
+ memory_score = memory.score or 0.0
+ if memory_score < fuse_score_threshold:
+ continue
+
+ # Calculate type-based adjustment factor
+ memory_type = memory.metadata.get("memory_type", "default")
+ if memory_type not in fuse_ratio_dict:
+ logger.warning(f"{memory_type} factor is not configured!")
+ type_ratio: float = fuse_ratio_dict.get(memory_type, 0.1)
+
+ # Determine time relevance adjustment factor
+ match_event_flag, match_msg_flag = self.match_memory_time(
+ extract_time_dict=extract_time_dict, memory=memory)
+ time_ratio: float = fuse_time_ratio if match_event_flag or match_msg_flag else 1.0
+
+ # Apply reranking score adjustments
+ memory.score = memory_score * type_ratio * time_ratio
+ reranked_memories.append(memory)
+
+ # Sort and select top-k memories
+ reranked_memories = sorted(reranked_memories,
+ key=lambda x: x.score or 0.0,
+ reverse=True)[:output_memory_max_count]
+
+ # Build result
+ formatted_memories = []
+ for memory in reranked_memories:
+ # Log reranking details including flags for event and message matches
+ logger.info(f"Rerank Stage: Content={memory.content}, Score={memory.score}, "
+ f"Event Flag={memory.metadata.get('match_event_flag', '0')}, "
+ f"Message Flag={memory.metadata.get('match_msg_flag', '0')}")
+
+ # Format memory with timestamp if available
+ if hasattr(memory, 'timestamp') and memory.timestamp:
+ dt_handler = DatetimeHandler(memory.timestamp)
+ datetime_str = dt_handler.datetime_format("%Y-%m-%d %H:%M:%S")
+ weekday = dt_handler.get_dt_info_dict(self.language)["weekday"]
+ formatted_content = f"[{datetime_str} {weekday}] {memory.content}"
+ else:
+ formatted_content = memory.content
+
+ formatted_memories.append(formatted_content)
+
+ # Store results in context
+ self.context.response.metadata["memory_list"] = reranked_memories
+ self.context.response.answer = "\n".join(formatted_memories)
diff --git a/reme_ai/retrieve/personal/fuse_rerank_worker.py b/reme_ai/retrieve/personal/fuse_rerank_worker.py
deleted file mode 100644
index b137f354..00000000
--- a/reme_ai/retrieve/personal/fuse_rerank_worker.py
+++ /dev/null
@@ -1,109 +0,0 @@
-from typing import Dict, List
-
-from memoryscope.constants.common_constants import EXTRACT_TIME_DICT, RANKED_MEMORY_NODES, RESULT
-from memoryscope.core.utils.datetime_handler import DatetimeHandler
-from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
-from memoryscope.scheme.memory_node import MemoryNode
-
-
-class FuseRerankWorker(MemoryBaseWorker):
- """
- Reranks the memory nodes by scores, types, and temporal relevance. Formats the top-K reranked nodes to print.
- """
-
- def _parse_params(self, **kwargs):
- self.fuse_score_threshold: float = kwargs.get("fuse_score_threshold", 0.1)
- self.fuse_ratio_dict: Dict[str, float] = kwargs.get("fuse_ratio_dict", {})
- self.fuse_time_ratio: float = kwargs.get("fuse_time_ratio", 2.0)
- self.output_memory_max_count: int = self.memoryscope_context.meta_data["output_memory_max_count"]
-
- @staticmethod
- def match_node_time(extract_time_dict: Dict[str, str], node: MemoryNode):
- """
- Determines whether the node is relevant.
- """
- if extract_time_dict:
- match_event_flag = True
- for k, v in extract_time_dict.items():
- event_value = node.meta_data.get(f"event_{k}", "")
- if event_value in ["-1", v]:
- continue
- else:
- match_event_flag = False
- break
-
- match_msg_flag = True
- for k, v in extract_time_dict.items():
- msg_value = node.meta_data.get(f"msg_{k}", "")
- if msg_value == v:
- continue
- else:
- match_msg_flag = False
- break
- else:
- match_event_flag = False
- match_msg_flag = False
-
- node.meta_data["match_event_flag"] = str(int(match_event_flag))
- node.meta_data["match_msg_flag"] = str(int(match_msg_flag))
- return match_event_flag, match_msg_flag
-
- def _run(self):
- """
- Executes the reranking process on memory nodes considering their scores, types, and temporal relevance.
-
- This method performs the following steps:
- 1. Retrieves extraction time data and a list of ranked memory nodes from the worker's context.
- 2. Reranks nodes based on a combination of their original rank score, type,
- and temporal alignment with extracted events/messages.
- 3. Selects the top-K reranked nodes according to the predefined threshold.
- 4. Optionally infuses inferred time information into the content of selected nodes.
- 5. Logs reranking details and formats the final list of memories for output.
- """
- # Parse input parameters from the worker's context
- extract_time_dict: Dict[str, str] = self.get_workflow_context(EXTRACT_TIME_DICT)
- memory_node_list: List[MemoryNode] = self.memory_manager.get_memories(RANKED_MEMORY_NODES)
-
- # Check if memory nodes are available; warn and return if not
- if not memory_node_list:
- self.logger.warning("Ranked memory nodes list is empty.")
- return
-
- # Perform reranking based on score, type, and time relevance
- reranked_memory_nodes = []
- for node in memory_node_list:
- # Skip nodes below the fuse score threshold
- if node.score_rank < self.fuse_score_threshold:
- continue
-
- # Calculate type-based adjustment factor
- if node.memory_type not in self.fuse_ratio_dict:
- self.logger.warning(f"{node.memory_type} 'factor is not configured!")
- type_ratio: float = self.fuse_ratio_dict.get(node.memory_type, 0.1)
-
- # Determine time relevance adjustment factor
- match_event_flag, match_msg_flag = self.match_node_time(extract_time_dict=extract_time_dict, node=node)
- fuse_time_ratio: float = self.fuse_time_ratio if match_event_flag or match_msg_flag else 1.0
-
- # Apply reranking score adjustments
- node.score_rerank = node.score_rank * type_ratio * fuse_time_ratio
- reranked_memory_nodes.append(node)
-
- # build result
- memories: List[str] = []
- reranked_memory_nodes = sorted(reranked_memory_nodes,
- key=lambda x: x.score_rerank,
- reverse=True)[: self.output_memory_max_count]
- for node in reranked_memory_nodes:
- # Log reranking details including flags for event and message matches
- self.logger.info(f"Rerank Stage: Content={node.content}, Score={node.score_rerank}, "
- f"Event Flag={node.meta_data['match_event_flag']}, "
- f"Message Flag={node.meta_data['match_msg_flag']}")
-
- dt_handler = DatetimeHandler(node.timestamp)
- datetime = dt_handler.datetime_format("%Y-%m-%d %H:%M:%S")
- weekday = dt_handler.get_dt_info_dict(self.language)["weekday"]
- memories.append(f"[{datetime} {weekday}] {node.content}")
-
- # Set the final list of formatted memories back into the worker's context
- self.set_workflow_context(RESULT, "\n".join(memories))
diff --git a/reme_ai/retrieve/personal/print_memory_op.py b/reme_ai/retrieve/personal/print_memory_op.py
new file mode 100644
index 00000000..7a83cefc
--- /dev/null
+++ b/reme_ai/retrieve/personal/print_memory_op.py
@@ -0,0 +1,64 @@
+from typing import List
+
+from flowllm import C, BaseOp
+from loguru import logger
+
+from reme_ai.schema.memory import BaseMemory
+
+
+@C.register_op()
+class PrintMemoryOp(BaseOp):
+ """
+ Formats the memories to print.
+ """
+ file_path: str = __file__
+
+ def execute(self):
+ """
+ Executes the primary function, it involves:
+ 1. Fetches the memories.
+ 2. Formats them for printing.
+ 3. Set the formatted string back into the context
+ """
+ # Get memory list from context
+ memory_list: List[BaseMemory] = self.context.response.metadata.get("memory_list", [])
+
+ if not memory_list:
+ logger.info("No memories to print")
+ self.context.response.answer = "No memories found."
+ return
+
+ logger.info(f"Formatting {len(memory_list)} memories for printing")
+
+ # Format memories for printing
+ formatted_memories = self._format_memories_for_print(memory_list)
+
+ # Store result in context
+ self.context.response.answer = formatted_memories
+ logger.info(f"Formatted memories: {formatted_memories}")
+
+ @staticmethod
+ def _format_memories_for_print(memories: List[BaseMemory]) -> str:
+ """Format memories for printing"""
+ if not memories:
+ return "No memories available."
+
+ formatted_memories = []
+
+ for i, memory in enumerate(memories, 1):
+ memory_text = f"Memory {i}:\n"
+ memory_text += f" When to use: {memory.when_to_use}\n"
+ memory_text += f" Content: {memory.content}\n"
+
+ # Add additional metadata if available
+ if hasattr(memory, 'metadata') and memory.metadata:
+ metadata_items = []
+ for key, value in memory.metadata.items():
+ if key not in ['when_to_use', 'content']:
+ metadata_items.append(f"{key}: {value}")
+ if metadata_items:
+ memory_text += f" Metadata: {', '.join(metadata_items)}\n"
+
+ formatted_memories.append(memory_text)
+
+ return "\n".join(formatted_memories)
diff --git a/reme_ai/retrieve/personal/print_memory_prompt.yaml b/reme_ai/retrieve/personal/print_memory_prompt.yaml
new file mode 100644
index 00000000..a295dda8
--- /dev/null
+++ b/reme_ai/retrieve/personal/print_memory_prompt.yaml
@@ -0,0 +1,22 @@
+print_template_zh: |
+ ========== {user_name}关于{target_name}的长期记忆 ==========
+ ----- 观察记忆 -----
+ {observation_memory}
+
+ ----- 洞察记忆 -----
+ {insight_memory}
+
+ ----- 过期记忆 -----
+ {expired_memory}
+
+
+print_template: |
+ ========== The {user_name}'s long-term memory about {target_name} ==========
+ ----- observation memory -----
+ {observation_memory}
+
+ ----- insight memory -----
+ {insight_memory}
+
+ ----- expired memory -----
+ {expired_memory}
\ No newline at end of file
diff --git a/reme_ai/retrieve/personal/print_memory_worker.py b/reme_ai/retrieve/personal/print_memory_worker.py
deleted file mode 100644
index 7421614d..00000000
--- a/reme_ai/retrieve/personal/print_memory_worker.py
+++ /dev/null
@@ -1,66 +0,0 @@
-from typing import List
-
-from memoryscope.constants.common_constants import RETRIEVE_MEMORY_NODES, RESULT
-from memoryscope.core.utils.datetime_handler import DatetimeHandler
-from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
-from memoryscope.enumeration.memory_type_enum import MemoryTypeEnum
-from memoryscope.enumeration.store_status_enum import StoreStatusEnum
-from memoryscope.scheme.memory_node import MemoryNode
-
-
-class PrintMemoryWorker(MemoryBaseWorker):
- """
- Formats the memories to print.
- """
- FILE_PATH: str = __file__
-
- def _run(self):
- """
- Executes the primary function, it involves:
- 1. Fetches the memories.
- 2. Formats them by 'print_template'.
- 3. Set the formatted string back into the worker's context
- """
- # get long-term memory
- memory_node_list: List[MemoryNode] = self.memory_manager.get_memories(RETRIEVE_MEMORY_NODES)
- memory_node_list = sorted(memory_node_list, key=lambda x: x.timestamp, reverse=True)
-
- observation_memory_list: List[str] = []
- insight_memory_list: List[str] = []
- expired_memory_list: List[str] = []
-
- i = 0
- j = 0
- k = 0
- # remove duplicate content
- expired_content_set = set()
- for node in memory_node_list:
- if not node.content:
- continue
-
- dt_handler = DatetimeHandler(node.timestamp)
- dt = dt_handler.datetime_format("%Y%m%d %H:%M:%S")
- if StoreStatusEnum(node.store_status) is StoreStatusEnum.EXPIRED:
- if node.content in expired_content_set:
- continue
- else:
- expired_content_set.add(node.content)
- i += 1
- expired_memory_list.append(f"{dt}] {i}. {node.content}")
-
- elif MemoryTypeEnum(node.memory_type) in [MemoryTypeEnum.OBSERVATION, MemoryTypeEnum.OBS_CUSTOMIZED]:
- j += 1
- observation_memory_list.append(f"{dt}] {j}. {node.content} "
- f"[status({node.obs_reflected},{node.obs_updated})")
-
- elif MemoryTypeEnum(node.memory_type) is MemoryTypeEnum.INSIGHT:
- k += 1
- insight_memory_list.append(f"{dt}] {k}. {node.content}")
-
- result: str = self.prompt_handler.print_template.format(
- user_name=self.user_name,
- target_name=self.target_name,
- observation_memory="\n".join(observation_memory_list),
- insight_memory="\n".join(insight_memory_list),
- expired_memory="\n".join(expired_memory_list)).strip()
- self.set_workflow_context(RESULT, result)
diff --git a/reme_ai/retrieve/personal/print_memory_worker.yaml b/reme_ai/retrieve/personal/print_memory_worker.yaml
deleted file mode 100644
index d102a10c..00000000
--- a/reme_ai/retrieve/personal/print_memory_worker.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-print_template:
- cn: |
- ========== {user_name}关于{target_name}的长期记忆 ==========
- ----- 观察记忆 -----
- {observation_memory}
-
- ----- 洞察记忆 -----
- {insight_memory}
-
- ----- 过期记忆 -----
- {expired_memory}
-
- en: |
- ========== The {user_name}'s long-term memory about {target_name} ==========
- ----- observation memory -----
- {observation_memory}
-
- ----- insight memory -----
- {insight_memory}
-
- ----- expired memory -----
- {expired_memory}
\ No newline at end of file
diff --git a/reme_ai/retrieve/personal/read_message_op.py b/reme_ai/retrieve/personal/read_message_op.py
new file mode 100644
index 00000000..9b8b6b6a
--- /dev/null
+++ b/reme_ai/retrieve/personal/read_message_op.py
@@ -0,0 +1,52 @@
+from typing import List
+
+from flowllm import C, BaseOp
+from flowllm.schema.message import Message
+from loguru import logger
+
+
+@C.register_op()
+class ReadMessageOp(BaseOp):
+ """
+ Fetches unmemorized chat messages.
+ """
+ file_path: str = __file__
+
+ def execute(self):
+ """
+ Executes the primary function to fetch unmemorized chat messages.
+ """
+ # Get chat messages from context
+ chat_messages = self.context.chat_messages
+ target_name = self.context.target_name
+ contextual_msg_max_count = self.op_params.get('contextual_msg_max_count', 10)
+
+ chat_messages_not_memorized: List[List[Message]] = []
+ for messages in chat_messages:
+ if not messages:
+ continue
+
+ if hasattr(messages[0], 'memorized') and messages[0].memorized:
+ continue
+
+ contain_flag = False
+
+ for msg in messages:
+ if hasattr(msg, 'role_name') and msg.role_name == target_name:
+ contain_flag = True
+ break
+
+ if contain_flag:
+ chat_messages_not_memorized.append(messages)
+
+ chat_message_scatter = []
+ for messages in chat_messages_not_memorized[-contextual_msg_max_count:]:
+ chat_message_scatter.extend(messages)
+
+ # Sort by time_created if available
+ if chat_message_scatter and hasattr(chat_message_scatter[0], 'time_created'):
+ chat_message_scatter.sort(key=lambda _: _.time_created)
+
+ # Store result in context
+ self.context.chat_messages = chat_message_scatter
+ logger.info(f"Retrieved {len(chat_message_scatter)} unmemorized chat messages")
diff --git a/reme_ai/retrieve/personal/read_message_worker.py b/reme_ai/retrieve/personal/read_message_worker.py
deleted file mode 100644
index 2f378eef..00000000
--- a/reme_ai/retrieve/personal/read_message_worker.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from typing import List
-
-from memoryscope.constants.common_constants import RESULT
-from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
-from memoryscope.scheme.message import Message
-
-
-class ReadMessageWorker(MemoryBaseWorker):
- """
- Fetches unmemorized chat messages.
- """
-
- def _run(self):
- """
- Executes the primary function to fetch unmemorized chat messages.
- """
- chat_messages_not_memorized: List[List[Message]] = []
- for messages in self.chat_messages:
- if not messages:
- continue
-
- if messages[0].memorized:
- continue
-
- contain_flag = False
-
- for msg in messages:
- if msg.role_name == self.target_name:
- contain_flag = True
- break
-
- if contain_flag:
- chat_messages_not_memorized.append(messages)
-
- contextual_msg_max_count: int = self.chat_kwargs["contextual_msg_max_count"]
- chat_message_scatter = []
- for messages in chat_messages_not_memorized[-contextual_msg_max_count:]:
- chat_message_scatter.extend(messages)
- chat_message_scatter.sort(key=lambda _: _.time_created)
- self.set_workflow_context(RESULT, chat_message_scatter)
diff --git a/reme_ai/retrieve/personal/retrieve_memory_op.py b/reme_ai/retrieve/personal/retrieve_memory_op.py
new file mode 100644
index 00000000..c03b5b89
--- /dev/null
+++ b/reme_ai/retrieve/personal/retrieve_memory_op.py
@@ -0,0 +1,13 @@
+from flowllm import C
+
+from reme_ai.vector_store import RecallVectorStoreOp
+
+
+@C.register_op()
+class RetrieveMemoryOp(RecallVectorStoreOp):
+ """
+ Retrieves memories based on specified criteria such as status, type, and timestamp.
+ Processes these memories concurrently, sorts them by similarity, and logs the activity,
+ facilitating efficient memory retrieval operations within a given scope.
+ """
+ file_path: str = __file__
diff --git a/reme_ai/retrieve/personal/retrieve_memory_worker.py b/reme_ai/retrieve/personal/retrieve_memory_worker.py
deleted file mode 100644
index a539c7f4..00000000
--- a/reme_ai/retrieve/personal/retrieve_memory_worker.py
+++ /dev/null
@@ -1,143 +0,0 @@
-from typing import List
-
-from memoryscope.constants.common_constants import QUERY_WITH_TS, RETRIEVE_MEMORY_NODES
-from memoryscope.core.utils.timer import timer
-from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
-from memoryscope.enumeration.action_status_enum import ActionStatusEnum
-from memoryscope.enumeration.memory_type_enum import MemoryTypeEnum
-from memoryscope.enumeration.store_status_enum import StoreStatusEnum
-from memoryscope.scheme.memory_node import MemoryNode
-
-
-class RetrieveMemoryWorker(MemoryBaseWorker):
- """
- Retrieves memories based on specified criteria such as status, type, and timestamp.
- Processes these memories concurrently, sorts them by similarity, and logs the activity,
- facilitating efficient memory retrieval operations within a given scope.
- """
-
- def _parse_params(self, **kwargs):
- self.retrieve_obs_top_k: int = kwargs.get("retrieve_obs_top_k", 0)
- self.retrieve_ins_top_k: int = kwargs.get("retrieve_ins_top_k", 0)
- self.retrieve_expired_top_k: int = kwargs.get("retrieve_expired_top_k", 0)
-
- @timer
- def retrieve_from_observation(self, query: str) -> List[MemoryNode]:
- """
- Retrieves memory nodes from observation based on a query, considering active memories
- with specific types. If the retrieval limit is not set, an empty list is returned.
-
- Args:
- query (str): The query string used to filter and rank the memory nodes.
-
- Returns:
- List[MemoryNode]: A list of MemoryNode objects that match the query criteria,
- sorted by their relevance. Returns an empty list if no retrieval limit is configured.
- """
- if not self.retrieve_obs_top_k:
- return []
-
- filter_dict = {
- "user_name": self.user_name,
- "target_name": self.target_name,
- "store_status": StoreStatusEnum.VALID.value,
- "memory_type": [MemoryTypeEnum.OBSERVATION.value, MemoryTypeEnum.OBS_CUSTOMIZED.value],
- }
- # Retrieve memories matching the query, filtered by the specified conditions,
- # limited to a certain number, and sorted by relevance.
- return self.memory_store.retrieve_memories(query=query,
- top_k=self.retrieve_obs_top_k,
- filter_dict=filter_dict)
-
- @timer
- def retrieve_from_insight(self, query: str) -> List[MemoryNode]:
- """
- Retrieves memories marked as insights from the store based on a query, filtered by user, target,
- and set to active status.
-
- Args:
- query (str): The search query to match against the insights.
-
- Returns:
- List[MemoryNode]: A list of MemoryNode objects that match the query criteria,
- limited by 'retrieve_ins_pf_top_k'.
- Returns an empty list if 'retrieve_ins_pf_top_k' is not set.
- """
- if not self.retrieve_ins_top_k:
- return []
-
- filter_dict = {
- "user_name": self.user_name,
- "target_name": self.target_name,
- "store_status": StoreStatusEnum.VALID.value,
- "memory_type": MemoryTypeEnum.INSIGHT.value,
- }
- # ⭐ Retrieve insights matching the query, filtered, and limited by top_k
- return self.memory_store.retrieve_memories(query=query,
- top_k=self.retrieve_ins_top_k,
- filter_dict=filter_dict)
-
- @timer
- def retrieve_expired_memory(self, query: str) -> List[MemoryNode]:
- """
- Retrieves expired memories marked as observation from the store based on a query, filtered by user, target,
- and set to active status.
-
- Args:
- query (str): The search query to match against the memories.
-
- Returns:
- List[MemoryNode]: A list of MemoryNode objects that match the query criteria,
- limited by 'retrieve_expired_top_k'.
- Returns an empty list if 'retrieve_expired_top_k' is not set.
- """
- if not self.retrieve_expired_top_k:
- return []
-
- filter_dict = {
- "user_name": self.user_name,
- "target_name": self.target_name,
- "store_status": StoreStatusEnum.EXPIRED.value,
- "memory_type": [MemoryTypeEnum.OBSERVATION.value, MemoryTypeEnum.OBS_CUSTOMIZED.value],
- }
- return self.memory_store.retrieve_memories(query=query,
- top_k=self.retrieve_expired_top_k,
- filter_dict=filter_dict)
-
- def _run(self):
- """
- Executes the main retrieval for memories. It fetches the query from the context, initiates concurrent tasks
- to retrieve memories from observations, insights, and expired sources, collects the results, sorts them by
- similarity score, logs the details, and finally sets the retrieved memory nodes.
-
- The method follows these steps:
- 1. Retrieves the query from the worker's context.
- 2. Submits tasks to asynchronously retrieve memories from various sources.
- 3. Gathers the results from all submitted tasks.
- 4. Logs the total number of collected memory nodes.
- 5. Sorts the memory nodes based on their similarity scores in descending order.
- 6. Logs detailed information about each memory node.
- 7. Stores the processed memory nodes for further use.
- """
- query, _ = self.get_workflow_context(QUERY_WITH_TS)
- self.logger.info(f"retrieve memory with query={query}.")
- self.submit_thread_task(self.retrieve_from_observation, query=query)
- self.submit_thread_task(self.retrieve_from_insight, query=query)
- self.submit_thread_task(self.retrieve_expired_memory, query=query)
-
- memory_node_list: List[MemoryNode] = []
- for result in self.gather_thread_result():
- if result:
- memory_node_list.extend(result)
- self.logger.info(f"memory_node_list.size={len(memory_node_list)}")
-
- if not memory_node_list:
- return
-
- memory_node_list = sorted(memory_node_list, key=lambda x: x.score_recall, reverse=True)
- for node in memory_node_list:
- node.action_status = ActionStatusEnum.NONE.value
- self.logger.info(f"recall_stage: content={node.content} score={node.score_recall} type={node.memory_type} "
- f"store_status={node.store_status} action_status={node.action_status}")
-
- self.memory_manager.set_memories(RETRIEVE_MEMORY_NODES, memory_node_list)
diff --git a/reme_ai/retrieve/personal/semantic_rank_op.py b/reme_ai/retrieve/personal/semantic_rank_op.py
new file mode 100644
index 00000000..969e7195
--- /dev/null
+++ b/reme_ai/retrieve/personal/semantic_rank_op.py
@@ -0,0 +1,166 @@
+from typing import List
+
+from flowllm import C, BaseLLMOp
+from loguru import logger
+
+from reme_ai.schema.memory import BaseMemory
+
+
+def _parse_ranking_response(response: str) -> List[dict]:
+ """Parse LLM ranking response"""
+ import json
+ import re
+
+ try:
+ # Try to extract JSON blocks
+ json_pattern = r'```json\s*([\s\S]*?)\s*```'
+ json_blocks = re.findall(json_pattern, response)
+
+ if json_blocks:
+ parsed = json.loads(json_blocks[0])
+ if isinstance(parsed, dict) and "rankings" in parsed:
+ return parsed["rankings"]
+
+ # Fallback: try to parse the entire response as JSON
+ parsed = json.loads(response)
+ if isinstance(parsed, dict) and "rankings" in parsed:
+ return parsed["rankings"]
+
+ except json.JSONDecodeError:
+ logger.warning("Failed to parse ranking response as JSON")
+
+ return []
+
+
+@C.register_op()
+class SemanticRankOp(BaseLLMOp):
+ """
+ The SemanticRankOp class processes queries by retrieving memory nodes,
+ removing duplicates, ranking them based on semantic relevance using a model,
+ assigning scores, sorting the nodes, and storing the ranked nodes back,
+ while logging relevant information.
+ """
+ file_path: str = __file__
+
+ def execute(self):
+ """
+ Executes the primary workflow of the SemanticRankOp which includes:
+ - Retrieves query and memory list from context.
+ - Removes duplicate memories.
+ - Ranks memories semantically using LLM.
+ - Assigns scores to memories.
+ - Sorts memories by score.
+ - Saves the ranked memories back to context.
+
+ If no memories are retrieved or if the ranking fails,
+ appropriate warnings are logged.
+ """
+ # Get memory list from context
+ memory_list: List[BaseMemory] = self.context.response.metadata.get("memory_list", [])
+ query: str = self.context.query
+
+ # Get parameters from op_params
+ enable_ranker: bool = self.op_params.get("enable_ranker", True)
+ output_memory_max_count: int = self.op_params.get("output_memory_max_count", 10)
+
+ if not memory_list:
+ logger.warning("Memory list is empty!")
+ return
+
+ if not enable_ranker or len(memory_list) <= output_memory_max_count:
+ # Use original scores if ranker is disabled or memory count is small
+ logger.warning("Using original scores instead of semantic ranking!")
+ else:
+ # Remove duplicates based on content
+ memory_dict = {memory.content.strip(): memory for memory in memory_list if memory.content.strip()}
+ memory_list = list(memory_dict.values())
+
+ # Perform semantic ranking using LLM
+ ranked_memories = self._semantic_rank_memories(query, memory_list)
+ if ranked_memories:
+ memory_list = ranked_memories
+
+ # Sort by score (assuming score is available in BaseMemory)
+ memory_list = sorted(memory_list, key=lambda m: getattr(m, 'score', 0.0), reverse=True)
+
+ # Log ranked memories
+ logger.info(f"Semantic rank stage: query={query}")
+ for i, memory in enumerate(memory_list):
+ score = getattr(memory, 'score', 0.0)
+ logger.info(f"Rank stage: Memory {i + 1}: Content={memory.content[:100]}..., Score={score}")
+
+ # Save ranked memories back to context
+ self.context.response.metadata["memory_list"] = memory_list
+
+ def _semantic_rank_memories(self, query: str, memories: List[BaseMemory]) -> List[BaseMemory]:
+ """
+ Use LLM to semantically rank memories based on relevance to the query
+ """
+ if not memories:
+ return memories
+
+ try:
+ # Format memories for ranking
+ formatted_memories = self._format_memories_for_ranking(memories)
+
+ # Create prompt for semantic ranking
+ prompt = f"""Given the query: "{query}"
+
+Please rank the following memories by their semantic relevance to the query.
+Rate each memory on a scale of 0.0 to 1.0 where 1.0 is most relevant.
+
+Memories:
+{formatted_memories}
+
+Please respond in JSON format:
+{{"rankings": [{{"index": 0, "score": 0.8}}, {{"index": 1, "score": 0.6}}, ...]}}"""
+
+ # Get LLM response
+ from flowllm.schema.message import Message
+ from flowllm.enumeration.role import Role
+
+ response = self.llm.chat([Message(role=Role.USER, content=prompt)])
+
+ if not response or not response.content:
+ logger.warning("LLM ranking failed, using original order")
+ return memories
+
+ # Parse ranking results
+ rankings = _parse_ranking_response(response.content)
+
+ if rankings:
+ # Apply scores to memories
+ for ranking in rankings:
+ idx = ranking.get("index", -1)
+ score = ranking.get("score", 0.0)
+ if 0 <= idx < len(memories):
+ # Set score on memory object
+ if hasattr(memories[idx], 'score'):
+ memories[idx].score = score
+ else:
+ # Add score as metadata if score attribute doesn't exist
+ if not hasattr(memories[idx], 'metadata'):
+ memories[idx].metadata = {}
+ memories[idx].metadata['semantic_score'] = score
+
+ logger.info(f"Successfully applied semantic rankings to {len(rankings)} memories")
+ else:
+ logger.warning("Failed to parse ranking results")
+
+ except Exception as e:
+ logger.error(f"Error in semantic ranking: {e}")
+
+ return memories
+
+ @staticmethod
+ def _format_memories_for_ranking(memories: List[BaseMemory]) -> str:
+ """Format memories for LLM ranking"""
+ formatted_memories = []
+
+ for i, memory in enumerate(memories):
+ memory_text = f"Memory {i}:\n"
+ memory_text += f"When to use: {memory.when_to_use}\n"
+ memory_text += f"Content: {memory.content}\n"
+ formatted_memories.append(memory_text)
+
+ return "\n---\n".join(formatted_memories)
diff --git a/reme_ai/retrieve/personal/semantic_rank_worker.py b/reme_ai/retrieve/personal/semantic_rank_worker.py
deleted file mode 100644
index 894785fd..00000000
--- a/reme_ai/retrieve/personal/semantic_rank_worker.py
+++ /dev/null
@@ -1,72 +0,0 @@
-from typing import List, Dict
-
-from memoryscope.constants.common_constants import RETRIEVE_MEMORY_NODES, QUERY_WITH_TS, RANKED_MEMORY_NODES
-from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
-from memoryscope.scheme.memory_node import MemoryNode
-
-
-class SemanticRankWorker(MemoryBaseWorker):
- """
- The `SemanticRankWorker` class processes queries by retrieving memory nodes,
- removing duplicates, ranking them based on semantic relevance using a model,
- assigning scores, sorting the nodes, and storing the ranked nodes back,
- while logging relevant information.
- """
-
- def _parse_params(self, **kwargs):
- self.enable_ranker: bool = self.memoryscope_context.meta_data["enable_ranker"]
- self.output_memory_max_count: int = self.memoryscope_context.meta_data["output_memory_max_count"]
-
- def _run(self):
- """
- Executes the primary workflow of the SemanticRankWorker which includes:
- - Retrieves query and timestamp from context.
- - Fetches memory nodes.
- - Removes duplicate nodes.
- - Ranks nodes semantically.
- - Assigns scores to nodes.
- - Sorts nodes by score.
- - Saves the ranked nodes back with logging.
-
- If no memory nodes are retrieved or if the ranking model fails,
- appropriate warnings are logged.
- """
- # query
- query, _ = self.get_workflow_context(QUERY_WITH_TS)
- memory_node_list: List[MemoryNode] = self.memory_manager.get_memories(RETRIEVE_MEMORY_NODES)
- if not memory_node_list:
- self.logger.warning("Retrieve memory nodes is empty!")
- return
-
- if not self.enable_ranker or len(memory_node_list) <= self.output_memory_max_count:
- for node in memory_node_list:
- node.score_rank = node.score_recall
- self.logger.warning("use score_recall instead of score_rank!")
-
- else:
- # drop repeated
- memory_node_dict: Dict[str, MemoryNode] = {n.content.strip(): n for n in memory_node_list if
- n.content.strip()}
- memory_node_list = list(memory_node_dict.values())
-
- response = self.rank_model.call(query=query, documents=[n.content for n in memory_node_list])
- if not response.status or not response.rank_scores:
- return
-
- # set score
- for idx, score in response.rank_scores.items():
- if idx >= len(memory_node_list):
- self.logger.warning(f"Idx={idx} exceeds the maximum length of rank_scores!")
- continue
- memory_node_list[idx].score_rank = score
-
- # sort by score
- memory_node_list = sorted(memory_node_list, key=lambda n: n.score_rank, reverse=True)
-
- # log ranked nodes
- self.logger.info(f"Rank stage: query={query}")
- for node in memory_node_list:
- self.logger.info(f"Rank stage: Content={node.content}, Score={node.score_rank}")
-
- # save ranked nodes back to memory
- self.memory_manager.set_memories(RANKED_MEMORY_NODES, memory_node_list, log_repeat=False)
diff --git a/reme_ai/retrieve/personal/set_query_op.py b/reme_ai/retrieve/personal/set_query_op.py
new file mode 100644
index 00000000..937589d4
--- /dev/null
+++ b/reme_ai/retrieve/personal/set_query_op.py
@@ -0,0 +1,64 @@
+import datetime
+from typing import Tuple
+
+from flowllm import C, BaseOp
+from loguru import logger
+
+from reme_ai.constants.common_constants import QUERY_WITH_TS
+
+
+@C.register_op()
+class SetQueryOp(BaseOp):
+ """
+ The `SetQueryOp` class is responsible for setting a query and its associated timestamp
+ into the context, utilizing either provided parameters or details from the context.
+ """
+ file_path: str = __file__
+
+ def execute(self):
+ """
+ Executes the operation's primary function, which involves determining the query and its
+ timestamp, then storing these values within the context.
+
+ If 'query' exists in context, it is used directly. Otherwise, extracts query from
+ messages or other context parameters.
+ """
+ query = "" # Default query value
+ timestamp = int(datetime.datetime.now().timestamp()) # Current timestamp as default
+
+ try:
+ # Check if query already exists in context
+ if hasattr(self.context, 'query') and self.context.query:
+ query = str(self.context.query).strip()
+ logger.info(f"Using existing query from context: {query}")
+
+ # Check for query in op_params
+ elif "query" in self.op_params:
+ query = self.op_params["query"]
+ if not query:
+ query = ""
+ query = query.strip()
+ logger.info(f"Using query from op_params: {query}")
+
+ # Check for messages in context
+ elif hasattr(self.context, 'messages') and self.context.messages:
+ # Use the last message content as query
+ last_message = self.context.messages[-1]
+ query = last_message.content.strip() if hasattr(last_message, 'content') else ""
+ logger.info(f"Using query from last message: {query}")
+
+ # Set timestamp if provided in op_params
+ _timestamp = self.op_params.get("timestamp")
+ if _timestamp and isinstance(_timestamp, int):
+ timestamp = _timestamp
+
+ # Store the determined query and its timestamp in the context
+ query_with_ts: Tuple[str, int] = (query, timestamp)
+ self.context[QUERY_WITH_TS] = query_with_ts
+
+ logger.info(f"Set query with timestamp: query='{query}', timestamp={timestamp}")
+
+ except Exception as e:
+ logger.error(f"Error in SetQueryOp execution: {e}")
+ # Fallback: set empty query with current timestamp
+ self.context[QUERY_WITH_TS] = ("", timestamp)
diff --git a/reme_ai/retrieve/personal/set_query_worker.py b/reme_ai/retrieve/personal/set_query_worker.py
deleted file mode 100644
index fe541c11..00000000
--- a/reme_ai/retrieve/personal/set_query_worker.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import datetime
-
-from memoryscope.constants.common_constants import QUERY_WITH_TS
-from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
-
-
-class SetQueryWorker(MemoryBaseWorker):
- """
- The `SetQueryWorker` class is responsible for setting a query and its associated timestamp
- into the context, utilizing either provided chat parameters or details from the most recent
- chat message.
- """
-
- def _run(self):
- """
- Executes the worker's primary function, which involves determining the query and its
- timestamp, then storing these values within the context.
-
- If 'query' is found within `self.chat_kwargs`, it is considered as the query input.
- Otherwise, the content of the last message in `self.chat_messages` is used as the query,
- along with its creation timestamp.
- """
- query = "" # Default query value
- timestamp = int(datetime.datetime.now().timestamp()) # Current timestamp as default
-
- if "query" in self.chat_kwargs:
- # set query if exists
- query = self.chat_kwargs["query"]
- if not query:
- query = ""
- query = query.strip()
-
- # set ts if exists
- _timestamp = self.chat_kwargs.get("timestamp")
- if _timestamp and isinstance(_timestamp, int):
- timestamp = _timestamp
-
- # Store the determined query and its timestamp in the context
- self.set_workflow_context(QUERY_WITH_TS, (query, timestamp))
diff --git a/reme_ai/retrieve/task/rerank_memory_op.py b/reme_ai/retrieve/task/rerank_memory_op.py
index 45923232..029d2ed4 100644
--- a/reme_ai/retrieve/task/rerank_memory_op.py
+++ b/reme_ai/retrieve/task/rerank_memory_op.py
@@ -63,8 +63,7 @@ class RerankMemoryOp(BaseLLMOp):
prompt_name="memory_rerank_prompt",
query=query,
candidates=candidates_text,
- num_candidates=len(candidates)
- )
+ num_candidates=len(candidates))
response = self.llm.chat([Message(role=Role.USER, content=prompt)])
diff --git a/reme_ai/retrieve/task/rerank_memory_prompt.yaml b/reme_ai/retrieve/task/rerank_memory_prompt.yaml
index 2d344d6e..8cba9147 100644
--- a/reme_ai/retrieve/task/rerank_memory_prompt.yaml
+++ b/reme_ai/retrieve/task/rerank_memory_prompt.yaml
@@ -1,4 +1,4 @@
-experience_rerank_prompt: |
+memory_rerank_prompt: |
You are an expert AI analyst tasked with reranking retrieved experiences based on their relevance to a specific query.
Your task is to analyze the candidates and rank them by relevance, considering:
diff --git a/reme_ai/retrieve/task/rewrite_memory_op.py b/reme_ai/retrieve/task/rewrite_memory_op.py
index 13e19363..f50d7ec1 100644
--- a/reme_ai/retrieve/task/rewrite_memory_op.py
+++ b/reme_ai/retrieve/task/rewrite_memory_op.py
@@ -70,8 +70,7 @@ class RewriteMemoryOp(BaseLLMOp):
prompt_name="memory_rewrite_prompt",
current_query=query,
current_context=current_context,
- original_context=context_content
- )
+ original_context=context_content)
response = self.llm.chat([Message(role=Role.USER, content=prompt)])
diff --git a/reme_ai/retrieve/task/rewrite_memory_prompt.yaml b/reme_ai/retrieve/task/rewrite_memory_prompt.yaml
index e3459c04..d8804474 100644
--- a/reme_ai/retrieve/task/rewrite_memory_prompt.yaml
+++ b/reme_ai/retrieve/task/rewrite_memory_prompt.yaml
@@ -1,4 +1,4 @@
-experience_rewrite_prompt: |
+memory_rewrite_prompt: |
You are an expert AI assistant tasked with rewriting and reorganizing context content to make it more relevant and actionable for the current task.
Your task is to take the original context (containing multiple experiences) and rewrite it as a cohesive, task-specific guidance that directly addresses the current situation.
@@ -32,46 +32,3 @@ experience_rewrite_prompt: |
- Consolidate overlapping insights into coherent recommendations
- Prioritize experiences most relevant to the current situation
- Make the guidance feel custom-written for this specific task
-
-context_generation_prompt: |
- You are an expert AI assistant tasked with synthesizing retrieved experiences into actionable context for an AI agent.
-
- Your task is to create a coherent, actionable context message that helps the agent leverage relevant past experiences.
-
- SYNTHESIS GUIDELINES:
- ● RELEVANCE FOCUS: Emphasize the most relevant aspects of each experience
- ● ACTIONABLE INSIGHTS: Extract specific, actionable guidance
- ● COHERENT NARRATIVE: Create a flowing narrative rather than disconnected tips
- ● SITUATIONAL AWARENESS: Adapt the guidance to the current situation
-
- # Current Query/Task
- {query}
-
- # Current Step Info
- {context}
-
- # Retrieved Experiences ({num_experiences} total)
- {experiences}
-
- OUTPUT FORMAT:
- Create a synthesized context message:
- ```json
- {{
- "context": "A coherent, actionable context message that synthesizes the relevant experiences and provides specific guidance for the current task",
- "key_insights": [
- "Key pattern from successful approaches",
- "Common pitfall to avoid",
- "Specific technique that worked well"
- ],
- "recommended_actions": [
- "Specific action recommendation based on experiences",
- "Decision point with recommended choice"
- ]
- }}
- ```
-
- Guidelines:
- - Make the context immediately actionable
- - Prioritize the most relevant experiences
- - Use clear, direct language
- - Focus on practical guidance rather than abstract principles
\ No newline at end of file
diff --git a/reme_ai/schema/memory.py b/reme_ai/schema/memory.py
index 90b36a15..93b12142 100644
--- a/reme_ai/schema/memory.py
+++ b/reme_ai/schema/memory.py
@@ -1,6 +1,5 @@
import datetime
from abc import ABC
-from typing import List
from uuid import uuid4
from flowllm.schema.vector_node import VectorNode
@@ -64,40 +63,81 @@ class TaskMemory(BaseMemory):
metadata=node.metadata.get("metadata"))
-class FunctionArg(BaseModel):
- arg_name: str = Field(default=...)
- arg_type: str = Field(default=...)
- required: bool = Field(default=True)
-
-
-class Function(BaseModel):
- func_code: str = Field(default=..., description="function code")
- func_name: str = Field(default=..., description="function name")
- func_args: List[FunctionArg] = Field(default_factory=list)
-
-
-class FuncMemory(BaseMemory):
- memory_type: str = Field(default="function")
- functions: List[Function] = Field(default_factory=list)
-
-
class PersonalMemory(BaseMemory):
memory_type: str = Field(default="personal")
target: str = Field(default="")
+ reflection_subject: str = Field(default="") # For storing reflection subject attributes
+
+ def to_vector_node(self) -> VectorNode:
+ return VectorNode(unique_id=self.memory_id,
+ workspace_id=self.workspace_id,
+ content=self.when_to_use,
+ metadata={
+ "memory_type": self.memory_type,
+ "content": self.content,
+ "target": self.target,
+ "reflection_subject": self.reflection_subject,
+ "score": self.score,
+ "created_time": self.created_time,
+ "modified_time": self.modified_time,
+ "author": self.author,
+ "metadata": self.metadata,
+ })
+
+ @classmethod
+ def from_vector_node(cls, node: VectorNode) -> "PersonalMemory":
+ return cls(workspace_id=node.workspace_id,
+ memory_id=node.unique_id,
+ memory_type=node.metadata.get("memory_type"),
+ when_to_use=node.content,
+ content=node.metadata.get("content"),
+ target=node.metadata.get("target", ""),
+ reflection_subject=node.metadata.get("reflection_subject", ""),
+ score=node.metadata.get("score"),
+ created_time=node.metadata.get("created_time"),
+ modified_time=node.metadata.get("modified_time"),
+ author=node.metadata.get("author"),
+ metadata=node.metadata.get("metadata"))
class PersonalTopicMemory(PersonalMemory):
memory_type: str = Field(default="personal_topic")
+ def to_vector_node(self) -> VectorNode:
+ return VectorNode(unique_id=self.memory_id,
+ workspace_id=self.workspace_id,
+ content=self.when_to_use,
+ metadata={
+ "memory_type": self.memory_type,
+ "content": self.content,
+ "target": self.target,
+ "score": self.score,
+ "created_time": self.created_time,
+ "modified_time": self.modified_time,
+ "author": self.author,
+ "metadata": self.metadata,
+ })
+
+ @classmethod
+ def from_vector_node(cls, node: VectorNode) -> "PersonalTopicMemory":
+ return cls(workspace_id=node.workspace_id,
+ memory_id=node.unique_id,
+ memory_type=node.metadata.get("memory_type"),
+ when_to_use=node.content,
+ content=node.metadata.get("content"),
+ target=node.metadata.get("target", ""),
+ score=node.metadata.get("score"),
+ created_time=node.metadata.get("created_time"),
+ modified_time=node.metadata.get("modified_time"),
+ author=node.metadata.get("author"),
+ metadata=node.metadata.get("metadata"))
+
def vector_node_to_memory(node: VectorNode) -> BaseMemory:
memory_type = node.metadata.get("memory_type")
if memory_type == "task":
return TaskMemory.from_vector_node(node)
- elif memory_type == "function":
- return FuncMemory.from_vector_node(node)
-
elif memory_type == "personal":
return PersonalMemory.from_vector_node(node)
@@ -113,9 +153,6 @@ def dict_to_experience(memory_dict: dict):
if memory_type == "task":
return TaskMemory(**memory_dict)
- elif memory_type == "function":
- return FuncMemory(**memory_dict)
-
elif memory_type == "personal":
return PersonalMemory(**memory_dict)
diff --git a/reme_ai/summary/personal/__init__.py b/reme_ai/summary/personal/__init__.py
index 01d3682e..7d024ec7 100644
--- a/reme_ai/summary/personal/__init__.py
+++ b/reme_ai/summary/personal/__init__.py
@@ -1,21 +1,19 @@
-from .contra_repeat_worker import ContraRepeatWorker
-from .get_observation_with_time_worker import GetObservationWithTimeWorker
-from .get_observation_worker import GetObservationWorker
-from .get_reflection_subject_worker import GetReflectionSubjectWorker
-from .info_filter_worker import InfoFilterWorker
-from .load_memory_worker import LoadMemoryWorker
-from .long_contra_repeat_worker import LongContraRepeatWorker
-from .update_insight_worker import UpdateInsightWorker
-from .update_memory_worker import UpdateMemoryWorker
+from .contra_repeat_op import ContraRepeatOp
+from .get_observation_op import GetObservationOp
+from .get_observation_with_time_op import GetObservationWithTimeOp
+from .get_reflection_subject_op import GetReflectionSubjectOp
+from .info_filter_op import InfoFilterOp
+from .load_memory_op import LoadMemoryOp
+from .long_contra_repeat_op import LongContraRepeatOp
+from .update_insight_op import UpdateInsightOp
__all__ = [
- "ContraRepeatWorker",
- "GetObservationWithTimeWorker",
- "GetObservationWorker",
- "GetReflectionSubjectWorker",
- "InfoFilterWorker",
- "LoadMemoryWorker",
- "LongContraRepeatWorker",
- "UpdateInsightWorker",
- "UpdateMemoryWorker"
+ "ContraRepeatOp",
+ "GetObservationWithTimeOp",
+ "GetObservationOp",
+ "GetReflectionSubjectOp",
+ "InfoFilterOp",
+ "LoadMemoryOp",
+ "LongContraRepeatOp",
+ "UpdateInsightOp"
]
diff --git a/reme_ai/summary/personal/contra_repeat_op.py b/reme_ai/summary/personal/contra_repeat_op.py
new file mode 100644
index 00000000..087a9fda
--- /dev/null
+++ b/reme_ai/summary/personal/contra_repeat_op.py
@@ -0,0 +1,131 @@
+from typing import List
+
+from flowllm import C, BaseLLMOp
+from flowllm.enumeration.role import Role
+from flowllm.schema.message import Message
+from loguru import logger
+
+from reme_ai.schema.memory import BaseMemory
+
+
+@C.register_op()
+class ContraRepeatOp(BaseLLMOp):
+ """
+ The `ContraRepeatOp` class specializes in processing memory nodes to identify and handle
+ contradictory and repetitive information. It extends the base functionality of `BaseLLMOp`.
+
+ Responsibilities:
+ - Collects observation memories from context.
+ - Constructs a prompt with these observations for language model analysis.
+ - Parses the model's response to detect contradictions or redundancies.
+ - Filters and returns the processed memories.
+ """
+ file_path: str = __file__
+
+ def execute(self):
+ """
+ Executes the primary routine of the ContraRepeatOp which involves:
+ 1. Gets memory list from context
+ 2. Constructs a prompt with these memories for language model analysis
+ 3. Parses the model's response to detect contradictions or redundancies
+ 4. Filters and returns the processed memories
+ """
+ # Get memory list from context
+ memory_list: List[BaseMemory] = self.context.response.metadata.get("memory_list", [])
+
+ if not memory_list:
+ logger.info("memory_list is empty!")
+ return
+
+ # Get operation parameters
+ contra_repeat_max_count: int = self.op_params.get("contra_repeat_max_count", 50)
+ enable_contra_repeat: bool = self.op_params.get("enable_contra_repeat", True)
+
+ if not enable_contra_repeat:
+ logger.warning("contra_repeat is not enabled!")
+ return
+
+ # Sort and limit memories by count
+ sorted_memories = sorted(memory_list, key=lambda x: getattr(x, 'created_at', ''), reverse=True)[
+ :contra_repeat_max_count]
+
+ if len(sorted_memories) <= 1:
+ logger.info("sorted_memories.size<=1, stop.")
+ return
+
+ # Build prompt
+ user_query_list = []
+ for i, memory in enumerate(sorted_memories):
+ user_query_list.append(f"{i + 1} {memory.content}")
+
+ user_name = self.context.get("user_name", "user")
+
+ # Create prompt using the new pattern
+ system_prompt = self.prompt_format(prompt_name="contra_repeat_system",
+ num_obs=len(user_query_list),
+ user_name=user_name)
+ few_shot = self.prompt_format(prompt_name="contra_repeat_few_shot", user_name=user_name)
+ user_query = self.prompt_format(prompt_name="contra_repeat_user_query",
+ user_query="\n".join(user_query_list))
+
+ full_prompt = f"{system_prompt}\n\n{few_shot}\n\n{user_query}"
+ logger.info(f"contra_repeat_prompt={full_prompt}")
+
+ # Call LLM
+ response = self.llm.chat([Message(role=Role.USER, content=full_prompt)])
+
+ # Return if empty
+ if not response or not response.content:
+ logger.warning("Empty response from LLM")
+ return
+
+ response_text = response.content
+ logger.info(f"contra_repeat_response={response_text}")
+
+ # Parse response and filter memories
+ filtered_memories = self._parse_and_filter_memories(response_text, sorted_memories, user_name)
+
+ # Update context with filtered memories
+ self.context.response.metadata["memory_list"] = filtered_memories
+ logger.info(f"Filtered {len(memory_list)} memories to {len(filtered_memories)} memories")
+
+ def _parse_and_filter_memories(self, response_text: str, memories: List[BaseMemory], user_name: str) -> List[
+ BaseMemory]:
+ """Parse LLM response and filter memories based on contradiction/containment analysis"""
+ import re
+
+ # Parse the response to extract judgments
+ pattern = r"<(\d+)>\s*<(矛盾|被包含|无|Contradiction|Contained|None)>"
+ matches = re.findall(pattern, response_text, re.IGNORECASE)
+
+ if not matches:
+ logger.warning("No valid judgments found in response")
+ return memories
+
+ # Create a set of indices to remove (contradictory or contained memories)
+ indices_to_remove = set()
+
+ for idx_str, judgment in matches:
+ try:
+ idx = int(idx_str) - 1 # Convert to 0-based index
+ if idx >= len(memories):
+ logger.warning(f"Invalid index {idx} for memories list of length {len(memories)}")
+ continue
+
+ judgment_lower = judgment.lower()
+ if judgment_lower in ['矛盾', 'contradiction', '被包含', 'contained']:
+ indices_to_remove.add(idx)
+ logger.info(f"Marking memory {idx + 1} for removal: {judgment} - {memories[idx].content[:100]}...")
+
+ except ValueError:
+ logger.warning(f"Invalid index format: {idx_str}")
+ continue
+
+ # Filter out the memories marked for removal
+ filtered_memories = [memory for i, memory in enumerate(memories) if i not in indices_to_remove]
+
+ return filtered_memories
+
+ def get_language_value(self, value_dict: dict):
+ """Get language-specific value from dictionary"""
+ return value_dict.get(self.language, value_dict.get("en"))
diff --git a/reme_ai/summary/personal/contra_repeat_prompt.yaml b/reme_ai/summary/personal/contra_repeat_prompt.yaml
new file mode 100644
index 00000000..4e21259d
--- /dev/null
+++ b/reme_ai/summary/personal/contra_repeat_prompt.yaml
@@ -0,0 +1,127 @@
+contra_repeat_system_zh: |
+ 任务:对下面的{num_obs}句句子,逐一判断是否与“前面序号”的任意句子存在信息的矛盾,或者句子的主要信息被“前面序号”的任意句子中的信息包含。
+ 注意:对每句句子,只判断与“前面序号”的句子的关系,不要判断与“后面序号”的句子的关系。
+ 其中矛盾的形式可以有很多种,可以是逻辑上的矛盾,可以是属性上的变化导致的矛盾,比如不能同时在两个地方工作,同一个时刻不能在两个地点,同一个时刻不能干两件事情等等。
+ 对每个句子都做一个判断,最后一共输出{num_obs}条判断。
+ 请一步步思考,并按如下格式输出:
+ 思考:思考的依据和过程,30字以内。
+ 判断:<句子序号> <矛盾,被包含,无>,一定加<>
+
+
+contra_repeat_system: |
+ Task: For the following {num_obs} sentences, determine whether each sentence contradicts any of the previous numbered sentences or if the main information in the sentence is contained within the information from any of the previous numbered sentences.
+ Note: Only determine the relationship with the "previous numbered" sentences, do not judge the "later numbered" sentences.
+ The forms of contradiction can be varied. It can be a logical contradiction, or a contradiction due to changes in attributes, for example, not being able to work in two places at once, not being able to be in two places at the same time, not being able to do two things at the same time, etc.
+ Make a judgment for each sentence and output a total of {num_obs} judgments.
+ Think step by step and output in the following format:
+ Thought: Basis and process of thinking, within 30 words.
+ Judgment: , must be enclosed in <>.
+
+
+contra_repeat_few_shot_zh: |
+ 示例1
+ 句子:
+ 1 {user_name}经常失眠,对安眠药的效果感兴趣,暗示可能考虑使用。
+ 2 {user_name}经常失眠,寻求缓解方法。
+ 3 陈伟业是{user_name}的领导
+ 4 陈伟业是{user_name}的领导
+ 5 陈伟业是{user_name}的领导,是银行分行行长
+ 6 {user_name}喜欢吃西瓜
+ 7 {user_name}喜欢吃苹果
+
+ 思考:第1句不会存在与前面序号句子的矛盾或者完全重复。
+ 判断:<1> <无>
+ 思考:第2句中所有信息都被前面序号中第1句的信息完全包含。
+ 判断:<2> <被包含>
+ 思考:第3句信息没有在前面序号句子中出现
+ 判断:<3> <无>
+ 思考:第4句与前面序号中第3句的信息完全重复,即被完全包含。
+ 判断:<4> <被包含>
+ 思考:第5句中陈伟业是{user_name}的领导的信息被前面序号中第3句的信息包含,但新增了陈伟业是银行分行行长的信息,故不是被完全包含。
+ 判断:<5> <无>
+ 思考:第6句中表达了{user_name}的水果偏好,喜欢吃西瓜,信息没有在前面序号句子中出现。
+ 判断:<6> <无>
+ 思考:第7句也表达了{user_name}的水果偏好,喜欢吃桃子,和前面序号中的第6句不冲突,喜好可以同时存在。
+ 判断:<7> <无>
+
+ 示例2
+ 句子:
+ 1 {user_name}的孩子成绩不太好。
+ 2 {user_name}的孩子在学校经常逃课。
+ 3 {user_name}的父亲生日在2024年6月2日,{user_name}打算准备礼物。
+ 4 {user_name}的父亲生日在2024年5月1日。
+ 5 {user_name}很喜欢和同班同学打篮球。
+ 6 {user_name}喜欢打篮球。
+
+ 思考:第1句不会存在与前面序号句子的矛盾或者完全重复。
+ 判断:<1> <无>
+ 思考:第2句与前面序号句子既不矛盾也不重复。
+ 判断:<2> <无>
+ 思考:第3句与前面序号句子既不矛盾也不重复。
+ 判断:<3> <无>
+ 思考:第4句关于{user_name}父亲生日的日期信息与前面序号句子第3句矛盾了。
+ 判断:<4> <矛盾>
+ 思考:第5句与前面序号句子既不矛盾也不重复。
+ 判断:<5> <无>
+ 思考:第6句中所有信息都被前面序号中第5句的信息完全包含。
+ 判断:<2> <被包含>
+
+
+contra_repeat_few_shot: |
+ Example 1
+ Sentences:
+ 1 {user_name} suffers from insomnia frequently and is interested in the effects of sleeping pills, suggesting a possible consideration of their use.
+ 2 {user_name} suffers from insomnia frequently and seeks remedies.
+ 3 Charles is {user_name}'s supervisor.
+ 4 Charles is {user_name}'s supervisor.
+ 5 Charles is {user_name}'s supervisor and the branch manager of a bank.
+ 6 {user_name} loves playing basketball with classmates.
+ 7 {user_name} likes playing basketball.
+
+ Thought: The first sentence does not have any contradictions or complete repetitions with the previously numbered sentences.
+ Judgment: <1>
+ Thought: All information in the second sentence is completely contained within the information of the first sentence.
+ Judgment: <2>
+ Thought: The information in the third sentence does not appear in the previously numbered sentences.
+ Judgment: <3>
+ Thought: The fourth sentence is completely repetitive of the information in the third sentence, i.e., it is completely contained.
+ Judgment: <4>
+ Thought: The information that Charles is {user_name}'s supervisor in the fifth sentence is contained within the information of the third sentence, but the new information that Charles is the branch manager of a bank is not, so it is not contained.
+ Judgment: <5>
+ Thought: Sentence 6 expresses {user_name}'s fruit preference, liking to eat watermelon, which is information not present in any preceding sentences.
+ Judgment: <6>
+ Thought: Sentence 7 also expresses {user_name}'s fruit preference, liking to eat apples; it does not conflict with sentence 6, and both preferences can coexist.
+ Judgment: <7>
+
+ Example 2
+ Sentences:
+ 1 {user_name}'s child does not perform well academically.
+ 2 {user_name}'s child often skips school.
+ 3 {user_name}'s father's birthday is on June 2, 2024, and {user_name} plans to prepare a gift.
+ 4 {user_name}'s father's birthday is on May 1, 2024.
+ 5 {user_name} loves playing basketball with classmates.
+ 6 {user_name} likes playing basketball.
+
+ Thought: The first sentence does not have any contradictions or complete repetitions with the previously numbered sentences.
+ Judgment: <1>
+ Thought: The second sentence neither contradicts nor repeats any of the previously numbered sentences.
+ Judgment: <2>
+ Thought: The third sentence neither contradicts nor repeats any of the previously numbered sentences.
+ Judgment: <3>
+ Thought: The date of {user_name}'s father's birthday in the fourth sentence contradicts the information in the third sentence.
+ Judgment: <4>
+ Thought: The fifth sentence neither contradicts nor repeats any of the previously numbered sentences.
+ Judgment: <5>
+ Thought: All information in the sixth sentence is completely contained within the information of the fifth sentence.
+ Judgment: <6>
+
+
+
+contra_repeat_user_query_zh: |
+ 句子:
+ {user_query}
+
+
+contra_repeat_user_query: |
+ Sentences:
+ {user_query}
diff --git a/reme_ai/summary/personal/contra_repeat_worker.py b/reme_ai/summary/personal/contra_repeat_worker.py
deleted file mode 100644
index 9507b23b..00000000
--- a/reme_ai/summary/personal/contra_repeat_worker.py
+++ /dev/null
@@ -1,129 +0,0 @@
-from typing import List
-
-from memoryscope.constants.common_constants import NEW_OBS_NODES, NEW_OBS_WITH_TIME_NODES, MERGE_OBS_NODES, TODAY_NODES
-from memoryscope.constants.language_constants import NONE_WORD, CONTRADICTORY_WORD, CONTAINED_WORD
-from memoryscope.core.utils.response_text_parser import ResponseTextParser
-from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
-from memoryscope.enumeration.store_status_enum import StoreStatusEnum
-from memoryscope.scheme.memory_node import MemoryNode
-
-
-class ContraRepeatWorker(MemoryBaseWorker):
- """
- The `ContraRepeatWorker` class specializes in processing memory nodes to identify and handle
- contradictory and repetitive information. It extends the base functionality of `MemoryBaseWorker`.
-
- Responsibilities:
- - Collects observation nodes from various memory categories.
- - Constructs a prompt with these observations for language model analysis.
- - Parses the model's response to detect contradictions or redundancies.
- - Adjusts the status of memory nodes based on the analysis.
- - Persists the updated node statuses back into memory.
- """
- FILE_PATH: str = __file__
-
- def _parse_params(self, **kwargs):
- self.generation_model_kwargs: dict = kwargs.get("generation_model_kwargs", {})
- self.retrieve_top_k: int = kwargs.get("retrieve_top_k", 30)
- self.contra_repeat_max_count: int = kwargs.get("contra_repeat_max_count", 50)
- self.enable_today_contra_repeat: bool = self.memoryscope_context.meta_data["enable_today_contra_repeat"]
-
- def _run(self):
- """
- Executes the primary routine of the ContraRepeatWorker which involves fetching memory nodes,
- constructing a prompt, querying a language model, parsing the response to identify nodes for merging,
- updating node statuses, and saving the updated nodes back to memory.
-
- Steps:
- 1. Retrieves new observation nodes and nodes observed on the current day.
- 2. Optionally combines today's nodes with the new ones, sorts, and limits the list by a predefined count.
- 3. Constructs a prompt using the combined nodes, system prompt, and a few-shot example.
- 4. Queries a language model with the constructed prompt.
- 5. Parses the model's response to identify nodes to merge or exclude based on contradiction or redundancy.
- 6. Updates the status of nodes accordingly.
- 7. Persists the changes back to memory storage.
- """
- if not self.enable_today_contra_repeat:
- self.logger.warning("today_contra_repeat is not enabled!")
- return
-
- all_obs_nodes: List[MemoryNode] = self.memory_manager.get_memories([NEW_OBS_NODES, NEW_OBS_WITH_TIME_NODES])
- if not all_obs_nodes:
- self.logger.info("all_obs_nodes is empty!")
- # self.continue_run = False
- return
-
- today_obs_nodes: List[MemoryNode] = self.memory_manager.get_memories(TODAY_NODES)
-
- if today_obs_nodes:
- all_obs_nodes.extend(today_obs_nodes)
- all_obs_nodes = sorted(all_obs_nodes, key=lambda x: x.timestamp, reverse=True)[:self.contra_repeat_max_count]
-
- if len(all_obs_nodes) == 1:
- self.logger.info("all_obs_nodes.size=1, stop.")
- return
-
- # build prompt
- user_query_list = []
- for i, n in enumerate(all_obs_nodes):
- user_query_list.append(f"{i + 1} {n.content}")
-
- system_prompt = self.prompt_handler.contra_repeat_system.format(num_obs=len(user_query_list),
- user_name=self.target_name)
- few_shot = self.prompt_handler.contra_repeat_few_shot.format(user_name=self.target_name)
- user_query = self.prompt_handler.contra_repeat_user_query.format(user_query="\n".join(user_query_list))
- contra_repeat_message = self.prompt_to_msg(system_prompt=system_prompt, few_shot=few_shot,
- user_query=user_query)
- self.logger.info(f"contra_repeat_message={contra_repeat_message}")
-
- # call LLM
- response = self.generation_model.call(messages=contra_repeat_message, **self.generation_model_kwargs)
-
- # return if empty
- if not response.status or not response.message.content:
- return
- response_text = response.message.content
-
- # parse text
- idx_merge_obs_list = ResponseTextParser(response_text, self.language, self.__class__.__name__).parse_v1()
- if len(idx_merge_obs_list) <= 0:
- self.logger.warning("idx_merge_obs_list is empty!")
- return
-
- # add merged obs
- merge_obs_nodes: List[MemoryNode] = []
- for obs_content_list in idx_merge_obs_list:
- if not obs_content_list:
- continue
-
- # Expecting a pair [index, flag]
- if len(obs_content_list) != 2:
- self.logger.warning(f"obs_content_list={obs_content_list} is invalid!")
- continue
-
- idx, keep_flag = obs_content_list
-
- if not idx.isdigit():
- self.logger.warning(f"idx={idx} is invalid!")
- continue
-
- # index number needs to be corrected to -1
- idx = int(idx) - 1
- if idx >= len(all_obs_nodes):
- self.logger.warning(f"idx={idx} is invalid!")
- continue
-
- # judge flag
- keep_flag = keep_flag.lower()
- if keep_flag not in self.get_language_value([NONE_WORD, CONTRADICTORY_WORD, CONTAINED_WORD]):
- self.logger.warning(f"keep_flag={keep_flag} is invalid!")
- continue
-
- node: MemoryNode = all_obs_nodes[idx]
- if keep_flag != self.get_language_value(NONE_WORD):
- node.store_status = StoreStatusEnum.EXPIRED.value
- self.logger.info(f"contra_repeat stage: {node.content} {node.store_status} {node.action_status}")
- merge_obs_nodes.append(node)
-
- # save context
- self.memory_manager.set_memories(MERGE_OBS_NODES, merge_obs_nodes, log_repeat=False)
diff --git a/reme_ai/summary/personal/contra_repeat_worker.yaml b/reme_ai/summary/personal/contra_repeat_worker.yaml
deleted file mode 100644
index 3be7d3f8..00000000
--- a/reme_ai/summary/personal/contra_repeat_worker.yaml
+++ /dev/null
@@ -1,126 +0,0 @@
-contra_repeat_system:
- cn: |
- 任务:对下面的{num_obs}句句子,逐一判断是否与“前面序号”的任意句子存在信息的矛盾,或者句子的主要信息被“前面序号”的任意句子中的信息包含。
- 注意:对每句句子,只判断与“前面序号”的句子的关系,不要判断与“后面序号”的句子的关系。
- 其中矛盾的形式可以有很多种,可以是逻辑上的矛盾,可以是属性上的变化导致的矛盾,比如不能同时在两个地方工作,同一个时刻不能在两个地点,同一个时刻不能干两件事情等等。
- 对每个句子都做一个判断,最后一共输出{num_obs}条判断。
- 请一步步思考,并按如下格式输出:
- 思考:思考的依据和过程,30字以内。
- 判断:<句子序号> <矛盾,被包含,无>,一定加<>
-
- en: |
- Task: For the following {num_obs} sentences, determine whether each sentence contradicts any of the previous numbered sentences or if the main information in the sentence is contained within the information from any of the previous numbered sentences.
- Note: Only determine the relationship with the "previous numbered" sentences, do not judge the "later numbered" sentences.
- The forms of contradiction can be varied. It can be a logical contradiction, or a contradiction due to changes in attributes, for example, not being able to work in two places at once, not being able to be in two places at the same time, not being able to do two things at the same time, etc.
- Make a judgment for each sentence and output a total of {num_obs} judgments.
- Think step by step and output in the following format:
- Thought: Basis and process of thinking, within 30 words.
- Judgment: , must be enclosed in <>.
-
-contra_repeat_few_shot:
- cn: |
- 示例1
- 句子:
- 1 {user_name}经常失眠,对安眠药的效果感兴趣,暗示可能考虑使用。
- 2 {user_name}经常失眠,寻求缓解方法。
- 3 陈伟业是{user_name}的领导
- 4 陈伟业是{user_name}的领导
- 5 陈伟业是{user_name}的领导,是银行分行行长
- 6 {user_name}喜欢吃西瓜
- 7 {user_name}喜欢吃苹果
-
- 思考:第1句不会存在与前面序号句子的矛盾或者完全重复。
- 判断:<1> <无>
- 思考:第2句中所有信息都被前面序号中第1句的信息完全包含。
- 判断:<2> <被包含>
- 思考:第3句信息没有在前面序号句子中出现
- 判断:<3> <无>
- 思考:第4句与前面序号中第3句的信息完全重复,即被完全包含。
- 判断:<4> <被包含>
- 思考:第5句中陈伟业是{user_name}的领导的信息被前面序号中第3句的信息包含,但新增了陈伟业是银行分行行长的信息,故不是被完全包含。
- 判断:<5> <无>
- 思考:第6句中表达了{user_name}的水果偏好,喜欢吃西瓜,信息没有在前面序号句子中出现。
- 判断:<6> <无>
- 思考:第7句也表达了{user_name}的水果偏好,喜欢吃桃子,和前面序号中的第6句不冲突,喜好可以同时存在。
- 判断:<7> <无>
-
- 示例2
- 句子:
- 1 {user_name}的孩子成绩不太好。
- 2 {user_name}的孩子在学校经常逃课。
- 3 {user_name}的父亲生日在2024年6月2日,{user_name}打算准备礼物。
- 4 {user_name}的父亲生日在2024年5月1日。
- 5 {user_name}很喜欢和同班同学打篮球。
- 6 {user_name}喜欢打篮球。
-
- 思考:第1句不会存在与前面序号句子的矛盾或者完全重复。
- 判断:<1> <无>
- 思考:第2句与前面序号句子既不矛盾也不重复。
- 判断:<2> <无>
- 思考:第3句与前面序号句子既不矛盾也不重复。
- 判断:<3> <无>
- 思考:第4句关于{user_name}父亲生日的日期信息与前面序号句子第3句矛盾了。
- 判断:<4> <矛盾>
- 思考:第5句与前面序号句子既不矛盾也不重复。
- 判断:<5> <无>
- 思考:第6句中所有信息都被前面序号中第5句的信息完全包含。
- 判断:<2> <被包含>
-
- en: |
- Example 1
- Sentences:
- 1 {user_name} suffers from insomnia frequently and is interested in the effects of sleeping pills, suggesting a possible consideration of their use.
- 2 {user_name} suffers from insomnia frequently and seeks remedies.
- 3 Charles is {user_name}'s supervisor.
- 4 Charles is {user_name}'s supervisor.
- 5 Charles is {user_name}'s supervisor and the branch manager of a bank.
- 6 {user_name} loves playing basketball with classmates.
- 7 {user_name} likes playing basketball.
-
- Thought: The first sentence does not have any contradictions or complete repetitions with the previously numbered sentences.
- Judgment: <1>
- Thought: All information in the second sentence is completely contained within the information of the first sentence.
- Judgment: <2>
- Thought: The information in the third sentence does not appear in the previously numbered sentences.
- Judgment: <3>
- Thought: The fourth sentence is completely repetitive of the information in the third sentence, i.e., it is completely contained.
- Judgment: <4>
- Thought: The information that Charles is {user_name}'s supervisor in the fifth sentence is contained within the information of the third sentence, but the new information that Charles is the branch manager of a bank is not, so it is not contained.
- Judgment: <5>
- Thought: Sentence 6 expresses {user_name}'s fruit preference, liking to eat watermelon, which is information not present in any preceding sentences.
- Judgment: <6>
- Thought: Sentence 7 also expresses {user_name}'s fruit preference, liking to eat apples; it does not conflict with sentence 6, and both preferences can coexist.
- Judgment: <7>
-
- Example 2
- Sentences:
- 1 {user_name}'s child does not perform well academically.
- 2 {user_name}'s child often skips school.
- 3 {user_name}'s father's birthday is on June 2, 2024, and {user_name} plans to prepare a gift.
- 4 {user_name}'s father's birthday is on May 1, 2024.
- 5 {user_name} loves playing basketball with classmates.
- 6 {user_name} likes playing basketball.
-
- Thought: The first sentence does not have any contradictions or complete repetitions with the previously numbered sentences.
- Judgment: <1>
- Thought: The second sentence neither contradicts nor repeats any of the previously numbered sentences.
- Judgment: <2>
- Thought: The third sentence neither contradicts nor repeats any of the previously numbered sentences.
- Judgment: <3>
- Thought: The date of {user_name}'s father's birthday in the fourth sentence contradicts the information in the third sentence.
- Judgment: <4>
- Thought: The fifth sentence neither contradicts nor repeats any of the previously numbered sentences.
- Judgment: <5>
- Thought: All information in the sixth sentence is completely contained within the information of the fifth sentence.
- Judgment: <6>
-
-
-
-contra_repeat_user_query:
- cn: |
- 句子:
- {user_query}
-
- en: |
- Sentences:
- {user_query}
diff --git a/reme_ai/summary/personal/get_observation_op.py b/reme_ai/summary/personal/get_observation_op.py
new file mode 100644
index 00000000..b67f40cb
--- /dev/null
+++ b/reme_ai/summary/personal/get_observation_op.py
@@ -0,0 +1,118 @@
+from typing import List
+
+from flowllm import C, BaseLLMOp
+from flowllm.schema.message import Message
+from loguru import logger
+
+from reme_ai.schema.memory import BaseMemory, PersonalMemory
+from reme_ai.utils.datetime_handler import DatetimeHandler
+from reme_ai.utils.op_utils import parse_observation_response
+
+
+@C.register_op()
+class GetObservationOp(BaseLLMOp):
+ """
+ A specialized operation class to generate observations from chat messages using BaseLLMOp.
+ """
+ file_path: str = __file__
+
+ def execute(self):
+ """Extract personal observations from chat messages"""
+ # Get messages from context
+ messages: List[Message] = self.context.get("messages", [])
+ if not messages:
+ logger.warning("No messages found in context")
+ return
+
+ # Filter messages - exclude those with time-related keywords
+ filtered_messages = self._filter_messages(messages)
+ if not filtered_messages:
+ logger.warning("No messages left after filtering")
+ return
+
+ logger.info(f"Extracting observations from {len(filtered_messages)} filtered messages")
+
+ # Extract observations using LLM
+ observation_memories = self._extract_observations_from_messages(filtered_messages)
+
+ # Store results in context
+ self.context.response.metadata["observation_memories"] = observation_memories
+ logger.info(f"Generated {len(observation_memories)} observation memories")
+
+ def _filter_messages(self, messages: List[Message]) -> List[Message]:
+ """
+ Filters the chat messages to exclude those containing time-related keywords.
+
+ Args:
+ messages: List of messages to filter
+
+ Returns:
+ List[Message]: A list of filtered messages without time keywords.
+ """
+ filtered_messages = []
+ for msg in messages:
+ if not DatetimeHandler.has_time_word(query=msg.content, language=self.language):
+ filtered_messages.append(msg)
+
+ logger.info(f"Filtered messages from {len(messages)} to {len(filtered_messages)}")
+ return filtered_messages
+
+ def _extract_observations_from_messages(self, filtered_messages: List[Message]) -> List[BaseMemory]:
+ """Extract observations from filtered messages using LLM"""
+ user_name = self.context.get("user_name", "user")
+
+ # Build prompt for observation extraction
+ user_query_list = []
+ for i, msg in enumerate(filtered_messages):
+ user_query_list.append(f"{i + 1} {user_name}: {msg.content}")
+
+ # Create prompt using the prompt format method
+ system_prompt = self.prompt_format(prompt_name="get_observation_system",
+ num_obs=len(user_query_list),
+ user_name=user_name)
+ few_shot = self.prompt_format(prompt_name="get_observation_few_shot", user_name=user_name)
+ user_query = self.prompt_format(prompt_name="get_observation_user_query",
+ user_query="\n".join(user_query_list),
+ user_name=user_name)
+
+ full_prompt = f"{system_prompt}\n\n{few_shot}\n\n{user_query}"
+ logger.info(f"get_observation_prompt={full_prompt}")
+
+ def parse_observations(message: Message) -> List[BaseMemory]:
+ """Parse LLM response and create observation memories"""
+ response_text = message.content
+ logger.info(f"get_observation_response={response_text}")
+
+ # Parse observations using utility function
+ parsed_observations = parse_observation_response(response_text)
+
+ observation_memories = []
+ for obs in parsed_observations:
+ idx = obs["index"] - 1 # Convert to 0-based index
+ if idx >= len(filtered_messages):
+ logger.warning(f"Invalid index {idx} for messages list of length {len(filtered_messages)}")
+ continue
+
+ # Create observation memory
+ observation = PersonalMemory(
+ workspace_id=self.context.get("workspace_id", ""),
+ content=obs["content"],
+ target=user_name,
+ author=getattr(self.llm, "model_name", "system"),
+ metadata={
+ "keywords": obs["keywords"],
+ "source_message": filtered_messages[idx].content,
+ "observation_type": "personal_info"
+ }
+ )
+ observation_memories.append(observation)
+ logger.info(f"Created observation: {obs['content'][:50]}...")
+
+ return observation_memories
+
+ # Use LLM chat with callback function
+ return self.llm.chat(messages=[Message(content=full_prompt)], callback_fn=parse_observations)
+
+ def get_language_value(self, value_dict: dict):
+ """Get language-specific value from dictionary"""
+ return value_dict.get(self.language, value_dict.get("en"))
diff --git a/reme_ai/summary/personal/get_observation_prompt.yaml b/reme_ai/summary/personal/get_observation_prompt.yaml
new file mode 100644
index 00000000..365fc424
--- /dev/null
+++ b/reme_ai/summary/personal/get_observation_prompt.yaml
@@ -0,0 +1,163 @@
+get_observation_system_zh: |
+ 任务:从下面的{num_obs}句{user_name}句子中依次提取出关于{user_name}的重要信息,与相应的关键词。如果没有重要信息则回答“无”,最多提取{num_obs}条信息。
+ {user_name}的重要信息可以包含用户基本信息,用户画像信息,用户兴趣偏好信息,用户性格,用户价值观,用户人际关系,用户重大事件转折点等等重要信息。
+ 如果句子中只包含{user_name}假设的信息或者{user_name}虚构的内容比如{user_name}创作的小说或剧本,回答“无”。
+ 对每个句子都做一次信息提取,最后一共输出{num_obs}条信息。
+ 请一步步思考,并一定要按如下格式依次输出,最后的结果一定要加<>:
+ 思考:思考的依据和过程,50字以内。
+ 信息:<句子序号> <> <明确的重要信息或“无”> <关键词>
+
+
+get_observation_system: |
+ Task: Sequentially extract important information about {user_name} from the following {num_obs} sentences along with corresponding keywords. If there is no important information, answer "None". Extract up to {num_obs} pieces of information.
+ Important information about {user_name} can include basic information, user profile information, user interests and preferences, user personality, user values, user relationships, major turning points in the user's life, and other important information.
+ If the sentence only contains hypothetical information about {user_name} or fictional content created by {user_name} such as novels or scripts, respond "None".
+ Perform information extraction for each sentence, and output {num_obs} pieces of information in total.
+ Please think step-by-step, and be sure to output in the following format, ending with '<>':
+ Thought: Basis and process of thinking, within 50 words.
+ Information: <>
+
+
+get_observation_few_shot_zh: |
+ 示例1:
+ {user_name}句子:
+ 1 {user_name}:我现在处境很糟,没有工作,负债几万,怎么办
+ 2 {user_name}:有人说兴趣是最好的老师,也建议兴趣和职业联系起来,但我发现喜欢打篮球的人很多,但靠打篮球成职业的稀少,赚钱的更少,此外,怎么分辨兴趣和喜欢
+ 3 {user_name}:我现在心情很糟糕
+ 4 {user_name}:我是一个刚毕业的学生,对社会,行业不了解,给我介绍一下社会系统和行业格局
+ 5 {user_name}:我花5000元买了100股海天味业。
+ 6 {user_name}:我花50000元买了100股阿里巴巴。
+ 思考:从第1句可以得知{user_name}现在没有工作,负债几万,这是关于{user_name}工作与经济状况的重要信息。
+ 信息:<1> <> <{user_name}当前无工作且负债几万> <无工作, 负债几万>
+ 思考:第2句是{user_name}对他人观点的讨论和疑问,没有明确提及{user_name}个人信息。
+ 信息:<2> <> <无> <>
+ 思考:从第3句可以得知{user_name}当前心情不好。
+ 信息:<3> <> <{user_name}当前心情不好> <心情>
+ 思考:从第4句可以得知{user_name}是一个刚毕业的学生,这是关于{user_name}身份背景状况的重要信息。其余信息重要性不足。
+ 信息:<4> <> <{user_name}是一名刚毕业的学生。> <刚毕业, 学生>
+ 思考:从第5句可以得知{user_name}购买了海天味业股票,购买数量为100股,购买金额为5000元,这是关于{user_name}的投资决策的重要信息。
+ 信息:<5> <> <{user_name}购买了海天味业股票,购买数量为100股,购买金额为5000元。> <海天味业, 股票>
+ 思考:第6句含有的信息与第1句相似,可以得知{user_name}购买了阿里巴巴股票。
+ 信息:<6> <> <{user_name}购买了阿里巴巴股票,购买数量为100股,购买金额为50000元。> <阿里巴巴, 股票>
+
+ 示例2:
+ {user_name}句子:
+ 1 {user_name}:帮我写一段给同事张三女儿三岁生日的祝福语。
+ 2 {user_name}:能给我整理一张如何使用大模型的技巧列表吗,要求内容尽量精简。
+ 3 {user_name}:两个坏消息,我打羽毛球把拍子打断线了。。。然后我去我朋友家撸猫,结果我猫毛过敏,今天疯狂打喷嚏。。。
+ 4 {user_name}:公元1400年至1550年中国历史大事表。
+ 5 {user_name}:谢啦。我中午在公司附近吃,帮我推荐一家阿里巴巴徐汇滨江园区附近的餐厅吧。
+ 思考:从第1句可以得知张三是{user_name}的同事,这是关于{user_name}的人际关系的重要信息。其余信息重要性不足。
+ 信息:<1> <> <张三是{user_name}的同事。> <张三, 同事>
+ 思考:第2句是{user_name}提出的要求,没有明确提及{user_name}个人信息。
+ 信息:<2> <> <无> <>
+ 思考:从第3句可以得知{user_name}前天打羽毛球时把球拍打断了线,但这不是重要的信息。还可以得知{user_name}对猫毛过敏,这是关于{user_name}的健康的重要信息。
+ 信息:<3> <> <{user_name}对猫毛过敏。> <猫毛, 过敏>
+ 思考:从第4句是{user_name}提出的要求,没有明确提及{user_name}个人信息。
+ 信息:<4> <> <无> <>
+ 思考:从第5句可以得知{user_name}在阿里巴巴徐汇滨江园区工作,这是关于{user_name}的工作地点的重要信息。
+ 信息:<5> <> <{user_name}在阿里巴巴徐汇滨江园区工作。> <阿里巴巴, 徐汇滨江园区, 工作>
+
+ 示例3:
+ {user_name}句子:
+ 1 {user_name}:我想买辆新能源汽车,有什么推荐吗?
+ 2 {user_name}:我在上海,想买辆新能源汽车,有什么推荐吗?
+ 3 {user_name}:案外人异议审查期间,人民法院不得对执行标的进行处分,不就是中止执行的意思吗?
+ 4 {user_name}:请写两句藏头诗分别以“胜”和“利”开头。
+ 5 {user_name}:我花5000元买了100股海天味业。
+ 6 {user_name}:李增杰:这个是星座蛙设,但是我是处女座的,我妈感觉因为我的不正常,我妈不让我看了\n雌猴摸了摸李增杰的头,这样啊\n雌猴打开了哔哩哔哩看了看\n雌猴:要不换个设吧,我听你未来的你说,有一个叫难忘的朱古力232这个人,他弄的设是Windows设\n这是剧本1,剧本2未完待续
+ 思考:从第1句可以得知{user_name}寻求购买新能源汽车的建议或推荐,这是这是关于{user_name}的大宗消费的重要的信息。
+ 信息:<1> <> <{user_name}寻求购买新能源汽车的建议或推荐。> <购买, 新能源汽车>
+ 思考:从第2句可以得知{user_name}当前所在城市为上海,这是关于{user_name}的生活地区的重要信息。
+ 信息:<2> <> <{user_name}所在的城市是上海。> <上海>
+ 思考:第3句是{user_name}对某个观点的讨论和疑问,没有明确提及{user_name}个人信息。
+ 信息:<3> <> <无> <>
+ 思考:第4句是{user_name}提出的要求,没有明确提及{user_name}个人信息。
+ 信息:<4> <> <无> <>
+ 思考:从第5句可以得知{user_name}购买了海天味业股票,购买数量为100股,购买金额为5000元,这是关于{user_name}的投资决策的重要信息。
+ 信息:<5> <> <{user_name}购买了海天味业股票,购买数量为100股,购买金额为5000元。> <海天味业, 股票>
+ 思考:第6句是{user_name}创作的剧本内容,无法提取{user_name}个人信息。
+ 信息:<6> <> <无> <>
+
+ 示例4:
+ {user_name}句子:
+ 1 {user_name}:李子好酸啊,我不太喜欢吃。
+ 2 {user_name}:桃子上的毛太多了,我不爱吃他。
+ 思考:从第1句可以得知{user_name}不太喜欢吃李子。
+ 信息:<1> <> <{user_name}不喜欢吃李子。> <李子>
+ 思考:从第2句可以得知{user_name}不喜欢吃桃子,和上一句相似都是对某一种水果不喜欢,但是表达了不同的信息。
+ 信息:<2> <> <{user_name}不喜欢吃桃子。> <西瓜>
+
+
+get_observation_few_shot: |
+ Example 1:
+ {user_name} sentences:
+ 1 {user_name}: I'm in a terrible situation right now, I don't have a job, and I'm in debt by tens of thousands. What should I do?
+ 2 {user_name}: Someone said that passion is the best teacher and suggested linking passion with a career, but I found that many people like playing basketball, but few make it a profession and even fewer make money from it. Also, how do you distinguish passion from liking?
+ 3 {user_name}: I'm in a terrible situation right now, I don't have a job, and I'm in debt by tens of thousands. What should I do?
+ 4 {user_name}: I'm a recent graduate who doesn't understand society or the industry. Can you introduce me to the social system and industry structure?
+ 5 {user_name}: I spent $5000 to buy 100 shares of General Motors.
+ 6 {user_name}: I spent $50000 to buy 100 shares of Alibaba.
+
+ Thought: From the first sentence, it can be inferred that {user_name} currently has no job and is in debt by tens of thousands. This is important information about {user_name}'s employment and financial status.
+ Information: <1> <> <{user_name} currently has no job and is in debt by tens of thousands>
+ Thought: The second sentence is a discussion and query about others' opinions by {user_name}, with no clear mention of {user_name}'s personal information.
+ Information: <2> <> <>
+ Thought: The information in the third sentence is a repeat of the first sentence.
+ Information: <3> <> <>
+ Thought: From the fourth sentence, it can be inferred that {user_name} is a recent graduate, which is important information about {user_name}'s background. The remaining information is of insufficient importance.
+ Information: <4> <> <{user_name} is a recent graduate>
+ Thought: It can be inferred that {user_name} bought 100 shares of General Motors stock for $5000. This is important information about {user_name}'s investment decision.
+ Information: <5> <> <{user_name} bought 100 shares of General Motors stock for $5000>
+ Thought: The information of the sentence is similar to, but not a repetition of the sentence before. It can be deduced that {user_name} purchased Alibaba stock.
+ Information: <6> <> <{user_name} purchased 100 shares of Alibaba stock for 50,000 RMB.>
+
+ Example 2:
+ {user_name} sentences:
+ 1 {user_name}: Please help me write a birthday greeting for my colleague Jason's daughter who is turning three.
+ 2 {user_name}: Can you compile a list of tips on how to use large models for me, and try to keep the content concise?
+ 3 {user_name}: Two pieces of bad news: I broke my badminton racket while playing... Then I went to my friend's house to pet the cat and ended up having an allergic reaction to the cat fur, sneezing like crazy today...
+ 4 {user_name}: Chronology of major events in Chinese history from 1400 to 1550 AD.
+ 5 {user_name}: Thanks. I'm having lunch near the company at noon; can you recommend a restaurant near Alibaba Xuhui Riverside Campus for me?
+ Thought: From the first sentence, it can be inferred that Zhang San is {user_name}'s colleague, which is important information about {user_name}'s interpersonal relationships. The remaining information is of insufficient importance.
+ Information: <1> <>
+ Thought: The second sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
+ Information: <2> <> <>
+ Thought: From the third sentence, it can be inferred that {user_name} broke their badminton racket the other day, but this is not important information. It can also be inferred that {user_name} is allergic to cat fur, which is important information about {user_name}'s health.
+ Information: <3> <> <{user_name} is allergic to cat fur>
+ Thought: The fourth sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
+ Information: <4> <> <>
+ Thought: From the fifth sentence, it can be inferred that {user_name} works at Alibaba Xuhui Riverside Campus, which is important information about {user_name}'s workplace.
+ Information: <5> <> <{user_name} works at Alibaba Xuhui Riverside Campus>
+
+ Example 3:
+ {user_name} sentences:
+ 1 {user_name}: I want to buy a new energy vehicle. Any recommendations?
+ 2 {user_name}: I'm in San Jose and want to buy a new energy vehicle. Any recommendations?
+ 3 {user_name}: During the objection review period by a third party, the court must not dispose of the execution object. Doesn't this mean suspension of execution?
+ 4 {user_name}: Please write two acrostic poems, starting with "Victory" and "Success".
+ 5 {user_name}: I spent $5000 to buy 100 shares of General Motors.
+ 6 {user_name}: Zack: This is a constellation frog setting, but I am a Virgo. My mom feels I'm abnormal and doesn't let me watch it. \n The female monkey patted Zack's head, "Is that so?" \n The female monkey opened Bilibili and took a look. \n Female monkey: "Why don't you switch the setting? I heard from your future self that there is someone called 'Unforgettable Chocolate 232' who created a Windows setting." \n This is script 1; script 2 is to be continued.
+ Thought: From the first sentence, it can be inferred that {user_name} is seeking advice or recommendations for purchasing a new energy vehicle. This is important information about {user_name}'s major consumption.
+ Information: <1> <> <{user_name} is seeking advice or recommendations for purchasing a new energy vehicle>
+ Thought: From the second sentence, it can be inferred that {user_name} is currently in San Jose, which is important information about {user_name}'s living location. The remaining information is a repeat of the first sentence.
+ Information: <2> <> <{user_name} is currently in San Jose>
+ Thought: The third sentence is a discussion and query about a specific legal opinion by {user_name}, with no clear mention of {user_name}'s personal information.
+ Information: <3> <> <>
+ Thought: The fourth sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
+ Information: <4> <> <>
+ Thought: From the fifth sentence, it can be inferred that {user_name} bought 100 shares of General Motors stock for $5000. This is important information about {user_name}'s investment decision.
+ Information: <5> <> <{user_name} bought 100 shares of General Motors stock for $5000>
+ Thought: The sixth sentence is content from a script written by {user_name}, with no extractable personal information about {user_name}.
+ Information: <6> <> <>
+
+
+get_observation_user_query_zh: |
+ {user_name}句子:
+ {user_query}
+
+
+get_observation_user_query: |
+ {user_name} sentences:
+ {user_query}
+
diff --git a/reme_ai/summary/personal/get_observation_with_time_op.py b/reme_ai/summary/personal/get_observation_with_time_op.py
new file mode 100644
index 00000000..1afeaf5d
--- /dev/null
+++ b/reme_ai/summary/personal/get_observation_with_time_op.py
@@ -0,0 +1,129 @@
+from typing import List
+
+from flowllm import C, BaseLLMOp
+from flowllm.schema.message import Message
+from loguru import logger
+
+from reme_ai.schema.memory import BaseMemory, PersonalMemory
+from reme_ai.utils.datetime_handler import DatetimeHandler
+from reme_ai.utils.op_utils import parse_observation_with_time_response
+
+
+@C.register_op()
+class GetObservationWithTimeOp(BaseLLMOp):
+ """
+ A specialized operation class to extract observations with time information from chat messages using BaseLLMOp.
+ """
+ file_path: str = __file__
+
+ def execute(self):
+ """Extract personal observations with time information from chat messages"""
+ # Get messages from context
+ messages: List[Message] = self.context.get("messages", [])
+ if not messages:
+ logger.warning("No messages found in context")
+ return
+
+ # Filter messages - only include those with time-related keywords
+ filtered_messages = self._filter_messages(messages)
+ if not filtered_messages:
+ logger.warning("No messages with time keywords found")
+ return
+
+ logger.info(f"Extracting observations with time from {len(filtered_messages)} filtered messages")
+
+ # Extract observations using LLM
+ observation_memories = self._extract_observations_with_time_from_messages(filtered_messages)
+
+ # Store results in context
+ self.context.response.metadata["observation_with_time_memories"] = observation_memories
+ logger.info(f"Generated {len(observation_memories)} observation memories with time")
+
+ def _filter_messages(self, messages: List[Message]) -> List[Message]:
+ """
+ Filters the chat messages to only include those containing time-related keywords.
+
+ Args:
+ messages: List of messages to filter
+
+ Returns:
+ List[Message]: A list of filtered messages that mention time.
+ """
+ filtered_messages = []
+ for msg in messages:
+ if DatetimeHandler.has_time_word(query=msg.content, language=self.language):
+ filtered_messages.append(msg)
+
+ logger.info(f"Filtered messages from {len(messages)} to {len(filtered_messages)}")
+ return filtered_messages
+
+ def _extract_observations_with_time_from_messages(self, filtered_messages: List[Message]) -> List[BaseMemory]:
+ """Extract observations with time information from filtered messages using LLM"""
+ user_name = self.context.get("user_name", "user")
+
+ # Build prompt for observation extraction with time
+ user_query_list = []
+ for i, msg in enumerate(filtered_messages):
+ # Create a DatetimeHandler instance for each message's timestamp and format it
+ dt_handler = DatetimeHandler(dt=msg.time_created)
+
+ # Get time format from prompt configuration
+ time_format = self.prompt_format(prompt_name="time_string_format")
+ dt = dt_handler.string_format(string_format=time_format, language=self.language)
+
+ # Append formatted timestamp-query pairs to the user_query_list
+ colon = self._get_colon_word()
+ user_query_list.append(f"{i + 1} {dt} {user_name}{colon}{msg.content}")
+
+ # Create prompt using the prompt format method
+ system_prompt = self.prompt_format(prompt_name="get_observation_with_time_system",
+ num_obs=len(user_query_list),
+ user_name=user_name)
+ few_shot = self.prompt_format(prompt_name="get_observation_with_time_few_shot", user_name=user_name)
+ user_query = self.prompt_format(prompt_name="get_observation_with_time_user_query",
+ user_query="\n".join(user_query_list),
+ user_name=user_name)
+
+ full_prompt = f"{system_prompt}\n\n{few_shot}\n\n{user_query}"
+ logger.info(f"get_observation_with_time_prompt={full_prompt}")
+
+ def parse_observations(message: Message) -> List[BaseMemory]:
+ """Parse LLM response and create observation memories with time"""
+ response_text = message.content
+ logger.info(f"get_observation_with_time_response={response_text}")
+
+ # Parse observations using utility function
+ parsed_observations = parse_observation_with_time_response(response_text)
+
+ observation_memories = []
+ for obs in parsed_observations:
+ idx = obs["index"] - 1 # Convert to 0-based index
+ if idx >= len(filtered_messages):
+ logger.warning(f"Invalid index {idx} for messages list of length {len(filtered_messages)}")
+ continue
+
+ # Create observation memory
+ observation = PersonalMemory(
+ workspace_id=self.context.get("workspace_id", ""),
+ content=obs["content"],
+ target=user_name,
+ author=getattr(self.llm, "model_name", "system"),
+ metadata={
+ "keywords": obs["keywords"],
+ "time_info": obs.get("time_info", ""),
+ "source_message": filtered_messages[idx].content,
+ "observation_type": "personal_info_with_time"
+ }
+ )
+ observation_memories.append(observation)
+ logger.info(f"Created observation with time: {obs['content'][:50]}...")
+
+ return observation_memories
+
+ # Use LLM chat with callback function
+ return self.llm.chat(messages=[Message(content=full_prompt)], callback_fn=parse_observations)
+
+ def _get_colon_word(self) -> str:
+ """Get language-specific colon word"""
+ colon_dict = {"zh": ":", "cn": ":", "en": ": "}
+ return colon_dict.get(self.language, ": ")
diff --git a/reme_ai/summary/personal/get_observation_with_time_prompt.yaml b/reme_ai/summary/personal/get_observation_with_time_prompt.yaml
new file mode 100644
index 00000000..156ed478
--- /dev/null
+++ b/reme_ai/summary/personal/get_observation_with_time_prompt.yaml
@@ -0,0 +1,158 @@
+time_string_format_zh: |
+ {year}年{month}{day}日{weekday}{hour}点
+
+
+time_string_format: |
+ {month} {day}, {year}, {weekday}, at {hour}
+
+
+get_observation_with_time_system_zh: |
+ 任务:从下面的{num_obs}句{user_name}句子中依次提取出关于{user_name}的重要信息,相应的关键词与时间信息。如果没有重要信息则回答“无”,最多提取{num_obs}条信息。
+ 每一句{user_name}句子的格式是:<序号> <对话时间> {user_name}:<句子>
+ {user_name}的重要信息可以包含用户基本信息,用户画像信息,用户兴趣偏好信息,用户性格,用户价值观,用户人际关系,用户重大事件转折点等等重要信息。
+ 如果句子中只包含{user_name}假设的信息或者{user_name}虚构的内容比如{user_name}创作的小说或剧本,回答“无”。
+ 如果{user_name}信息涉及时间,则结合对话时间推断{user_name}信息的时间信息,没有则不输出。
+ 对每个句子都做一次信息提取,最后一共输出{num_obs}条信息。
+ 请一步步思考,并一定要按如下格式依次输出,最后的结果一定要加<>:
+ 思考:思考的依据和过程,50字以内。
+ 信息:<句子序号> <时间信息或不输出> <明确的重要信息或“无”> <关键词>
+
+
+get_observation_with_time_system: |
+ Task: Extract important information about {user_name} from the following {num_obs} sentences of {user_name}, including relevant keywords and time information. If there is no important information, answer "none", with a maximum of {num_obs} pieces of information extracted.
+ Each sentence from {user_name} is formatted as follows: {user_name}: .
+ Important information about {user_name} can include basic information, user profile information, interest preferences, personality, values, human relationships, significant life events, etc.
+ If a sentence only contains hypothetical information or fictional content created by {user_name} (e.g., novels or scripts), answer "none".
+ If {user_name}'s information involves time, infer the time information based on the conversation time; if not, do not output.
+ Analyze each sentence once to extract information and output a total of {num_obs} pieces of information.
+ Please think step-by-step and be sure to output in the following format, with the final results enclosed in <>:
+ Thought: Basis and process of thought, within 50 words.
+ Information: