diff --git a/examples/api/chat_example.py b/examples/api/chat_example.py index b7781ef5..393b004e 100644 --- a/examples/api/chat_example.py +++ b/examples/api/chat_example.py @@ -54,7 +54,7 @@ def chat_example4(): memory_chat.memory_service.consolidate_memory() response = memory_chat.chat_with_memory(query="你知道我的乐器爱好是什么?", - add_messages=False) + history_message_strategy=None) print("回答2:\n" + response.message.content) print("记忆2:\n" + response.meta_data["memories"]) diff --git a/memoryscope/core/chat/api_memory_chat.py b/memoryscope/core/chat/api_memory_chat.py index d1e8d491..6a8078f7 100644 --- a/memoryscope/core/chat/api_memory_chat.py +++ b/memoryscope/core/chat/api_memory_chat.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Optional, Literal from memoryscope.constants.common_constants import MEMORIES from memoryscope.constants.language_constants import DEFAULT_HUMAN_NAME @@ -126,7 +126,7 @@ class ApiMemoryChat(BaseMemoryChat): system_prompt: Optional[str] = None, memory_prompt: Optional[str] = None, extra_memories: Optional[str] = None, - add_messages: bool = True, + history_message_strategy: Literal["auto", None] | int = "auto", remember_response: bool = True, **kwargs): """ @@ -138,7 +138,11 @@ class ApiMemoryChat(BaseMemoryChat): system_prompt (str, optional): System prompt. Defaults to the system_prompt in "memory_chat_prompt.yaml". memory_prompt (str, optional): Memory prompt. Defaults to the memory_prompt in "memory_chat_prompt.yaml". extra_memories (str, optional): Manually added user memory in this function. - add_messages (bool, optional): whether add not memorized messages to LLM. + history_message_strategy ("auto", None, int): + - If it is set to "auto", the history messages in the conversation will retain those that have not + yet been summarized. Default to "auto". + - If it is set to None, no conversation history will be saved. + - If it is set to an integer value "n", the most recent "n" messages will be retained. remember_response (bool, optional): Flag indicating whether to save the AI's response to memory. Defaults to False. Returns: @@ -181,8 +185,15 @@ class ApiMemoryChat(BaseMemoryChat): chat_messages.append(system_message) # Include past conversation history in the message list - if add_messages: - history_messages = self.memory_service.read_message() + if history_message_strategy: + history_messages = [] + + if history_message_strategy == "auto": + history_messages = self.memory_service.read_message() + + elif isinstance(history_message_strategy, int): + history_messages = self.memory_service.chat_messages[-history_message_strategy:] + if history_messages: chat_messages.extend(history_messages) diff --git a/memoryscope/core/chat/base_memory_chat.py b/memoryscope/core/chat/base_memory_chat.py index 5707c398..e311f072 100644 --- a/memoryscope/core/chat/base_memory_chat.py +++ b/memoryscope/core/chat/base_memory_chat.py @@ -1,5 +1,5 @@ from abc import ABCMeta, abstractmethod -from typing import Optional +from typing import Optional, Literal from memoryscope.core.service.base_memory_service import BaseMemoryService from memoryscope.core.utils.logger import Logger @@ -32,9 +32,30 @@ class BaseMemoryChat(metaclass=ABCMeta): system_prompt: Optional[str] = None, memory_prompt: Optional[str] = None, extra_memories: Optional[str] = None, - add_messages: bool = True, + history_message_strategy: Literal["auto", None] | int = "auto", remember_response: bool = True, **kwargs): + """ + The core function that carries out conversation with memory accepts user queries through query and returns the + conversation results through model_response. The retrieved memories are stored in the memories within meta_data. + Args: + query (str, optional): User's query, includes the user's question. + role_name (str, optional): User's role name. + system_prompt (str, optional): System prompt. Defaults to the system_prompt in "memory_chat_prompt.yaml". + memory_prompt (str, optional): Memory prompt. Defaults to the memory_prompt in "memory_chat_prompt.yaml". + extra_memories (str, optional): Manually added user memory in this function. + history_message_strategy ("auto", None, int): + - If it is set to "auto", the history messages in the conversation will retain those that have not + yet been summarized. Default to "auto". + - If it is set to None, no conversation history will be saved. + - If it is set to an integer value "n", the most recent "n" messages will be retained. + remember_response (bool, optional): Flag indicating whether to save the AI's response to memory. + Defaults to False. + Returns: + - ModelResponse: In non-streaming mode, returns a complete AI response. + - ModelResponseGen: In streaming mode, returns a generator yielding AI response parts. + - Memories: To obtain the memory by invoking the method of model_response.meta_data[MEMORIES] + """ raise NotImplementedError def run(self): diff --git a/memoryscope/core/chat/cli_memory_chat.py b/memoryscope/core/chat/cli_memory_chat.py index 3d610468..150c79b3 100644 --- a/memoryscope/core/chat/cli_memory_chat.py +++ b/memoryscope/core/chat/cli_memory_chat.py @@ -1,6 +1,6 @@ import os import time -from typing import Optional +from typing import Optional, Literal import questionary @@ -40,7 +40,7 @@ class CliMemoryChat(ApiMemoryChat): system_prompt: Optional[str] = None, memory_prompt: Optional[str] = None, extra_memories: Optional[str] = None, - add_messages: bool = True, + history_message_strategy: Literal["auto", None] | int = "auto", remember_response: bool = True, **kwargs): resp = super().chat_with_memory(query=query, @@ -48,7 +48,7 @@ class CliMemoryChat(ApiMemoryChat): system_prompt=system_prompt, memory_prompt=memory_prompt, extra_memories=extra_memories, - add_messages=add_messages, + history_message_strategy=history_message_strategy, remember_response=remember_response, **kwargs) diff --git a/memoryscope/core/operation/backend_operation.py b/memoryscope/core/operation/backend_operation.py index fd6a55a4..e4b4a41c 100644 --- a/memoryscope/core/operation/backend_operation.py +++ b/memoryscope/core/operation/backend_operation.py @@ -103,7 +103,7 @@ class BackendOperation(BaseWorkflow, BaseOperation): if self._loop_switch: self.run_operation() - def run_operation_backend(self): + def start_operation_backend(self): """ Initiates the background operation loop if it's not already running. Sets the _loop_switch to True and submits the _loop_operation to a thread from the global thread pool. diff --git a/memoryscope/core/operation/base_operation.py b/memoryscope/core/operation/base_operation.py index 600e3dd4..b5276a7c 100644 --- a/memoryscope/core/operation/base_operation.py +++ b/memoryscope/core/operation/base_operation.py @@ -50,7 +50,7 @@ class BaseOperation(metaclass=ABCMeta): """ raise NotImplementedError - def run_operation_backend(self): + def start_operation_backend(self): """ Placeholder method for running an operation specific to the backend. Intended to be overridden by subclasses if backend operations are required. diff --git a/memoryscope/core/service/base_memory_service.py b/memoryscope/core/service/base_memory_service.py index d997fa98..68acc566 100644 --- a/memoryscope/core/service/base_memory_service.py +++ b/memoryscope/core/service/base_memory_service.py @@ -28,6 +28,7 @@ class BaseMemoryService(metaclass=ABCMeta): self.kwargs = kwargs self._operation_dict: Dict[str, BaseOperation] = {} + self.chat_messages: List[Message] = [] self.logger = Logger.get_logger() @property @@ -51,7 +52,7 @@ class BaseMemoryService(metaclass=ABCMeta): def init_service(self, **kwargs): raise NotImplementedError - def start_backend_service(self): + def start_backend_service(self, name: str = None): pass def stop_backend_service(self, wait_service_end: bool = False): diff --git a/memoryscope/core/service/memory_scope_service.py b/memoryscope/core/service/memory_scope_service.py index 88c644d4..e60dfd2f 100644 --- a/memoryscope/core/service/memory_scope_service.py +++ b/memoryscope/core/service/memory_scope_service.py @@ -37,7 +37,6 @@ class MemoryScopeService(BaseMemoryService): if assistant_name: self.context.meta_data["assistant_name"] = assistant_name - self.chat_messages: List[Message] = [] self.message_lock = threading.Lock() def add_messages(self, messages: List[Message] | Message): @@ -89,13 +88,17 @@ class MemoryScopeService(BaseMemoryService): for name, operation_config in self.memory_operations_conf.items(): self.register_operation(name, operation_config, **kwargs) - def start_backend_service(self): + def start_backend_service(self, name: str = None): """ Start all backend operations. """ - for _, operation in self._operation_dict.items(): - if operation.operation_type == "backend": - operation.run_operation_backend() + for op_name, operation in self._operation_dict.items(): + if name: + if op_name == name: + operation.start_operation_backend() + else: + if operation.operation_type == "backend": + operation.start_operation_backend() def stop_backend_service(self, wait_service_end: bool = False): """ diff --git a/memoryscope/core/worker/backend/update_insight_worker.py b/memoryscope/core/worker/backend/update_insight_worker.py index fbc7698b..51f8d7e9 100644 --- a/memoryscope/core/worker/backend/update_insight_worker.py +++ b/memoryscope/core/worker/backend/update_insight_worker.py @@ -56,14 +56,15 @@ class UpdateInsightWorker(MemoryBaseWorker): return insight_node, filtered_nodes, max_score if use_dummy_ranker: - 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 + 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 - documents_vector = [x.vector for x in obs_nodes] - score_recall_list = cosine_similarity(key_vector, documents_vector) + 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)}"