mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-08-28 05:25:04 +00:00
refactor(reme_ai): rename and restructure modules for better organization
- Rename `worker` to `op` for operation classes - Remove `memoryscope` package - Create `constants` package with common constants - Update module imports and class names accordingly
This commit is contained in:
parent
9fb7aa4e78
commit
8600015632
87 changed files with 4101 additions and 3018 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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", {})
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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", {})
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class PrintMemoryWorker(MemoryBaseWorker):
|
|||
"""
|
||||
Formats the memories to print.
|
||||
"""
|
||||
FILE_PATH: str = __file__
|
||||
file_path: str = __file__
|
||||
|
||||
def _run(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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 = "",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
from .react_v1_op import ReactV1Op
|
||||
|
|
@ -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"<tool_response>\n{tool_result}\n</tool_response>")
|
||||
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
|
||||
|
|
@ -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}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
7
reme_ai/constants/__init__.py
Normal file
7
reme_ai/constants/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from . import common_constants
|
||||
from . import language_constants
|
||||
|
||||
__all__ = [
|
||||
"common_constants",
|
||||
"language_constants"
|
||||
]
|
||||
48
reme_ai/constants/common_constants.py
Normal file
48
reme_ai/constants/common_constants.py
Normal file
|
|
@ -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"
|
||||
215
reme_ai/constants/language_constants.py
Normal file
215
reme_ai/constants/language_constants.py
Normal file
|
|
@ -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}."
|
||||
}
|
||||
215
reme_ai/enumeration/language_constants.py
Normal file
215
reme_ai/enumeration/language_constants.py
Normal file
|
|
@ -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}."
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
|
|
|
|||
76
reme_ai/retrieve/personal/extract_time_op.py
Normal file
76
reme_ai/retrieve/personal/extract_time_op.py
Normal file
|
|
@ -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
|
||||
135
reme_ai/retrieve/personal/extract_time_prompt.yaml
Normal file
135
reme_ai/retrieve/personal/extract_time_prompt.yaml
Normal file
|
|
@ -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:
|
||||
|
||||
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
@ -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:
|
||||
|
||||
|
||||
|
||||
128
reme_ai/retrieve/personal/fuse_rerank_op.py
Normal file
128
reme_ai/retrieve/personal/fuse_rerank_op.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -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))
|
||||
64
reme_ai/retrieve/personal/print_memory_op.py
Normal file
64
reme_ai/retrieve/personal/print_memory_op.py
Normal file
|
|
@ -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)
|
||||
22
reme_ai/retrieve/personal/print_memory_prompt.yaml
Normal file
22
reme_ai/retrieve/personal/print_memory_prompt.yaml
Normal file
|
|
@ -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}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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}
|
||||
52
reme_ai/retrieve/personal/read_message_op.py
Normal file
52
reme_ai/retrieve/personal/read_message_op.py
Normal file
|
|
@ -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")
|
||||
|
|
@ -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)
|
||||
13
reme_ai/retrieve/personal/retrieve_memory_op.py
Normal file
13
reme_ai/retrieve/personal/retrieve_memory_op.py
Normal file
|
|
@ -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__
|
||||
|
|
@ -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)
|
||||
166
reme_ai/retrieve/personal/semantic_rank_op.py
Normal file
166
reme_ai/retrieve/personal/semantic_rank_op.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
64
reme_ai/retrieve/personal/set_query_op.py
Normal file
64
reme_ai/retrieve/personal/set_query_op.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -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))
|
||||
|
|
@ -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)])
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)])
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
|
|
|
|||
131
reme_ai/summary/personal/contra_repeat_op.py
Normal file
131
reme_ai/summary/personal/contra_repeat_op.py
Normal file
|
|
@ -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"))
|
||||
127
reme_ai/summary/personal/contra_repeat_prompt.yaml
Normal file
127
reme_ai/summary/personal/contra_repeat_prompt.yaml
Normal file
|
|
@ -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: <Sentence Number> <Contradiction, Contained, None>, 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> <None>
|
||||
Thought: All information in the second sentence is completely contained within the information of the first sentence.
|
||||
Judgment: <2> <Contained>
|
||||
Thought: The information in the third sentence does not appear in the previously numbered sentences.
|
||||
Judgment: <3> <None>
|
||||
Thought: The fourth sentence is completely repetitive of the information in the third sentence, i.e., it is completely contained.
|
||||
Judgment: <4> <Contained>
|
||||
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> <None>
|
||||
Thought: Sentence 6 expresses {user_name}'s fruit preference, liking to eat watermelon, which is information not present in any preceding sentences.
|
||||
Judgment: <6> <None>
|
||||
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> <None>
|
||||
|
||||
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> <None>
|
||||
Thought: The second sentence neither contradicts nor repeats any of the previously numbered sentences.
|
||||
Judgment: <2> <None>
|
||||
Thought: The third sentence neither contradicts nor repeats any of the previously numbered sentences.
|
||||
Judgment: <3> <None>
|
||||
Thought: The date of {user_name}'s father's birthday in the fourth sentence contradicts the information in the third sentence.
|
||||
Judgment: <4> <Contradiction>
|
||||
Thought: The fifth sentence neither contradicts nor repeats any of the previously numbered sentences.
|
||||
Judgment: <5> <None>
|
||||
Thought: All information in the sixth sentence is completely contained within the information of the fifth sentence.
|
||||
Judgment: <6> <Contained>
|
||||
|
||||
|
||||
|
||||
contra_repeat_user_query_zh: |
|
||||
句子:
|
||||
{user_query}
|
||||
|
||||
|
||||
contra_repeat_user_query: |
|
||||
Sentences:
|
||||
{user_query}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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: <Sentence Number> <Contradiction, Contained, None>, 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> <None>
|
||||
Thought: All information in the second sentence is completely contained within the information of the first sentence.
|
||||
Judgment: <2> <Contained>
|
||||
Thought: The information in the third sentence does not appear in the previously numbered sentences.
|
||||
Judgment: <3> <None>
|
||||
Thought: The fourth sentence is completely repetitive of the information in the third sentence, i.e., it is completely contained.
|
||||
Judgment: <4> <Contained>
|
||||
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> <None>
|
||||
Thought: Sentence 6 expresses {user_name}'s fruit preference, liking to eat watermelon, which is information not present in any preceding sentences.
|
||||
Judgment: <6> <None>
|
||||
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> <None>
|
||||
|
||||
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> <None>
|
||||
Thought: The second sentence neither contradicts nor repeats any of the previously numbered sentences.
|
||||
Judgment: <2> <None>
|
||||
Thought: The third sentence neither contradicts nor repeats any of the previously numbered sentences.
|
||||
Judgment: <3> <None>
|
||||
Thought: The date of {user_name}'s father's birthday in the fourth sentence contradicts the information in the third sentence.
|
||||
Judgment: <4> <Contradiction>
|
||||
Thought: The fifth sentence neither contradicts nor repeats any of the previously numbered sentences.
|
||||
Judgment: <5> <None>
|
||||
Thought: All information in the sixth sentence is completely contained within the information of the fifth sentence.
|
||||
Judgment: <6> <Contained>
|
||||
|
||||
|
||||
|
||||
contra_repeat_user_query:
|
||||
cn: |
|
||||
句子:
|
||||
{user_query}
|
||||
|
||||
en: |
|
||||
Sentences:
|
||||
{user_query}
|
||||
118
reme_ai/summary/personal/get_observation_op.py
Normal file
118
reme_ai/summary/personal/get_observation_op.py
Normal file
|
|
@ -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"))
|
||||
163
reme_ai/summary/personal/get_observation_prompt.yaml
Normal file
163
reme_ai/summary/personal/get_observation_prompt.yaml
Normal file
|
|
@ -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: <Sentence number> <> <Clear important information or "None"> <Keywords>
|
||||
|
||||
|
||||
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> <no job, 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> <> <None> <>
|
||||
Thought: The information in the third sentence is a repeat of the first sentence.
|
||||
Information: <3> <> <Repeat> <>
|
||||
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> <recent graduate, student>
|
||||
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> <General Motors, stock>
|
||||
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.> <Alibaba, stock>
|
||||
|
||||
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> <> <Jason is {user_name}'s colleague> <Jason, colleague>
|
||||
Thought: The second sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
|
||||
Information: <2> <> <None> <>
|
||||
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> <cat fur, allergy>
|
||||
Thought: The fourth sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
|
||||
Information: <4> <> <None> <>
|
||||
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> <Alibaba, Xuhui Riverside Campus, work>
|
||||
|
||||
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> <purchase, 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> <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> <> <None> <>
|
||||
Thought: The fourth sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
|
||||
Information: <4> <> <None> <>
|
||||
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> <General Motors, stock>
|
||||
Thought: The sixth sentence is content from a script written by {user_name}, with no extractable personal information about {user_name}.
|
||||
Information: <6> <> <None> <>
|
||||
|
||||
|
||||
get_observation_user_query_zh: |
|
||||
{user_name}句子:
|
||||
{user_query}
|
||||
|
||||
|
||||
get_observation_user_query: |
|
||||
{user_name} sentences:
|
||||
{user_query}
|
||||
|
||||
129
reme_ai/summary/personal/get_observation_with_time_op.py
Normal file
129
reme_ai/summary/personal/get_observation_with_time_op.py
Normal file
|
|
@ -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, ": ")
|
||||
158
reme_ai/summary/personal/get_observation_with_time_prompt.yaml
Normal file
158
reme_ai/summary/personal/get_observation_with_time_prompt.yaml
Normal file
|
|
@ -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: <serial number> <conversation time> {user_name}: <sentence>.
|
||||
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: <Sentence Number> <Time information or do not output> <Clear important information or "None"> <Keywords>
|
||||
|
||||
|
||||
get_observation_with_time_few_shot_zh: |
|
||||
示例1:
|
||||
{user_name}句子:
|
||||
1 2022年5月1日周二3点 {user_name}:帮我写一段给同事张三女儿三岁生日的祝福语。
|
||||
2 2022年5月2日周二17点 {user_name}:公元1400年至1550年中国历史大事表。
|
||||
3 2022年5月3日周二18点 {user_name}:能给我整理一张如何使用大模型的技巧列表吗,要求内容尽量精简。
|
||||
4 2022年7月3日周四12点 {user_name}:上上个月我办了游泳卡。
|
||||
|
||||
思考:从第1句可以得知张三是{user_name}的同事,这是关于{user_name}的人际关系的重要信息。其余信息重要性不足。{user_name}信息不涉及时间。
|
||||
信息:<1> <> <张三是{user_name}的同事。> <张三, 同事>
|
||||
思考:第2句是{user_name}提出的要求,没有明确提及{user_name}个人信息。
|
||||
信息:<2> <> <无> <>
|
||||
思考:第3句是{user_name}提出的要求,没有明确提及{user_name}个人信息。
|
||||
信息:<3> <> <无> <>
|
||||
思考:从第4句可以得出{user_name}上上个月办了游泳卡。{user_name}信息涉及时间,结合对话时间为2022年7月,推断{user_name}在2022年5月{user_name}办了游泳卡。
|
||||
信息:<4> <2022年5月> <{user_name}在2022年5月办了游泳卡。> <游泳卡>
|
||||
|
||||
|
||||
示例2:
|
||||
{user_name}句子:
|
||||
1 2020年1月4日周日10点 {user_name}:我花5000元买了100股海天味业。
|
||||
2 2023年4月27日周五8点 {user_name}:明天是我和妻子的结婚纪念日,帮我推荐一家餐厅。
|
||||
3 2020年1月4日周日10点 {user_name}:我花50000元买了100股阿里巴巴股票。
|
||||
4 2021年6月2日周四23点 {user_name}:谢啦。我中午在公司附近吃,帮我推荐一家阿里巴巴徐汇滨江园区附近的餐厅吧。
|
||||
5 2021年7月9日周六11点 {user_name}:两个坏消息,我打羽毛球把拍子打断线了。。。然后我去我朋友家撸猫,结果我猫毛过敏,今天疯狂打喷嚏。。。
|
||||
|
||||
思考:从第1句可以得知{user_name}购买了海天味业股票,购买数量为100股,购买金额为5000元,这是关于{user_name}的投资决策的重要信息。{user_name}信息不涉及时间。
|
||||
信息:<1> <> <{user_name}购买了海天味业股票,购买数量为100股,购买金额为5000元。> <海天味业, 股票>
|
||||
思考:从第2句可以得知{user_name}与妻子的结婚纪念日是明天,这是关于{user_name}重要纪念日的信息。其余信息重要性不足。{user_name}信息涉及时间,结合对话时间为2023年4月27日,
|
||||
以及结婚纪念日为周期性日期,推断{user_name}与妻子的结婚纪念日是每年4月28日。
|
||||
信息:<2> <每年4月28日> <{user_name}与妻子的结婚纪念日是每年4月28日。> <妻子, 结婚纪念日>
|
||||
思考:第3句含有的信息与第1句相似,但是不重复,可以得知{user_name}购买了阿里巴巴股票。
|
||||
信息:<3> <> <{user_name}购买了阿里巴巴股票,购买数量为100股,购买金额为50000元。> <阿里巴巴, 股票>
|
||||
思考:从第4句以得知{user_name}在阿里巴巴徐汇滨江园区工作,这是关于{user_name}的工作的重要信息。其余信息重要性不足。{user_name}信息不涉及时间。
|
||||
信息:<4> <> <{user_name}在阿里巴巴徐汇滨江园区工作。> <阿里巴巴, 徐汇滨江园区, 工作>
|
||||
思考:从第5句可以得知{user_name}前天打羽毛球时把球拍打断了线,但这不是重要的信息。还可以得知{user_name}对猫毛过敏,这是关于{user_name}的健康的重要信息。{user_name}信息不涉及时间。
|
||||
信息:<5> <> <{user_name}对猫毛过敏。> <猫毛, 过敏>
|
||||
|
||||
|
||||
示例3:
|
||||
{user_name}句子:
|
||||
1 2023年6月30日周五15点 {user_name}:上个月我和家人一起去杭州旅游,景色很不错。
|
||||
2 2023年7月2日周二10点 {user_name}:昨天是我生日,一个人过的。
|
||||
3 2020年7月3日周四11点 {user_name}:提醒我下周一去体检。
|
||||
4 2023年5月21日周六14点 {user_name}:有人说兴趣是最好的老师,也建议兴趣和职业联系起来,但我发现喜欢打篮球的人很多,但靠打篮球成职业的稀少,赚钱的更少,此外,怎么分辨兴趣和喜欢
|
||||
5 2018年3月6日周四19点 {user_name}:李增杰:这个是星座蛙设,但是我是处女座的,我妈感觉因为我的不正常,我妈不让我看了\n雌猴摸了摸李增杰的头,这样啊\n雌猴打开了哔哩哔哩看了看\n雌猴:要不换个设吧,我听你未来的你说,有一个叫难忘的朱古力232这个人,他弄的设是Windows设\n这是剧本1,剧本2未完待续
|
||||
|
||||
思考:从第1句可以得知{user_name}和家人上个月去杭州旅游了,这是关于{user_name}的经历的重要信息。其余信息重要性不足。{user_name}信息涉及时间,结合对话时间为2023年6月推断{user_name}和家人2023年5月去杭州旅游了。
|
||||
信息:<1> <2023年5月> <{user_name}和家人2023年5月去杭州旅游了。> <家人, 杭州, 旅游>
|
||||
思考:从第2句可以得知{user_name}的生日是昨天,这是关于{user_name}重要纪念日的信息。其余信息重要性不足。{user_name}信息涉及时间,结合对话时间为2023年7月2日,
|
||||
以及生日为周期性日期,推断{user_name}的生日是每年7月2日。
|
||||
信息:<2> <每年7月2日> <{user_name}的生日是每年7月2日。> <生日>
|
||||
思考:从第3句可以得出{user_name}下周一去体检,这是{user_name}要求记忆的重要信息。{user_name}信息涉及时间,结合对话时间为2020年7月3日周四,推断{user_name}2020年7月6日周一去体检。
|
||||
信息:<3> <2020年7月6日周一> <{user_name}2020年7月6日周一去体检。> <体检>
|
||||
思考:第4句是{user_name}对他人观点的讨论和疑问,没有明确提及{user_name}个人信息。
|
||||
信息:<4> <> <无> <>
|
||||
思考:第5句是{user_name}创作的剧本内容,无法提取{user_name}个人信息。
|
||||
信息:<5> <> <无> <>
|
||||
|
||||
|
||||
get_observation_with_time_few_shot: |
|
||||
Example 1:
|
||||
{user_name} sentences:
|
||||
1 May 1, 2022, Tuesday, at 3 {user_name}: Please help me write a birthday greeting for my colleague Jason's daughter who is turning three.
|
||||
2 May 2, 2022, Tuesday, at 17 {user_name}: Chronology of major events in Chinese history from 1400 to 1550 AD.
|
||||
3 May 3, 2022, Tuesday, at 18 {user_name}: Can you compile a list of tips on how to use large models for me, and try to keep the content concise?
|
||||
4 July 3, 2022, Thursday, at 12 {user_name}: I got a swimming pass two months ago.
|
||||
|
||||
Thought: From the first sentence, it can be inferred that Jason is {user_name}'s colleague, which is important information about {user_name}'s interpersonal relationships. The remaining information is of insufficient importance. {user_name}'s information does not involve time.
|
||||
Information: <1> <> <Zhang San is {user_name}'s colleague> <Zhang San, colleague>
|
||||
Thought: The second sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
|
||||
Information: <2> <> <none> <>
|
||||
Thought: The third sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
|
||||
Information: <3> <> <none> <>
|
||||
Thought: From the fourth sentence, it can be inferred that {user_name} got a swimming pass two months ago. {user_name}'s information involves time. Combining it with the conversation time of July 2022, it can be inferred that {user_name} got the swimming pass in May 2022.
|
||||
Information: <4> <May 2022> <{user_name} got a swimming pass in May 2022> <swimming pass>
|
||||
|
||||
Example 2:
|
||||
{user_name} sentences:
|
||||
1 January 4, 2020, Sunday, at 10 {user_name}: I spent $5000 to buy 100 shares of General Motors.
|
||||
2 April 27, 2023, Friday, at 8 {user_name}: Tomorrow is my wedding anniversary with my wife. Could you recommend a restaurant?
|
||||
3 January 4, 2020, Sunday, at 10 {user_name}: I spent $50000 to buy 100 shares of Alibaba.
|
||||
4 June 2, 2021, Thursday, at 23 {user_name}: Thanks. I'm having lunch near the company at noon; can you recommend a restaurant near Alibaba Xuhui Riverside Campus for me?
|
||||
5 July 9, 2021, Saturday, at 11 {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...
|
||||
|
||||
Thought: From the first 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. {user_name}'s information does not involve time.
|
||||
Information: <1> <> <{user_name} bought 100 shares of General Motors stock for $5000> <General Motors, stock>
|
||||
Thought: From the second sentence, it can be inferred that {user_name}'s wedding anniversary with his wife is tomorrow, which is important information about {user_name}'s significant dates. The remaining information is of insufficient importance. {user_name}'s information involves time. Combining it with the conversation date of April 27, 2023, and knowing that the anniversary is a recurring date, it can be inferred that {user_name}'s wedding anniversary is on April 28th each year.
|
||||
Information: <2> <April 28 each year> <{user_name}'s wedding anniversary with his wife is on April 28 each year> <wife, wedding anniversary>
|
||||
Thought: The information in the third sentence is similar to, but not a repetition of the first sentence. It can be deduced that {user_name} purchased Alibaba stock.
|
||||
Information: <3> <> <{user_name} purchased 100 shares of Alibaba stock for 50,000 RMB.> <Alibaba, stock>
|
||||
Thought: From the fourth sentence, it can be inferred that {user_name} works at Alibaba Xuhui Riverside Campus, which is important information about {user_name}'s job. The remaining information is of insufficient importance. {user_name}'s information does not involve time.
|
||||
Information: <4> <> <{user_name} works at Alibaba Xuhui Riverside Campus> <Alibaba, Xuhui Riverside Campus, job>
|
||||
Thought: From the fifth sentence, it can be inferred that {user_name} broke their badminton racket the other day while playing, 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. {user_name}'s information does not involve time.
|
||||
Information: <5> <> <{user_name} is allergic to cat fur> <cat fur, allergy>
|
||||
|
||||
|
||||
Example 3:
|
||||
{user_name} sentences:
|
||||
1 June 30, 2023, Friday, at 15 {user_name}: Last month, my family and I went to San Jose for a trip. The scenery was very nice.
|
||||
2 July 2, 2023, Tuesday, at 10 {user_name}: Yesterday was my birthday. I spent it alone.
|
||||
3 July 3, 2020, Thursday, at 11 {user_name}: Remind me to go for a medical check-up next Monday.
|
||||
4 May 21, 2023, Saturday, at 14 {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 a career out of it, and even fewer make money from it. Also, how do you distinguish passion from liking?
|
||||
5 March 6, 2018, Thursday, at 19 {user_name}: Zack:This is a constellation frog setting, but I am a Virgo. My mom feels I am 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's 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} and their family went to San Jose for a trip last month. This is important information about {user_name}'s experience. The remaining information is of insufficient importance. {user_name}'s information involves time. Combining it with the conversation time of June 2023, it can be inferred that {user_name} and their family went to San Jose for a trip in May 2023.
|
||||
Information: <1> <May 2023> <{user_name} and their family went to San Jose for a trip in May 2023> <family, San Jose, trip>
|
||||
Thought: From the second sentence, it can be inferred that {user_name}'s birthday was yesterday. This is important information about {user_name}'s significant dates. The remaining information is of insufficient importance. {user_name}'s information involves time. Combining it with the conversation time of July 2, 2023, and knowing that the birthday is a recurring date, it can be inferred that {user_name}'s birthday is on July 2 each year.
|
||||
Information: <2> <July 2 each year> <{user_name}'s birthday is on July 2 each year> <birthday>
|
||||
Thought: From the third sentence, it can be inferred that {user_name} will go for a medical check-up next Monday, which is an important reminder for {user_name}. {user_name}'s information involves time. Combining it with the conversation time of July 3, 2020, Thursday, it can be inferred that {user_name} will go for a check-up on July 6, 2020, Monday.
|
||||
Information: <3> <July 6, 2020, Monday> <{user_name} will go for a medical check-up on July 6, 2020, Monday> <medical check-up>
|
||||
Thought: The fourth sentence is a discussion and query about other people's opinions by {user_name}, with no clear mention of {user_name}'s personal information.
|
||||
Information: <4> <> <none> <>
|
||||
Thought: The fifth sentence is content from a script written by {user_name}, with no extractable personal information about {user_name}.
|
||||
Information: <5> <> <none> <>
|
||||
|
||||
|
||||
get_observation_with_time_user_query_zh: |
|
||||
{user_name}句子:
|
||||
{user_query}
|
||||
|
||||
|
||||
get_observation_with_time_user_query: |
|
||||
{user_name} sentences:
|
||||
{user_query}
|
||||
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from memoryscope.constants.common_constants import NEW_OBS_WITH_TIME_NODES
|
||||
from memoryscope.constants.language_constants import COLON_WORD
|
||||
from memoryscope.core.utils.datetime_handler import DatetimeHandler
|
||||
from memoryscope.core.worker.backend.get_observation_worker import GetObservationWorker
|
||||
from memoryscope.scheme.message import Message
|
||||
|
||||
|
||||
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__
|
||||
OBS_STORE_KEY: str = NEW_OBS_WITH_TIME_NODES
|
||||
|
||||
def filter_messages(self) -> List[Message]:
|
||||
"""
|
||||
Filters the chat messages to only include those which contain time-related keywords.
|
||||
|
||||
Returns:
|
||||
List[Message]: A list of filtered messages that mention time.
|
||||
"""
|
||||
filter_messages = []
|
||||
for msg in self.chat_messages_scatter:
|
||||
# Checks if the message content has any time reference words
|
||||
if DatetimeHandler.has_time_word(query=msg.content, language=self.language):
|
||||
filter_messages.append(msg)
|
||||
return filter_messages
|
||||
|
||||
def build_message(self, filter_messages: List[Message]) -> List[Message]:
|
||||
"""
|
||||
Constructs a prompt message for obtaining observations with timestamp information
|
||||
based on filtered chat messages.
|
||||
|
||||
This method processes each filtered message with the timestamp information.
|
||||
It then organizes these timestamped messages into a structured prompt that includes a system prompt,
|
||||
few-shot examples, and the concatenated user queries.
|
||||
|
||||
Args:
|
||||
filter_messages (List[Message]): A list of Message objects that have been filtered for processing.
|
||||
|
||||
Returns:
|
||||
List[Message]: A list containing the newly constructed Message object for further interaction.
|
||||
"""
|
||||
user_query_list = []
|
||||
for i, msg in enumerate(filter_messages):
|
||||
# Create a DatetimeHandler instance for each message's timestamp and format it
|
||||
dt_handler = DatetimeHandler(dt=msg.time_created)
|
||||
dt = dt_handler.string_format(string_format=self.prompt_handler.time_string_format, language=self.language)
|
||||
# Append formatted timestamp-query pairs to the user_query_list
|
||||
user_query_list.append(f"{i + 1} {dt} {self.target_name}{self.get_language_value(COLON_WORD)}{msg.content}")
|
||||
|
||||
# Construct the system prompt with the count of observations
|
||||
system_prompt = self.prompt_handler.get_observation_with_time_system.format(num_obs=len(user_query_list),
|
||||
user_name=self.target_name)
|
||||
|
||||
# Retrieve the few-shot examples for the prompt
|
||||
few_shot = self.prompt_handler.get_observation_with_time_few_shot.format(user_name=self.target_name)
|
||||
|
||||
# Format the user query section with the concatenated list of timestamped queries
|
||||
user_query = self.prompt_handler.get_observation_with_time_user_query.format(
|
||||
user_query="\n".join(user_query_list),
|
||||
user_name=self.target_name)
|
||||
|
||||
# Assemble the final message for observation retrieval
|
||||
get_observation_message_wt = self.prompt_to_msg(system_prompt=system_prompt,
|
||||
few_shot=few_shot,
|
||||
user_query=user_query)
|
||||
|
||||
# Log the constructed message for debugging purposes
|
||||
self.logger.info(f"get_observation_message_wt={get_observation_message_wt}")
|
||||
|
||||
# Return the newly created message
|
||||
return get_observation_message_wt
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
time_string_format:
|
||||
cn: |
|
||||
{year}年{month}{day}日{weekday}{hour}点
|
||||
en: |
|
||||
{month} {day}, {year}, {weekday}, at {hour}
|
||||
|
||||
get_observation_with_time_system:
|
||||
cn: |
|
||||
任务:从下面的{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字以内。
|
||||
信息:<句子序号> <时间信息或不输出> <明确的重要信息或“无”> <关键词>
|
||||
|
||||
en: |
|
||||
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: <serial number> <conversation time> {user_name}: <sentence>.
|
||||
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: <Sentence Number> <Time information or do not output> <Clear important information or "None"> <Keywords>
|
||||
|
||||
|
||||
get_observation_with_time_few_shot:
|
||||
cn: |
|
||||
示例1:
|
||||
{user_name}句子:
|
||||
1 2022年5月1日周二3点 {user_name}:帮我写一段给同事张三女儿三岁生日的祝福语。
|
||||
2 2022年5月2日周二17点 {user_name}:公元1400年至1550年中国历史大事表。
|
||||
3 2022年5月3日周二18点 {user_name}:能给我整理一张如何使用大模型的技巧列表吗,要求内容尽量精简。
|
||||
4 2022年7月3日周四12点 {user_name}:上上个月我办了游泳卡。
|
||||
|
||||
思考:从第1句可以得知张三是{user_name}的同事,这是关于{user_name}的人际关系的重要信息。其余信息重要性不足。{user_name}信息不涉及时间。
|
||||
信息:<1> <> <张三是{user_name}的同事。> <张三, 同事>
|
||||
思考:第2句是{user_name}提出的要求,没有明确提及{user_name}个人信息。
|
||||
信息:<2> <> <无> <>
|
||||
思考:第3句是{user_name}提出的要求,没有明确提及{user_name}个人信息。
|
||||
信息:<3> <> <无> <>
|
||||
思考:从第4句可以得出{user_name}上上个月办了游泳卡。{user_name}信息涉及时间,结合对话时间为2022年7月,推断{user_name}在2022年5月{user_name}办了游泳卡。
|
||||
信息:<4> <2022年5月> <{user_name}在2022年5月办了游泳卡。> <游泳卡>
|
||||
|
||||
|
||||
示例2:
|
||||
{user_name}句子:
|
||||
1 2020年1月4日周日10点 {user_name}:我花5000元买了100股海天味业。
|
||||
2 2023年4月27日周五8点 {user_name}:明天是我和妻子的结婚纪念日,帮我推荐一家餐厅。
|
||||
3 2020年1月4日周日10点 {user_name}:我花50000元买了100股阿里巴巴股票。
|
||||
4 2021年6月2日周四23点 {user_name}:谢啦。我中午在公司附近吃,帮我推荐一家阿里巴巴徐汇滨江园区附近的餐厅吧。
|
||||
5 2021年7月9日周六11点 {user_name}:两个坏消息,我打羽毛球把拍子打断线了。。。然后我去我朋友家撸猫,结果我猫毛过敏,今天疯狂打喷嚏。。。
|
||||
|
||||
思考:从第1句可以得知{user_name}购买了海天味业股票,购买数量为100股,购买金额为5000元,这是关于{user_name}的投资决策的重要信息。{user_name}信息不涉及时间。
|
||||
信息:<1> <> <{user_name}购买了海天味业股票,购买数量为100股,购买金额为5000元。> <海天味业, 股票>
|
||||
思考:从第2句可以得知{user_name}与妻子的结婚纪念日是明天,这是关于{user_name}重要纪念日的信息。其余信息重要性不足。{user_name}信息涉及时间,结合对话时间为2023年4月27日,
|
||||
以及结婚纪念日为周期性日期,推断{user_name}与妻子的结婚纪念日是每年4月28日。
|
||||
信息:<2> <每年4月28日> <{user_name}与妻子的结婚纪念日是每年4月28日。> <妻子, 结婚纪念日>
|
||||
思考:第3句含有的信息与第1句相似,但是不重复,可以得知{user_name}购买了阿里巴巴股票。
|
||||
信息:<3> <> <{user_name}购买了阿里巴巴股票,购买数量为100股,购买金额为50000元。> <阿里巴巴, 股票>
|
||||
思考:从第4句以得知{user_name}在阿里巴巴徐汇滨江园区工作,这是关于{user_name}的工作的重要信息。其余信息重要性不足。{user_name}信息不涉及时间。
|
||||
信息:<4> <> <{user_name}在阿里巴巴徐汇滨江园区工作。> <阿里巴巴, 徐汇滨江园区, 工作>
|
||||
思考:从第5句可以得知{user_name}前天打羽毛球时把球拍打断了线,但这不是重要的信息。还可以得知{user_name}对猫毛过敏,这是关于{user_name}的健康的重要信息。{user_name}信息不涉及时间。
|
||||
信息:<5> <> <{user_name}对猫毛过敏。> <猫毛, 过敏>
|
||||
|
||||
|
||||
示例3:
|
||||
{user_name}句子:
|
||||
1 2023年6月30日周五15点 {user_name}:上个月我和家人一起去杭州旅游,景色很不错。
|
||||
2 2023年7月2日周二10点 {user_name}:昨天是我生日,一个人过的。
|
||||
3 2020年7月3日周四11点 {user_name}:提醒我下周一去体检。
|
||||
4 2023年5月21日周六14点 {user_name}:有人说兴趣是最好的老师,也建议兴趣和职业联系起来,但我发现喜欢打篮球的人很多,但靠打篮球成职业的稀少,赚钱的更少,此外,怎么分辨兴趣和喜欢
|
||||
5 2018年3月6日周四19点 {user_name}:李增杰:这个是星座蛙设,但是我是处女座的,我妈感觉因为我的不正常,我妈不让我看了\n雌猴摸了摸李增杰的头,这样啊\n雌猴打开了哔哩哔哩看了看\n雌猴:要不换个设吧,我听你未来的你说,有一个叫难忘的朱古力232这个人,他弄的设是Windows设\n这是剧本1,剧本2未完待续
|
||||
|
||||
思考:从第1句可以得知{user_name}和家人上个月去杭州旅游了,这是关于{user_name}的经历的重要信息。其余信息重要性不足。{user_name}信息涉及时间,结合对话时间为2023年6月推断{user_name}和家人2023年5月去杭州旅游了。
|
||||
信息:<1> <2023年5月> <{user_name}和家人2023年5月去杭州旅游了。> <家人, 杭州, 旅游>
|
||||
思考:从第2句可以得知{user_name}的生日是昨天,这是关于{user_name}重要纪念日的信息。其余信息重要性不足。{user_name}信息涉及时间,结合对话时间为2023年7月2日,
|
||||
以及生日为周期性日期,推断{user_name}的生日是每年7月2日。
|
||||
信息:<2> <每年7月2日> <{user_name}的生日是每年7月2日。> <生日>
|
||||
思考:从第3句可以得出{user_name}下周一去体检,这是{user_name}要求记忆的重要信息。{user_name}信息涉及时间,结合对话时间为2020年7月3日周四,推断{user_name}2020年7月6日周一去体检。
|
||||
信息:<3> <2020年7月6日周一> <{user_name}2020年7月6日周一去体检。> <体检>
|
||||
思考:第4句是{user_name}对他人观点的讨论和疑问,没有明确提及{user_name}个人信息。
|
||||
信息:<4> <> <无> <>
|
||||
思考:第5句是{user_name}创作的剧本内容,无法提取{user_name}个人信息。
|
||||
信息:<5> <> <无> <>
|
||||
|
||||
en: |
|
||||
Example 1:
|
||||
{user_name} sentences:
|
||||
1 May 1, 2022, Tuesday, at 3 {user_name}: Please help me write a birthday greeting for my colleague Jason's daughter who is turning three.
|
||||
2 May 2, 2022, Tuesday, at 17 {user_name}: Chronology of major events in Chinese history from 1400 to 1550 AD.
|
||||
3 May 3, 2022, Tuesday, at 18 {user_name}: Can you compile a list of tips on how to use large models for me, and try to keep the content concise?
|
||||
4 July 3, 2022, Thursday, at 12 {user_name}: I got a swimming pass two months ago.
|
||||
|
||||
Thought: From the first sentence, it can be inferred that Jason is {user_name}'s colleague, which is important information about {user_name}'s interpersonal relationships. The remaining information is of insufficient importance. {user_name}'s information does not involve time.
|
||||
Information: <1> <> <Zhang San is {user_name}'s colleague> <Zhang San, colleague>
|
||||
Thought: The second sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
|
||||
Information: <2> <> <none> <>
|
||||
Thought: The third sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
|
||||
Information: <3> <> <none> <>
|
||||
Thought: From the fourth sentence, it can be inferred that {user_name} got a swimming pass two months ago. {user_name}'s information involves time. Combining it with the conversation time of July 2022, it can be inferred that {user_name} got the swimming pass in May 2022.
|
||||
Information: <4> <May 2022> <{user_name} got a swimming pass in May 2022> <swimming pass>
|
||||
|
||||
Example 2:
|
||||
{user_name} sentences:
|
||||
1 January 4, 2020, Sunday, at 10 {user_name}: I spent $5000 to buy 100 shares of General Motors.
|
||||
2 April 27, 2023, Friday, at 8 {user_name}: Tomorrow is my wedding anniversary with my wife. Could you recommend a restaurant?
|
||||
3 January 4, 2020, Sunday, at 10 {user_name}: I spent $50000 to buy 100 shares of Alibaba.
|
||||
4 June 2, 2021, Thursday, at 23 {user_name}: Thanks. I'm having lunch near the company at noon; can you recommend a restaurant near Alibaba Xuhui Riverside Campus for me?
|
||||
5 July 9, 2021, Saturday, at 11 {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...
|
||||
|
||||
Thought: From the first 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. {user_name}'s information does not involve time.
|
||||
Information: <1> <> <{user_name} bought 100 shares of General Motors stock for $5000> <General Motors, stock>
|
||||
Thought: From the second sentence, it can be inferred that {user_name}'s wedding anniversary with his wife is tomorrow, which is important information about {user_name}'s significant dates. The remaining information is of insufficient importance. {user_name}'s information involves time. Combining it with the conversation date of April 27, 2023, and knowing that the anniversary is a recurring date, it can be inferred that {user_name}'s wedding anniversary is on April 28th each year.
|
||||
Information: <2> <April 28 each year> <{user_name}'s wedding anniversary with his wife is on April 28 each year> <wife, wedding anniversary>
|
||||
Thought: The information in the third sentence is similar to, but not a repetition of the first sentence. It can be deduced that {user_name} purchased Alibaba stock.
|
||||
Information: <3> <> <{user_name} purchased 100 shares of Alibaba stock for 50,000 RMB.> <Alibaba, stock>
|
||||
Thought: From the fourth sentence, it can be inferred that {user_name} works at Alibaba Xuhui Riverside Campus, which is important information about {user_name}'s job. The remaining information is of insufficient importance. {user_name}'s information does not involve time.
|
||||
Information: <4> <> <{user_name} works at Alibaba Xuhui Riverside Campus> <Alibaba, Xuhui Riverside Campus, job>
|
||||
Thought: From the fifth sentence, it can be inferred that {user_name} broke their badminton racket the other day while playing, 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. {user_name}'s information does not involve time.
|
||||
Information: <5> <> <{user_name} is allergic to cat fur> <cat fur, allergy>
|
||||
|
||||
|
||||
Example 3:
|
||||
{user_name} sentences:
|
||||
1 June 30, 2023, Friday, at 15 {user_name}: Last month, my family and I went to San Jose for a trip. The scenery was very nice.
|
||||
2 July 2, 2023, Tuesday, at 10 {user_name}: Yesterday was my birthday. I spent it alone.
|
||||
3 July 3, 2020, Thursday, at 11 {user_name}: Remind me to go for a medical check-up next Monday.
|
||||
4 May 21, 2023, Saturday, at 14 {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 a career out of it, and even fewer make money from it. Also, how do you distinguish passion from liking?
|
||||
5 March 6, 2018, Thursday, at 19 {user_name}: Zack:This is a constellation frog setting, but I am a Virgo. My mom feels I am 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's 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} and their family went to San Jose for a trip last month. This is important information about {user_name}'s experience. The remaining information is of insufficient importance. {user_name}'s information involves time. Combining it with the conversation time of June 2023, it can be inferred that {user_name} and their family went to San Jose for a trip in May 2023.
|
||||
Information: <1> <May 2023> <{user_name} and their family went to San Jose for a trip in May 2023> <family, San Jose, trip>
|
||||
Thought: From the second sentence, it can be inferred that {user_name}'s birthday was yesterday. This is important information about {user_name}'s significant dates. The remaining information is of insufficient importance. {user_name}'s information involves time. Combining it with the conversation time of July 2, 2023, and knowing that the birthday is a recurring date, it can be inferred that {user_name}'s birthday is on July 2 each year.
|
||||
Information: <2> <July 2 each year> <{user_name}'s birthday is on July 2 each year> <birthday>
|
||||
Thought: From the third sentence, it can be inferred that {user_name} will go for a medical check-up next Monday, which is an important reminder for {user_name}. {user_name}'s information involves time. Combining it with the conversation time of July 3, 2020, Thursday, it can be inferred that {user_name} will go for a check-up on July 6, 2020, Monday.
|
||||
Information: <3> <July 6, 2020, Monday> <{user_name} will go for a medical check-up on July 6, 2020, Monday> <medical check-up>
|
||||
Thought: The fourth sentence is a discussion and query about other people's opinions by {user_name}, with no clear mention of {user_name}'s personal information.
|
||||
Information: <4> <> <none> <>
|
||||
Thought: The fifth sentence is content from a script written by {user_name}, with no extractable personal information about {user_name}.
|
||||
Information: <5> <> <none> <>
|
||||
|
||||
|
||||
get_observation_with_time_user_query:
|
||||
cn: |
|
||||
{user_name}句子:
|
||||
{user_query}
|
||||
|
||||
en: |
|
||||
{user_name} sentences:
|
||||
{user_query}
|
||||
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from memoryscope.constants.common_constants import NEW_OBS_NODES, TIME_INFER
|
||||
from memoryscope.constants.language_constants import REPEATED_WORD, NONE_WORD, COLON_WORD, TIME_INFER_WORD
|
||||
from memoryscope.core.utils.datetime_handler import DatetimeHandler
|
||||
from memoryscope.core.utils.response_text_parser import ResponseTextParser
|
||||
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.scheme.memory_node import MemoryNode
|
||||
from memoryscope.scheme.message import Message
|
||||
|
||||
|
||||
class GetObservationWorker(MemoryBaseWorker):
|
||||
"""
|
||||
A specialized worker class to generate the observations from the original chat histories.
|
||||
"""
|
||||
FILE_PATH: str = __file__
|
||||
OBS_STORE_KEY: str = NEW_OBS_NODES
|
||||
|
||||
def _parse_params(self, **kwargs):
|
||||
self.generation_model_kwargs: dict = kwargs.get("generation_model_kwargs", {})
|
||||
|
||||
def add_observation(self, message: Message, time_infer: str, obs_content: str, keywords: str):
|
||||
"""
|
||||
Builds a MemoryNode containing the observation details.
|
||||
|
||||
Args:
|
||||
message (Message): The source message from which the observation is derived.
|
||||
time_infer (str): The inferred time if available.
|
||||
obs_content (str): The content of the observation.
|
||||
keywords (str): Keywords associated with the observation.
|
||||
|
||||
Returns:
|
||||
MemoryNode: The constructed MemoryNode containing the observation.
|
||||
"""
|
||||
dt_handler = DatetimeHandler(dt=message.time_created)
|
||||
|
||||
# build meta data
|
||||
meta_data = {
|
||||
MemoryTypeEnum.CONVERSATION.value: message.content,
|
||||
TIME_INFER: time_infer,
|
||||
"keywords": keywords,
|
||||
**{k: str(v) for k, v in dt_handler.get_dt_info_dict(self.language).items()},
|
||||
}
|
||||
|
||||
if time_infer:
|
||||
dt_info_dict = DatetimeHandler.extract_date_parts(input_string=time_infer, language=self.language)
|
||||
meta_data.update({f"event_{k}": str(v) for k, v in dt_info_dict.items()})
|
||||
obs_content = (f"{obs_content} ({self.get_language_value(TIME_INFER_WORD)}"
|
||||
f"{self.get_language_value(COLON_WORD)} {time_infer})")
|
||||
|
||||
return MemoryNode(user_name=self.user_name,
|
||||
target_name=self.target_name,
|
||||
meta_data=meta_data,
|
||||
content=obs_content,
|
||||
memory_type=MemoryTypeEnum.OBSERVATION.value,
|
||||
action_status=ActionStatusEnum.NEW.value,
|
||||
timestamp=message.time_created)
|
||||
|
||||
def filter_messages(self) -> List[Message]:
|
||||
"""
|
||||
Filters the chat messages to only include those which not contain time-related keywords.
|
||||
|
||||
Returns:
|
||||
List[Message]: A list of filtered messages that mention time.
|
||||
"""
|
||||
filter_messages = []
|
||||
for msg in self.chat_messages_scatter:
|
||||
if not DatetimeHandler.has_time_word(query=msg.content, language=self.language):
|
||||
filter_messages.append(msg)
|
||||
|
||||
self.logger.info(f"after filter_messages.size from {len(self.chat_messages_scatter)} to {len(filter_messages)}")
|
||||
return filter_messages
|
||||
|
||||
def build_message(self, filter_messages: List[Message]) -> List[Message]:
|
||||
"""
|
||||
Constructs a formatted message for observation based on input messages, incorporating system prompts,
|
||||
few-shot examples, and user queries.
|
||||
|
||||
Args:
|
||||
filter_messages (List[Message]): A list of messages filtered for observation processing.
|
||||
|
||||
Returns:
|
||||
List[Message]: A list containing the constructed message ready for observation.
|
||||
"""
|
||||
user_query_list = []
|
||||
for i, msg in enumerate(filter_messages):
|
||||
# Construct each user query item with index, target name, and message content
|
||||
user_query_list.append(f"{i + 1} {self.target_name}{self.get_language_value(COLON_WORD)}{msg.content}")
|
||||
|
||||
# Format the system prompt with the number of observations and target name
|
||||
system_prompt = self.prompt_handler.get_observation_system.format(num_obs=len(user_query_list),
|
||||
user_name=self.target_name)
|
||||
|
||||
# Incorporate few-shot examples into the prompt with the target name
|
||||
few_shot = self.prompt_handler.get_observation_few_shot.format(user_name=self.target_name)
|
||||
|
||||
# Assemble the user query part of the prompt with the list of formatted user queries
|
||||
user_query = self.prompt_handler.get_observation_user_query.format(user_query="\n".join(user_query_list),
|
||||
user_name=self.target_name)
|
||||
|
||||
# Combine system prompt, few-shot, and user query into a single message for obtaining observations
|
||||
get_observation_message = self.prompt_to_msg(system_prompt=system_prompt,
|
||||
few_shot=few_shot,
|
||||
user_query=user_query)
|
||||
|
||||
# Log the constructed observation message
|
||||
self.logger.info(f"get_observation_message={get_observation_message}")
|
||||
|
||||
# Return the processed message(s) for further steps in the observation workflow
|
||||
return get_observation_message
|
||||
|
||||
def _run(self):
|
||||
"""
|
||||
Processes chat messages to extract observations, inferring timestamps and content relevance,
|
||||
and stores the extracted information as MemoryNode objects within the conversation memory.
|
||||
|
||||
Steps:
|
||||
1. Filter messages based on predefined criteria.
|
||||
2. Constructs a message for the language model to generate observations.
|
||||
3. Calls the language model to predict observation details.
|
||||
4. Parses the model's response to extract observation lists.
|
||||
5. Validates and structures each observed event into MemoryNode objects.
|
||||
6. Stores these MemoryNodes in the conversation memory under a specific key.
|
||||
"""
|
||||
# Filters messages and constructs an input message for the language model
|
||||
filter_messages = self.filter_messages()
|
||||
if not filter_messages:
|
||||
self.logger.warning("get obs filter_messages is empty!")
|
||||
return
|
||||
|
||||
obtain_obs_message = self.build_message(filter_messages)
|
||||
|
||||
# Generates observations using the language model
|
||||
response = self.generation_model.call(messages=obtain_obs_message, **self.generation_model_kwargs)
|
||||
if not response.status or not response.message.content:
|
||||
return
|
||||
|
||||
response_text = response.message.content
|
||||
|
||||
# Parses the generated text to extract observation indices, times, contents, and keywords
|
||||
idx_obs_list = ResponseTextParser(response_text, self.language, self.__class__.__name__).parse_v1()
|
||||
if len(idx_obs_list) <= 0:
|
||||
self.logger.warning("idx_obs_list is empty!")
|
||||
return
|
||||
|
||||
# Processes each extracted observation to create MemoryNode objects
|
||||
new_obs_nodes: List[MemoryNode] = []
|
||||
for obs_content_list in idx_obs_list:
|
||||
if not obs_content_list:
|
||||
continue
|
||||
|
||||
# Expected format: [index, time_inference, observation_content, keywords]
|
||||
if len(obs_content_list) != 4:
|
||||
self.logger.warning(f"obs_content_list={obs_content_list} is invalid!")
|
||||
continue
|
||||
|
||||
idx, time_infer, obs_content, keywords = obs_content_list
|
||||
|
||||
# Skips processing if content indicates no meaningful observation
|
||||
obs_content = obs_content.lower()
|
||||
if obs_content in self.get_language_value([NONE_WORD, REPEATED_WORD]):
|
||||
continue
|
||||
|
||||
# Validates index format
|
||||
if not idx.isdigit():
|
||||
self.logger.warning(f"idx={idx} is invalid!")
|
||||
continue
|
||||
|
||||
time_infer = time_infer.lower()
|
||||
if time_infer == self.get_language_value(NONE_WORD):
|
||||
time_infer = ""
|
||||
|
||||
# Adjusts index to zero-based and checks validity against filtered messages
|
||||
idx = int(idx) - 1
|
||||
if idx >= len(filter_messages):
|
||||
self.logger.warning(f"idx={idx} is invalid! filter_messages.size={len(filter_messages)}")
|
||||
continue
|
||||
|
||||
# Creates a MemoryNode for the validated observation and adds it to the list
|
||||
new_obs_nodes.append(self.add_observation(message=filter_messages[idx],
|
||||
time_infer=time_infer,
|
||||
obs_content=obs_content,
|
||||
keywords=keywords))
|
||||
|
||||
# Stores the extracted and structured observations in the conversation memory
|
||||
self.memory_manager.set_memories(self.OBS_STORE_KEY, new_obs_nodes)
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
get_observation_system:
|
||||
cn: |
|
||||
任务:从下面的{num_obs}句{user_name}句子中依次提取出关于{user_name}的重要信息,与相应的关键词。如果没有重要信息则回答“无”,最多提取{num_obs}条信息。
|
||||
{user_name}的重要信息可以包含用户基本信息,用户画像信息,用户兴趣偏好信息,用户性格,用户价值观,用户人际关系,用户重大事件转折点等等重要信息。
|
||||
如果句子中只包含{user_name}假设的信息或者{user_name}虚构的内容比如{user_name}创作的小说或剧本,回答“无”。
|
||||
对每个句子都做一次信息提取,最后一共输出{num_obs}条信息。
|
||||
请一步步思考,并一定要按如下格式依次输出,最后的结果一定要加<>:
|
||||
思考:思考的依据和过程,50字以内。
|
||||
信息:<句子序号> <> <明确的重要信息或“无”> <关键词>
|
||||
|
||||
en: |
|
||||
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: <Sentence number> <> <Clear important information or "None"> <Keywords>
|
||||
|
||||
|
||||
get_observation_few_shot:
|
||||
cn: |
|
||||
示例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}不喜欢吃桃子。> <西瓜>
|
||||
|
||||
|
||||
en: |
|
||||
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> <no job, 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> <> <None> <>
|
||||
Thought: The information in the third sentence is a repeat of the first sentence.
|
||||
Information: <3> <> <Repeat> <>
|
||||
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> <recent graduate, student>
|
||||
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> <General Motors, stock>
|
||||
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.> <Alibaba, stock>
|
||||
|
||||
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> <> <Jason is {user_name}'s colleague> <Jason, colleague>
|
||||
Thought: The second sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
|
||||
Information: <2> <> <None> <>
|
||||
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> <cat fur, allergy>
|
||||
Thought: The fourth sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
|
||||
Information: <4> <> <None> <>
|
||||
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> <Alibaba, Xuhui Riverside Campus, work>
|
||||
|
||||
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> <purchase, 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> <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> <> <None> <>
|
||||
Thought: The fourth sentence is a request made by {user_name}, with no clear mention of {user_name}'s personal information.
|
||||
Information: <4> <> <None> <>
|
||||
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> <General Motors, stock>
|
||||
Thought: The sixth sentence is content from a script written by {user_name}, with no extractable personal information about {user_name}.
|
||||
Information: <6> <> <None> <>
|
||||
|
||||
|
||||
get_observation_user_query:
|
||||
cn: |
|
||||
{user_name}句子:
|
||||
{user_query}
|
||||
|
||||
en: |
|
||||
{user_name} sentences:
|
||||
{user_query}
|
||||
|
||||
133
reme_ai/summary/personal/get_reflection_subject_op.py
Normal file
133
reme_ai/summary/personal/get_reflection_subject_op.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
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.op_utils import parse_reflection_subjects_response
|
||||
|
||||
|
||||
@C.register_op()
|
||||
class GetReflectionSubjectOp(BaseLLMOp):
|
||||
"""
|
||||
A specialized operation class responsible for retrieving unreflected memory nodes,
|
||||
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__
|
||||
|
||||
def new_insight_memory(self, insight_content: str, target: str) -> PersonalMemory:
|
||||
"""
|
||||
Creates a new PersonalMemory for an insight with the given content.
|
||||
|
||||
Args:
|
||||
insight_content (str): The content of the insight.
|
||||
target (str): The target person the insight is about.
|
||||
|
||||
Returns:
|
||||
PersonalMemory: A new PersonalMemory instance representing the insight.
|
||||
"""
|
||||
return PersonalMemory(
|
||||
workspace_id=self.context.get("workspace_id", ""),
|
||||
content=insight_content,
|
||||
target=target,
|
||||
reflection_subject=insight_content, # Store the subject in the dedicated field
|
||||
author=getattr(self.llm, "model_name", "system"),
|
||||
metadata={
|
||||
"insight_type": "reflection_subject",
|
||||
"memory_type": "personal_topic"
|
||||
}
|
||||
)
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
Executes the main logic of reflecting on personal memories to derive new insights.
|
||||
|
||||
Steps include:
|
||||
- Retrieving personal memories from context.
|
||||
- Checking if there are enough memories to process.
|
||||
- Compiling existing insight subjects.
|
||||
- Generating a reflection prompt with system message, few-shot examples, and user queries.
|
||||
- Calling the language model for new insights.
|
||||
- Parsing the model's responses for new insight subjects.
|
||||
- Creating new insight memories and storing them in context.
|
||||
"""
|
||||
# Get personal memories from context
|
||||
personal_memories: List[BaseMemory] = self.context.response.metadata.get("personal_memories", [])
|
||||
existing_insights: List[BaseMemory] = self.context.response.metadata.get("existing_insights", [])
|
||||
|
||||
# Get parameters from operation config
|
||||
reflect_obs_cnt_threshold: int = self.op_params.get("reflect_obs_cnt_threshold", 10)
|
||||
reflect_num_questions: int = self.op_params.get("reflect_num_questions", 1)
|
||||
|
||||
user_name = self.context.get("user_name", "user")
|
||||
|
||||
# Check if we have enough memories to reflect on
|
||||
if len(personal_memories) < reflect_obs_cnt_threshold:
|
||||
logger.info(
|
||||
f"personal_memories count({len(personal_memories)}) < threshold({reflect_obs_cnt_threshold}), skip reflection.")
|
||||
return
|
||||
|
||||
# Compile existing insight subjects
|
||||
exist_keys: List[str] = []
|
||||
if existing_insights:
|
||||
exist_keys = [memory.content for memory in existing_insights if hasattr(memory, 'content')]
|
||||
|
||||
logger.info(f"exist_keys={exist_keys}")
|
||||
|
||||
# Generate reflection prompt components
|
||||
user_query_list = []
|
||||
for memory in personal_memories:
|
||||
if hasattr(memory, 'content') and memory.content:
|
||||
user_query_list.append(memory.content)
|
||||
|
||||
# Determine number of questions to ask
|
||||
if reflect_num_questions > 0:
|
||||
num_questions = reflect_num_questions
|
||||
else:
|
||||
num_questions = len(user_query_list)
|
||||
|
||||
# Create prompt using the prompt format method
|
||||
system_prompt = self.prompt_format(prompt_name="get_reflection_subject_system",
|
||||
user_name=user_name,
|
||||
num_questions=num_questions)
|
||||
few_shot = self.prompt_format(prompt_name="get_reflection_subject_few_shot", user_name=user_name)
|
||||
user_query = self.prompt_format(prompt_name="get_reflection_subject_user_query",
|
||||
user_name=user_name,
|
||||
exist_keys=", ".join(exist_keys),
|
||||
user_query="\n".join(user_query_list))
|
||||
|
||||
full_prompt = f"{system_prompt}\n\n{few_shot}\n\n{user_query}"
|
||||
logger.info(f"reflection_subject_prompt={full_prompt}")
|
||||
|
||||
def parse_reflection_subjects(message: Message) -> List[BaseMemory]:
|
||||
"""Parse LLM response and create insight memories"""
|
||||
response_text = message.content
|
||||
logger.info(f"reflection_subject_response={response_text}")
|
||||
|
||||
# Parse new insight subjects using utility function
|
||||
new_subjects = parse_reflection_subjects_response(response_text, exist_keys)
|
||||
|
||||
insight_memories = []
|
||||
for subject in new_subjects:
|
||||
# Create insight memory
|
||||
insight_memory = self.new_insight_memory(
|
||||
insight_content=subject,
|
||||
target=user_name
|
||||
)
|
||||
insight_memories.append(insight_memory)
|
||||
logger.info(f"Created reflection subject: {subject}")
|
||||
|
||||
return insight_memories
|
||||
|
||||
# Use LLM chat with callback function
|
||||
insight_memories = self.llm.chat(messages=[Message(content=full_prompt)], callback_fn=parse_reflection_subjects)
|
||||
|
||||
# Store results in context
|
||||
self.context.response.metadata["insight_memories"] = insight_memories
|
||||
logger.info(f"Generated {len(insight_memories)} reflection subject 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"))
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from memoryscope.constants.common_constants import NOT_REFLECTED_NODES, INSIGHT_NODES
|
||||
from memoryscope.constants.language_constants import COMMA_WORD
|
||||
from memoryscope.core.utils.datetime_handler import DatetimeHandler
|
||||
from memoryscope.core.utils.response_text_parser import ResponseTextParser
|
||||
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.scheme.memory_node import MemoryNode
|
||||
|
||||
|
||||
class GetReflectionSubjectWorker(MemoryBaseWorker):
|
||||
"""
|
||||
A specialized worker class responsible for retrieving unreflected memory nodes,
|
||||
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__
|
||||
|
||||
def _parse_params(self, **kwargs):
|
||||
self.reflect_obs_cnt_threshold: int = kwargs.get("reflect_obs_cnt_threshold", 10)
|
||||
self.generation_model_kwargs: dict = kwargs.get("generation_model_kwargs", {})
|
||||
self.reflect_num_questions: int = kwargs.get("reflect_num_questions", 1)
|
||||
|
||||
def new_insight_node(self, insight_key: str) -> MemoryNode:
|
||||
"""
|
||||
Creates a new MemoryNode for an insight with the given key, enriched with current datetime metadata.
|
||||
|
||||
Args:
|
||||
insight_key (str): The unique identifier for the insight.
|
||||
|
||||
Returns:
|
||||
MemoryNode: A new MemoryNode instance representing the insight, marked as new and of type INSIGHT.
|
||||
"""
|
||||
dt_handler = DatetimeHandler()
|
||||
# Prepare metadata with current datetime info
|
||||
meta_data = {k: str(v) for k, v in dt_handler.get_dt_info_dict(self.language).items()}
|
||||
|
||||
return MemoryNode(user_name=self.user_name,
|
||||
target_name=self.target_name,
|
||||
meta_data=meta_data,
|
||||
key=insight_key,
|
||||
memory_type=MemoryTypeEnum.INSIGHT.value,
|
||||
action_status=ActionStatusEnum.NEW.value)
|
||||
|
||||
def _run(self):
|
||||
"""
|
||||
Executes the main logic of reflecting on unaudited memory nodes to derive new insights.
|
||||
|
||||
Steps include:
|
||||
- Retrieving unaudited memory nodes.
|
||||
- Checking the count against a threshold to decide whether to proceed.
|
||||
- Compiling a list of existing insight keys.
|
||||
- Generating a reflection prompt with system message, few-shot examples, and user queries.
|
||||
- Calling the language model for new insights.
|
||||
- Parsing the model's responses for new insight keys.
|
||||
- Creating new insight nodes and updating the memory status accordingly.
|
||||
"""
|
||||
not_reflected_nodes: List[MemoryNode] = self.memory_manager.get_memories(NOT_REFLECTED_NODES)
|
||||
insight_nodes: List[MemoryNode] = self.memory_manager.get_memories(INSIGHT_NODES)
|
||||
|
||||
# Count unaudited nodes
|
||||
not_reflected_count = len(not_reflected_nodes)
|
||||
if not_reflected_count < self.reflect_obs_cnt_threshold:
|
||||
self.logger.info(f"not_reflected_count({not_reflected_count}) < threshold({self.reflect_obs_cnt_threshold})"
|
||||
f" is not enough, skip.")
|
||||
# self.continue_run = False
|
||||
return
|
||||
|
||||
# Compile existing insight keys
|
||||
exist_keys: List[str] = [n.key for n in insight_nodes]
|
||||
self.logger.info(f"exist_keys={exist_keys}")
|
||||
|
||||
# Generate reflection prompt components
|
||||
user_query_list = [n.content for n in not_reflected_nodes]
|
||||
if self.reflect_num_questions > 0:
|
||||
num_questions = self.reflect_num_questions
|
||||
else:
|
||||
num_questions = len(user_query_list)
|
||||
|
||||
system_prompt = self.prompt_handler.get_reflection_subject_system.format(
|
||||
user_name=self.target_name,
|
||||
num_questions=num_questions)
|
||||
few_shot = self.prompt_handler.get_reflection_subject_few_shot.format(user_name=self.target_name)
|
||||
user_query = self.prompt_handler.get_reflection_subject_user_query.format(
|
||||
user_name=self.target_name,
|
||||
exist_keys=self.get_language_value(COMMA_WORD).join(exist_keys),
|
||||
user_query="\n".join(user_query_list))
|
||||
|
||||
# Construct and log reflection message
|
||||
reflect_message = self.prompt_to_msg(system_prompt=system_prompt, few_shot=few_shot, user_query=user_query)
|
||||
self.logger.info(f"reflect_message={reflect_message}")
|
||||
|
||||
# Invoke Language Model for new insights
|
||||
response = self.generation_model.call(messages=reflect_message, **self.generation_model_kwargs)
|
||||
|
||||
# Handle empty response
|
||||
if not response.status or not response.message.content:
|
||||
return
|
||||
|
||||
# Parse LLM response for new insight keys and update memory
|
||||
new_insight_keys = ResponseTextParser(response.message.content, self.language,
|
||||
self.__class__.__name__).parse_v2()
|
||||
if new_insight_keys:
|
||||
for insight_key in new_insight_keys:
|
||||
self.memory_manager.add_memories(INSIGHT_NODES, self.new_insight_node(insight_key))
|
||||
|
||||
# Mark unaudited nodes as reflected
|
||||
for node in not_reflected_nodes:
|
||||
node.obs_reflected = 1
|
||||
node.action_status = ActionStatusEnum.MODIFIED
|
||||
139
reme_ai/summary/personal/info_filter_op.py
Normal file
139
reme_ai/summary/personal/info_filter_op.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
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 PersonalMemory
|
||||
from reme_ai.utils.op_utils import parse_info_filter_response
|
||||
|
||||
|
||||
@C.register_op()
|
||||
class InfoFilterOp(BaseLLMOp):
|
||||
"""
|
||||
A specialized operation class to filter messages based on information content scores using BaseLLMOp.
|
||||
This filters chat messages by retaining only those that include significant information about the user.
|
||||
"""
|
||||
file_path: str = __file__
|
||||
|
||||
def execute(self):
|
||||
"""Filter messages based on information content scores"""
|
||||
# Get messages from context
|
||||
messages: List[Message] = self.context.get("messages", [])
|
||||
if not messages:
|
||||
logger.warning("No messages found in context")
|
||||
return
|
||||
|
||||
# Get operation parameters
|
||||
preserved_scores = self.op_params.get("preserved_scores", "2,3")
|
||||
info_filter_msg_max_size = self.op_params.get("info_filter_msg_max_size", 200)
|
||||
user_name = self.context.get("user_name", "user")
|
||||
|
||||
# Filter and process messages
|
||||
info_messages = self._filter_and_process_messages(messages, user_name, info_filter_msg_max_size)
|
||||
if not info_messages:
|
||||
logger.warning("No messages left after filtering")
|
||||
return
|
||||
|
||||
logger.info(f"Filtering {len(info_messages)} messages for information content")
|
||||
|
||||
# Filter messages using LLM
|
||||
filtered_memories = self._filter_messages_with_llm(info_messages, user_name, preserved_scores)
|
||||
|
||||
# Store results in context
|
||||
self.context.response.metadata["filtered_memories"] = filtered_memories
|
||||
logger.info(f"Filtered to {len(filtered_memories)} high-information messages")
|
||||
|
||||
def _filter_and_process_messages(self, messages: List[Message], user_name: str, max_size: int) -> List[Message]:
|
||||
"""Filter and process messages for information filtering"""
|
||||
info_messages = []
|
||||
|
||||
for msg in messages:
|
||||
# Skip memorized messages
|
||||
if hasattr(msg, 'memorized') and msg.memorized:
|
||||
continue
|
||||
|
||||
# Only process messages from the target user
|
||||
if hasattr(msg, 'role_name') and msg.role_name != user_name:
|
||||
continue
|
||||
elif hasattr(msg, 'role') and msg.role != 'user':
|
||||
continue
|
||||
|
||||
# Truncate long messages
|
||||
if len(msg.content) >= max_size:
|
||||
half_size = int(max_size * 0.5 + 0.5)
|
||||
msg.content = msg.content[:half_size] + msg.content[-half_size:]
|
||||
|
||||
info_messages.append(msg)
|
||||
|
||||
logger.info(f"Filtered messages from {len(messages)} to {len(info_messages)}")
|
||||
return info_messages
|
||||
|
||||
def _filter_messages_with_llm(self, info_messages: List[Message], user_name: str, preserved_scores: str) -> List[
|
||||
PersonalMemory]:
|
||||
"""Filter messages using LLM to score information content"""
|
||||
|
||||
# Build prompt for information filtering
|
||||
user_query_list = []
|
||||
colon = self._get_colon_word()
|
||||
for i, msg in enumerate(info_messages):
|
||||
user_query_list.append(f"{i + 1} {user_name}{colon} {msg.content}")
|
||||
|
||||
# Create prompt using the prompt format method
|
||||
system_prompt = self.prompt_format(prompt_name="info_filter_system",
|
||||
batch_size=len(info_messages),
|
||||
user_name=user_name)
|
||||
few_shot = self.prompt_format(prompt_name="info_filter_few_shot", user_name=user_name)
|
||||
user_query = self.prompt_format(prompt_name="info_filter_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"info_filter_prompt={full_prompt}")
|
||||
|
||||
def parse_and_filter(message: Message) -> List[PersonalMemory]:
|
||||
"""Parse LLM response and create filtered memories"""
|
||||
response_text = message.content
|
||||
logger.info(f"info_filter_response={response_text}")
|
||||
|
||||
# Parse scores using utility function
|
||||
info_scores = parse_info_filter_response(response_text)
|
||||
|
||||
if len(info_scores) != len(info_messages):
|
||||
logger.warning(f"score_size != messages_size, {len(info_scores)} vs {len(info_messages)}")
|
||||
|
||||
filtered_memories = []
|
||||
for idx, score in info_scores:
|
||||
# Convert to 0-based index
|
||||
msg_idx = idx - 1
|
||||
if msg_idx >= len(info_messages):
|
||||
logger.warning(f"Invalid index {msg_idx} for messages list of length {len(info_messages)}")
|
||||
continue
|
||||
|
||||
# Check if score should be preserved
|
||||
if score in preserved_scores:
|
||||
message_obj = info_messages[msg_idx]
|
||||
|
||||
# Create memory from filtered message
|
||||
memory = PersonalMemory(
|
||||
workspace_id=self.context.get("workspace_id", ""),
|
||||
content=message_obj.content,
|
||||
target=user_name,
|
||||
author=getattr(self.llm, "model_name", "system"),
|
||||
metadata={
|
||||
"info_score": score,
|
||||
"filter_type": "info_content",
|
||||
"original_message_time": getattr(message_obj, 'time_created', None)
|
||||
}
|
||||
)
|
||||
filtered_memories.append(memory)
|
||||
logger.info(f"Info filter: kept message with score {score}: {message_obj.content[:50]}...")
|
||||
|
||||
return filtered_memories
|
||||
|
||||
# Use LLM chat with callback function
|
||||
return self.llm.chat(messages=[Message(content=full_prompt)], callback_fn=parse_and_filter)
|
||||
|
||||
def _get_colon_word(self) -> str:
|
||||
"""Get language-specific colon word"""
|
||||
colon_dict = {"zh": ":", "cn": ":", "en": ": "}
|
||||
return colon_dict.get(self.language, ": ")
|
||||
172
reme_ai/summary/personal/info_filter_prompt.yaml
Normal file
172
reme_ai/summary/personal/info_filter_prompt.yaml
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
info_filter_system_zh: |
|
||||
任务:对所给{batch_size}个句子中所含有的关于{user_name}的信息打分,分数为0,1,2或3。
|
||||
注意:其中0表示不包含用户信息,1表示句子中只包含用户假设的信息或者用户虚构的内容比如用户创作的小说或剧本,2表示包含用户的一般信息,时效性信息或者需要猜测才能得到的用户信息,3表示明确含有或者可以确定推断出关于用户的重要信息,或者用户要求记录。
|
||||
{user_name}的重要信息可以包含用户基本信息,用户画像信息,用户兴趣偏好信息,用户性格,用户价值观,用户人际关系,用户重大事件转折点等等重要信息。
|
||||
对每个句子都做一次信息打分,一共输出{batch_size}个分数,不需要写最终结果。
|
||||
请一定要按如下格式依次输出,最后的结果一定要加<>:
|
||||
思考:思考的依据和过程,30字以内。
|
||||
结果:<句子序号> <分数:0或1或2或3>
|
||||
|
||||
info_filter_system: |
|
||||
Task: Score the information about {user_name} contained in the given batch of {batch_size} sentences, with scores of 0, 1, 2, or 3.
|
||||
Note:
|
||||
0 indicates no user information is included.
|
||||
1 indicates only hypothetical information about the user or fictitious content such as novels or scripts created by the user.
|
||||
2 indicates general information about the user, timely information, or information that requires inference.
|
||||
3 indicates clear and important information about the user, or information explicitly requested for recording.
|
||||
Important information about {user_name} can include basic information about the user, user profile information, user interests and preferences, user personality, user values, user relationships, significant life events, and other crucial information.
|
||||
Score each sentence individually and output a total of {batch_size} scores without providing the final result.
|
||||
Please ensure to output in the following format and wrap the final result in <>:
|
||||
Thought: Basis and reasoning process, within 30 characters.
|
||||
Result: <Sentence Index> <Score: 0, 1, 2, or 3>
|
||||
|
||||
|
||||
info_filter_few_shot_zh: |
|
||||
示例1
|
||||
句子:
|
||||
1 {user_name}:帮我写一段给同事张三女儿三岁生日的祝福语。
|
||||
2 {user_name}:公元1400年至1550年中国历史大事表。
|
||||
3 {user_name}:你吃午饭了吗?
|
||||
4 {user_name}:我今天心情不好,可以安慰我一下吗?
|
||||
5 {user_name}:能给我整理一张如何使用大模型的技巧列表吗,要求内容尽量精简。
|
||||
6 {user_name}:记一下,明天下午3点提醒我去拿一下文件。
|
||||
|
||||
思考:从第1句可以确定推断出张三是{user_name}同事这一重要信息。
|
||||
结果:<1> <3>
|
||||
思考:第2句不包含{user_name}信息。
|
||||
结果:<2> <0>
|
||||
思考:第3句不包含{user_name}信息。
|
||||
结果:<3> <0>
|
||||
思考:从第4句可以得知{user_name}今天心情不好,是时效性信息。
|
||||
结果:<4> <2>
|
||||
思考:从第5句可以猜测{user_name}对大模型感兴趣,是不确定的信息。
|
||||
结果:<5> <2>
|
||||
思考:第6句是{user_name}要求记录的信息。
|
||||
结果:<6> <3>
|
||||
|
||||
示例2
|
||||
句子:
|
||||
1 {user_name}:我刚刚入职了阿里巴巴。
|
||||
2 {user_name}:露天睡觉蚊子多,咋搞。
|
||||
3 {user_name}:创造力和外倾性有关?
|
||||
4 {user_name}:一个区县的所有的事业人员的档案审核、修改和规范,应该是县委组织部下属的干部档案中心负责还是县人社局负责?
|
||||
5 {user_name}:假如我要和一个女人准备要孩子,我作为男人,怎么保护女人和孩子以及怎么备孕确保精子质量高对后代好
|
||||
6 {user_name}:我和你一起出去玩,你会感觉开心吗?
|
||||
7 {user_name}:林浅,一位对未来充满好奇的年轻女孩,偶然间发现了这家能寄信给未来的邮局。出于对逝去祖父的怀念,她决定写下一封信,寄给五年后的自己,希望能收到祖父生前未说完的故事。五年期限将至,当她几乎忘记这段往事时,一封泛黄的回信悄然降临,不仅带来了祖父未完的冒险故事,还藏着一段关于勇气、爱与自我发现的深刻启示。续写成3000字小说。
|
||||
|
||||
思考:从第1句可以确定得出{user_name}工作单位是阿里巴巴这一重要信息。
|
||||
结果:<1> <3>
|
||||
思考:从第2句可以猜测{user_name}近期露天睡觉,是不确定的信息。
|
||||
结果:<2> <2>
|
||||
思考:第3句不包含{user_name}信息。
|
||||
结果:<3> <0>
|
||||
思考:第4句不包含{user_name}信息。
|
||||
结果:<4> <0>
|
||||
思考:第5句虽然有假设成分,但可以确定推断出{user_name}是男性这一重要信息。
|
||||
结果:<5> <3>
|
||||
思考:第6句是{user_name}假设的信息。
|
||||
结果:<6> <1>
|
||||
思考:第7句是{user_name}虚构的内容。
|
||||
结果:<7> <1>
|
||||
|
||||
示例3
|
||||
句子:
|
||||
1 {user_name}:你的妈妈患有焦虑症,怎么安慰和开导她?
|
||||
2 {user_name}:肾脏严重亏空
|
||||
3 {user_name}:我很喜欢打篮球,所以我身体很好
|
||||
4 {user_name}:篮球明星有哪些?
|
||||
5 {user_name}:李增杰:这个是星座蛙设,但是我是处女座的,我妈感觉因为我的不正常,我妈不让我看了\n雌猴摸了摸李增杰的头,这样啊\n雌猴打开了哔哩哔哩看了看\n雌猴:要不换个设吧,我听你未来的你说,有一个叫难忘的朱古力232这个人,他弄的设是Windows设\n这是剧本1,剧本2未完待续
|
||||
6 {user_name}:我想知道昨天我们聊了什么?
|
||||
|
||||
思考:第1句是{user_name}假设的信息。
|
||||
结果:<1> <1>
|
||||
思考:第2句信息不明,可能是{user_name}假设的信息。
|
||||
结果:<2> <1>
|
||||
思考:从第3句可以确定得出{user_name}喜欢打篮球,身体好这两个重要信息。
|
||||
结果:<3> <3>
|
||||
思考:第4句不包含{user_name}信息。
|
||||
结果:<4> <0>
|
||||
思考:第5句是{user_name}虚构的内容。
|
||||
结果:<5> <1>
|
||||
思考:第6句是{user_name}的疑问句,没有包含信息。
|
||||
结果:<6> <0>
|
||||
|
||||
info_filter_few_shot: |
|
||||
Example 1
|
||||
Sentences:
|
||||
1 {user_name}: Please help me write a birthday greeting for my colleague Jason's daughter who is turning three.
|
||||
2 {user_name}: Chronology of major events in Mediterranean history from 1400 to 1550 AD.
|
||||
3 {user_name}: Have you had lunch?
|
||||
4 {user_name}: I'm feeling down today. Can you comfort me a bit?
|
||||
5 {user_name}: Can you compile a list of tips on how to use large models for me, and try to keep the content concise?
|
||||
6 {user_name}: Note this down: remind me tomorrow at 3 PM to pick up the documents.
|
||||
|
||||
Thought: From the first sentence, it can be inferred that Zhang San is a colleague of {user_name}, which is important information.
|
||||
Result: <1> <3>
|
||||
Thought: The second sentence does not contain information about {user_name}.
|
||||
Result: <2> <0>
|
||||
Thought: The third sentence does not contain information about {user_name}.
|
||||
Result: <3> <0>
|
||||
Thought: The fourth sentence indicates that {user_name} is in a bad mood today, which is time-sensitive information.
|
||||
Result: <4> <2>
|
||||
Thought: The fifth sentence suggests that {user_name} may be interested in large models, which is uncertain information.
|
||||
Result: <5> <2>
|
||||
Thought: The sixth sentence contains information that {user_name} requested to be recorded.
|
||||
Result: <6> <3>
|
||||
|
||||
Example 2
|
||||
Sentences:
|
||||
1 {user_name}: I've just joined Google.
|
||||
2 {user_name}: There are too many mosquitoes when sleeping outdoors. What should I do?
|
||||
3 {user_name}: Is creativity related to extraversion?
|
||||
4 {user_name}: Should the review, modification, and standardization of all personnel files in a district or county be handled by the cadre archive center or by the county human resources and social security bureau?
|
||||
5 {user_name}: If I am planning to have a child with a woman, as a man, how can I protect the woman and the baby and how can I prepare to ensure high sperm quality for the benefit of the offspring?
|
||||
6 {user_name}: If we go out to play together, would you feel happy?
|
||||
7 {user_name}: Rose, a young girl full of curiosity about the future, accidentally discovered this post office that can send letters to the future. Out of nostalgia for her late grandfather, she decided to write a letter to herself five years in the future, hoping to receive the unfinished stories of her grandfather. As the five-year deadline approached, when she had almost forgotten about this event, a yellowed reply quietly arrived, bringing not only her grandfather's unfinished adventure story but also a profound revelation about courage, love, and self-discovery. Continue writing this into a 3000-word novel.
|
||||
|
||||
Thought: From the first sentence, it can be determined that {user_name} works at Alibaba, which is important information.
|
||||
Result: <1> <3>
|
||||
Thought: The second sentence suggests that {user_name} might has been sleeping outdoors recently, which is uncertain information.
|
||||
Result: <2> <2>
|
||||
Thought: The third sentence does not contain information about {user_name}.
|
||||
Result: <3> <0>
|
||||
Thought: The fourth sentence does not contain information about {user_name}.
|
||||
Result: <4> <0>
|
||||
Thought: Although the fifth sentence contains hypothetical elements, it can be determined that {user_name} is male, which is important information.
|
||||
Result: <5> <3>
|
||||
Thought: The sixth sentence contains only hypothetical information from {user_name}.
|
||||
Result: <6> <1>
|
||||
Thought: The seventh sentence contains only fictitious content from {user_name}.
|
||||
Result: <7> <1>
|
||||
|
||||
Example 3
|
||||
Sentences:
|
||||
1 {user_name}: Your mother is suffering from anxiety. How can you comfort and guide her?
|
||||
2 {user_name}: Severe kidney deficiency
|
||||
3 {user_name}: I really enjoy playing basketball, so I am in good health.
|
||||
4 {user_name}: Who are some famous basketball stars?
|
||||
5 {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.
|
||||
6 {user_name}: I want to know what we talked about yesterday.
|
||||
|
||||
Thought: The first sentence contains only hypothetical information from {user_name}.
|
||||
Result: <1> <1>
|
||||
Thought: The second sentence is unclear and may contain hypothetical information from {user_name}.
|
||||
Result: <2> <1>
|
||||
Thought: From the third sentence, it can be determined that {user_name} likes playing basketball and is in good health, which are two important pieces of information.
|
||||
Result: <3> <3>
|
||||
Thought: The fourth sentence does not contain information about {user_name}.
|
||||
Result: <4> <0>
|
||||
Thought: The fifth sentence contains only fictitious content from {user_name}.
|
||||
Result: <5> <1>
|
||||
Thought: Sentence 6 is a question from {user_name} that doesn't include any specific information.
|
||||
Result: <6> <0>
|
||||
|
||||
info_filter_user_query_zh: |
|
||||
句子:
|
||||
{user_query}
|
||||
|
||||
|
||||
info_filter_user_query: |
|
||||
Sentences:
|
||||
{user_query}
|
||||
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from memoryscope.constants.language_constants import COLON_WORD
|
||||
from memoryscope.core.utils.response_text_parser import ResponseTextParser
|
||||
from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
|
||||
from memoryscope.scheme.message import Message
|
||||
|
||||
|
||||
class InfoFilterWorker(MemoryBaseWorker):
|
||||
"""
|
||||
This worker filters and modifies the chat message history (`self.chat_messages`) by retaining only the messages
|
||||
that include significant information. It then constructs a prompt from these filtered messages, utilizes an AI
|
||||
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__
|
||||
|
||||
def _parse_params(self, **kwargs):
|
||||
self.preserved_scores: str = kwargs.get("preserved_scores", "2,3")
|
||||
self.info_filter_msg_max_size: int = kwargs.get("info_filter_msg_max_size", 200)
|
||||
self.generation_model_kwargs: dict = kwargs.get("generation_model_kwargs", {})
|
||||
|
||||
def _run(self):
|
||||
"""
|
||||
Filters user messages in the chat, generates a prompt incorporating these messages,
|
||||
utilizes an LLM to rate the information score for each message,
|
||||
and updates `self.chat_messages` to only include messages with designated scores.
|
||||
|
||||
This method executes the following steps:
|
||||
1. Filters out non-user messages and truncates long messages.
|
||||
2. Constructs a prompt with user messages for LLM input.
|
||||
3. Calls the LLM model with the constructed prompt.
|
||||
4. Parses the LLM's response to extract message scores.
|
||||
5. Retains message in `self.chat_messages` based on their scores.
|
||||
"""
|
||||
# filter user msg
|
||||
info_messages: List[Message] = []
|
||||
for msg in self.chat_messages_scatter:
|
||||
if msg.memorized:
|
||||
continue
|
||||
|
||||
# TODO: add memory for all messages
|
||||
if msg.role_name != self.target_name:
|
||||
continue
|
||||
|
||||
if len(msg.content) >= self.info_filter_msg_max_size:
|
||||
half_size = int(self.info_filter_msg_max_size * 0.5 + 0.5)
|
||||
msg.content = msg.content[: half_size] + msg.content[-half_size:]
|
||||
info_messages.append(msg)
|
||||
|
||||
if not info_messages:
|
||||
self.logger.warning("info_messages is empty!")
|
||||
self.continue_run = False
|
||||
return
|
||||
|
||||
# generate prompt
|
||||
user_query_list = []
|
||||
for i, msg in enumerate(info_messages):
|
||||
user_query_list.append(f"{i + 1} {self.target_name}{self.get_language_value(COLON_WORD)} {msg.content}")
|
||||
system_prompt = self.prompt_handler.info_filter_system.format(batch_size=len(info_messages),
|
||||
user_name=self.target_name)
|
||||
few_shot = self.prompt_handler.info_filter_few_shot.format(user_name=self.target_name)
|
||||
user_query = self.prompt_handler.info_filter_user_query.format(user_query="\n".join(user_query_list))
|
||||
info_filter_message = self.prompt_to_msg(system_prompt=system_prompt, few_shot=few_shot, user_query=user_query)
|
||||
self.logger.info(f"info_filter_message={info_filter_message}")
|
||||
|
||||
# call llm
|
||||
response = self.generation_model.call(messages=info_filter_message, **self.generation_model_kwargs)
|
||||
|
||||
# return if empty
|
||||
if not response.status or not response.message.content:
|
||||
self.continue_run = False
|
||||
return
|
||||
response_text = response.message.content
|
||||
|
||||
# parse text
|
||||
info_score_list = ResponseTextParser(response_text, self.language, self.__class__.__name__).parse_v1()
|
||||
if len(info_score_list) != len(info_messages):
|
||||
self.logger.warning(f"score_size != messages_size, {len(info_score_list)} vs {len(info_messages)}")
|
||||
|
||||
# filter messages
|
||||
filtered_messages: List[Message] = []
|
||||
for info_score in info_score_list:
|
||||
if not info_score:
|
||||
continue
|
||||
|
||||
if len(info_score) != 2:
|
||||
self.logger.warning(f"info_score={info_score} is invalid!")
|
||||
continue
|
||||
|
||||
idx, score = info_score
|
||||
|
||||
idx = int(idx) - 1
|
||||
if idx >= len(info_messages):
|
||||
self.logger.warning(f"idx={idx} is invalid! info_messages.size={len(info_messages)}")
|
||||
continue
|
||||
message = info_messages[idx]
|
||||
|
||||
if score in self.preserved_scores:
|
||||
message.meta_data["info_score"] = score
|
||||
filtered_messages.append(message)
|
||||
self.logger.info(f"info filter stage: keep {message.content}")
|
||||
|
||||
if not filtered_messages:
|
||||
self.logger.warning("filtered_messages is empty!")
|
||||
self.continue_run = False
|
||||
return
|
||||
|
||||
self.chat_messages_scatter = filtered_messages
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
info_filter_system:
|
||||
cn: |
|
||||
任务:对所给{batch_size}个句子中所含有的关于{user_name}的信息打分,分数为0,1,2或3。
|
||||
注意:其中0表示不包含用户信息,1表示句子中只包含用户假设的信息或者用户虚构的内容比如用户创作的小说或剧本,2表示包含用户的一般信息,时效性信息或者需要猜测才能得到的用户信息,3表示明确含有或者可以确定推断出关于用户的重要信息,或者用户要求记录。
|
||||
{user_name}的重要信息可以包含用户基本信息,用户画像信息,用户兴趣偏好信息,用户性格,用户价值观,用户人际关系,用户重大事件转折点等等重要信息。
|
||||
对每个句子都做一次信息打分,一共输出{batch_size}个分数,不需要写最终结果。
|
||||
请一定要按如下格式依次输出,最后的结果一定要加<>:
|
||||
思考:思考的依据和过程,30字以内。
|
||||
结果:<句子序号> <分数:0或1或2或3>
|
||||
|
||||
en: |
|
||||
Task: Score the information about {user_name} contained in the given batch of {batch_size} sentences, with scores of 0, 1, 2, or 3.
|
||||
Note:
|
||||
0 indicates no user information is included.
|
||||
1 indicates only hypothetical information about the user or fictitious content such as novels or scripts created by the user.
|
||||
2 indicates general information about the user, timely information, or information that requires inference.
|
||||
3 indicates clear and important information about the user, or information explicitly requested for recording.
|
||||
Important information about {user_name} can include basic information about the user, user profile information, user interests and preferences, user personality, user values, user relationships, significant life events, and other crucial information.
|
||||
Score each sentence individually and output a total of {batch_size} scores without providing the final result.
|
||||
Please ensure to output in the following format and wrap the final result in <>:
|
||||
Thought: Basis and reasoning process, within 30 characters.
|
||||
Result: <Sentence Index> <Score: 0, 1, 2, or 3>
|
||||
|
||||
|
||||
info_filter_few_shot:
|
||||
cn: |
|
||||
示例1
|
||||
句子:
|
||||
1 {user_name}:帮我写一段给同事张三女儿三岁生日的祝福语。
|
||||
2 {user_name}:公元1400年至1550年中国历史大事表。
|
||||
3 {user_name}:你吃午饭了吗?
|
||||
4 {user_name}:我今天心情不好,可以安慰我一下吗?
|
||||
5 {user_name}:能给我整理一张如何使用大模型的技巧列表吗,要求内容尽量精简。
|
||||
6 {user_name}:记一下,明天下午3点提醒我去拿一下文件。
|
||||
|
||||
思考:从第1句可以确定推断出张三是{user_name}同事这一重要信息。
|
||||
结果:<1> <3>
|
||||
思考:第2句不包含{user_name}信息。
|
||||
结果:<2> <0>
|
||||
思考:第3句不包含{user_name}信息。
|
||||
结果:<3> <0>
|
||||
思考:从第4句可以得知{user_name}今天心情不好,是时效性信息。
|
||||
结果:<4> <2>
|
||||
思考:从第5句可以猜测{user_name}对大模型感兴趣,是不确定的信息。
|
||||
结果:<5> <2>
|
||||
思考:第6句是{user_name}要求记录的信息。
|
||||
结果:<6> <3>
|
||||
|
||||
|
||||
示例2
|
||||
句子:
|
||||
1 {user_name}:我刚刚入职了阿里巴巴。
|
||||
2 {user_name}:露天睡觉蚊子多,咋搞。
|
||||
3 {user_name}:创造力和外倾性有关?
|
||||
4 {user_name}:一个区县的所有的事业人员的档案审核、修改和规范,应该是县委组织部下属的干部档案中心负责还是县人社局负责?
|
||||
5 {user_name}:假如我要和一个女人准备要孩子,我作为男人,怎么保护女人和孩子以及怎么备孕确保精子质量高对后代好
|
||||
6 {user_name}:我和你一起出去玩,你会感觉开心吗?
|
||||
7 {user_name}:林浅,一位对未来充满好奇的年轻女孩,偶然间发现了这家能寄信给未来的邮局。出于对逝去祖父的怀念,她决定写下一封信,寄给五年后的自己,希望能收到祖父生前未说完的故事。五年期限将至,当她几乎忘记这段往事时,一封泛黄的回信悄然降临,不仅带来了祖父未完的冒险故事,还藏着一段关于勇气、爱与自我发现的深刻启示。续写成3000字小说。
|
||||
|
||||
思考:从第1句可以确定得出{user_name}工作单位是阿里巴巴这一重要信息。
|
||||
结果:<1> <3>
|
||||
思考:从第2句可以猜测{user_name}近期露天睡觉,是不确定的信息。
|
||||
结果:<2> <2>
|
||||
思考:第3句不包含{user_name}信息。
|
||||
结果:<3> <0>
|
||||
思考:第4句不包含{user_name}信息。
|
||||
结果:<4> <0>
|
||||
思考:第5句虽然有假设成分,但可以确定推断出{user_name}是男性这一重要信息。
|
||||
结果:<5> <3>
|
||||
思考:第6句是{user_name}假设的信息。
|
||||
结果:<6> <1>
|
||||
思考:第7句是{user_name}虚构的内容。
|
||||
结果:<7> <1>
|
||||
|
||||
|
||||
示例3
|
||||
句子:
|
||||
1 {user_name}:你的妈妈患有焦虑症,怎么安慰和开导她?
|
||||
2 {user_name}:肾脏严重亏空
|
||||
3 {user_name}:我很喜欢打篮球,所以我身体很好
|
||||
4 {user_name}:篮球明星有哪些?
|
||||
5 {user_name}:李增杰:这个是星座蛙设,但是我是处女座的,我妈感觉因为我的不正常,我妈不让我看了\n雌猴摸了摸李增杰的头,这样啊\n雌猴打开了哔哩哔哩看了看\n雌猴:要不换个设吧,我听你未来的你说,有一个叫难忘的朱古力232这个人,他弄的设是Windows设\n这是剧本1,剧本2未完待续
|
||||
6 {user_name}:我想知道昨天我们聊了什么?
|
||||
|
||||
思考:第1句是{user_name}假设的信息。
|
||||
结果:<1> <1>
|
||||
思考:第2句信息不明,可能是{user_name}假设的信息。
|
||||
结果:<2> <1>
|
||||
思考:从第3句可以确定得出{user_name}喜欢打篮球,身体好这两个重要信息。
|
||||
结果:<3> <3>
|
||||
思考:第4句不包含{user_name}信息。
|
||||
结果:<4> <0>
|
||||
思考:第5句是{user_name}虚构的内容。
|
||||
结果:<5> <1>
|
||||
思考:第6句是{user_name}的疑问句,没有包含信息。
|
||||
结果:<6> <0>
|
||||
|
||||
en: |
|
||||
Example 1
|
||||
Sentences:
|
||||
1 {user_name}: Please help me write a birthday greeting for my colleague Jason's daughter who is turning three.
|
||||
2 {user_name}: Chronology of major events in Mediterranean history from 1400 to 1550 AD.
|
||||
3 {user_name}: Have you had lunch?
|
||||
4 {user_name}: I'm feeling down today. Can you comfort me a bit?
|
||||
5 {user_name}: Can you compile a list of tips on how to use large models for me, and try to keep the content concise?
|
||||
6 {user_name}: Note this down: remind me tomorrow at 3 PM to pick up the documents.
|
||||
|
||||
Thought: From the first sentence, it can be inferred that Zhang San is a colleague of {user_name}, which is important information.
|
||||
Result: <1> <3>
|
||||
Thought: The second sentence does not contain information about {user_name}.
|
||||
Result: <2> <0>
|
||||
Thought: The third sentence does not contain information about {user_name}.
|
||||
Result: <3> <0>
|
||||
Thought: The fourth sentence indicates that {user_name} is in a bad mood today, which is time-sensitive information.
|
||||
Result: <4> <2>
|
||||
Thought: The fifth sentence suggests that {user_name} may be interested in large models, which is uncertain information.
|
||||
Result: <5> <2>
|
||||
Thought: The sixth sentence contains information that {user_name} requested to be recorded.
|
||||
Result: <6> <3>
|
||||
|
||||
Example 2
|
||||
Sentences:
|
||||
1 {user_name}: I've just joined Google.
|
||||
2 {user_name}: There are too many mosquitoes when sleeping outdoors. What should I do?
|
||||
3 {user_name}: Is creativity related to extraversion?
|
||||
4 {user_name}: Should the review, modification, and standardization of all personnel files in a district or county be handled by the cadre archive center or by the county human resources and social security bureau?
|
||||
5 {user_name}: If I am planning to have a child with a woman, as a man, how can I protect the woman and the baby and how can I prepare to ensure high sperm quality for the benefit of the offspring?
|
||||
6 {user_name}: If we go out to play together, would you feel happy?
|
||||
7 {user_name}: Rose, a young girl full of curiosity about the future, accidentally discovered this post office that can send letters to the future. Out of nostalgia for her late grandfather, she decided to write a letter to herself five years in the future, hoping to receive the unfinished stories of her grandfather. As the five-year deadline approached, when she had almost forgotten about this event, a yellowed reply quietly arrived, bringing not only her grandfather's unfinished adventure story but also a profound revelation about courage, love, and self-discovery. Continue writing this into a 3000-word novel.
|
||||
|
||||
Thought: From the first sentence, it can be determined that {user_name} works at Alibaba, which is important information.
|
||||
Result: <1> <3>
|
||||
Thought: The second sentence suggests that {user_name} might has been sleeping outdoors recently, which is uncertain information.
|
||||
Result: <2> <2>
|
||||
Thought: The third sentence does not contain information about {user_name}.
|
||||
Result: <3> <0>
|
||||
Thought: The fourth sentence does not contain information about {user_name}.
|
||||
Result: <4> <0>
|
||||
Thought: Although the fifth sentence contains hypothetical elements, it can be determined that {user_name} is male, which is important information.
|
||||
Result: <5> <3>
|
||||
Thought: The sixth sentence contains only hypothetical information from {user_name}.
|
||||
Result: <6> <1>
|
||||
Thought: The seventh sentence contains only fictitious content from {user_name}.
|
||||
Result: <7> <1>
|
||||
|
||||
Example 3
|
||||
Sentences:
|
||||
1 {user_name}: Your mother is suffering from anxiety. How can you comfort and guide her?
|
||||
2 {user_name}: Severe kidney deficiency
|
||||
3 {user_name}: I really enjoy playing basketball, so I am in good health.
|
||||
4 {user_name}: Who are some famous basketball stars?
|
||||
5 {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.
|
||||
6 {user_name}: I want to know what we talked about yesterday.
|
||||
|
||||
Thought: The first sentence contains only hypothetical information from {user_name}.
|
||||
Result: <1> <1>
|
||||
Thought: The second sentence is unclear and may contain hypothetical information from {user_name}.
|
||||
Result: <2> <1>
|
||||
Thought: From the third sentence, it can be determined that {user_name} likes playing basketball and is in good health, which are two important pieces of information.
|
||||
Result: <3> <3>
|
||||
Thought: The fourth sentence does not contain information about {user_name}.
|
||||
Result: <4> <0>
|
||||
Thought: The fifth sentence contains only fictitious content from {user_name}.
|
||||
Result: <5> <1>
|
||||
Thought: Sentence 6 is a question from {user_name} that doesn't include any specific information.
|
||||
Result: <6> <0>
|
||||
|
||||
info_filter_user_query:
|
||||
cn: |
|
||||
句子:
|
||||
{user_query}
|
||||
|
||||
|
||||
en: |
|
||||
Sentences:
|
||||
{user_query}
|
||||
|
||||
165
reme_ai/summary/personal/load_memory_op.py
Normal file
165
reme_ai/summary/personal/load_memory_op.py
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
from typing import List
|
||||
|
||||
from flowllm import C, BaseLLMOp
|
||||
from loguru import logger
|
||||
|
||||
from reme_ai.schema.memory import PersonalMemory
|
||||
from reme_ai.utils.datetime_handler import DatetimeHandler
|
||||
from reme_ai.utils.op_utils import load_memories_from_vector_store
|
||||
|
||||
|
||||
@C.register_op()
|
||||
class LoadMemoryOp(BaseLLMOp):
|
||||
"""
|
||||
A specialized operation class to load various types of personal memories using BaseLLMOp.
|
||||
This loads different categories of memories including observations, insights, and recent memories.
|
||||
"""
|
||||
file_path: str = __file__
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
Executes the main routine of the LoadMemoryOp. This involves loading various types
|
||||
of personal memories based on the configuration parameters.
|
||||
"""
|
||||
# Get operation parameters
|
||||
retrieve_not_reflected_top_k: int = self.op_params.get("retrieve_not_reflected_top_k", 0)
|
||||
retrieve_not_updated_top_k: int = self.op_params.get("retrieve_not_updated_top_k", 0)
|
||||
retrieve_insight_top_k: int = self.op_params.get("retrieve_insight_top_k", 0)
|
||||
retrieve_today_top_k: int = self.op_params.get("retrieve_today_top_k", 0)
|
||||
|
||||
# Get context parameters
|
||||
workspace_id = self.context.get("workspace_id", "")
|
||||
user_name = self.context.get("user_name", "user")
|
||||
|
||||
logger.info(f"Loading memories for user: {user_name} in workspace: {workspace_id}")
|
||||
|
||||
# Load different types of memories
|
||||
all_memories = []
|
||||
|
||||
# Load not reflected memories
|
||||
if retrieve_not_reflected_top_k > 0:
|
||||
not_reflected_memories = self._retrieve_not_reflected_memories(
|
||||
workspace_id, user_name, retrieve_not_reflected_top_k
|
||||
)
|
||||
all_memories.extend(not_reflected_memories)
|
||||
logger.info(f"Loaded {len(not_reflected_memories)} not reflected memories")
|
||||
|
||||
# Load not updated memories
|
||||
if retrieve_not_updated_top_k > 0:
|
||||
not_updated_memories = self._retrieve_not_updated_memories(
|
||||
workspace_id, user_name, retrieve_not_updated_top_k
|
||||
)
|
||||
all_memories.extend(not_updated_memories)
|
||||
logger.info(f"Loaded {len(not_updated_memories)} not updated memories")
|
||||
|
||||
# Load insight memories
|
||||
if retrieve_insight_top_k > 0:
|
||||
insight_memories = self._retrieve_insight_memories(
|
||||
workspace_id, user_name, retrieve_insight_top_k
|
||||
)
|
||||
all_memories.extend(insight_memories)
|
||||
logger.info(f"Loaded {len(insight_memories)} insight memories")
|
||||
|
||||
# Load today's memories
|
||||
if retrieve_today_top_k > 0:
|
||||
today_memories = self._retrieve_today_memories(
|
||||
workspace_id, user_name, retrieve_today_top_k
|
||||
)
|
||||
all_memories.extend(today_memories)
|
||||
logger.info(f"Loaded {len(today_memories)} today's memories")
|
||||
|
||||
# Store results in context
|
||||
self.context.response.metadata["loaded_memories"] = all_memories
|
||||
self.context.response.metadata["not_reflected_memories"] = [
|
||||
m for m in all_memories if m.metadata.get("memory_category") == "not_reflected"
|
||||
]
|
||||
self.context.response.metadata["not_updated_memories"] = [
|
||||
m for m in all_memories if m.metadata.get("memory_category") == "not_updated"
|
||||
]
|
||||
self.context.response.metadata["insight_memories"] = [
|
||||
m for m in all_memories if m.metadata.get("memory_category") == "insight"
|
||||
]
|
||||
self.context.response.metadata["today_memories"] = [
|
||||
m for m in all_memories if m.metadata.get("memory_category") == "today"
|
||||
]
|
||||
|
||||
logger.info(f"Total memories loaded: {len(all_memories)}")
|
||||
|
||||
def _retrieve_not_reflected_memories(self, workspace_id: str, user_name: str, top_k: int) -> List[PersonalMemory]:
|
||||
"""
|
||||
Retrieves top-K not reflected memories based on the query.
|
||||
"""
|
||||
filter_criteria = {
|
||||
"memory_type": "personal",
|
||||
"target": user_name,
|
||||
"reflected": False
|
||||
}
|
||||
|
||||
memories = load_memories_from_vector_store(
|
||||
workspace_id=workspace_id,
|
||||
filter_criteria=filter_criteria,
|
||||
top_k=top_k,
|
||||
memory_category="not_reflected"
|
||||
)
|
||||
|
||||
return memories
|
||||
|
||||
def _retrieve_not_updated_memories(self, workspace_id: str, user_name: str, top_k: int) -> List[PersonalMemory]:
|
||||
"""
|
||||
Retrieves top-K not updated memories based on the query.
|
||||
"""
|
||||
filter_criteria = {
|
||||
"memory_type": "personal",
|
||||
"target": user_name,
|
||||
"updated": False
|
||||
}
|
||||
|
||||
memories = load_memories_from_vector_store(
|
||||
workspace_id=workspace_id,
|
||||
filter_criteria=filter_criteria,
|
||||
top_k=top_k,
|
||||
memory_category="not_updated"
|
||||
)
|
||||
|
||||
return memories
|
||||
|
||||
def _retrieve_insight_memories(self, workspace_id: str, user_name: str, top_k: int) -> List[PersonalMemory]:
|
||||
"""
|
||||
Retrieves top-K insight memories based on the query.
|
||||
"""
|
||||
filter_criteria = {
|
||||
"memory_type": "personal_insight",
|
||||
"target": user_name
|
||||
}
|
||||
|
||||
memories = load_memories_from_vector_store(
|
||||
workspace_id=workspace_id,
|
||||
filter_criteria=filter_criteria,
|
||||
top_k=top_k,
|
||||
memory_category="insight"
|
||||
)
|
||||
|
||||
return memories
|
||||
|
||||
def _retrieve_today_memories(self, workspace_id: str, user_name: str, top_k: int) -> List[PersonalMemory]:
|
||||
"""
|
||||
Retrieves top-K memories from today based on the query.
|
||||
"""
|
||||
# Get today's date
|
||||
dt = DatetimeHandler().datetime_format()
|
||||
today_date = dt.split()[0] # Extract date part
|
||||
|
||||
filter_criteria = {
|
||||
"memory_type": "personal",
|
||||
"target": user_name,
|
||||
"created_date": today_date
|
||||
}
|
||||
|
||||
memories = load_memories_from_vector_store(
|
||||
workspace_id=workspace_id,
|
||||
filter_criteria=filter_criteria,
|
||||
top_k=top_k,
|
||||
memory_category="today"
|
||||
)
|
||||
|
||||
return memories
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from memoryscope.constants.common_constants import NOT_REFLECTED_NODES, NOT_UPDATED_NODES, INSIGHT_NODES, TODAY_NODES
|
||||
from memoryscope.core.utils.datetime_handler import DatetimeHandler
|
||||
from memoryscope.core.utils.timer import timer
|
||||
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 LoadMemoryWorker(MemoryBaseWorker):
|
||||
def _parse_params(self, **kwargs):
|
||||
self.retrieve_not_reflected_top_k: int = kwargs.get("retrieve_not_reflected_top_k", 0)
|
||||
self.retrieve_not_updated_top_k: int = kwargs.get("retrieve_not_updated_top_k", 0)
|
||||
self.retrieve_insight_top_k: int = kwargs.get("retrieve_insight_top_k", 0)
|
||||
self.retrieve_today_top_k: int = kwargs.get("retrieve_today_top_k", 0)
|
||||
|
||||
@timer
|
||||
def retrieve_not_reflected_memory(self):
|
||||
"""
|
||||
Retrieves top-K not reflected memories based on the query and stores them in the memory handler.
|
||||
"""
|
||||
if not self.retrieve_not_reflected_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],
|
||||
"obs_reflected": 0,
|
||||
}
|
||||
nodes: List[MemoryNode] = self.memory_store.retrieve_memories(top_k=self.retrieve_not_reflected_top_k,
|
||||
filter_dict=filter_dict)
|
||||
self.memory_manager.set_memories(NOT_REFLECTED_NODES, nodes)
|
||||
|
||||
@timer
|
||||
def retrieve_not_updated_memory(self):
|
||||
"""
|
||||
Retrieves top-K not updated memories based on the query and stores them in the memory handler.
|
||||
"""
|
||||
if not self.retrieve_not_updated_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],
|
||||
"obs_updated": 0,
|
||||
}
|
||||
nodes: List[MemoryNode] = self.memory_store.retrieve_memories(top_k=self.retrieve_not_updated_top_k,
|
||||
filter_dict=filter_dict)
|
||||
self.memory_manager.set_memories(NOT_UPDATED_NODES, nodes)
|
||||
|
||||
@timer
|
||||
def retrieve_insight_memory(self):
|
||||
"""
|
||||
Retrieves top-K insight memories based on the query and stores them in the memory handler.
|
||||
"""
|
||||
if not self.retrieve_insight_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,
|
||||
}
|
||||
nodes: List[MemoryNode] = self.memory_store.retrieve_memories(top_k=self.retrieve_insight_top_k,
|
||||
filter_dict=filter_dict)
|
||||
self.memory_manager.set_memories(INSIGHT_NODES, nodes)
|
||||
|
||||
@timer
|
||||
def retrieve_today_memory(self, dt: str):
|
||||
"""
|
||||
Retrieves top-K memories from today based on the query and stores them in the memory handler.
|
||||
|
||||
Args:
|
||||
dt (str): The date string to filter today's memories.
|
||||
"""
|
||||
if not self.retrieve_today_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],
|
||||
"dt": dt,
|
||||
}
|
||||
nodes: List[MemoryNode] = self.memory_store.retrieve_memories(top_k=self.retrieve_today_top_k,
|
||||
filter_dict=filter_dict)
|
||||
|
||||
self.memory_manager.set_memories(TODAY_NODES, nodes)
|
||||
|
||||
def _run(self):
|
||||
"""
|
||||
Initiates multithread tasks to retrieve various types of memory data including
|
||||
not reflected, not updated, insights, and data from today. After submitting all tasks,
|
||||
it waits for their completion by calling `gather_thread_result`.
|
||||
|
||||
This method serves as the controller for data retrieval operations, enhancing efficiency
|
||||
by handling tasks concurrently.
|
||||
"""
|
||||
|
||||
# Placeholder query
|
||||
dt = DatetimeHandler().datetime_format()
|
||||
self.submit_thread_task(self.retrieve_not_reflected_memory)
|
||||
self.submit_thread_task(self.retrieve_not_updated_memory)
|
||||
self.submit_thread_task(self.retrieve_insight_memory)
|
||||
self.submit_thread_task(self.retrieve_today_memory, dt=dt)
|
||||
|
||||
# Waits for all submitted tasks to complete
|
||||
for _ in self.gather_thread_result():
|
||||
pass
|
||||
157
reme_ai/summary/personal/long_contra_repeat_op.py
Normal file
157
reme_ai/summary/personal/long_contra_repeat_op.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
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, PersonalMemory
|
||||
from reme_ai.utils.op_utils import parse_long_contra_repeat_response
|
||||
|
||||
|
||||
@C.register_op()
|
||||
class LongContraRepeatOp(BaseLLMOp):
|
||||
"""
|
||||
Manages and updates memory entries within a conversation scope by identifying
|
||||
and handling contradictions or redundancies. It extends BaseLLMOp to provide
|
||||
specialized functionality for long conversations with potential contradictory
|
||||
or repetitive statements.
|
||||
"""
|
||||
file_path: str = __file__
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
Executes the primary routine of the LongContraRepeatOp which involves:
|
||||
1. Gets memory list from context
|
||||
2. Retrieves similar memories for each memory
|
||||
3. Constructs a prompt with these memories for language model analysis
|
||||
4. Parses the model's response to detect contradictions or redundancies
|
||||
5. 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
|
||||
long_contra_repeat_max_count: int = self.op_params.get("long_contra_repeat_max_count", 50)
|
||||
enable_long_contra_repeat: bool = self.op_params.get("enable_long_contra_repeat", True)
|
||||
|
||||
if not enable_long_contra_repeat:
|
||||
logger.warning("long_contra_repeat is not enabled!")
|
||||
return
|
||||
|
||||
# Sort and limit memories by count
|
||||
sorted_memories = sorted(memory_list, key=lambda x: getattr(x, 'created_time', ''), reverse=True)[
|
||||
:long_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="long_contra_repeat_system",
|
||||
num_obs=len(user_query_list),
|
||||
user_name=user_name)
|
||||
few_shot = self.prompt_format(prompt_name="long_contra_repeat_few_shot", user_name=user_name)
|
||||
user_query = self.prompt_format(prompt_name="long_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"long_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"long_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"""
|
||||
|
||||
# Use utility function to parse the response
|
||||
judgments = parse_long_contra_repeat_response(response_text)
|
||||
|
||||
if not judgments:
|
||||
logger.warning("No valid judgments found in response")
|
||||
return memories
|
||||
|
||||
# Process each judgment
|
||||
filtered_memories = []
|
||||
processed_indices = set()
|
||||
|
||||
for idx, judgment, modified_content in judgments:
|
||||
try:
|
||||
memory_idx = idx - 1 # Convert to 0-based index
|
||||
if memory_idx >= len(memories):
|
||||
logger.warning(f"Invalid index {memory_idx} for memories list of length {len(memories)}")
|
||||
continue
|
||||
|
||||
processed_indices.add(memory_idx)
|
||||
memory = memories[memory_idx]
|
||||
judgment_lower = judgment.lower()
|
||||
|
||||
if judgment_lower in ['矛盾', 'contradiction']:
|
||||
# For contradictory memories, either modify content or mark for removal
|
||||
if modified_content.strip():
|
||||
# Create new memory with modified content
|
||||
modified_memory = PersonalMemory(
|
||||
workspace_id=memory.workspace_id,
|
||||
memory_id=memory.memory_id,
|
||||
content=modified_content.strip(),
|
||||
target=memory.target if hasattr(memory, 'target') else user_name,
|
||||
author=memory.author,
|
||||
metadata={**memory.metadata, 'modified_by': 'long_contra_repeat'}
|
||||
)
|
||||
modified_memory.update_modified_time()
|
||||
filtered_memories.append(modified_memory)
|
||||
logger.info(f"Modified contradictory memory {idx}: {modified_content.strip()[:50]}...")
|
||||
else:
|
||||
# Remove contradictory memory without modification
|
||||
logger.info(f"Removing contradictory memory {idx}: {memory.content[:50]}...")
|
||||
|
||||
elif judgment_lower in ['被包含', 'contained']:
|
||||
# Remove contained/redundant memories
|
||||
logger.info(f"Removing contained memory {idx}: {memory.content[:50]}...")
|
||||
|
||||
else: # 'none' case
|
||||
# Keep the memory as is
|
||||
filtered_memories.append(memory)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error processing judgment for index {idx}: {e}")
|
||||
continue
|
||||
|
||||
# Add any memories that weren't processed (shouldn't happen with correct LLM response)
|
||||
for i, memory in enumerate(memories):
|
||||
if i not in processed_indices:
|
||||
filtered_memories.append(memory)
|
||||
logger.warning(f"Memory {i + 1} was not processed by LLM, keeping as is")
|
||||
|
||||
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"))
|
||||
120
reme_ai/summary/personal/long_contra_repeat_prompt.yaml
Normal file
120
reme_ai/summary/personal/long_contra_repeat_prompt.yaml
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
long_contra_repeat_system_zh: |
|
||||
对下面的{num_obs}句句子,逐一判断是否与“前面序号”的任意句子存在信息的矛盾,或者句子的主要信息被“前面序号”的任意句子中的信息包含。
|
||||
注意:只判断与“前面序号”的句子的关系,不要判断“后面序号”。
|
||||
其中矛盾的形式可以有很多种,可以是逻辑上的矛盾,可以是属性上的变化导致的矛盾,比如不能同时在两个地方工作,同一个时刻不能在两个地点,同一个时刻不能干两件事情等等。
|
||||
对每个句子都做一个判断,最后一共输出{num_obs}条判断。如果句子与前面序号的句子存在矛盾,则以前面序号的句子中的信息为准,修改句子中矛盾的部分。
|
||||
请一步步思考,并按如下格式输出:
|
||||
思考:思考的依据和过程,30字以内。
|
||||
判断:<句子序号> <矛盾,被包含,无> <修改后的内容>,一定加<>
|
||||
|
||||
long_contra_repeat_system: |
|
||||
For the following {num_obs} sentences, determine one by one whether there is any information contradiction with any sentences preceding their sequence number, or if the main information of the sentence is contained within information from any preceding sentences.
|
||||
Note: Only judge the relationship with the sentences of the preceding sequence number, do not judge the ones after.
|
||||
The forms of contradiction could be many, including logical contradictions or contradictions caused by changes in attributes, such as not being able to work in two places simultaneously, not being able to be in two places at the same time, or 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, following this format:
|
||||
Thought: The basis and process of thinking, within 30 characters.
|
||||
Judgment: <Sentence Number> <Contradiction, Contained, None> <Modified content in case of contradiction>, using <> for each part.
|
||||
|
||||
long_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> <矛盾> <{user_name}的父亲生日在2024年6月2日>
|
||||
思考:第5句与前面序号句子既不矛盾也不重复。
|
||||
判断:<5> <无> <>
|
||||
思考:第6句中所有信息都被前面序号中第5句的信息完全包含。
|
||||
判断:<2> <被包含> <>
|
||||
|
||||
long_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} likes to eat watermelon.
|
||||
7. {user_name} likes to eat apples.
|
||||
|
||||
Thought: The first sentence does not have any contradictions or complete repetitions with the previously numbered sentences.
|
||||
Judgment: <1> <None> <>
|
||||
Thought: All information in the second sentence is completely contained within the information of the first sentence.
|
||||
Judgment: <2> <Contained> <>
|
||||
Thought: The information in the third sentence does not appear in the previously numbered sentences.
|
||||
Judgment: <3> <None> <>
|
||||
Thought: The fourth sentence is completely repetitive of the information in the third sentence, i.e., it is completely contained.
|
||||
Judgment: <4> <Contained> <>
|
||||
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> <None> <>
|
||||
Thought: Sentence 6 expresses {user_name}'s fruit preference, liking to eat watermelon, which is information not present in any preceding sentences.
|
||||
Judgment: <6> <None> <>
|
||||
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> <None> <>
|
||||
|
||||
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> <None> <>
|
||||
Thought: The second sentence neither contradicts nor repeats any of the previously numbered sentences.
|
||||
Judgment: <2> <None> <>
|
||||
Thought: The third sentence neither contradicts nor repeats any of the previously numbered sentences.
|
||||
Judgment: <3> <None> <>
|
||||
Thought: The date of {user_name}'s father's birthday in the fourth sentence contradicts the information in the third sentence.
|
||||
Judgment: <4> <Contradiction> <{user_name}'s father's birthday is on June 2, 2024.>
|
||||
Thought: The fifth sentence neither contradicts nor repeats any of the previously numbered sentences.
|
||||
Judgment: <5> <None> <>
|
||||
Thought: All information in the sixth sentence is completely contained within the information of the fifth sentence.
|
||||
Judgment: <6> <Contained> <>
|
||||
|
||||
long_contra_repeat_user_query_zh: |
|
||||
句子:
|
||||
{user_query}
|
||||
|
||||
long_contra_repeat_user_query: |
|
||||
Sentences:
|
||||
{user_query}
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
from typing import List, Dict
|
||||
|
||||
from memoryscope.constants.common_constants import NOT_UPDATED_NODES, MERGE_OBS_NODES
|
||||
from memoryscope.constants.language_constants import NONE_WORD, CONTAINED_WORD, CONTRADICTORY_WORD
|
||||
from memoryscope.core.utils.response_text_parser import ResponseTextParser
|
||||
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 LongContraRepeatWorker(MemoryBaseWorker):
|
||||
"""
|
||||
Manages and updates memory entries within a conversation scope by identifying
|
||||
and handling contradictions or redundancies. It extends the base MemoryBaseWorker
|
||||
to provide specialized functionality for long conversations with potential
|
||||
contradictory or repetitive statements.
|
||||
"""
|
||||
FILE_PATH: str = __file__
|
||||
|
||||
def _parse_params(self, **kwargs):
|
||||
self.unit_test_flag = False
|
||||
self.long_contra_repeat_top_k: int = kwargs.get("long_contra_repeat_top_k", 2)
|
||||
self.long_contra_repeat_threshold: float = kwargs.get("long_contra_repeat_threshold", 0.1)
|
||||
self.generation_model_kwargs: dict = kwargs.get("generation_model_kwargs", {})
|
||||
self.enable_long_contra_repeat: bool = self.memoryscope_context.meta_data["enable_long_contra_repeat"]
|
||||
|
||||
def retrieve_similar_content(self, node: MemoryNode) -> (MemoryNode, List[MemoryNode]):
|
||||
"""
|
||||
Retrieves memory nodes with content similar to the given node, filtering by user/target/status/memory_type.
|
||||
Only returns nodes whose similarity score meets or exceeds the predefined threshold.
|
||||
|
||||
Args:
|
||||
node (MemoryNode): The reference node used to find similar content in memory.
|
||||
|
||||
Returns:
|
||||
Tuple[MemoryNode, List[MemoryNode]]: A tuple containing the original node and a list of similar nodes
|
||||
that passed the similarity threshold.
|
||||
"""
|
||||
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 similar to the node's content, limited by top_k and filtered by filter_dict
|
||||
retrieve_nodes = self.memory_store.retrieve_memories(query=node.content,
|
||||
top_k=self.long_contra_repeat_top_k,
|
||||
filter_dict=filter_dict)
|
||||
# Filter retrieved nodes based on the similarity threshold
|
||||
return node, [n for n in retrieve_nodes if n.score_recall >= self.long_contra_repeat_threshold]
|
||||
|
||||
def _run(self):
|
||||
"""
|
||||
Executes the primary routine of the LongContraRepeatWorker. This involves:
|
||||
1. Retrieve not updated memory nodes.
|
||||
2. Gather similar content for these nodes.
|
||||
3. Organize observed nodes and generating a prompt for the language model.
|
||||
4. Call the language model to judge the contradictions or redundancies in retrieved memories.
|
||||
5. Parse the model's response to update memory node statuses.
|
||||
6. Save the modified memory nodes.
|
||||
|
||||
The process helps in maintaining conversation coherence by resolving contradictions and redundancies.
|
||||
"""
|
||||
if not self.enable_long_contra_repeat:
|
||||
self.logger.warning("long_contra_repeat is not enabled!")
|
||||
return
|
||||
|
||||
not_updated_nodes: List[MemoryNode] = self.memory_manager.get_memories(NOT_UPDATED_NODES)
|
||||
for node in not_updated_nodes:
|
||||
self.submit_thread_task(fn=self.retrieve_similar_content, node=node)
|
||||
|
||||
if self.unit_test_flag:
|
||||
all_obs_nodes: List[MemoryNode] = not_updated_nodes
|
||||
else:
|
||||
obs_node_dict: Dict[str, MemoryNode] = {}
|
||||
for origin_node, retrieve_nodes in self.gather_thread_result():
|
||||
if not retrieve_nodes:
|
||||
continue
|
||||
obs_node_dict[origin_node.memory_id] = origin_node
|
||||
for node in retrieve_nodes:
|
||||
if node.memory_id in obs_node_dict:
|
||||
continue
|
||||
obs_node_dict[node.memory_id] = node
|
||||
all_obs_nodes: List[MemoryNode] = sorted(obs_node_dict.values(), key=lambda x: x.timestamp, reverse=True)
|
||||
|
||||
if not all_obs_nodes:
|
||||
self.logger.warning("all_obs_nodes is empty, stop.")
|
||||
return
|
||||
|
||||
if len(all_obs_nodes) == 1:
|
||||
self.logger.info("all_obs_nodes.size=1, stop.")
|
||||
return
|
||||
|
||||
# gene 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.long_contra_repeat_system.format(num_obs=len(user_query_list),
|
||||
user_name=self.target_name)
|
||||
few_shot = self.prompt_handler.long_contra_repeat_few_shot.format(user_name=self.target_name)
|
||||
user_query = self.prompt_handler.long_contra_repeat_user_query.format(user_query="\n".join(user_query_list))
|
||||
|
||||
long_contra_repeat_message = self.prompt_to_msg(system_prompt=system_prompt,
|
||||
few_shot=few_shot,
|
||||
user_query=user_query)
|
||||
self.logger.info(f"long_contra_repeat_message={long_contra_repeat_message}")
|
||||
|
||||
# Invokes the language model for processing the constructed prompt
|
||||
response = self.generation_model.call(messages=long_contra_repeat_message, **self.generation_model_kwargs)
|
||||
|
||||
# Handles the case where the model's response is empty
|
||||
if not response or not response.message.content:
|
||||
return
|
||||
|
||||
# Parses the model's response text to identify updates for memory nodes
|
||||
idx_obs_info_list = ResponseTextParser(response.message.content, self.language,
|
||||
self.__class__.__name__).parse_v1()
|
||||
if len(idx_obs_info_list) <= 0:
|
||||
self.logger.warning("idx_obs_info_list is empty!")
|
||||
return
|
||||
|
||||
# Processes parsed information to update memory nodes' statuses
|
||||
merge_obs_nodes: List[MemoryNode] = []
|
||||
for idx_obs_info in idx_obs_info_list:
|
||||
if not idx_obs_info:
|
||||
continue
|
||||
|
||||
if len(idx_obs_info) != 3:
|
||||
self.logger.warning(f"idx_obs_info={idx_obs_info} is invalid!")
|
||||
continue
|
||||
idx, status, content = idx_obs_info
|
||||
|
||||
if not idx.isdigit():
|
||||
self.logger.warning(f"idx={idx} is invalid!")
|
||||
continue
|
||||
|
||||
idx = int(idx) - 1
|
||||
if idx >= len(all_obs_nodes):
|
||||
self.logger.warning(f"idx={idx} is invalid!")
|
||||
continue
|
||||
|
||||
status = status.lower()
|
||||
if status not in self.get_language_value([CONTRADICTORY_WORD, CONTAINED_WORD, NONE_WORD]):
|
||||
self.logger.warning(f"status={status} is invalid!")
|
||||
continue
|
||||
|
||||
node: MemoryNode = all_obs_nodes[idx]
|
||||
if status == self.get_language_value(CONTRADICTORY_WORD):
|
||||
if not content:
|
||||
node.store_status = StoreStatusEnum.EXPIRED.value
|
||||
else:
|
||||
node.content = content
|
||||
node.action_status = ActionStatusEnum.CONTENT_MODIFIED.value
|
||||
|
||||
elif status == self.get_language_value(CONTAINED_WORD):
|
||||
node.store_status = StoreStatusEnum.EXPIRED.value
|
||||
|
||||
merge_obs_nodes.append(node)
|
||||
self.logger.info(f"after_long_contra_repeat: {node.content} store_status={node.store_status} "
|
||||
f"action_status={node.action_status}")
|
||||
|
||||
# save context
|
||||
self.memory_manager.set_memories(MERGE_OBS_NODES, merge_obs_nodes)
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
long_contra_repeat_system:
|
||||
cn: |
|
||||
对下面的{num_obs}句句子,逐一判断是否与“前面序号”的任意句子存在信息的矛盾,或者句子的主要信息被“前面序号”的任意句子中的信息包含。
|
||||
注意:只判断与“前面序号”的句子的关系,不要判断“后面序号”。
|
||||
其中矛盾的形式可以有很多种,可以是逻辑上的矛盾,可以是属性上的变化导致的矛盾,比如不能同时在两个地方工作,同一个时刻不能在两个地点,同一个时刻不能干两件事情等等。
|
||||
对每个句子都做一个判断,最后一共输出{num_obs}条判断。如果句子与前面序号的句子存在矛盾,则以前面序号的句子中的信息为准,修改句子中矛盾的部分。
|
||||
请一步步思考,并按如下格式输出:
|
||||
思考:思考的依据和过程,30字以内。
|
||||
判断:<句子序号> <矛盾,被包含,无> <修改后的内容>,一定加<>
|
||||
|
||||
en: |
|
||||
For the following {num_obs} sentences, determine one by one whether there is any information contradiction with any sentences preceding their sequence number, or if the main information of the sentence is contained within information from any preceding sentences.
|
||||
Note: Only judge the relationship with the sentences of the preceding sequence number, do not judge the ones after.
|
||||
The forms of contradiction could be many, including logical contradictions or contradictions caused by changes in attributes, such as not being able to work in two places simultaneously, not being able to be in two places at the same time, or 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, following this format:
|
||||
Thought: The basis and process of thinking, within 30 characters.
|
||||
Judgment: <Sentence Number> <Contradiction, Contained, None> <Modified content in case of contradiction>, using <> for each part.
|
||||
|
||||
long_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> <矛盾> <{user_name}的父亲生日在2024年6月2日>
|
||||
思考:第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} likes to eat watermelon.
|
||||
7. {user_name} likes to eat apples.
|
||||
|
||||
Thought: The first sentence does not have any contradictions or complete repetitions with the previously numbered sentences.
|
||||
Judgment: <1> <None> <>
|
||||
Thought: All information in the second sentence is completely contained within the information of the first sentence.
|
||||
Judgment: <2> <Contained> <>
|
||||
Thought: The information in the third sentence does not appear in the previously numbered sentences.
|
||||
Judgment: <3> <None> <>
|
||||
Thought: The fourth sentence is completely repetitive of the information in the third sentence, i.e., it is completely contained.
|
||||
Judgment: <4> <Contained> <>
|
||||
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> <None> <>
|
||||
Thought: Sentence 6 expresses {user_name}'s fruit preference, liking to eat watermelon, which is information not present in any preceding sentences.
|
||||
Judgment: <6> <None> <>
|
||||
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> <None> <>
|
||||
|
||||
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> <None> <>
|
||||
Thought: The second sentence neither contradicts nor repeats any of the previously numbered sentences.
|
||||
Judgment: <2> <None> <>
|
||||
Thought: The third sentence neither contradicts nor repeats any of the previously numbered sentences.
|
||||
Judgment: <3> <None> <>
|
||||
Thought: The date of {user_name}'s father's birthday in the fourth sentence contradicts the information in the third sentence.
|
||||
Judgment: <4> <Contradiction> <{user_name}'s father's birthday is on June 2, 2024.>
|
||||
Thought: The fifth sentence neither contradicts nor repeats any of the previously numbered sentences.
|
||||
Judgment: <5> <None> <>
|
||||
Thought: All information in the sixth sentence is completely contained within the information of the fifth sentence.
|
||||
Judgment: <6> <Contained> <>
|
||||
|
||||
long_contra_repeat_user_query:
|
||||
cn: |
|
||||
句子:
|
||||
{user_query}
|
||||
en: |
|
||||
Sentences:
|
||||
{user_query}
|
||||
189
reme_ai/summary/personal/update_insight_op.py
Normal file
189
reme_ai/summary/personal/update_insight_op.py
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
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 PersonalMemory
|
||||
from reme_ai.utils.op_utils import parse_update_insight_response
|
||||
|
||||
|
||||
@C.register_op()
|
||||
class UpdateInsightOp(BaseLLMOp):
|
||||
"""
|
||||
This class is responsible for updating insight value in a memory system. It filters insight nodes
|
||||
based on their association with observed nodes, utilizes a ranking model to prioritize them,
|
||||
generates refreshed insights via an LLM, and manages node statuses and content updates.
|
||||
"""
|
||||
file_path: str = __file__
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
Executes the main routine of the UpdateInsightOp. This involves filtering and updating insight nodes
|
||||
based on their association with observed nodes.
|
||||
"""
|
||||
# Get insight memories from context
|
||||
insight_memories: List[PersonalMemory] = self.context.response.metadata.get("insight_memories", [])
|
||||
observation_memories: List[PersonalMemory] = self.context.response.metadata.get("observation_memories", [])
|
||||
|
||||
if not insight_memories:
|
||||
logger.warning("insight_memories is empty, stopping processing.")
|
||||
return
|
||||
|
||||
if not observation_memories:
|
||||
logger.warning("observation_memories is empty, stopping processing.")
|
||||
return
|
||||
|
||||
# Get operation parameters
|
||||
update_insight_threshold: float = self.op_params.get("update_insight_threshold", 0.1)
|
||||
update_insight_max_count: int = self.op_params.get("update_insight_max_count", 5)
|
||||
user_name = self.context.get("user_name", "user")
|
||||
|
||||
logger.info(
|
||||
f"Processing {len(insight_memories)} insight memories with {len(observation_memories)} observations")
|
||||
|
||||
# Filter and score insight memories based on relevance to observations
|
||||
scored_insights = self._filter_and_score_insights(insight_memories, observation_memories,
|
||||
update_insight_threshold, user_name)
|
||||
|
||||
if not scored_insights:
|
||||
logger.warning("No relevant insights found after filtering")
|
||||
return
|
||||
|
||||
# Select top insights to update
|
||||
top_insights = sorted(scored_insights, key=lambda x: x[1], reverse=True)[:update_insight_max_count]
|
||||
logger.info(f"Selected {len(top_insights)} insights for updating")
|
||||
|
||||
# Update each selected insight
|
||||
updated_insights = []
|
||||
for insight_memory, score, relevant_observations in top_insights:
|
||||
updated_insight = self._update_single_insight(insight_memory, relevant_observations, user_name)
|
||||
if updated_insight:
|
||||
updated_insights.append(updated_insight)
|
||||
|
||||
# Store updated insights in context
|
||||
self.context.response.metadata["updated_insight_memories"] = updated_insights
|
||||
logger.info(f"Successfully updated {len(updated_insights)} insight memories")
|
||||
|
||||
def _filter_and_score_insights(self, insight_memories: List[PersonalMemory],
|
||||
observation_memories: List[PersonalMemory],
|
||||
threshold: float, user_name: str) -> List[tuple]:
|
||||
"""
|
||||
Filter and score insight memories based on their relevance to observation memories.
|
||||
|
||||
Returns:
|
||||
List[tuple]: List of (insight_memory, max_score, relevant_observations)
|
||||
"""
|
||||
scored_insights = []
|
||||
|
||||
for insight_memory in insight_memories:
|
||||
# For each insight, find observations that are relevant to the same subject
|
||||
relevant_observations = []
|
||||
max_score = 0.0
|
||||
|
||||
insight_subject = insight_memory.reflection_subject or ""
|
||||
insight_keywords = set(insight_memory.content.lower().split())
|
||||
|
||||
for obs_memory in observation_memories:
|
||||
score = 0.0
|
||||
|
||||
# If both have the same reflection subject, they're highly relevant
|
||||
if (insight_subject and
|
||||
hasattr(obs_memory, 'reflection_subject') and
|
||||
obs_memory.reflection_subject == insight_subject):
|
||||
score = 0.8
|
||||
else:
|
||||
# Otherwise, use keyword-based similarity
|
||||
obs_keywords = set(obs_memory.content.lower().split())
|
||||
intersection = len(insight_keywords.intersection(obs_keywords))
|
||||
union = len(insight_keywords.union(obs_keywords))
|
||||
score = intersection / union if union > 0 else 0.0
|
||||
|
||||
if score >= threshold:
|
||||
relevant_observations.append(obs_memory)
|
||||
max_score = max(max_score, score)
|
||||
|
||||
if relevant_observations:
|
||||
scored_insights.append((insight_memory, max_score, relevant_observations))
|
||||
logger.info(
|
||||
f"Insight '{insight_memory.content[:50]}...' (subject: {insight_subject}) scored {max_score:.3f} with {len(relevant_observations)} relevant observations")
|
||||
|
||||
return scored_insights
|
||||
|
||||
def _update_single_insight(self, insight_memory: PersonalMemory,
|
||||
relevant_observations: List[PersonalMemory],
|
||||
user_name: str) -> PersonalMemory:
|
||||
"""
|
||||
Update a single insight memory based on relevant observations using LLM.
|
||||
|
||||
Args:
|
||||
insight_memory: The insight memory to update
|
||||
relevant_observations: List of relevant observation memories
|
||||
user_name: The target user name
|
||||
|
||||
Returns:
|
||||
PersonalMemory: Updated insight memory or None if update failed
|
||||
"""
|
||||
logger.info(
|
||||
f"Updating insight: {insight_memory.content[:50]}... with {len(relevant_observations)} observations")
|
||||
|
||||
# Build observation context
|
||||
observation_texts = [obs.content for obs in relevant_observations]
|
||||
|
||||
# Create prompt using the prompt format method
|
||||
insight_key = insight_memory.reflection_subject or "personal_info"
|
||||
insight_key_value = f"{insight_key}: {insight_memory.content}"
|
||||
|
||||
system_prompt = self.prompt_format(prompt_name="update_insight_system", user_name=user_name)
|
||||
few_shot = self.prompt_format(prompt_name="update_insight_few_shot", user_name=user_name)
|
||||
user_query = self.prompt_format(prompt_name="update_insight_user_query",
|
||||
user_query="\n".join(observation_texts),
|
||||
insight_key=insight_key,
|
||||
insight_key_value=insight_key_value)
|
||||
|
||||
full_prompt = f"{system_prompt}\n\n{few_shot}\n\n{user_query}"
|
||||
logger.info(f"update_insight_prompt={full_prompt}")
|
||||
|
||||
def parse_update_response(message: Message) -> PersonalMemory:
|
||||
"""Parse LLM response and create updated insight memory"""
|
||||
response_text = message.content
|
||||
logger.info(f"update_insight_response={response_text}")
|
||||
|
||||
# Parse the response to extract updated insight
|
||||
updated_content = parse_update_insight_response(response_text, self.language)
|
||||
|
||||
if not updated_content or updated_content.lower() in ['无', 'none', '']:
|
||||
logger.info(f"No update needed for insight: {insight_memory.content[:50]}...")
|
||||
return insight_memory
|
||||
|
||||
if updated_content == insight_memory.content:
|
||||
logger.info(f"Insight content unchanged: {insight_memory.content[:50]}...")
|
||||
return insight_memory
|
||||
|
||||
# Create updated insight memory
|
||||
updated_insight = PersonalMemory(
|
||||
workspace_id=insight_memory.workspace_id,
|
||||
memory_id=insight_memory.memory_id,
|
||||
memory_type="personal_insight",
|
||||
content=updated_content,
|
||||
target=insight_memory.target,
|
||||
reflection_subject=insight_memory.reflection_subject,
|
||||
author=getattr(self.llm, "model_name", "system"),
|
||||
metadata={
|
||||
**insight_memory.metadata,
|
||||
"updated_by": "update_insight_op",
|
||||
"original_content": insight_memory.content,
|
||||
"update_reason": "integrated_new_observations"
|
||||
}
|
||||
)
|
||||
updated_insight.update_modified_time()
|
||||
|
||||
logger.info(f"Updated insight: {updated_content[:50]}...")
|
||||
return updated_insight
|
||||
|
||||
# Use LLM chat with callback function
|
||||
try:
|
||||
return self.llm.chat(messages=[Message(content=full_prompt)], callback_fn=parse_update_response)
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating insight: {e}")
|
||||
return insight_memory
|
||||
149
reme_ai/summary/personal/update_insight_prompt.yaml
Normal file
149
reme_ai/summary/personal/update_insight_prompt.yaml
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
update_insight_system_zh: |
|
||||
从下面的句子中提取出给定类别的{user_name}的资料信息,并判断与已有信息是否矛盾,若矛盾以新信息为准整合已有信息和新信息并输出。若不需要更改,则回答“无”。
|
||||
其中矛盾的形式可以有很多种,可以是逻辑上的矛盾,可以是属性上的变化导致的矛盾,比如不能同时在两个地方工作,同一个时刻不能在两个地点,同一个时刻不能干两件事情等等。
|
||||
请一步步思考,并按如下格式输出, 其中信息一定加<>:
|
||||
思考: 思考的依据和过程,150字以内。
|
||||
{user_name}的资料: <信息>
|
||||
|
||||
update_insight_system: |
|
||||
Extract the given category of {user_name}'s profile information from the following sentences and determine if it contradicts the existing information. If there is a contradiction, integrate the existing information and the new information, prioritizing the new information, and output the result. If no changes are needed, respond with 'None'.
|
||||
The contradictions can come in many forms, such as logical contradictions or changes in attributes leading to contradictions, for example, not being able to work in two places simultaneously, being unable to be in two locations at the same time, or being unable to perform two tasks at the same time, etc.
|
||||
Think step by step, and output in the following format, with information enclosed in <>:
|
||||
Thoughts: The basis and process of your thinking, within 150 words.
|
||||
{user_name}'s profile: <Information>
|
||||
|
||||
update_insight_few_shot_zh: |
|
||||
示例1:
|
||||
因为昨天成都下大雨,{user_name}全身都被淋湿了。
|
||||
{user_name}关心明天成都的天气预报。
|
||||
类别:{user_name}所在地区
|
||||
已有信息:{user_name}所在地区: 杭州
|
||||
思考:从第一句句子可以得出{user_name}在成都。第二句句子没有直接透露{user_name}所在地信息,但与第一句句子{user_name}在成都的信息吻合。这与已有信息({user_name}在杭州)矛盾,输出更新的信息。
|
||||
{user_name}的资料:<成都>
|
||||
|
||||
示例2:
|
||||
{user_name}最近养好了肠胃。
|
||||
{user_name}关注中医养生。
|
||||
类别:{user_name}健康状况
|
||||
已有信息:{user_name}健康状况: 肠胃不好,高血压
|
||||
思考:从第一句句子可以得出{user_name}最近养好了肠胃,与已有信息矛盾,以新信息为准。第二句句子与{user_name}健康状况无关。整合已有信息和新信息得到{user_name}健康状况是肠胃健康,高血压。
|
||||
{user_name}的资料:<肠胃健康,高血压>
|
||||
|
||||
示例3:
|
||||
{user_name}刚刚毕业,第一份工作是银行前台。
|
||||
{user_name}的理想工作是职业游戏选手。
|
||||
类别:{user_name}职业
|
||||
已有信息:{user_name}职业:在招商银行工作
|
||||
思考:整合已有信息和第一句句子的信息可以得出{user_name}的现在的职业是招商银行前台。第二句句子说明了{user_name}的理想工作但并不是现在的职业。
|
||||
{user_name}的资料:<招商银行前台>
|
||||
|
||||
示例4:
|
||||
{user_name}大学期间接触过优化算法的研究。
|
||||
类别:{user_name}学习专业
|
||||
已有信息:{user_name}学习专业:与人工智能相关
|
||||
思考:从句子可以得出{user_name}大学学习的专业与优化算法相关,这与已有信息({user_name}学习专业与人工智能相关)不矛盾,整合可以得出{user_name}大学学习的专业与人工智能和优化算法相关。
|
||||
{user_name}的资料:<与人工智能和优化算法相关>
|
||||
|
||||
示例5:
|
||||
{user_name}单身。
|
||||
{user_name}受到一名18岁男生的追求,但不想接受又不想伤害他。
|
||||
{user_name}喜欢成熟且情绪稳定的男生。
|
||||
类别:{user_name}情感状况
|
||||
已有信息:{user_name}情感状况:有男朋友
|
||||
思考:从第一句句子可以得出{user_name}现在单身,与已有信息矛盾,以新信息为准。从第二句句子得出{user_name}受到一名18岁男生的追求但并不喜欢他。第三句话表达了{user_name}理想的伴侣类型但与{user_name}情感状况无关。整合得出{user_name}情感状况为单身,受到一名18岁男生的追求但并不喜欢他。
|
||||
{user_name}的资料:<单身,受到一名18岁男生的追求但并不喜欢他。>
|
||||
|
||||
示例6:
|
||||
{user_name}女朋友下个月过生日。
|
||||
{user_name}生日在7月15日。
|
||||
{user_name}的还在在学校经常逃课。
|
||||
{user_name}喜欢打篮球。
|
||||
类别:{user_name}生日
|
||||
已有信息:{user_name}生日:1987年7月15日。
|
||||
思考:第一句句子中提及生日,但并不是用户的生日,无法得出用户生日信息。从第二句句子可以得出用户生日在7月15日,与已有信息不矛盾,整合可以得出用户生日是1987年7月15日。
|
||||
{user_name}的资料:<1987年7月15日>
|
||||
|
||||
示例7:
|
||||
今天{user_name}和同学去打球了。
|
||||
明天{user_name}和女朋友一起去杭州旅游。
|
||||
今天{user_name}买入了100股阿里巴巴股票。
|
||||
类别:{user_name}公司地址
|
||||
已有信息:{user_name}公司地址:
|
||||
思考:和公司地址都没有关联,没有新提取的信息。
|
||||
{user_name}的资料:<无>
|
||||
|
||||
update_insight_few_shot: |
|
||||
Example 1:
|
||||
Because it rained heavily in Chengdu yesterday, {user_name} got completely soaked.
|
||||
{user_name} is concerned about Chengdu's weather forecast for tomorrow.
|
||||
Category: {user_name}'s location
|
||||
Existing information: {user_name}'s location: Hangzhou
|
||||
Thought: From the first sentence, it can be inferred that {user_name} is in Chengdu. The second sentence does not directly reveal {user_name}'s location but matches the information that {user_name} is in Chengdu from the first sentence. This contradicts the existing information (that {user_name} is in Hangzhou), so we output the updated information.
|
||||
{user_name}'s profile: <Chengdu>
|
||||
|
||||
Example 2:
|
||||
{user_name} recently recovered from stomach issues.
|
||||
{user_name} is interested in traditional Chinese medicine.
|
||||
Category: {user_name}'s health status
|
||||
Existing information: {user_name}'s health status: Stomach issues, high blood pressure
|
||||
Thought: From the first sentence, it can be inferred that {user_name} recently recovered from stomach issues, which contradicts the existing information. Therefore, the new information should take precedence. The second sentence is not related to {user_name}'s health status. Integrating the existing information and the new information, we get that {user_name}'s health status is healthy stomach and high blood pressure.
|
||||
{user_name}'s profile: <Healthy stomach, high blood pressure>
|
||||
|
||||
Example 3:
|
||||
{user_name} just graduated, and their first job is as a bank receptionist.
|
||||
{user_name}'s dream job is to be a professional gamer.
|
||||
Category: {user_name}'s occupation
|
||||
Existing information: {user_name}'s occupation: Works at China Merchants Bank
|
||||
Thought: Integrating the existing information and the information from the first sentence, it can be inferred that {user_name}'s current occupation is a receptionist at China Merchants Bank. The second sentence explains {user_name}'s dream job but not the current occupation.
|
||||
{user_name}'s profile: <Receptionist at China Merchants Bank>
|
||||
|
||||
Example 4:
|
||||
{user_name} was exposed to optimization algorithm research during university.
|
||||
Category: {user_name}'s field of study
|
||||
Existing information: {user_name}'s field of study: Related to artificial intelligence
|
||||
Thought: From the sentence, it can be inferred that {user_name}'s university major is related to optimization algorithms. This does not contradict the existing information (that {user_name}'s major is related to artificial intelligence). Integrating both, we can conclude that {user_name}'s university major is related to artificial intelligence and optimization algorithms.
|
||||
{user_name}'s profile: <Related to artificial intelligence and optimization algorithms>
|
||||
|
||||
Example 5:
|
||||
{user_name} is single.
|
||||
{user_name} is pursued by an 18-year-old male but doesn't want to accept his advances or hurt him.
|
||||
{user_name} prefers mature and emotionally stable men.
|
||||
Category: {user_name}'s relationship status
|
||||
Existing information: {user_name}'s relationship status: Has a boyfriend
|
||||
Thought: From the first sentence, it can be inferred that {user_name} is currently single, which contradicts the existing information. Therefore, the new information should take precedence. From the second sentence, it can be inferred that {user_name} is being pursued by an 18-year-old male but does not like him. The third sentence expresses {user_name}'s ideal partner type but is not related to {user_name}'s relationship status. Integrating this, we conclude that {user_name}'s relationship status is single and being pursued by an 18-year-old male but does not like him.
|
||||
{user_name}'s profile: <Single, pursued by an 18-year-old male but does not like him>
|
||||
|
||||
Example 6:
|
||||
{user_name}'s girlfriend's birthday is next month.
|
||||
{user_name}'s birthday is on July 15th.
|
||||
{user_name} often skips classes at school.
|
||||
{user_name} likes playing basketball.
|
||||
Category: {user_name}'s Birthday
|
||||
Existing Information: {user_name}'s Birthday: July 15, 1987.
|
||||
Thoughts: The first sentence mentions a birthday, but it is not the user's birthday, so it does not provide information about the user's birthday. From the second sentence, we know that the user's birthday is on July 15th, which is consistent with the existing information. We can conclude that the user's birthday is July 15, 1987.
|
||||
{user_name}'s profile: <July 15, 1987>
|
||||
|
||||
Example 7:
|
||||
Today, {user_name} played basketball with classmates.
|
||||
Tomorrow, {user_name} is going to Hangzhou with his girlfriend.
|
||||
Today, {user_name} bought 100 shares of Alibaba stock.
|
||||
Category: {user_name}'s Company Address
|
||||
Existing Information: {user_name}'s Company Address:
|
||||
Thoughts: There is no information related to the company address, no new information extracted.
|
||||
{user_name}'s profile: <None>
|
||||
|
||||
update_insight_user_query_zh: |
|
||||
{user_query}
|
||||
类别:{insight_key}
|
||||
已有信息:{insight_key_value}
|
||||
|
||||
update_insight_user_query: |
|
||||
{user_query}
|
||||
Category: {insight_key}
|
||||
Existing information: {insight_key_value}
|
||||
|
||||
insight_string_format_zh: |
|
||||
{name}的{key}
|
||||
|
||||
insight_string_format: |
|
||||
The {key} of {name}
|
||||
|
|
@ -1,253 +0,0 @@
|
|||
import time
|
||||
from typing import List
|
||||
|
||||
from memoryscope.constants.common_constants import INSIGHT_NODES, NOT_UPDATED_NODES, NOT_REFLECTED_NODES
|
||||
from memoryscope.constants.language_constants import COLON_WORD, NONE_WORD, REPEATED_WORD
|
||||
from memoryscope.core.utils.datetime_handler import DatetimeHandler
|
||||
from memoryscope.core.utils.response_text_parser import ResponseTextParser
|
||||
from memoryscope.core.utils.tool_functions import cosine_similarity
|
||||
from memoryscope.core.worker.memory_base_worker import MemoryBaseWorker
|
||||
from memoryscope.enumeration.action_status_enum import ActionStatusEnum
|
||||
from memoryscope.scheme.memory_node import MemoryNode
|
||||
|
||||
|
||||
class UpdateInsightWorker(MemoryBaseWorker):
|
||||
"""
|
||||
This class is responsible for updating insight value in a memory system. It filters insight nodes
|
||||
based on their association with observed nodes, utilizes a ranking model to prioritize them,
|
||||
generates refreshed insights via an LLM, and manages node statuses and content updates,
|
||||
incorporating features for concurrent execution and logging.
|
||||
"""
|
||||
FILE_PATH: str = __file__
|
||||
|
||||
def _parse_params(self, **kwargs):
|
||||
self.update_insight_threshold: float = kwargs.get("update_insight_threshold", 0.1)
|
||||
self.generation_model_kwargs: dict = kwargs.get("generation_model_kwargs", {})
|
||||
self.update_insight_max_count: int = kwargs.get("update_insight_max_count", 5)
|
||||
self.enable_ranker: bool = self.memoryscope_context.meta_data["enable_ranker"]
|
||||
|
||||
def filter_obs_nodes(self,
|
||||
insight_node: MemoryNode,
|
||||
obs_nodes: List[MemoryNode]) -> (MemoryNode, List[MemoryNode], float):
|
||||
"""
|
||||
Filters observed nodes based on their relevance to a given insight node using a ranking model.
|
||||
|
||||
Args:
|
||||
insight_node (MemoryNode): The insight node used as the basis for filtering.
|
||||
obs_nodes (List[MemoryNode]): A list of observed nodes to be filtered.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing:
|
||||
- The original insight node.
|
||||
- A list of filtered observed nodes that are relevant to the insight node.
|
||||
- The maximum relevance score among the filtered nodes.
|
||||
"""
|
||||
max_score: float = 0
|
||||
filtered_nodes: List[MemoryNode] = []
|
||||
|
||||
# Check if insight node key or value is empty and log a warning
|
||||
if not insight_node.key:
|
||||
self.logger.warning(f"insight_key={insight_node.key} is empty!")
|
||||
return insight_node, filtered_nodes, max_score
|
||||
|
||||
if not obs_nodes:
|
||||
self.logger.warning("obs_nodes is empty!")
|
||||
return insight_node, filtered_nodes, max_score
|
||||
|
||||
if not self.enable_ranker:
|
||||
if not insight_node.key_vector:
|
||||
key_vector: List[float] = self.embedding_model.call(text=insight_node.key).embedding_results
|
||||
if not key_vector:
|
||||
self.logger.warning(f"embedding call {insight_node.key} failed!")
|
||||
return insight_node, filtered_nodes, max_score
|
||||
|
||||
insight_node.key_vector = key_vector
|
||||
|
||||
score_recall_list = cosine_similarity(insight_node.key_vector, [x.vector for x in obs_nodes])
|
||||
assert len(score_recall_list) == len(obs_nodes), \
|
||||
f"size is not as excepted. {len(score_recall_list)} v.s. {len(obs_nodes)}"
|
||||
|
||||
for score, node in zip(score_recall_list, obs_nodes):
|
||||
keep_flag = score >= self.update_insight_threshold
|
||||
if keep_flag:
|
||||
filtered_nodes.append(node)
|
||||
max_score = max(max_score, score)
|
||||
|
||||
# Log information about each node's processing
|
||||
self.logger.info(f"insight_key={insight_node.key} content={node.content} "
|
||||
f"score={score} keep_flag={keep_flag}")
|
||||
|
||||
else:
|
||||
# Call the ranking model to get scores for each observed node's content against the insight key
|
||||
documents = [x.content for x in obs_nodes]
|
||||
self.logger.debug(f"update.insight.rank key={insight_node.key} \n docs={'|'.join(documents)}")
|
||||
response = self.rank_model.call(query=insight_node.key, documents=documents)
|
||||
if not response.status:
|
||||
return insight_node, filtered_nodes, max_score
|
||||
|
||||
# Iterate over the ranked scores to filter nodes
|
||||
for index, score in response.rank_scores.items():
|
||||
node = obs_nodes[index]
|
||||
# Determine if the node should be kept based on the threshold
|
||||
keep_flag = score >= self.update_insight_threshold
|
||||
if keep_flag:
|
||||
filtered_nodes.append(node)
|
||||
max_score = max(max_score, score)
|
||||
# Log information about each node's processing
|
||||
self.logger.info(f"insight_key={insight_node.key} content={node.content} "
|
||||
f"score={score} keep_flag={keep_flag}")
|
||||
|
||||
# Warn if no nodes were filtered
|
||||
if not filtered_nodes:
|
||||
self.logger.warning(f"update_insight={insight_node.key} filtered_nodes is empty!")
|
||||
|
||||
# Return the original insight node, the list of filtered nodes, and the highest score
|
||||
return insight_node, filtered_nodes, max_score
|
||||
|
||||
def update_insight_node(self, insight_node: MemoryNode, insight_value: str):
|
||||
"""
|
||||
Updates the MemoryNode with the new insight value.
|
||||
|
||||
Args:
|
||||
insight_node (MemoryNode): The MemoryNode whose insight value needs to be updated.
|
||||
insight_value (str): The new insight value.
|
||||
|
||||
Returns:
|
||||
MemoryNode: The updated MemoryNode with potentially revised insight value.
|
||||
"""
|
||||
dt_handler = DatetimeHandler()
|
||||
key = self.prompt_handler.insight_string_format.format(name=self.target_name, key=insight_node.key)
|
||||
content = f"{key}{self.get_language_value(COLON_WORD)} {insight_value}"
|
||||
insight_node.content = content
|
||||
insight_node.value = insight_value
|
||||
insight_node.meta_data.update({k: str(v) for k, v in dt_handler.get_dt_info_dict(self.language).items()})
|
||||
insight_node.timestamp = dt_handler.timestamp
|
||||
insight_node.dt = dt_handler.datetime_format()
|
||||
if insight_node.action_status == ActionStatusEnum.NONE.value:
|
||||
insight_node.action_status = ActionStatusEnum.CONTENT_MODIFIED.value
|
||||
self.logger.info(f"after_update_{insight_node.key} value={insight_value}")
|
||||
return insight_node
|
||||
|
||||
def update_insight(self, insight_node: MemoryNode, filtered_nodes: List[MemoryNode]) -> MemoryNode:
|
||||
"""
|
||||
Updates the insight value of a given MemoryNode based on the context from a list of filtered MemoryNodes.
|
||||
|
||||
Args:
|
||||
insight_node (MemoryNode): The MemoryNode whose insight value needs to be updated.
|
||||
filtered_nodes (List[MemoryNode]): A list of MemoryNodes used as context for updating the insight.
|
||||
|
||||
Returns:
|
||||
MemoryNode: The updated MemoryNode with potentially revised insight value.
|
||||
"""
|
||||
self.logger.info(f"Updating insight for key={insight_node.key}, old_value={insight_node.value}, "
|
||||
f"with {len(filtered_nodes)} documents considered.")
|
||||
|
||||
# Generate the prompt for updating insight
|
||||
user_query_list = [n.content for n in filtered_nodes]
|
||||
system_prompt = self.prompt_handler.update_insight_system.format(user_name=self.target_name)
|
||||
few_shot = self.prompt_handler.update_insight_few_shot.format(user_name=self.target_name)
|
||||
user_query = self.prompt_handler.update_insight_user_query.format(
|
||||
user_query="\n".join(user_query_list),
|
||||
insight_key=insight_node.key,
|
||||
insight_key_value=insight_node.key + self.get_language_value(COLON_WORD) + insight_node.value)
|
||||
# Construct the message for LLM interaction
|
||||
update_insight_message = self.prompt_to_msg(system_prompt=system_prompt, few_shot=few_shot,
|
||||
user_query=user_query)
|
||||
self.logger.info(f"Generated insight update message: {update_insight_message}")
|
||||
|
||||
# Call the Language Model for insight update
|
||||
response = self.generation_model.call(messages=update_insight_message, **self.generation_model_kwargs)
|
||||
|
||||
# Handle empty or invalid responses
|
||||
if not response.status or not response.message.content:
|
||||
return insight_node
|
||||
|
||||
insight_value_list = ResponseTextParser(response.message.content, self.language,
|
||||
f"update_{insight_node.key}").parse_v1()
|
||||
if not insight_value_list:
|
||||
self.logger.warning(f"update_{insight_node.key} insight_value_list is empty!")
|
||||
return insight_node
|
||||
|
||||
insight_value_list = insight_value_list[0]
|
||||
if not insight_value_list:
|
||||
self.logger.warning(f"update_{insight_node.key} insight_value_list is empty!")
|
||||
return insight_node
|
||||
|
||||
insight_value = insight_value_list[0].lower()
|
||||
if not insight_value or insight_value in self.get_language_value([NONE_WORD, REPEATED_WORD]):
|
||||
self.logger.info(f"update_{insight_node.key} insight_value={insight_value} is invalid.")
|
||||
return insight_node
|
||||
|
||||
if insight_node.value == insight_value:
|
||||
self.logger.info(f"value={insight_value} is same!")
|
||||
return insight_node
|
||||
|
||||
self.update_insight_node(insight_node=insight_node, insight_value=insight_value)
|
||||
return insight_node
|
||||
|
||||
def _run(self):
|
||||
"""
|
||||
Executes the main routine of the UpdateInsightWorker. This involves filtering and updating insight nodes
|
||||
based on their association with observed nodes. It processes nodes in batches, selects the top nodes
|
||||
according to a scoring mechanism, and then initiates tasks to update these insights using an LLM. Finally,
|
||||
it updates the status of processed nodes and gathers the results from all threads.
|
||||
|
||||
Steps include:
|
||||
1. Get lists of insight node.
|
||||
2. Get not updated, and not reflected observation nodes from memory.
|
||||
3. Filter and process active insight nodes with respective not updated observation nodes.
|
||||
4. Sort processed results by score and select the top N.
|
||||
5. Submit tasks to update insight value for the selected nodes.
|
||||
6. Gather the results of all update tasks.
|
||||
7. Mark processed nodes as updated in memory.
|
||||
"""
|
||||
insight_nodes: List[MemoryNode] = self.memory_manager.get_memories(INSIGHT_NODES)
|
||||
not_updated_nodes: List[MemoryNode] = self.memory_manager.get_memories(NOT_UPDATED_NODES)
|
||||
not_reflected_nodes: List[MemoryNode] = self.memory_manager.get_memories(keys=[NOT_REFLECTED_NODES,
|
||||
NOT_UPDATED_NODES])
|
||||
|
||||
if not insight_nodes:
|
||||
self.logger.warning("insight_nodes is empty, stopping processing.")
|
||||
return
|
||||
|
||||
# Process active insight nodes with corresponding not updated nodes
|
||||
for node in insight_nodes:
|
||||
if self.enable_parallel:
|
||||
time.sleep(1)
|
||||
if node.action_status == ActionStatusEnum.NEW.value:
|
||||
self.submit_thread_task(fn=self.filter_obs_nodes,
|
||||
insight_node=node,
|
||||
obs_nodes=not_reflected_nodes)
|
||||
else:
|
||||
self.submit_thread_task(fn=self.filter_obs_nodes,
|
||||
insight_node=node,
|
||||
obs_nodes=not_updated_nodes)
|
||||
|
||||
# select top n
|
||||
result_list = []
|
||||
for result in self.gather_thread_result():
|
||||
insight_node, filtered_nodes, max_score = result
|
||||
if not filtered_nodes:
|
||||
continue
|
||||
result_list.append(result)
|
||||
result_sorted = sorted(result_list, key=lambda x: x[2], reverse=True)[: self.update_insight_max_count]
|
||||
|
||||
# Submit tasks to update insights for the top nodes
|
||||
for insight_node, filtered_nodes, _ in result_sorted:
|
||||
if self.enable_parallel:
|
||||
time.sleep(1)
|
||||
self.submit_thread_task(fn=self.update_insight,
|
||||
insight_node=insight_node,
|
||||
filtered_nodes=filtered_nodes)
|
||||
|
||||
# Gather the final results from all update tasks
|
||||
for _ in self.gather_thread_result():
|
||||
pass
|
||||
|
||||
# delete empty nodes
|
||||
empty_nodes = [n for n in insight_nodes if not n.content.strip()]
|
||||
self.memory_manager.delete_memories(empty_nodes)
|
||||
|
||||
for node in not_updated_nodes:
|
||||
node.obs_updated = 1
|
||||
node.action_status = ActionStatusEnum.MODIFIED
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
update_insight_system:
|
||||
cn: |
|
||||
从下面的句子中提取出给定类别的{user_name}的资料信息,并判断与已有信息是否矛盾,若矛盾以新信息为准整合已有信息和新信息并输出。若不需要更改,则回答“无”。
|
||||
其中矛盾的形式可以有很多种,可以是逻辑上的矛盾,可以是属性上的变化导致的矛盾,比如不能同时在两个地方工作,同一个时刻不能在两个地点,同一个时刻不能干两件事情等等。
|
||||
请一步步思考,并按如下格式输出, 其中信息一定加<>:
|
||||
思考: 思考的依据和过程,150字以内。
|
||||
{user_name}的资料: <信息>
|
||||
en: |
|
||||
Extract the given category of {user_name}'s profile information from the following sentences and determine if it contradicts the existing information. If there is a contradiction, integrate the existing information and the new information, prioritizing the new information, and output the result. If no changes are needed, respond with 'None'.
|
||||
The contradictions can come in many forms, such as logical contradictions or changes in attributes leading to contradictions, for example, not being able to work in two places simultaneously, being unable to be in two locations at the same time, or being unable to perform two tasks at the same time, etc.
|
||||
Think step by step, and output in the following format, with information enclosed in <>:
|
||||
Thoughts: The basis and process of your thinking, within 150 words.
|
||||
{user_name}'s profile: <Information>
|
||||
|
||||
|
||||
update_insight_few_shot:
|
||||
cn: |
|
||||
示例1:
|
||||
因为昨天成都下大雨,{user_name}全身都被淋湿了。
|
||||
{user_name}关心明天成都的天气预报。
|
||||
类别:{user_name}所在地区
|
||||
已有信息:{user_name}所在地区: 杭州
|
||||
思考:从第一句句子可以得出{user_name}在成都。第二句句子没有直接透露{user_name}所在地信息,但与第一句句子{user_name}在成都的信息吻合。这与已有信息({user_name}在杭州)矛盾,输出更新的信息。
|
||||
{user_name}的资料:<成都>
|
||||
|
||||
示例2:
|
||||
{user_name}最近养好了肠胃。
|
||||
{user_name}关注中医养生。
|
||||
类别:{user_name}健康状况
|
||||
已有信息:{user_name}健康状况: 肠胃不好,高血压
|
||||
思考:从第一句句子可以得出{user_name}最近养好了肠胃,与已有信息矛盾,以新信息为准。第二句句子与{user_name}健康状况无关。整合已有信息和新信息得到{user_name}健康状况是肠胃健康,高血压。
|
||||
{user_name}的资料:<肠胃健康,高血压>
|
||||
|
||||
示例3:
|
||||
{user_name}刚刚毕业,第一份工作是银行前台。
|
||||
{user_name}的理想工作是职业游戏选手。
|
||||
类别:{user_name}职业
|
||||
已有信息:{user_name}职业:在招商银行工作
|
||||
思考:整合已有信息和第一句句子的信息可以得出{user_name}的现在的职业是招商银行前台。第二句句子说明了{user_name}的理想工作但并不是现在的职业。
|
||||
{user_name}的资料:<招商银行前台>
|
||||
|
||||
示例4:
|
||||
{user_name}大学期间接触过优化算法的研究。
|
||||
类别:{user_name}学习专业
|
||||
已有信息:{user_name}学习专业:与人工智能相关
|
||||
思考:从句子可以得出{user_name}大学学习的专业与优化算法相关,这与已有信息({user_name}学习专业与人工智能相关)不矛盾,整合可以得出{user_name}大学学习的专业与人工智能和优化算法相关。
|
||||
{user_name}的资料:<与人工智能和优化算法相关>
|
||||
|
||||
示例5:
|
||||
{user_name}单身。
|
||||
{user_name}受到一名18岁男生的追求,但不想接受又不想伤害他。
|
||||
{user_name}喜欢成熟且情绪稳定的男生。
|
||||
类别:{user_name}情感状况
|
||||
已有信息:{user_name}情感状况:有男朋友
|
||||
思考:从第一句句子可以得出{user_name}现在单身,与已有信息矛盾,以新信息为准。从第二句句子得出{user_name}受到一名18岁男生的追求但并不喜欢他。第三句话表达了{user_name}理想的伴侣类型但与{user_name}情感状况无关。整合得出{user_name}情感状况为单身,受到一名18岁男生的追求但并不喜欢他。
|
||||
{user_name}的资料:<单身,受到一名18岁男生的追求但并不喜欢他。>
|
||||
|
||||
示例6:
|
||||
{user_name}女朋友下个月过生日。
|
||||
{user_name}生日在7月15日。
|
||||
{user_name}的还在在学校经常逃课。
|
||||
{user_name}喜欢打篮球。
|
||||
类别:{user_name}生日
|
||||
已有信息:{user_name}生日:1987年7月15日。
|
||||
思考:第一句句子中提及生日,但并不是用户的生日,无法得出用户生日信息。从第二句句子可以得出用户生日在7月15日,与已有信息不矛盾,整合可以得出用户生日是1987年7月15日。
|
||||
{user_name}的资料:<1987年7月15日>
|
||||
|
||||
示例7:
|
||||
今天{user_name}和同学去打球了。
|
||||
明天{user_name}和女朋友一起去杭州旅游。
|
||||
今天{user_name}买入了100股阿里巴巴股票。
|
||||
类别:{user_name}公司地址
|
||||
已有信息:{user_name}公司地址:
|
||||
思考:和公司地址都没有关联,没有新提取的信息。
|
||||
{user_name}的资料:<无>
|
||||
|
||||
en: |
|
||||
Example 1:
|
||||
Because it rained heavily in Chengdu yesterday, {user_name} got completely soaked.
|
||||
{user_name} is concerned about Chengdu's weather forecast for tomorrow.
|
||||
Category: {user_name}'s location
|
||||
Existing information: {user_name}'s location: Hangzhou
|
||||
Thought: From the first sentence, it can be inferred that {user_name} is in Chengdu. The second sentence does not directly reveal {user_name}'s location but matches the information that {user_name} is in Chengdu from the first sentence. This contradicts the existing information (that {user_name} is in Hangzhou), so we output the updated information.
|
||||
{user_name}'s profile: <Chengdu>
|
||||
|
||||
Example 2:
|
||||
{user_name} recently recovered from stomach issues.
|
||||
{user_name} is interested in traditional Chinese medicine.
|
||||
Category: {user_name}'s health status
|
||||
Existing information: {user_name}'s health status: Stomach issues, high blood pressure
|
||||
Thought: From the first sentence, it can be inferred that {user_name} recently recovered from stomach issues, which contradicts the existing information. Therefore, the new information should take precedence. The second sentence is not related to {user_name}'s health status. Integrating the existing information and the new information, we get that {user_name}'s health status is healthy stomach and high blood pressure.
|
||||
{user_name}'s profile: <Healthy stomach, high blood pressure>
|
||||
|
||||
Example 3:
|
||||
{user_name} just graduated, and their first job is as a bank receptionist.
|
||||
{user_name}'s dream job is to be a professional gamer.
|
||||
Category: {user_name}'s occupation
|
||||
Existing information: {user_name}'s occupation: Works at China Merchants Bank
|
||||
Thought: Integrating the existing information and the information from the first sentence, it can be inferred that {user_name}'s current occupation is a receptionist at China Merchants Bank. The second sentence explains {user_name}'s dream job but not the current occupation.
|
||||
{user_name}'s profile: <Receptionist at China Merchants Bank>
|
||||
|
||||
Example 4:
|
||||
{user_name} was exposed to optimization algorithm research during university.
|
||||
Category: {user_name}'s field of study
|
||||
Existing information: {user_name}'s field of study: Related to artificial intelligence
|
||||
Thought: From the sentence, it can be inferred that {user_name}'s university major is related to optimization algorithms. This does not contradict the existing information (that {user_name}'s major is related to artificial intelligence). Integrating both, we can conclude that {user_name}'s university major is related to artificial intelligence and optimization algorithms.
|
||||
{user_name}'s profile: <Related to artificial intelligence and optimization algorithms>
|
||||
|
||||
Example 5:
|
||||
{user_name} is single.
|
||||
{user_name} is pursued by an 18-year-old male but doesn't want to accept his advances or hurt him.
|
||||
{user_name} prefers mature and emotionally stable men.
|
||||
Category: {user_name}'s relationship status
|
||||
Existing information: {user_name}'s relationship status: Has a boyfriend
|
||||
Thought: From the first sentence, it can be inferred that {user_name} is currently single, which contradicts the existing information. Therefore, the new information should take precedence. From the second sentence, it can be inferred that {user_name} is being pursued by an 18-year-old male but does not like him. The third sentence expresses {user_name}'s ideal partner type but is not related to {user_name}'s relationship status. Integrating this, we conclude that {user_name}'s relationship status is single and being pursued by an 18-year-old male but does not like him.
|
||||
{user_name}'s profile: <Single, pursued by an 18-year-old male but does not like him>
|
||||
|
||||
Example 6:
|
||||
{user_name}'s girlfriend's birthday is next month.
|
||||
{user_name}'s birthday is on July 15th.
|
||||
{user_name} often skips classes at school.
|
||||
{user_name} likes playing basketball.
|
||||
Category: {user_name}'s Birthday
|
||||
Existing Information: {user_name}'s Birthday: July 15, 1987.
|
||||
Thoughts: The first sentence mentions a birthday, but it is not the user's birthday, so it does not provide information about the user's birthday. From the second sentence, we know that the user's birthday is on July 15th, which is consistent with the existing information. We can conclude that the user's birthday is July 15, 1987.
|
||||
{user_name}'s profile: <July 15, 1987>
|
||||
|
||||
Example 7:
|
||||
Today, {user_name} played basketball with classmates.
|
||||
Tomorrow, {user_name} is going to Hangzhou with his girlfriend.
|
||||
Today, {user_name} bought 100 shares of Alibaba stock.
|
||||
Category: {user_name}'s Company Address
|
||||
Existing Information: {user_name}'s Company Address:
|
||||
Thoughts: There is no information related to the company address, no new information extracted.
|
||||
{user_name}'s profile: <None>
|
||||
|
||||
update_insight_user_query:
|
||||
cn: |
|
||||
{user_query}
|
||||
类别:{insight_key}
|
||||
已有信息:{insight_key_value}
|
||||
|
||||
en: |
|
||||
{user_query}
|
||||
Category: {insight_key}
|
||||
Existing information: {insight_key_value}
|
||||
|
||||
insight_string_format:
|
||||
cn: |
|
||||
{name}的{key}
|
||||
en: |
|
||||
The {key} of {name}
|
||||
|
|
@ -110,7 +110,7 @@ class ComparativeExtractionOp(BaseLLMOp):
|
|||
failure_steps: List[Message], similarity_score: float) -> List[BaseMemory]:
|
||||
"""Extract hard comparative task memory (success vs failure)"""
|
||||
prompt = self.prompt_format(
|
||||
prompt_name="comparative_step_task_memory_prompt",
|
||||
prompt_name="hard_comparative_step_task_memory_prompt",
|
||||
success_steps=merge_messages_content(success_steps),
|
||||
failure_steps=merge_messages_content(failure_steps),
|
||||
similarity_score=similarity_score
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ soft_comparative_step_task_memory_prompt: |
|
|||
]
|
||||
```
|
||||
|
||||
comparative_step_task_memory_prompt: |
|
||||
hard_comparative_step_task_memory_prompt: |
|
||||
You are an expert AI analyst comparing successful and failed step sequences to extract differential insights.
|
||||
|
||||
Your task is to identify the key differences between success and failure patterns at the step level.
|
||||
|
|
|
|||
|
|
@ -15,12 +15,11 @@ class MemoryValidationOp(BaseLLMOp):
|
|||
|
||||
def execute(self):
|
||||
"""Validate quality of extracted task memories"""
|
||||
self.context.memory_list = []
|
||||
self.context.memory_list.append(self.context.success_task_memories)
|
||||
self.context.memory_list.append(self.context.failure_task_memories)
|
||||
self.context.memory_list.append(self.context.comparative_task_memories)
|
||||
|
||||
task_memories: List[BaseMemory] = self.context.memory_list
|
||||
task_memories: List[BaseMemory] = []
|
||||
task_memories.extend(self.context.success_task_memories)
|
||||
task_memories.extend(self.context.failure_task_memories)
|
||||
task_memories.extend(self.context.comparative_task_memories)
|
||||
|
||||
if not task_memories:
|
||||
logger.info("No task memories found for validation")
|
||||
|
|
@ -34,6 +33,7 @@ class MemoryValidationOp(BaseLLMOp):
|
|||
for task_memory in task_memories:
|
||||
validation_result = self._validate_single_task_memory(task_memory)
|
||||
if validation_result and validation_result.get("is_valid", False):
|
||||
task_memory.score = validation_result.get("score", 0.0)
|
||||
validated_task_memories.append(task_memory)
|
||||
else:
|
||||
reason = validation_result.get("reason", "Unknown reason") if validation_result else "Validation failed"
|
||||
|
|
@ -42,7 +42,8 @@ class MemoryValidationOp(BaseLLMOp):
|
|||
logger.info(f"Validated {len(validated_task_memories)} out of {len(task_memories)} task memories")
|
||||
|
||||
# Update context
|
||||
self.context.memory_list = validated_task_memories
|
||||
self.context.response.answer = json.dumps([x.model_dump() for x in validated_task_memories])
|
||||
self.context.response.metadata["memory_list"] = validated_task_memories
|
||||
|
||||
def _validate_single_task_memory(self, task_memory: BaseMemory) -> Dict[str, Any]:
|
||||
"""Validate single task memory"""
|
||||
|
|
@ -56,8 +57,7 @@ class MemoryValidationOp(BaseLLMOp):
|
|||
prompt = self.prompt_format(
|
||||
prompt_name="task_memory_validation_prompt",
|
||||
condition=task_memory.when_to_use,
|
||||
task_memory_content=task_memory.content
|
||||
)
|
||||
task_memory_content=task_memory.content)
|
||||
|
||||
def parse_validation(message: Message) -> Dict[str, Any]:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -66,6 +66,6 @@ class SimpleComparativeSummaryOp(BaseLLMOp):
|
|||
memory_list.extend(task_memories)
|
||||
|
||||
self.context.response.answer = json.dumps([x.model_dump() for x in memory_list])
|
||||
self.context.memory_list = memory_list
|
||||
self.context.response.metadata["memory_list"] = memory_list
|
||||
for tm in memory_list:
|
||||
logger.info(f"add task memory when_to_use={tm.when_to_use}\ncontent={tm.content}")
|
||||
|
|
|
|||
|
|
@ -62,6 +62,6 @@ class SimpleSummaryOp(BaseLLMOp):
|
|||
memory_list.extend(memories)
|
||||
|
||||
self.context.response.answer = json.dumps([x.model_dump() for x in memory_list])
|
||||
self.context.memory_list = memory_list
|
||||
self.context.response.metadata["memory_list"] = memory_list
|
||||
for memory in memory_list:
|
||||
logger.info(f"add memory: when_to_use={memory.when_to_use}\ncontent={memory.content}")
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class TrajectorySegmentationOp(BaseLLMOp):
|
|||
logger.info(f"Segmented {segmented_count} trajectories")
|
||||
|
||||
# Update context with segmented trajectories
|
||||
self.context.segmented_trajectories = target_trajectories
|
||||
|
||||
|
||||
def _get_target_trajectories(self, all_trajectories: List[Trajectory],
|
||||
success_trajectories: List[Trajectory],
|
||||
|
|
@ -60,8 +60,7 @@ class TrajectorySegmentationOp(BaseLLMOp):
|
|||
prompt_name="step_segmentation_prompt",
|
||||
query=trajectory.metadata.get('query', ''),
|
||||
trajectory_content=trajectory_content,
|
||||
total_steps=len(trajectory.messages)
|
||||
)
|
||||
total_steps=len(trajectory.messages))
|
||||
|
||||
def parse_segmentation(message: Message) -> List[List[Message]]:
|
||||
content = message.content
|
||||
|
|
|
|||
341
reme_ai/utils/datetime_handler.py
Normal file
341
reme_ai/utils/datetime_handler.py
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
import datetime
|
||||
import re
|
||||
from typing import List
|
||||
|
||||
from reme_ai.constants.language_constants import WEEKDAYS, DATATIME_WORD_LIST, MONTH_DICT
|
||||
from reme_ai.enumeration.language_constants import LanguageEnum
|
||||
|
||||
|
||||
class DatetimeHandler(object):
|
||||
"""
|
||||
Handles operations related to datetime such as parsing, extraction, and formatting,
|
||||
with support for both Chinese and English contexts including weekday names and
|
||||
specialized text parsing for date components.
|
||||
"""
|
||||
|
||||
def __init__(self, dt: datetime.datetime | str | int | float = None):
|
||||
"""
|
||||
Initialize the DatetimeHandler instance with a datetime object, string, integer, or float representation
|
||||
of a timestamp. If no argument is provided, the current time is used.
|
||||
|
||||
Args:
|
||||
dt (datetime.datetime | str | int | float, optional):
|
||||
The datetime to be handled. Can be a datetime object, a timestamp string, or a numeric timestamp.
|
||||
Defaults to None, which sets the instance to the current datetime.
|
||||
|
||||
Attributes:
|
||||
self._dt (datetime.datetime): The internal datetime representation of the input.
|
||||
self._dt_info_dict (dict | None): A dictionary containing parsed datetime information, defaults to None.
|
||||
"""
|
||||
if isinstance(dt, str | int | float):
|
||||
if isinstance(dt, str):
|
||||
dt = float(dt)
|
||||
self._dt: datetime.datetime = datetime.datetime.fromtimestamp(dt)
|
||||
elif isinstance(dt, datetime.datetime):
|
||||
self._dt: datetime.datetime = dt
|
||||
else:
|
||||
self._dt: datetime.datetime = datetime.datetime.now()
|
||||
|
||||
self._dt_info_dict: dict | None = None
|
||||
|
||||
@staticmethod
|
||||
def language_transform(language: str | LanguageEnum) -> LanguageEnum:
|
||||
if not language:
|
||||
language = LanguageEnum.EN
|
||||
elif language == "zh":
|
||||
language = LanguageEnum.CN
|
||||
else:
|
||||
language = LanguageEnum(language)
|
||||
|
||||
return language
|
||||
|
||||
@classmethod
|
||||
def get_language_value(cls, language: str, value_dict: dict):
|
||||
return value_dict.get(cls.get_language_value(language))
|
||||
|
||||
def _parse_dt_info(self, language: LanguageEnum | str):
|
||||
"""
|
||||
Parses the datetime object (_dt) into a dictionary containing detailed date and time components,
|
||||
including language-specific weekday representation.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary with keys representing date and time parts such as 'year', 'month',
|
||||
'day', 'hour', 'minute', 'second', 'week', and 'weekday' with respective values.
|
||||
The 'weekday' value is translated based on the current language context.
|
||||
"""
|
||||
language = self.language_transform(language=language)
|
||||
|
||||
return {
|
||||
"year": self._dt.year,
|
||||
"month": MONTH_DICT[language][self._dt.month - 1],
|
||||
"day": self._dt.day,
|
||||
"hour": self._dt.hour,
|
||||
"minute": self._dt.minute,
|
||||
"second": self._dt.second,
|
||||
"week": self._dt.isocalendar().week,
|
||||
"weekday": WEEKDAYS[language][self._dt.isocalendar().weekday - 1],
|
||||
}
|
||||
|
||||
def get_dt_info_dict(self, language: LanguageEnum | str):
|
||||
"""
|
||||
Property method to get the dictionary containing parsed datetime information.
|
||||
If None, initialize using `_parse_dt_info`.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary with parsed datetime information.
|
||||
"""
|
||||
language = self.language_transform(language=language)
|
||||
|
||||
if self._dt_info_dict is None:
|
||||
self._dt_info_dict = self._parse_dt_info(language=language)
|
||||
return self._dt_info_dict
|
||||
|
||||
@classmethod
|
||||
def extract_date_parts_cn(cls, input_string: str) -> dict:
|
||||
"""
|
||||
Extracts various components of a date (year, month, day, etc.) from an input string based on Chinese formats.
|
||||
|
||||
This method identifies year, month, day, weekday, and hour components within the input
|
||||
string based on predefined patterns. It supports relative terms like '每' (every) and
|
||||
translates weekday names into numeric representations.
|
||||
|
||||
Args:
|
||||
input_string (str): The Chinese text containing date and time information.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary with keys 'year', 'month', 'day', 'weekday', and 'hour',
|
||||
each holding the corresponding extracted value. If a component is not found,
|
||||
it will not be included in the dictionary. For relative terms like '每' (every),
|
||||
the value is set to -1.
|
||||
|
||||
"""
|
||||
# Extending our pattern to handle every/每 as a possible value.
|
||||
patterns = {
|
||||
"year": r"(\d+|每)年",
|
||||
"month": r"(\d+|每)月",
|
||||
"day": r"(\d+|每)日",
|
||||
"weekday": r"周([一二三四五六日])",
|
||||
"hour": r"(\d+)点"
|
||||
}
|
||||
weekday_dict = {"一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "日": 7}
|
||||
extracted_data = {}
|
||||
|
||||
# Search for patterns in the input string and populate the dictionary
|
||||
for key, pattern in patterns.items():
|
||||
match = re.search(pattern, input_string)
|
||||
if match: # If there is a match, include it in the output dictionary
|
||||
if match.group(1) == "每":
|
||||
extracted_data[key] = -1
|
||||
elif match.group(1) in weekday_dict.keys():
|
||||
extracted_data[key] = weekday_dict[match.group(1)]
|
||||
else:
|
||||
extracted_data[key] = int(match.group(1))
|
||||
return extracted_data
|
||||
|
||||
@classmethod
|
||||
def extract_date_parts_en(cls, input_string: str) -> dict:
|
||||
"""
|
||||
Extracts various components of a date (year, month, day, etc.) from an input string based on English formats.
|
||||
|
||||
This method employs regex patterns to identify and parse different date and time elements within the provided
|
||||
text. It supports extraction of year, month name, day, 12-hour and 24-hour time formats, and weekdays.
|
||||
|
||||
Args:
|
||||
input_string (str): The English text containing date and time information.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the extracted date parts with default values of -1 where components are not
|
||||
found. Keys include 'year', 'month', 'day', 'hour', 'minute', 'second', and 'weekday'.
|
||||
"""
|
||||
date_info = {
|
||||
"year": -1,
|
||||
"month": -1,
|
||||
"day": -1,
|
||||
"hour": -1,
|
||||
"minute": -1,
|
||||
"second": -1,
|
||||
"weekday": -1
|
||||
}
|
||||
|
||||
# Patterns to extract the parts of the date/time
|
||||
patterns = {
|
||||
"year": r"\b(\d{4})\b",
|
||||
"month": r"\b(January|February|March|April|May|June|July|August|September|October|November|December)\b",
|
||||
"day_month_year": r"\b(?P<month>January|February|March|April|May|June|July|August|September|October"
|
||||
r"|November|December) (?P<day>\d{1,2}),? (?P<year>\d{4})\b",
|
||||
"day_month": r"\b(?P<month>January|February|March|April|May|June|July|August|September|October|November"
|
||||
r"|December) (?P<day>\d{1,2})\b",
|
||||
"hour_12": r"\b(\d{1,2})\s*(AM|PM|am|pm)\b",
|
||||
"hour_24": r"\b(\d{1,2}):(\d{2}):(\d{2})\b"
|
||||
}
|
||||
|
||||
month_mapping = {
|
||||
"January": 1, "February": 2, "March": 3, "April": 4, "May": 5, "June": 6, "July": 7, "August": 8,
|
||||
"September": 9, "October": 10, "November": 11, "December": 12
|
||||
}
|
||||
|
||||
weekday_mapping = {
|
||||
"Monday": 1, "Tuesday": 2, "Wednesday": 3, "Thursday": 4, "Friday": 5, "Saturday": 6, "Sunday": 7
|
||||
}
|
||||
|
||||
# Attempt to match full date (day month year)
|
||||
day_month_year_match = re.search(patterns["day_month_year"], input_string)
|
||||
if day_month_year_match:
|
||||
date_info["year"] = int(day_month_year_match.group("year"))
|
||||
date_info["month"] = month_mapping[day_month_year_match.group("month")]
|
||||
date_info["day"] = int(day_month_year_match.group("day"))
|
||||
|
||||
# If year wasn't found, try matching day and month without year
|
||||
elif date_info["year"] == -1:
|
||||
day_month_match = re.search(patterns["day_month"], input_string)
|
||||
if day_month_match:
|
||||
date_info["month"] = month_mapping[day_month_match.group("month")]
|
||||
date_info["day"] = int(day_month_match.group("day"))
|
||||
|
||||
# Extract year if not already found
|
||||
if date_info["year"] == -1:
|
||||
year_match = re.search(patterns["year"], input_string)
|
||||
if year_match:
|
||||
date_info["year"] = int(year_match.group(0))
|
||||
|
||||
# Extract month if not already found
|
||||
if date_info["month"] == -1:
|
||||
month_match = re.search(patterns["month"], input_string)
|
||||
if month_match:
|
||||
date_info["month"] = month_mapping[month_match.group(0)]
|
||||
|
||||
# Extract 12-hour format time
|
||||
hour_12_match = re.search(patterns["hour_12"], input_string)
|
||||
if hour_12_match:
|
||||
hour, period = int(hour_12_match.group(1)), hour_12_match.group(2).lower()
|
||||
if period == 'pm' and hour != 12:
|
||||
hour += 12
|
||||
elif period == 'am' and hour == 12:
|
||||
hour = 0
|
||||
date_info["hour"] = hour
|
||||
|
||||
# Identify weekday
|
||||
for week_day, value in weekday_mapping.items():
|
||||
if week_day in input_string:
|
||||
date_info["weekday"] = value
|
||||
break
|
||||
|
||||
return date_info
|
||||
|
||||
@classmethod
|
||||
def extract_date_parts(cls, input_string: str, language: LanguageEnum | str) -> dict:
|
||||
"""
|
||||
Extracts various date components from the input string based on the current language context.
|
||||
|
||||
This method dynamically selects a language-specific function to parse the input string and extract
|
||||
date parts such as year, month, day, etc. If the function for current language context does not exist,
|
||||
a warning is logged and an empty dictionary is returned.
|
||||
|
||||
Args:
|
||||
input_string (str): The string containing date information to be parsed.
|
||||
language (str): current language.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing extracted date components, or an empty dictionary if parsing fails.
|
||||
"""
|
||||
language = cls.language_transform(language=language)
|
||||
|
||||
func_name = f"extract_date_parts_{language.value}"
|
||||
if not hasattr(cls, func_name):
|
||||
# cls.logger.warning(f"language={language.value} needs to complete extract_date_parts func!")
|
||||
return {}
|
||||
return getattr(cls, func_name)(input_string=input_string)
|
||||
|
||||
@classmethod
|
||||
def has_time_word_cn(cls, query: str, datetime_word_list: List[str]) -> bool:
|
||||
"""
|
||||
Check if the input query contains any datetime-related words based on the cn language context.
|
||||
|
||||
Args:
|
||||
query (str): The input string to check for datetime-related words.
|
||||
datetime_word_list (list[str]): datetime keywords
|
||||
|
||||
Returns:
|
||||
bool: True if the query contains at least one datetime-related word, False otherwise.
|
||||
"""
|
||||
contain_datetime = False
|
||||
# TODO use re
|
||||
for datetime_word in datetime_word_list:
|
||||
if datetime_word in query:
|
||||
contain_datetime = True
|
||||
break
|
||||
return contain_datetime
|
||||
|
||||
@classmethod
|
||||
def has_time_word_en(cls, query: str, datetime_word_list: List[str]) -> bool:
|
||||
"""
|
||||
Check if the input query contains any datetime-related words based on the en language context.
|
||||
|
||||
Args:
|
||||
query (str): The input string to check for datetime-related words.
|
||||
datetime_word_list (list[str]): datetime keywords
|
||||
|
||||
Returns:
|
||||
bool: True if the query contains at least one datetime-related word, False otherwise.
|
||||
"""
|
||||
contain_datetime = False
|
||||
for datetime_word in datetime_word_list:
|
||||
datetime_word = datetime_word.lower()
|
||||
# TODO fix strip
|
||||
if datetime_word in [x.strip().lower().strip(",").strip(".").strip("?").strip(":")
|
||||
for x in query.split(" ")]:
|
||||
contain_datetime = True
|
||||
break
|
||||
return contain_datetime
|
||||
|
||||
@classmethod
|
||||
def has_time_word(cls, query: str, language: LanguageEnum | str) -> bool:
|
||||
language = cls.language_transform(language=language)
|
||||
|
||||
func_name = f"has_time_word_{language.value}"
|
||||
if not hasattr(cls, func_name):
|
||||
# cls.logger.warning(f"language={language.value} needs to complete has_time_word function!")
|
||||
return False
|
||||
|
||||
if language not in DATATIME_WORD_LIST:
|
||||
# cls.logger.warning(f"language={language.value} is missing in DATATIME_WORD_LIST!")
|
||||
return False
|
||||
|
||||
datetime_word_list = DATATIME_WORD_LIST[language]
|
||||
return getattr(cls, func_name)(query=query, datetime_word_list=datetime_word_list)
|
||||
|
||||
def datetime_format(self, dt_format: str = "%Y%m%d") -> str:
|
||||
"""
|
||||
Format the stored datetime object into a string based on the provided format.
|
||||
|
||||
Args:
|
||||
dt_format (str, optional): The datetime format string. Defaults to "%Y%m%d".
|
||||
|
||||
Returns:
|
||||
str: A formatted datetime string.
|
||||
"""
|
||||
return self._dt.strftime(dt_format)
|
||||
|
||||
def string_format(self, string_format: str, language: str | LanguageEnum) -> str:
|
||||
"""
|
||||
Format the datetime information stored in the instance using a custom string format.
|
||||
|
||||
Args:
|
||||
string_format (str): A format string where placeholders are keys from `dt_info_dict`.
|
||||
language (str): current language.
|
||||
|
||||
Returns:
|
||||
str: A formatted datetime string.
|
||||
"""
|
||||
language = self.language_transform(language=language)
|
||||
return string_format.format(**self.get_dt_info_dict(language=language))
|
||||
|
||||
@property
|
||||
def timestamp(self) -> int:
|
||||
"""
|
||||
Get the timestamp representation of the stored datetime.
|
||||
|
||||
Returns:
|
||||
int: A timestamp value.
|
||||
"""
|
||||
return int(self._dt.timestamp())
|
||||
|
|
@ -81,3 +81,241 @@ def get_trajectory_context(trajectory: Trajectory, step_sequence: List[Message])
|
|||
except Exception as e:
|
||||
logger.error(f"Error getting trajectory context: {e}")
|
||||
return f"Query: {trajectory.metadata.get('query', 'N/A')}"
|
||||
|
||||
|
||||
def parse_observation_response(response_text: str) -> List[dict]:
|
||||
"""Parse observation response to extract structured data"""
|
||||
# Pattern to match both Chinese and English observation formats
|
||||
pattern = r"信息:<(\d+)>\s*<>\s*<([^<>]+)>\s*<([^<>]*)>|Information:\s*<(\d+)>\s*<>\s*<([^<>]+)>\s*<([^<>]*)>"
|
||||
matches = re.findall(pattern, response_text, re.IGNORECASE | re.MULTILINE)
|
||||
|
||||
observations = []
|
||||
for match in matches:
|
||||
# Handle both Chinese and English patterns
|
||||
if match[0]: # Chinese pattern
|
||||
idx_str, content, keywords = match[0], match[1], match[2]
|
||||
else: # English pattern
|
||||
idx_str, content, keywords = match[3], match[4], match[5]
|
||||
|
||||
try:
|
||||
idx = int(idx_str)
|
||||
# Skip if content indicates no meaningful observation
|
||||
content_lower = content.lower().strip()
|
||||
if content_lower not in ['无', 'none', '', 'repeat']:
|
||||
observations.append({
|
||||
"index": idx,
|
||||
"content": content.strip(),
|
||||
"keywords": keywords.strip() if keywords else ""
|
||||
})
|
||||
except ValueError:
|
||||
logger.warning(f"Invalid index format: {idx_str}")
|
||||
continue
|
||||
|
||||
return observations
|
||||
|
||||
|
||||
def parse_observation_with_time_response(response_text: str) -> List[dict]:
|
||||
"""Parse observation with time response to extract structured data"""
|
||||
# Pattern to match both Chinese and English observation formats with time information
|
||||
# Chinese: 信息:<1> <时间信息或不输出> <明确的重要信息或"无"> <关键词>
|
||||
# English: Information: <1> <Time information or do not output> <Clear important information or "None"> <Keywords>
|
||||
pattern = r"信息:<(\d+)>\s*<([^<>]*)>\s*<([^<>]+)>\s*<([^<>]*)>|Information:\s*<(\d+)>\s*<([^<>]*)>\s*<([^<>]+)>\s*<([^<>]*)>"
|
||||
matches = re.findall(pattern, response_text, re.IGNORECASE | re.MULTILINE)
|
||||
|
||||
observations = []
|
||||
for match in matches:
|
||||
# Handle both Chinese and English patterns
|
||||
if match[0]: # Chinese pattern
|
||||
idx_str, time_info, content, keywords = match[0], match[1], match[2], match[3]
|
||||
else: # English pattern
|
||||
idx_str, time_info, content, keywords = match[4], match[5], match[6], match[7]
|
||||
|
||||
try:
|
||||
idx = int(idx_str)
|
||||
# Skip if content indicates no meaningful observation
|
||||
content_lower = content.lower().strip()
|
||||
if content_lower not in ['无', 'none', '', 'repeat']:
|
||||
observations.append({
|
||||
"index": idx,
|
||||
"time_info": time_info.strip() if time_info else "",
|
||||
"content": content.strip(),
|
||||
"keywords": keywords.strip() if keywords else ""
|
||||
})
|
||||
except ValueError:
|
||||
logger.warning(f"Invalid index format: {idx_str}")
|
||||
continue
|
||||
|
||||
return observations
|
||||
|
||||
|
||||
def parse_reflection_subjects_response(response_text: str, existing_subjects: List[str] = None) -> List[str]:
|
||||
"""Parse reflection subjects response to extract new subject attributes"""
|
||||
if existing_subjects is None:
|
||||
existing_subjects = []
|
||||
|
||||
# Split response into lines and clean up
|
||||
lines = response_text.strip().split('\n')
|
||||
subjects = []
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
# Skip empty lines, "None" responses, and existing subjects
|
||||
if (line and
|
||||
line not in ['无', 'None', ''] and
|
||||
line not in existing_subjects and
|
||||
not line.startswith('新增') and # Skip Chinese header
|
||||
not line.startswith('New ') and # Skip English header
|
||||
len(line) > 1): # Skip single character responses
|
||||
subjects.append(line)
|
||||
|
||||
logger.info(f"Parsed {len(subjects)} new reflection subjects from response")
|
||||
return subjects
|
||||
|
||||
|
||||
def parse_info_filter_response(response_text: str) -> List[tuple]:
|
||||
"""Parse info filter response to extract message scores"""
|
||||
import re
|
||||
|
||||
# Pattern to match both Chinese and English result formats
|
||||
# Chinese: 结果:<序号> <分数>
|
||||
# English: Result: <Index> <Score>
|
||||
pattern = r"结果:<(\d+)>\s*<([0-3])>|Result:\s*<(\d+)>\s*<([0-3])>"
|
||||
matches = re.findall(pattern, response_text, re.IGNORECASE | re.MULTILINE)
|
||||
|
||||
scores = []
|
||||
for match in matches:
|
||||
# Handle both Chinese and English patterns
|
||||
if match[0]: # Chinese pattern
|
||||
idx_str, score_str = match[0], match[1]
|
||||
else: # English pattern
|
||||
idx_str, score_str = match[2], match[3]
|
||||
|
||||
try:
|
||||
idx = int(idx_str)
|
||||
score = score_str
|
||||
scores.append((idx, score))
|
||||
except ValueError:
|
||||
logger.warning(f"Invalid index or score format: {idx_str}, {score_str}")
|
||||
continue
|
||||
|
||||
logger.info(f"Parsed {len(scores)} info filter scores from response")
|
||||
return scores
|
||||
|
||||
|
||||
def parse_long_contra_repeat_response(response_text: str) -> List[tuple]:
|
||||
"""Parse long contra repeat response to extract judgments"""
|
||||
import re
|
||||
|
||||
# Pattern to match both Chinese and English judgment formats
|
||||
# Chinese: 判断:<序号> <矛盾|被包含|无> <修改后的内容>
|
||||
# English: Judgment: <Index> <Contradiction|Contained|None> <Modified content>
|
||||
pattern = r"判断:<(\d+)>\s*<(矛盾|被包含|无)>\s*<([^<>]*)>|Judgment:\s*<(\d+)>\s*<(Contradiction|Contained|None)>\s*<([^<>]*)>"
|
||||
matches = re.findall(pattern, response_text, re.IGNORECASE | re.MULTILINE)
|
||||
|
||||
judgments = []
|
||||
for match in matches:
|
||||
# Handle both Chinese and English patterns
|
||||
if match[0]: # Chinese pattern
|
||||
idx_str, judgment, modified_content = match[0], match[1], match[2]
|
||||
else: # English pattern
|
||||
idx_str, judgment, modified_content = match[3], match[4], match[5]
|
||||
|
||||
try:
|
||||
idx = int(idx_str)
|
||||
judgments.append((idx, judgment, modified_content))
|
||||
except ValueError:
|
||||
logger.warning(f"Invalid index format: {idx_str}")
|
||||
continue
|
||||
|
||||
logger.info(f"Parsed {len(judgments)} long contra repeat judgments from response")
|
||||
return judgments
|
||||
|
||||
|
||||
def parse_update_insight_response(response_text: str, language: str = "en") -> str:
|
||||
"""Parse update insight response to extract updated insight content"""
|
||||
import re
|
||||
|
||||
# Pattern to match both Chinese and English insight formats
|
||||
# Chinese: {user_name}的资料: <信息>
|
||||
# English: {user_name}'s profile: <Information>
|
||||
if language in ["zh", "cn"]:
|
||||
pattern = r"的资料[::]\s*<([^<>]+)>"
|
||||
else:
|
||||
pattern = r"profile[::]\s*<([^<>]+)>"
|
||||
|
||||
matches = re.findall(pattern, response_text, re.IGNORECASE | re.MULTILINE)
|
||||
|
||||
if matches:
|
||||
insight_content = matches[0].strip()
|
||||
logger.info(f"Parsed insight content: {insight_content}")
|
||||
return insight_content
|
||||
|
||||
# Fallback: try to find content between angle brackets
|
||||
fallback_pattern = r"<([^<>]+)>"
|
||||
fallback_matches = re.findall(fallback_pattern, response_text)
|
||||
if fallback_matches:
|
||||
# Get the last match as it's likely the final answer
|
||||
insight_content = fallback_matches[-1].strip()
|
||||
logger.info(f"Parsed insight content (fallback): {insight_content}")
|
||||
return insight_content
|
||||
|
||||
logger.warning("No insight content found in response")
|
||||
return ""
|
||||
|
||||
|
||||
def load_memories_from_vector_store(workspace_id: str, filter_criteria: dict, top_k: int,
|
||||
memory_category: str = ""):
|
||||
"""
|
||||
Load memories from vector store based on filter criteria.
|
||||
|
||||
Args:
|
||||
workspace_id: The workspace identifier
|
||||
filter_criteria: Dictionary containing filter criteria for memory retrieval
|
||||
top_k: Maximum number of memories to retrieve
|
||||
memory_category: Category label to add to memory metadata
|
||||
|
||||
Returns:
|
||||
List of PersonalMemory objects loaded from vector store
|
||||
"""
|
||||
from reme_ai.schema.memory import PersonalMemory
|
||||
|
||||
try:
|
||||
# This is a placeholder implementation - in a real scenario, you would
|
||||
# integrate with your actual vector store (e.g., Chroma, Pinecone, etc.)
|
||||
logger.info(f"Loading memories from vector store for workspace: {workspace_id}")
|
||||
logger.info(f"Filter criteria: {filter_criteria}")
|
||||
logger.info(f"Top K: {top_k}")
|
||||
|
||||
# For now, return empty list as placeholder
|
||||
# In real implementation, this would:
|
||||
# 1. Connect to vector store
|
||||
# 2. Apply filter criteria
|
||||
# 3. Retrieve top_k memories
|
||||
# 4. Convert vector nodes to PersonalMemory objects
|
||||
# 5. Add memory_category to metadata
|
||||
|
||||
memories = []
|
||||
|
||||
# Placeholder: Create some example memories for testing
|
||||
if memory_category == "insight":
|
||||
for i in range(min(top_k, 2)):
|
||||
memory = PersonalMemory(
|
||||
workspace_id=workspace_id,
|
||||
memory_type="personal_insight",
|
||||
content=f"Sample insight memory {i + 1} for {filter_criteria.get('target', 'user')}",
|
||||
target=filter_criteria.get('target', 'user'),
|
||||
when_to_use=f"When analyzing user behavior patterns {i + 1}",
|
||||
author="system",
|
||||
metadata={
|
||||
"memory_category": memory_category,
|
||||
"filter_criteria": filter_criteria
|
||||
}
|
||||
)
|
||||
memories.append(memory)
|
||||
|
||||
logger.info(f"Loaded {len(memories)} memories from vector store")
|
||||
return memories
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading memories from vector store: {e}")
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ class UpdateVectorStoreOp(BaseLLMOp):
|
|||
def execute(self):
|
||||
workspace_id: str = self.context.workspace_id
|
||||
|
||||
deleted_memory_ids: List[str] = self.context.get("deleted_memory_ids", [])
|
||||
deleted_memory_ids: List[str] = self.context.response.metadata.get("deleted_memory_ids", [])
|
||||
if deleted_memory_ids:
|
||||
self.vector_store.delete(node_ids=deleted_memory_ids, workspace_id=workspace_id)
|
||||
logger.info(f"delete memory_ids={json.dumps(deleted_memory_ids, indent=2)}")
|
||||
|
||||
insert_memory_list: List[BaseMemory] | None = self.context.get("memory_list", [])
|
||||
insert_memory_list: List[BaseMemory] = self.context.response.metadata.get("memory_list", [])
|
||||
if insert_memory_list:
|
||||
insert_nodes: List[VectorNode] = [x.to_vector_node() for x in insert_memory_list]
|
||||
self.vector_store.insert(nodes=insert_nodes, workspace_id=workspace_id)
|
||||
|
|
|
|||
|
|
@ -56,3 +56,4 @@ async def main():
|
|||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
|
|
|
|||
116
test/test_update_insight_op.py
Normal file
116
test/test_update_insight_op.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple test script to verify the UpdateInsightOp implementation.
|
||||
This is a basic validation test to ensure the class structure is correct.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append('/Users/yuli/workspace/MemoryScope')
|
||||
|
||||
def test_update_insight_op_import():
|
||||
"""Test that we can import the UpdateInsightOp class"""
|
||||
try:
|
||||
from reme_ai.summary.personal.update_insight_op import UpdateInsightOp
|
||||
print("✓ Successfully imported UpdateInsightOp")
|
||||
return True
|
||||
except ImportError as e:
|
||||
print(f"✗ Failed to import UpdateInsightOp: {e}")
|
||||
return False
|
||||
|
||||
def test_personal_memory_import():
|
||||
"""Test that we can import PersonalMemory"""
|
||||
try:
|
||||
from reme_ai.schema.memory import PersonalMemory
|
||||
print("✓ Successfully imported PersonalMemory")
|
||||
return True
|
||||
except ImportError as e:
|
||||
print(f"✗ Failed to import PersonalMemory: {e}")
|
||||
return False
|
||||
|
||||
def test_op_utils_import():
|
||||
"""Test that we can import the utility functions"""
|
||||
try:
|
||||
from reme_ai.utils.op_utils import parse_update_insight_response
|
||||
print("✓ Successfully imported parse_update_insight_response")
|
||||
return True
|
||||
except ImportError as e:
|
||||
print(f"✗ Failed to import parse_update_insight_response: {e}")
|
||||
return False
|
||||
|
||||
def test_personal_memory_creation():
|
||||
"""Test PersonalMemory creation with reflection_subject"""
|
||||
try:
|
||||
from reme_ai.schema.memory import PersonalMemory
|
||||
|
||||
memory = PersonalMemory(
|
||||
workspace_id="test_workspace",
|
||||
content="User likes playing basketball",
|
||||
target="test_user",
|
||||
reflection_subject="hobbies",
|
||||
author="test_system"
|
||||
)
|
||||
|
||||
print(f"✓ Created PersonalMemory: {memory.content}")
|
||||
print(f" - Memory ID: {memory.memory_id}")
|
||||
print(f" - Target: {memory.target}")
|
||||
print(f" - Reflection Subject: {memory.reflection_subject}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to create PersonalMemory: {e}")
|
||||
return False
|
||||
|
||||
def test_parse_update_insight_response():
|
||||
"""Test the parse_update_insight_response function"""
|
||||
try:
|
||||
from reme_ai.utils.op_utils import parse_update_insight_response
|
||||
|
||||
# Test Chinese format
|
||||
chinese_response = "思考:用户喜欢篮球和足球\ntest_user的资料:<喜欢篮球和足球>"
|
||||
result_zh = parse_update_insight_response(chinese_response, "zh")
|
||||
print(f"✓ Parsed Chinese response: '{result_zh}'")
|
||||
|
||||
# Test English format
|
||||
english_response = "Thoughts: User likes basketball and football\ntest_user's profile: <Likes basketball and football>"
|
||||
result_en = parse_update_insight_response(english_response, "en")
|
||||
print(f"✓ Parsed English response: '{result_en}'")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to test parse_update_insight_response: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Run all tests"""
|
||||
print("Running UpdateInsightOp validation tests...\n")
|
||||
|
||||
tests = [
|
||||
test_personal_memory_import,
|
||||
test_op_utils_import,
|
||||
test_update_insight_op_import,
|
||||
test_personal_memory_creation,
|
||||
test_parse_update_insight_response
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test in tests:
|
||||
print(f"\nRunning {test.__name__}:")
|
||||
if test():
|
||||
passed += 1
|
||||
print()
|
||||
|
||||
print("=" * 50)
|
||||
print(f"Test Results: {passed}/{total} passed")
|
||||
|
||||
if passed == total:
|
||||
print("🎉 All tests passed! The UpdateInsightOp implementation looks good.")
|
||||
else:
|
||||
print("⚠️ Some tests failed. Please check the implementation.")
|
||||
|
||||
return passed == total
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
Loading…
Add table
Reference in a new issue