mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-09-05 08:06:15 +00:00
fix empty content insight bug && modify print memory
This commit is contained in:
parent
666e78879c
commit
7b09f0bd2b
20 changed files with 217 additions and 170 deletions
|
|
@ -5,13 +5,12 @@ global_config:
|
|||
memory_chat:
|
||||
cli_memory_chat:
|
||||
class: chat.cli_memory_chat
|
||||
memory_service: memory_chat_service
|
||||
memory_service: chat_memory_service
|
||||
generation_model: dashscope_generation
|
||||
|
||||
memory_service:
|
||||
memory_chat_service:
|
||||
chat_memory_service:
|
||||
class: memory.service.chat_memory_service
|
||||
contextual_msg_count: 6
|
||||
memory_operations:
|
||||
read_message:
|
||||
class: memory.operation.frontend_operation
|
||||
|
|
@ -47,13 +46,13 @@ memory_service:
|
|||
class: memory.operation.write_memory_op
|
||||
workflow: info_filter,[get_observation|get_observation_with_time|load_today_memory],contra_repeat,store_memory
|
||||
description: "write observation memory of the user"
|
||||
interval_time: 5
|
||||
interval_time: 1
|
||||
|
||||
summary_memory:
|
||||
class: memory.operation.backend_operation
|
||||
workflow: load_obs_and_insight,get_reflection_subject,update_insight,long_contra_repeat,store_memory
|
||||
description: "summary observation memory of the user"
|
||||
interval_time: 30
|
||||
interval_time: 15
|
||||
|
||||
worker:
|
||||
dummy:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
system_prompt:
|
||||
cn: |
|
||||
你是一个叫MemoryScope的AI小助手,善于倾听用户的问题和心声,并给出用户建议,回答时使用中文,不要太冗长。
|
||||
你是一个叫MemoryScope的AI小助手,善于倾听用户的问题和心声,回答时使用中文,不要太冗长。
|
||||
en: |
|
||||
You are an AI assistant named MemoryScope, good at listening to users' questions and feelings, and providing them with advice. Respond in English without being too lengthy.
|
||||
You are an AI assistant named MemoryScope, good at listening to users' questions and feelings. Respond in English without being too lengthy.
|
||||
|
||||
memory_prompt:
|
||||
cn: |
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
from memory_scope.constants.common_constants import CHAT_MESSAGES, RESULT, CHAT_KWARGS
|
||||
from memory_scope.constants.common_constants import CHAT_KWARGS, CHAT_MESSAGES, RESULT
|
||||
from memory_scope.enumeration.message_role_enum import MessageRoleEnum
|
||||
from memory_scope.memory.operation.backend_operation import BackendOperation
|
||||
|
||||
|
||||
class WriteMemoryOp(BackendOperation):
|
||||
|
||||
def __init__(self,
|
||||
message_lock=None,
|
||||
contextual_msg_count: int = 6,
|
||||
**kwargs):
|
||||
def __init__(self, **kwargs):
|
||||
super(WriteMemoryOp, self).__init__(**kwargs)
|
||||
|
||||
self.message_lock = message_lock
|
||||
self.contextual_msg_count: int = contextual_msg_count
|
||||
self.message_lock = kwargs.get("message_lock", None)
|
||||
self.contextual_msg_min_count: int = kwargs.get("contextual_msg_min_count", 0)
|
||||
|
||||
def _run_operation(self, **kwargs):
|
||||
"""
|
||||
|
|
@ -41,15 +38,15 @@ class WriteMemoryOp(BackendOperation):
|
|||
chat_messages = chat_messages[:-1]
|
||||
|
||||
not_memorized_size = sum([not x.memorized for x in chat_messages])
|
||||
if not_memorized_size < self.contextual_msg_count:
|
||||
if not_memorized_size < self.contextual_msg_min_count:
|
||||
self.logger.info(f"not_memorized_size({not_memorized_size}) < "
|
||||
f"contextual_msg_count({self.contextual_msg_count}), skip.")
|
||||
f"contextual_msg_min_count({self.contextual_msg_min_count}), skip.")
|
||||
return
|
||||
|
||||
self.context.clear()
|
||||
|
||||
# Add additional arguments to the context
|
||||
kwargs.update({"contextual_msg_count": self.contextual_msg_count, **self.kwargs})
|
||||
kwargs.update(**self.kwargs)
|
||||
self.context[CHAT_KWARGS] = kwargs
|
||||
|
||||
# Include the most recent messages in the operation context
|
||||
|
|
@ -62,9 +59,8 @@ class WriteMemoryOp(BackendOperation):
|
|||
result = self.context.get(RESULT)
|
||||
|
||||
# set message memorized
|
||||
if self.message_lock:
|
||||
with self.message_lock:
|
||||
for message in chat_messages:
|
||||
message.memorized = True
|
||||
with self.message_lock:
|
||||
for message in chat_messages:
|
||||
message.memorized = True
|
||||
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -7,11 +7,26 @@ from memory_scope.utils.tool_functions import init_instance_by_config
|
|||
|
||||
|
||||
class ChatMemoryService(BaseMemoryService):
|
||||
def __init__(self, history_msg_count: int = 100, contextual_msg_count: int = 6, **kwargs):
|
||||
def __init__(self,
|
||||
history_msg_count: int = 100,
|
||||
contextual_msg_max_count: int = 20,
|
||||
contextual_msg_min_count: int = 0,
|
||||
**kwargs):
|
||||
"""
|
||||
init function.
|
||||
Args:
|
||||
history_msg_count (int): The conversation history in memory, control the quantity, and reduce memory usage.
|
||||
contextual_msg_max_count (int): The maximum context length in a conversation. If it exceeds this length,
|
||||
it will not be included in the context to prevent token overflow.
|
||||
contextual_msg_min_count (int): The minimum context length in a conversation. If it is shorter than this
|
||||
length, no conversation summary will be made and no long-term memory will be generated.
|
||||
kwargs (dict): other kwargs
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.history_msg_count: int = history_msg_count
|
||||
self.contextual_msg_count: int = contextual_msg_count
|
||||
assert self.history_msg_count >= self.contextual_msg_count
|
||||
self.contextual_msg_max_count: int = contextual_msg_max_count
|
||||
self.contextual_msg_min_count: int = contextual_msg_min_count
|
||||
assert history_msg_count >= contextual_msg_max_count >= contextual_msg_min_count
|
||||
|
||||
def add_messages(self, messages: List[Message] | Message):
|
||||
"""
|
||||
|
|
@ -26,17 +41,18 @@ class ChatMemoryService(BaseMemoryService):
|
|||
if isinstance(messages, Message):
|
||||
messages = [messages]
|
||||
|
||||
# Sort the messages by their creation time to maintain chronological order
|
||||
messages = sorted(messages, key=lambda x: x.time_created)
|
||||
with self.message_lock:
|
||||
# Append the sorted messages to the chat history
|
||||
self.chat_messages.extend(messages)
|
||||
|
||||
# Append the sorted messages to the chat history
|
||||
self.chat_messages.extend(messages)
|
||||
# Sort the messages by their creation time to maintain chronological order
|
||||
self.chat_messages.sort(key=lambda x: x.time_created)
|
||||
|
||||
# If the chat history exceeds the allowed message count, remove the oldest messages
|
||||
if len(self.chat_messages) > self.history_msg_count:
|
||||
gap_size = len(self.chat_messages) - self.history_msg_count
|
||||
for _ in range(gap_size):
|
||||
self.chat_messages.pop(0)
|
||||
# If the chat history exceeds the allowed message count, remove the oldest messages
|
||||
if len(self.chat_messages) > self.history_msg_count:
|
||||
gap_size = len(self.chat_messages) - self.history_msg_count
|
||||
for _ in range(gap_size):
|
||||
self.chat_messages.pop(0)
|
||||
|
||||
def do_operation(self, op_name: str, **kwargs):
|
||||
"""
|
||||
|
|
@ -69,13 +85,17 @@ class ChatMemoryService(BaseMemoryService):
|
|||
name=name,
|
||||
chat_messages=self.chat_messages,
|
||||
message_lock=self.message_lock,
|
||||
contextual_msg_count=self.contextual_msg_count)
|
||||
contextual_msg_max_count=self.contextual_msg_max_count,
|
||||
contextual_msg_min_count=self.contextual_msg_min_count)
|
||||
operation.init_workflow(**kwargs) # Initialize workflow for each operation
|
||||
|
||||
self._operation_dict[name] = operation
|
||||
self.logger.info(f"service={self.__class__.__name__} init operation={name}")
|
||||
|
||||
def start_backend_service(self):
|
||||
"""
|
||||
Start all backend operations.
|
||||
"""
|
||||
for _, operation in self._operation_dict.items():
|
||||
if operation.operation_type == "backend":
|
||||
# Run backend operations
|
||||
|
|
|
|||
|
|
@ -7,60 +7,51 @@ from memory_scope.memory.worker.memory_base_worker import MemoryBaseWorker
|
|||
from memory_scope.scheme.memory_node import MemoryNode
|
||||
from memory_scope.utils.datetime_handler import DatetimeHandler
|
||||
|
||||
PRINT_TEMPLATE = """
|
||||
The memories of {user_name} about {target_name}.
|
||||
|
||||
{obs_content}
|
||||
|
||||
|
||||
{insight_content}
|
||||
|
||||
|
||||
{expired_content}
|
||||
"""
|
||||
|
||||
|
||||
class PrintMemoryWorker(MemoryBaseWorker):
|
||||
FILE_PATH: str = __file__
|
||||
|
||||
def _run(self):
|
||||
# get long-term memory
|
||||
memory_node_list: List[MemoryNode] = self.memory_handler.get_memories(RETRIEVE_MEMORY_NODES)
|
||||
memory_node_list = sorted(memory_node_list, key=lambda x: x.timestamp, reverse=True)
|
||||
|
||||
expired_content_list: List[str] = ["----- expired -----"]
|
||||
obs_content_list: List[str] = ["----- observation -----"]
|
||||
insight_content_list: List[str] = ["----- insight -----"]
|
||||
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")
|
||||
line = f"{dt} {node.content}"
|
||||
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_content_list.append(f" {i} {line}")
|
||||
expired_memory_list.append(f"{dt}] {i}. {node.content}")
|
||||
|
||||
elif MemoryTypeEnum(node.memory_type) in [MemoryTypeEnum.OBSERVATION, MemoryTypeEnum.OBS_CUSTOMIZED]:
|
||||
j += 1
|
||||
obs_content_list.append(f" {j} {line} status={node.obs_reflected}")
|
||||
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_content_list.append(f" {k} {line}")
|
||||
insight_memory_list.append(f"{dt}] {k}. {node.content}")
|
||||
|
||||
obs_content = "\n".join(obs_content_list)
|
||||
insight_content = "\n".join(insight_content_list)
|
||||
expired_content = "\n".join(expired_content_list)
|
||||
result: str = PRINT_TEMPLATE.format(
|
||||
result: str = self.prompt_handler.print_template.format(
|
||||
user_name=self.user_name,
|
||||
target_name=self.target_name,
|
||||
obs_content=obs_content,
|
||||
insight_content=insight_content,
|
||||
expired_content=expired_content,
|
||||
).strip()
|
||||
observation_memory="\n".join(observation_memory_list),
|
||||
insight_memory="\n".join(insight_memory_list),
|
||||
expired_memory="\n".join(expired_memory_list)).strip()
|
||||
self.set_context(RESULT, result)
|
||||
|
|
|
|||
22
memory_scope/memory/worker/frontend/print_memory_worker.yaml
Normal file
22
memory_scope/memory/worker/frontend/print_memory_worker.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
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}
|
||||
|
|
@ -6,10 +6,11 @@ from memory_scope.memory.worker.memory_base_worker import MemoryBaseWorker
|
|||
class ReadMessageWorker(MemoryBaseWorker):
|
||||
|
||||
def _run(self):
|
||||
contextual_msg_count: int = self.chat_kwargs["contextual_msg_count"]
|
||||
|
||||
chat_messages = self.chat_messages.copy()
|
||||
chat_messages = [x for x in self.chat_messages if not x.memorized]
|
||||
if len(chat_messages) > 0 and chat_messages[-1].role == MessageRoleEnum.USER.value:
|
||||
chat_messages = chat_messages[:-1]
|
||||
|
||||
self.set_context(RESULT, chat_messages[-contextual_msg_count:])
|
||||
contextual_msg_max_count: int = self.chat_kwargs["contextual_msg_max_count"]
|
||||
chat_messages = chat_messages[-contextual_msg_max_count:]
|
||||
|
||||
self.set_context(RESULT, chat_messages)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class SemanticRankWorker(MemoryBaseWorker):
|
|||
return
|
||||
|
||||
# drop repeated
|
||||
memory_node_dict: Dict[str, MemoryNode] = {n.content: n for n in memory_node_list}
|
||||
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])
|
||||
|
|
|
|||
|
|
@ -64,8 +64,9 @@ class GetReflectionSubjectWorker(MemoryBaseWorker):
|
|||
# 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}) < is not enough, skip.")
|
||||
self.continue_run = False
|
||||
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
|
||||
|
|
|
|||
|
|
@ -85,6 +85,10 @@ class LongContraRepeatWorker(MemoryBaseWorker):
|
|||
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):
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class UpdateInsightWorker(MemoryBaseWorker):
|
|||
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", 10)
|
||||
self.update_insight_max_count: int = kwargs.get("update_insight_max_count", 5)
|
||||
|
||||
def filter_obs_nodes(self,
|
||||
insight_node: MemoryNode,
|
||||
|
|
@ -165,7 +165,8 @@ class UpdateInsightWorker(MemoryBaseWorker):
|
|||
"""
|
||||
insight_nodes: List[MemoryNode] = self.memory_handler.get_memories(INSIGHT_NODES)
|
||||
not_updated_nodes: List[MemoryNode] = self.memory_handler.get_memories(NOT_UPDATED_NODES)
|
||||
not_reflected_nodes: List[MemoryNode] = self.memory_handler.get_memories(NOT_REFLECTED_NODES)
|
||||
not_reflected_nodes: List[MemoryNode] = self.memory_handler.get_memories(keys=[NOT_REFLECTED_NODES,
|
||||
NOT_UPDATED_NODES])
|
||||
|
||||
if not insight_nodes:
|
||||
self.logger.warning("insight_nodes is empty, stopping processing.")
|
||||
|
|
@ -201,10 +202,14 @@ class UpdateInsightWorker(MemoryBaseWorker):
|
|||
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_handler.delete_memories(empty_nodes)
|
||||
|
||||
for node in not_updated_nodes:
|
||||
node.obs_updated = 1
|
||||
node.action_status = ActionStatusEnum.MODIFIED
|
||||
|
||||
for node in not_reflected_nodes:
|
||||
node.obs_updated = 1
|
||||
node.action_status = ActionStatusEnum.MODIFIED
|
||||
# for node in not_reflected_nodes:
|
||||
# node.obs_updated = 1
|
||||
# node.action_status = ActionStatusEnum.MODIFIED
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class ContraRepeatWorker(MemoryBaseWorker):
|
|||
all_obs_nodes: List[MemoryNode] = self.memory_handler.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
|
||||
# self.continue_run = False
|
||||
return
|
||||
|
||||
today_obs_nodes: List[MemoryNode] = self.memory_handler.get_memories(TODAY_NODES)
|
||||
|
|
@ -55,6 +55,10 @@ class ContraRepeatWorker(MemoryBaseWorker):
|
|||
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):
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ class InfoFilterWorker(MemoryBaseWorker):
|
|||
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}")
|
||||
self.logger.warning(self.prompt_handler.prompt_dict)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ info_filter_few_shot:
|
|||
4 {user_name}:我今天心情不好,可以安慰我一下吗?
|
||||
5 {user_name}:能给我整理一张如何使用大模型的技巧列表吗,要求内容尽量精简。
|
||||
6 {user_name}:记一下,明天下午3点提醒我去拿一下文件。
|
||||
|
||||
思考:从第1句可以确定推断出张三是{user_name}同事这一重要信息。
|
||||
结果:<1> <3>
|
||||
思考:第2句不包含{user_name}信息。
|
||||
|
|
@ -55,6 +56,7 @@ info_filter_few_shot:
|
|||
5 {user_name}:假如我要和一个女人准备要孩子,我作为男人,怎么保护女人和孩子以及怎么备孕确保精子质量高对后代好
|
||||
6 {user_name}:我和你一起出去玩,你会感觉开心吗?
|
||||
7 {user_name}:林浅,一位对未来充满好奇的年轻女孩,偶然间发现了这家能寄信给未来的邮局。出于对逝去祖父的怀念,她决定写下一封信,寄给五年后的自己,希望能收到祖父生前未说完的故事。五年期限将至,当她几乎忘记这段往事时,一封泛黄的回信悄然降临,不仅带来了祖父未完的冒险故事,还藏着一段关于勇气、爱与自我发现的深刻启示。续写成3000字小说。
|
||||
|
||||
思考:从第1句可以确定得出{user_name}工作单位是阿里巴巴这一重要信息。
|
||||
结果:<1> <3>
|
||||
思考:从第2句可以猜测{user_name}近期露天睡觉,是不确定的信息。
|
||||
|
|
@ -78,6 +80,8 @@ info_filter_few_shot:
|
|||
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}假设的信息。
|
||||
|
|
@ -88,6 +92,8 @@ info_filter_few_shot:
|
|||
结果:<4> <0>
|
||||
思考:第5句是{user_name}虚构的内容。
|
||||
结果:<5> <1>
|
||||
思考:第6句是{user_name}的疑问句,没有包含信息。
|
||||
结果:<6> <0>
|
||||
|
||||
en: |
|
||||
Example 1
|
||||
|
|
@ -98,6 +104,7 @@ info_filter_few_shot:
|
|||
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}.
|
||||
|
|
@ -120,6 +127,7 @@ info_filter_few_shot:
|
|||
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.
|
||||
|
|
@ -142,6 +150,8 @@ info_filter_few_shot:
|
|||
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}.
|
||||
|
|
@ -152,6 +162,8 @@ info_filter_few_shot:
|
|||
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: |
|
||||
|
|
|
|||
|
|
@ -34,9 +34,7 @@ class BaseModel(metaclass=ABCMeta):
|
|||
self.raise_exception: bool = raise_exception
|
||||
self.kwargs: dict = kwargs
|
||||
|
||||
self.data = {}
|
||||
self._model: Any = None
|
||||
|
||||
self.logger = Logger.get_logger()
|
||||
|
||||
@property
|
||||
|
|
@ -56,44 +54,31 @@ class BaseModel(metaclass=ABCMeta):
|
|||
return self._model
|
||||
|
||||
@abstractmethod
|
||||
def before_call(self, **kwargs) -> None:
|
||||
"""prepare data before call
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
def before_call(self, model_response: ModelResponse, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def after_call(self, model_response: ModelResponse | ModelResponseGen,
|
||||
**kwargs) -> ModelResponse | ModelResponseGen:
|
||||
"""
|
||||
:param model_response:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
def after_call(self, model_response: ModelResponse, **kwargs) -> ModelResponse | ModelResponseGen:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _call(self, stream: bool = False, **kwargs) -> ModelResponse | ModelResponseGen:
|
||||
"""
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
def _call(self, model_response: ModelResponse, stream: bool = False, **kwargs):
|
||||
pass
|
||||
|
||||
def call(self, stream: bool = False, **kwargs) -> ModelResponse | ModelResponseGen:
|
||||
"""
|
||||
:param stream: only llm needs stream
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
with Timer(self.__class__.__name__, time_log_type="none") as t:
|
||||
self.before_call(stream=stream, **kwargs)
|
||||
model_response = ModelResponse(m_type=self.m_type)
|
||||
|
||||
self.before_call(stream=stream, model_response=model_response, **kwargs)
|
||||
for i in range(self.max_retries):
|
||||
if self.raise_exception:
|
||||
model_response = self._call(stream=stream, **kwargs)
|
||||
self._call(stream=stream, model_response=model_response, **kwargs)
|
||||
else:
|
||||
try:
|
||||
model_response = self._call(stream=stream, **kwargs)
|
||||
self._call(stream=stream, model_response=model_response, **kwargs)
|
||||
except Exception as e:
|
||||
model_response = ModelResponse(m_type=self.m_type, status=False, details=e.args)
|
||||
model_response.status = False
|
||||
model_response.details = e.args
|
||||
|
||||
if isinstance(model_response, ModelResponse) and not model_response.status:
|
||||
self.logger.warning(f"call model={self.model_name} failed! {t.cost_str} retry_cnt={i} "
|
||||
|
|
@ -103,27 +88,23 @@ class BaseModel(metaclass=ABCMeta):
|
|||
return self.after_call(stream=stream, model_response=model_response, **kwargs)
|
||||
|
||||
@abstractmethod
|
||||
async def _async_call(self, **kwargs) -> ModelResponse:
|
||||
"""
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
async def _async_call(self, model_response: ModelResponse, **kwargs) -> ModelResponse:
|
||||
pass
|
||||
|
||||
async def async_call(self, **kwargs) -> ModelResponse:
|
||||
""" 异步不需要stream
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
with Timer(self.__class__.__name__, time_log_type="none") as t:
|
||||
self.before_call(**kwargs)
|
||||
model_response = ModelResponse(m_type=self.m_type)
|
||||
|
||||
self.before_call(model_response=model_response, **kwargs)
|
||||
for i in range(self.max_retries):
|
||||
if self.raise_exception:
|
||||
model_response = self._async_call(**kwargs)
|
||||
await self._async_call(model_response=model_response, **kwargs)
|
||||
else:
|
||||
try:
|
||||
model_response = self._async_call(**kwargs)
|
||||
await self._async_call(model_response=model_response, **kwargs)
|
||||
except Exception as e:
|
||||
model_response = ModelResponse(m_type=self.m_type, status=False, details=e.args)
|
||||
model_response.status = False
|
||||
model_response.details = e.args
|
||||
|
||||
if not model_response.status:
|
||||
self.logger.warning(f"async_call model={self.model_name} failed! {t.cost_str} retry_cnt={i} "
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ class LlamaIndexEmbeddingModel(BaseModel):
|
|||
|
||||
MODEL_REGISTRY.register("dashscope_embedding", DashScopeEmbedding)
|
||||
|
||||
def before_call(self, **kwargs):
|
||||
def before_call(self, model_response: ModelResponse, **kwargs):
|
||||
text: str | List[str] = kwargs.pop("text", "")
|
||||
if isinstance(text, str):
|
||||
text = [text]
|
||||
self.data = dict(texts=text)
|
||||
model_response.meta_data["data"] = dict(texts=text)
|
||||
|
||||
def after_call(self, model_response: ModelResponse, **kwargs) -> ModelResponse:
|
||||
embeddings = model_response.raw
|
||||
|
|
@ -47,7 +47,7 @@ class LlamaIndexEmbeddingModel(BaseModel):
|
|||
model_response.embedding_results = embeddings
|
||||
return model_response
|
||||
|
||||
def _call(self, **kwargs) -> ModelResponse:
|
||||
def _call(self, model_response: ModelResponse, **kwargs):
|
||||
"""
|
||||
Executes a synchronous call to generate embeddings for the input data.
|
||||
|
||||
|
|
@ -61,9 +61,9 @@ class LlamaIndexEmbeddingModel(BaseModel):
|
|||
Returns:
|
||||
ModelResponse: An object containing the embedding results and the model type.
|
||||
"""
|
||||
return ModelResponse(m_type=self.m_type, raw=self.model.get_text_embedding_batch(**self.data))
|
||||
model_response.raw = self.model.get_text_embedding_batch(**model_response.meta_data["data"])
|
||||
|
||||
async def _async_call(self, **kwargs) -> ModelResponse:
|
||||
async def _async_call(self, model_response: ModelResponse, **kwargs):
|
||||
"""
|
||||
Executes an asynchronous call to generate embeddings for the input data.
|
||||
|
||||
|
|
@ -77,4 +77,4 @@ class LlamaIndexEmbeddingModel(BaseModel):
|
|||
Returns:
|
||||
ModelResponse: An object encapsulating the embedding output and the model's type.
|
||||
"""
|
||||
return ModelResponse(m_type=self.m_type, raw=await self.model.aget_text_embedding_batch(**self.data))
|
||||
model_response.raw = await self.model.aget_text_embedding_batch(**model_response.meta_data["data"])
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class LlamaIndexGenerationModel(BaseModel):
|
|||
|
||||
MODEL_REGISTRY.register("dashscope_generation", DashScope)
|
||||
|
||||
def before_call(self, **kwargs):
|
||||
def before_call(self, model_response: ModelResponse, **kwargs):
|
||||
"""
|
||||
Prepares the input data before making a call to the language model.
|
||||
It accepts either a 'prompt' directly or a list of 'messages'.
|
||||
|
|
@ -32,6 +32,7 @@ class LlamaIndexGenerationModel(BaseModel):
|
|||
Raises an error if neither 'prompt' nor 'messages' are supplied.
|
||||
|
||||
Args:
|
||||
model_response: model_response
|
||||
**kwargs: Arbitrary keyword arguments including 'prompt' and 'messages'.
|
||||
|
||||
Raises:
|
||||
|
|
@ -41,15 +42,16 @@ class LlamaIndexGenerationModel(BaseModel):
|
|||
messages: List[Message] | List[dict] = kwargs.pop("messages", [])
|
||||
|
||||
if prompt:
|
||||
self.data = {"prompt": prompt}
|
||||
data = {"prompt": prompt}
|
||||
elif messages:
|
||||
if isinstance(messages[0], dict):
|
||||
self.data = {"messages": [ChatMessage(role=msg["role"], content=msg["content"]) for msg in messages]}
|
||||
data = {"messages": [ChatMessage(role=msg["role"], content=msg["content"]) for msg in messages]}
|
||||
else:
|
||||
self.data = {"messages": [ChatMessage(role=msg.role, content=msg.content) for msg in messages]}
|
||||
data = {"messages": [ChatMessage(role=msg.role, content=msg.content) for msg in messages]}
|
||||
else:
|
||||
raise RuntimeError("prompt and messages are both empty!")
|
||||
self.data.update(**kwargs)
|
||||
data.update(**kwargs)
|
||||
model_response.meta_data["data"] = data
|
||||
|
||||
def after_call(self,
|
||||
model_response: ModelResponse,
|
||||
|
|
@ -76,24 +78,23 @@ class LlamaIndexGenerationModel(BaseModel):
|
|||
|
||||
return model_response
|
||||
|
||||
def _call(self, stream: bool = False, **kwargs) -> ModelResponse | ModelResponseGen:
|
||||
assert "prompt" in self.data or "messages" in self.data
|
||||
results = ModelResponse(m_type=self.m_type)
|
||||
def _call(self, model_response: ModelResponse, stream: bool = False, **kwargs):
|
||||
data = model_response.meta_data["data"]
|
||||
|
||||
if "prompt" in self.data:
|
||||
if "prompt" in data:
|
||||
if stream:
|
||||
response = self.model.stream_complete(**self.data)
|
||||
model_response.raw = self.model.stream_complete(**data)
|
||||
else:
|
||||
response = self.model.complete(**self.data)
|
||||
model_response.raw = self.model.complete(**data)
|
||||
elif "messages" in data:
|
||||
if stream:
|
||||
model_response.raw = self.model.stream_chat(**data)
|
||||
else:
|
||||
model_response.raw = self.model.chat(**data)
|
||||
else:
|
||||
if stream:
|
||||
response = self.model.stream_chat(**self.data)
|
||||
else:
|
||||
response = self.model.chat(**self.data)
|
||||
results.raw = response
|
||||
return results
|
||||
raise RuntimeError("prompt or messages is missing!")
|
||||
|
||||
async def _async_call(self, **kwargs) -> ModelResponse:
|
||||
async def _async_call(self, model_response: ModelResponse, **kwargs):
|
||||
"""
|
||||
Asynchronously calls the language model with the provided prompt or message history,
|
||||
and packages the raw response into a ModelResponse object.
|
||||
|
|
@ -108,13 +109,11 @@ class LlamaIndexGenerationModel(BaseModel):
|
|||
Returns:
|
||||
ModelResponse: An object containing the raw response from the language model.
|
||||
"""
|
||||
assert "prompt" in self.data or "messages" in self.data
|
||||
results = ModelResponse(m_type=self.m_type)
|
||||
data = model_response.meta_data["data"]
|
||||
|
||||
if "prompt" in self.data:
|
||||
response = await self.model.acomplete(**self.data)
|
||||
if "prompt" in data:
|
||||
model_response.raw = await self.model.acomplete(**data)
|
||||
elif "messages" in data:
|
||||
model_response.raw = await self.model.achat(**data)
|
||||
else:
|
||||
response = await self.model.achat(**self.data)
|
||||
|
||||
results.raw = response
|
||||
return results
|
||||
raise RuntimeError("prompt or messages is missing!")
|
||||
|
|
|
|||
|
|
@ -20,26 +20,29 @@ class LlamaIndexRankModel(BaseModel):
|
|||
|
||||
MODEL_REGISTRY.register("dashscope_rank", DashScopeRerank)
|
||||
|
||||
def before_call(self, **kwargs) -> None:
|
||||
def before_call(self, model_response: ModelResponse, **kwargs):
|
||||
"""
|
||||
Prepares necessary data before the ranking call by extracting the query and documents,
|
||||
ensuring they are valid, and initializing nodes with dummy scores.
|
||||
|
||||
Args:
|
||||
model_response: model response
|
||||
**kwargs: Keyword arguments containing 'query' and 'documents'.
|
||||
"""
|
||||
query: str = kwargs.pop("query", "")
|
||||
documents: List[str] = kwargs.pop("documents", [])
|
||||
if isinstance(documents, str):
|
||||
documents = [documents]
|
||||
|
||||
assert query and documents, f"query or documents is empty! query={query}, documents={len(documents)}"
|
||||
assert query and documents and all(documents), \
|
||||
f"query or documents is empty! query={query}, documents={len(documents)}"
|
||||
|
||||
# Using -1.0 as dummy scores
|
||||
nodes = [NodeWithScore(node=Node(text=doc), score=-1.0) for doc in documents]
|
||||
self._get_documents_mapping(documents)
|
||||
|
||||
self.data = {"nodes": nodes, "query_str": query}
|
||||
model_response.meta_data.update({
|
||||
"data": {"nodes": nodes, "query_str": query},
|
||||
"documents_map": {doc: idx for idx, doc in enumerate(documents)},
|
||||
})
|
||||
|
||||
def after_call(self, model_response: ModelResponse, **kwargs) -> ModelResponse:
|
||||
"""
|
||||
|
|
@ -56,13 +59,14 @@ class LlamaIndexRankModel(BaseModel):
|
|||
if not model_response.rank_scores:
|
||||
model_response.rank_scores = {}
|
||||
|
||||
documents_map = model_response.meta_data["documents_map"]
|
||||
for node in model_response.raw:
|
||||
text = node.node.text
|
||||
idx = self.documents_map[text]
|
||||
idx = documents_map[text]
|
||||
model_response.rank_scores[idx] = node.score
|
||||
return model_response
|
||||
|
||||
def _call(self, **kwargs) -> ModelResponse:
|
||||
def _call(self, model_response: ModelResponse, **kwargs):
|
||||
"""
|
||||
Executes the ranking process by passing prepared data to the model's postprocessing method.
|
||||
|
||||
|
|
@ -72,7 +76,7 @@ class LlamaIndexRankModel(BaseModel):
|
|||
Returns:
|
||||
ModelResponse: A response object encapsulating the ranked nodes.
|
||||
"""
|
||||
return ModelResponse(m_type=self.m_type, raw=self.model.postprocess_nodes(**self.data))
|
||||
model_response.raw = self.model.postprocess_nodes(**model_response.meta_data["data"])
|
||||
|
||||
async def _async_call(self, **kwargs) -> ModelResponse:
|
||||
"""
|
||||
|
|
@ -84,15 +88,4 @@ class LlamaIndexRankModel(BaseModel):
|
|||
Returns:
|
||||
ModelResponse: A response object encapsulating the ranked nodes.
|
||||
"""
|
||||
return self._call(**kwargs)
|
||||
|
||||
def _get_documents_mapping(self, documents):
|
||||
"""
|
||||
Generates a mapping of each document to its index within the provided document list.
|
||||
|
||||
Args:
|
||||
documents (List[str]): The list of documents.
|
||||
"""
|
||||
self.documents_map = {}
|
||||
for idx, doc in enumerate(documents):
|
||||
self.documents_map[doc] = idx
|
||||
raise NotImplementedError
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ class ModelResponse(BaseModel):
|
|||
|
||||
raw: Any = Field("", description="Raw response from model call")
|
||||
|
||||
meta_data: Dict[str, Any] = Field({}, description="meta data for model response")
|
||||
|
||||
def __str__(self, max_size=100, **kwargs):
|
||||
result = {}
|
||||
for key, value in self.model_dump().items():
|
||||
|
|
|
|||
|
|
@ -88,10 +88,28 @@ class MemoryHandler(object):
|
|||
|
||||
memory_ids: List[str] = self._key_id_dict.get(key.strip())
|
||||
if memory_ids:
|
||||
memories.update({x: self._id_memory_dict[x] for x in memory_ids})
|
||||
memories.update({x: self._id_memory_dict[x] for x in memory_ids if x in self._id_memory_dict})
|
||||
|
||||
return list(memories.values())
|
||||
|
||||
def delete_memories(self, nodes: MemoryNode | List[MemoryNode], key: str = None):
|
||||
if isinstance(nodes, MemoryNode):
|
||||
nodes = [nodes]
|
||||
|
||||
for n in nodes:
|
||||
_id = n.memory_id
|
||||
if _id in self._id_memory_dict:
|
||||
self._id_memory_dict.pop(_id, None)
|
||||
|
||||
if key is None:
|
||||
for _, id_list in self._key_id_dict.items():
|
||||
if _id in id_list:
|
||||
id_list.remove(_id)
|
||||
else:
|
||||
id_list = self._key_id_dict[key]
|
||||
if _id in id_list:
|
||||
id_list.remove(_id)
|
||||
|
||||
def update_memories(self, keys: str = "", nodes: MemoryNode | List[MemoryNode] = None):
|
||||
update_memories: Dict[str, MemoryNode] = {n.memory_id: n for n in self.get_memories(keys=keys)}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue