redefine memory chat interface

This commit is contained in:
jinli.yl 2024-07-26 16:23:28 +08:00
parent 5d6e6c0f21
commit 149df5ffa6
6 changed files with 170 additions and 129 deletions

View file

@ -74,7 +74,6 @@ class ApiMemoryChat(BaseMemoryChat):
self._memory_service: BaseMemoryService = self.context.memory_service_dict[self._memory_service]
# init service & update kwargs
self._memory_service.init_service(human_name=self.human_name, assistant_name=self.assistant_name)
self._memory_service.start_backend_service()
return self._memory_service
@property
@ -95,62 +94,68 @@ class ApiMemoryChat(BaseMemoryChat):
self._generation_model = self.context.model_dict[self._generation_model]
return self._generation_model
def chat_with_memory(self, query: str, role_name: str = "") -> ModelResponse | ModelResponseGen:
"""
Engages in a conversation with the AI model, utilizing conversation memory.
The function sends the user's query, incorporates conversation history and memory,
and optionally remembers the AI's response based on the user's preference.
Args:
query (str): The user's input or query for the AI.
role_name (str, optional): The user's name, default value is human_name.
Returns:
- ModelResponse: In non-streaming mode, returns a complete AI response.
- ModelResponseGen: In streaming mode, returns a generator yielding AI response parts.
Side Effects:
- Updates the conversation memory with the query of user and (optionally) the response of AI.
- Retrieves and includes historical messages and memory content in the context of conversation.
"""
def get_new_message(self, query: str, role_name: str = "") -> Message:
if not role_name:
role_name = self.human_name
new_message: Message = Message(role=MessageRoleEnum.USER.value, role_name=role_name, content=query)
self.add_messages(new_message)
messages: List[Message] = []
return Message(role=MessageRoleEnum.USER.value, role_name=role_name, content=query)
def get_system_message_with_memory(self, memories: str) -> Message:
# Incorporate memory into the system prompt if available
system_prompt = self.prompt_handler.system_prompt
memories: str = self.memory_service.retrieve_memory()
if memories:
memory_prompt = self.prompt_handler.memory_prompt
system_prompt = "\n".join([x.strip() for x in [system_prompt, memory_prompt, memories]])
messages.append(Message(role=MessageRoleEnum.SYSTEM, content=system_prompt))
return Message(role=MessageRoleEnum.SYSTEM, content=system_prompt)
def chat_with_memory(self,
query: str,
role_name: str = "",
remember_response: bool = True):
chat_messages: List[Message] = []
new_message: Message = self.get_new_message(query=query, role_name=role_name)
# To retrieve memory, prepare the query timestamp and role name by adding new_message.
memories: str = self.memory_service.retrieve_memory(query=new_message.content,
role_name=new_message.role_name,
timestamp=new_message.time_created)
# format system_message with memories
system_message: Message = self.get_system_message_with_memory(memories=memories)
chat_messages.append(system_message)
# Include past conversation history in the message list
history_messages = self.memory_service.read_message()
if history_messages:
messages.extend(history_messages)
chat_messages.extend(history_messages)
# Append the current user's message to the conversation context
messages.append(new_message)
self.logger.info(f"messages={messages}")
chat_messages.append(new_message)
self.logger.info(f"chat_messages={chat_messages}")
result = self.generation_model.call(messages=messages, stream=self.stream, **self.generation_model_kwargs)
resp = self.generation_model.call(messages=chat_messages, stream=self.stream, **self.generation_model_kwargs)
if self.stream:
assert isinstance(result, ModelResponseGen)
assert isinstance(resp, ModelResponseGen)
model_response: ModelResponse | None = None
for model_response in result:
for model_response in resp:
yield model_response
if model_response and model_response.message:
self.add_messages(model_response.message)
if remember_response:
if model_response and model_response.message:
model_response.message.role_name = self.assistant_name
self.memory_service.add_messages([new_message, model_response.message])
else:
self.logger.info("model_response or model_response.message is empty!")
else:
assert isinstance(result, ModelResponse)
model_response: ModelResponse = result
if model_response and model_response.message:
self.add_messages(model_response.message)
assert isinstance(resp, ModelResponse)
model_response: ModelResponse = resp
if remember_response:
if model_response and model_response.message:
model_response.message.role_name = self.assistant_name
self.memory_service.add_messages([new_message, model_response.message])
else:
self.logger.info("model_response or model_response.message is empty!")
return model_response

View file

@ -18,19 +18,27 @@ class BaseMemoryChat(metaclass=ABCMeta):
self.logger = Logger.get_logger()
@abstractmethod
def chat_with_memory(self, query: str, role_name: str = ""):
def get_new_message(self, query: str, role_name: str = "") -> Message:
raise NotImplementedError
@abstractmethod
def get_system_message_with_memory(self, memories: str) -> Message:
raise NotImplementedError
@abstractmethod
def chat_with_memory(self,
query: str,
role_name: str = "",
remember_response: bool = True):
"""
Initiates a chat interaction using the memory service, with the provided query as input.
Args:
query (str): The user's query or message to start the chat.
role_name (str): The role's name.
Returns:
This method should return the chat response generated after processing the query
with the associated memory context. The actual return type and content are defined by the implementing
subclass.
remember_response (bool): whether update memory service.
"""
raise NotImplementedError
@property
def memory_service(self) -> BaseMemoryService:
@ -45,6 +53,9 @@ class BaseMemoryChat(metaclass=ABCMeta):
def add_messages(self, messages: List[Message] | Message):
self.memory_service.add_messages(messages)
def start_backend_service(self):
self.memory_service.start_backend_service()
def do_memory_operation(self, op_name: str, **kwargs):
return self.memory_service.do_operation(op_name=op_name, **kwargs)

View file

@ -100,7 +100,6 @@ class CliMemoryChat(BaseMemoryChat):
self._memory_service: BaseMemoryService = self.context.memory_service_dict[self._memory_service]
# init service & update kwargs
self._memory_service.init_service(human_name=self.human_name, assistant_name=self.assistant_name)
self._memory_service.start_backend_service()
return self._memory_service
@property
@ -121,53 +120,71 @@ class CliMemoryChat(BaseMemoryChat):
self._generation_model = self.context.model_dict[self._generation_model]
return self._generation_model
def chat_with_memory(self, query: str, role_name: str = "") -> ModelResponse | ModelResponseGen:
"""
Engages in a conversation with the AI model, utilizing conversation memory.
The function sends the user's query, incorporates conversation history and memory,
and optionally remembers the AI's response based on the user's preference.
Args:
query (str): The user's input or query for the AI.
role_name (str, optional): The user's name, default value is human_name.
Returns:
- ModelResponse: In non-streaming mode, returns a complete AI response.
- ModelResponseGen: In streaming mode, returns a generator yielding AI response parts.
Side Effects:
- Updates the conversation memory with the query of user and (optionally) the response of AI.
- Retrieves and includes historical messages and memory content in the context of conversation.
"""
def get_new_message(self, query: str, role_name: str = "") -> Message:
if not role_name:
role_name = self.human_name
new_message: Message = Message(role=MessageRoleEnum.USER.value, role_name=role_name, content=query)
self.add_messages(new_message)
messages: List[Message] = []
return Message(role=MessageRoleEnum.USER.value, role_name=role_name, content=query)
def get_system_message_with_memory(self, memories: str) -> Message:
# Incorporate memory into the system prompt if available
system_prompt = self.prompt_handler.system_prompt
memories: str = self.memory_service.retrieve_memory()
if memories:
memory_prompt = self.prompt_handler.memory_prompt
system_prompt = "\n".join([x.strip() for x in [system_prompt, memory_prompt, memories]])
messages.append(Message(role=MessageRoleEnum.SYSTEM, content=system_prompt))
return Message(role=MessageRoleEnum.SYSTEM, content=system_prompt)
def chat_with_memory(self,
query: str,
role_name: str = "",
remember_response: bool = True):
chat_messages: List[Message] = []
new_message: Message = self.get_new_message(query=query, role_name=role_name)
# To retrieve memory, prepare the query timestamp and role name by adding new_message.
memories: str = self.memory_service.retrieve_memory(query=new_message.content,
role_name=new_message.role_name,
timestamp=new_message.time_created)
# format system_message with memories
system_message: Message = self.get_system_message_with_memory(memories=memories)
chat_messages.append(system_message)
# Include past conversation history in the message list
history_messages = self.memory_service.read_message()
if history_messages:
messages.extend(history_messages)
chat_messages.extend(history_messages)
# Append the current user's message to the conversation context
messages.append(new_message)
self.logger.info(f"messages={messages}")
chat_messages.append(new_message)
self.logger.info(f"chat_messages={chat_messages}")
# Invoke the Language Model with the constructed message context, respecting streaming setting
return self.generation_model.call(messages=messages,
resp = self.generation_model.call(messages=chat_messages,
stream=self.stream,
**self.generation_model_kwargs)
if self.stream:
assert isinstance(resp, ModelResponseGen)
model_response: ModelResponse | None = None
for model_response in resp:
questionary.print(model_response.delta, end="")
questionary.print("")
if remember_response and model_response and model_response.message:
model_response.message.role_name = self.assistant_name
self.memory_service.add_messages([new_message, model_response.message])
else:
assert isinstance(resp, ModelResponse)
model_response: ModelResponse = resp
questionary.print(model_response.message.content)
if remember_response and model_response and model_response.message:
model_response.message.role_name = self.assistant_name
self.memory_service.add_messages([new_message, model_response.message])
@staticmethod
def parse_query_command(query: str):
"""
@ -296,19 +313,8 @@ class CliMemoryChat(BaseMemoryChat):
questionary.print(f"{self.assistant_name}: ", end="", style="bold")
# Fetch and display AI's response
self.memory_service.start_backend_service()
if self.stream:
model_response = None
for model_response in self.chat_with_memory(query=query):
questionary.print(model_response.delta, end="")
questionary.print("")
else:
model_response = self.chat_with_memory(query=query)
questionary.print(model_response.message.content)
# Append AI's response to the conversation memory
model_response.message.role_name = self.assistant_name
self.add_messages(model_response.message)
self.start_backend_service()
self.chat_with_memory(query=query)
except KeyboardInterrupt:
# Handle user interruption and confirm exit

View file

@ -31,25 +31,6 @@ class BaseMemoryService(metaclass=ABCMeta):
self._op_description_dict: Dict[str, str] = {}
self.logger = Logger.get_logger()
@abstractmethod
def add_messages(self, messages: List[Message] | Message):
raise NotImplementedError
@abstractmethod
def do_operation(self, op_name: str, **kwargs):
"""
Abstract method defining the interface for executing a specific operation by its name.
This method must be implemented by subclasses to provide the actual operation logic.
Args:
op_name (str): The name identifying the operation to be performed.
**kwargs: Additional keyword arguments required for the operation execution.
Raises:
NotImplementedError: This exception is raised when the method is not overridden in a subclass.
"""
raise NotImplementedError
@property
def op_description_dict(self) -> Dict[str, str]:
"""
@ -63,27 +44,9 @@ class BaseMemoryService(metaclass=ABCMeta):
self._op_description_dict = {k: v.description for k, v in self._operation_dict.items()}
return self._op_description_dict
def retrieve_memory(self):
"""
Executes the operation associated with retrieved memory.
Asserts that the operation for retrieved memory has been initialized.
Returns:
Any: The result of the retrieved memory operation.
"""
assert self.retrieve_memory_key in self._operation_dict, f"op={self.retrieve_memory_key} is not inited!"
return self.do_operation(self.retrieve_memory_key)
def read_message(self):
"""
Executes the operation associated with reading messages.
Asserts that the operation for reading messages has been initialized.
Returns:
Any: The result of the read message operation.
"""
assert self.read_message_key in self._operation_dict, f"op={self.read_message_key} is not inited!"
return self.do_operation(self.read_message_key)
@abstractmethod
def add_messages(self, messages: List[Message] | Message):
raise NotImplementedError
@abstractmethod
def init_service(self, **kwargs):
@ -94,3 +57,27 @@ class BaseMemoryService(metaclass=ABCMeta):
def stop_backend_service(self):
pass
def do_operation(self, op_name: str, **kwargs):
"""
Executes a specific operation by its name with provided keyword arguments.
Args:
op_name (str): The name of the operation to execute.
**kwargs: Keyword arguments for the operation's execution.
Returns:
The result of the operation execution, if any. Otherwise, None.
Raises:
Warning: If the operation name is not initialized in `_operation_dict`.
"""
if op_name not in self._operation_dict:
self.logger.warning(f"op_name={op_name} is not inited!")
return
return self._operation_dict[op_name].run_operation(**kwargs)
def __getattr__(self, name: str):
return lambda **kwargs: self.do_operation(name, **kwargs)

View file

@ -22,22 +22,39 @@ class SetQueryWorker(MemoryBaseWorker):
along with its creation timestamp.
"""
query = "" # Default query value
query_timestamp = int(datetime.datetime.now().timestamp()) # Current timestamp as default
timestamp = int(datetime.datetime.now().timestamp()) # Current timestamp as default
if "query" in self.chat_kwargs:
# Check if a specific 'query' has been provided via 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
# check role_name
role_name = self.chat_kwargs.get("role_name")
if role_name:
assert role_name == self.target_name, \
f"role_name={role_name} is not supported in human/assistant memory workflow!"
elif self.chat_messages:
# If no explicit query is given, use the content of the latest chat message
chat_messages = [msg for msg in self.chat_messages if msg.role == MessageRoleEnum.USER.value]
if chat_messages:
message = chat_messages[-1]
query = message.content
query_timestamp = message.time_created
timestamp = message.time_created
# check role_name
role_name = message.role_name
if role_name:
assert role_name == self.target_name, \
f"role_name={role_name} is not supported in human/assistant memory workflow!"
# Store the determined query and its timestamp in the context
self.set_context(QUERY_WITH_TS, (query, query_timestamp))
self.set_context(QUERY_WITH_TS, (query, timestamp))

15
tests/other/test_attr.py Normal file
View file

@ -0,0 +1,15 @@
class MyClass:
def __init__(self):
self.existing_attribute = "I exist"
def do(self, name: str, **kwargs):
print("do %s %s" % (name, kwargs))
def __getattr__(self, name):
return lambda **kwargs: self.do(name, **kwargs)
# 创建类的实例
obj = MyClass()
obj.haha(a=1, b=2)