[dev] add chat examples

This commit is contained in:
jinli.yl 2024-07-28 22:04:45 +08:00
parent 2237a6abaf
commit db1e44a826
18 changed files with 177 additions and 214 deletions

View file

View file

@ -0,0 +1,66 @@
import sys
sys.path.append(".")
from memoryscope import MemoryScope, Arguments
arguments = Arguments(
language="cn",
logger_to_screen=False,
memory_chat_class="api_memory_chat",
generation_backend="dashscope_generation",
generation_model="qwen-max",
embedding_backend="dashscope_embedding",
embedding_model="text-embedding-v2",
use_dummy_ranker=False,
rank_backend="dashscope_rank",
rank_model="gte-rerank",
)
def chat_example1():
with MemoryScope(config_path="memoryscope/core/config/demo_config.yaml") as ms:
memory_chat = ms.default_memory_chat
response = memory_chat.chat_with_memory(query="我的爱好是弹琴。")
print("回答:\n" + response.message.content)
print("记忆:\n" + response.meta_data["memories"])
def chat_example2():
with MemoryScope(arguments=arguments) as ms:
memory_chat = ms.default_memory_chat
response = memory_chat.chat_with_memory(query="我的爱好是弹琴。")
print("回答:\n" + response.message.content)
print("记忆:\n" + response.meta_data["memories"])
def chat_example3():
with MemoryScope(arguments=arguments) as ms:
memory_chat = ms.default_memory_chat
response = memory_chat.chat_with_memory(
query="你知道我消灭了几个星系吗?",
extra_memories="用户是一只哥斯拉,这是真是的,不是虚构的。\n前天用户消灭了星系0x4be\n大前天用户消灭了星系0x5be")
print("回答:\n" + response.message.content)
def chat_example4():
with MemoryScope(arguments=arguments) as ms:
memory_chat = ms.default_memory_chat
response = memory_chat.chat_with_memory(query="我的爱好是弹琴。")
print("回答1\n" + response.message.content)
memory_chat.memory_service.consolidate_memory()
response = memory_chat.chat_with_memory(query="你知道我的乐器爱好是什么?",
add_messages=False)
print("回答2\n" + response.message.content)
print("记忆2\n" + response.meta_data["memories"])
if __name__ == "__main__":
chat_example1()
# chat_example2()
# chat_example3()
# chat_example4()

View file

@ -1,18 +0,0 @@
from memoryscope.cli import MemoryScope
from memoryscope.scheme.message import Message
ms = MemoryScope().read_config("config/demo_config_no_stream.yaml")
memory_service = ms.default_service
memory_chat = ms.default_chat_handle
# new_message: Message = Message(role=MessageRoleEnum.USER.value, role_name="我", content="我的爱好是弹琴并且喜欢看电影。")
# memory_service.add_messages(new_message)
res: Message = memory_chat.chat_with_memory(query="我的爱好是弹琴。", remember_response=True)
print(res.message.content)
res: Message = memory_chat.chat_with_memory(query="昨天弹出一个光粒消灭了星系0x4be。", remember_response=True)
print(res.message.content)
res: Message = memory_chat.chat_with_memory(query="今天弹出一个二向箔消灭了星系0xa2e。", remember_response=True)
print(res.message.content)

View file

@ -0,0 +1 @@
python memoryscope/cli.py -config_path=memoryscope/core/config/demo_config.yaml

View file

@ -0,0 +1,10 @@
python memoryscope/cli.py \
-language="cn" \
-memory_chat_class="cli_memory_chat" \
-generation_backend="dashscope_generation" \
-generation_model="qwen-max" \
-embedding_backend="dashscope_embedding" \
-embedding_model="text-embedding-v2" \
-use_dummy_ranker=False \
-rank_backend="dashscope_rank" \
-rank_model="gte-rerank"

View file

@ -1,2 +1,5 @@
from memoryscope.core.config.arguments import Arguments
from memoryscope.core.memoryscope import MemoryScope
""" Version of MemoryScope."""
__version__ = "0.1.0"

View file

@ -8,7 +8,9 @@ from memoryscope.core.memoryscope import MemoryScope
def cli_job(**kwargs):
MemoryScope(**kwargs).default_memory_chat.run()
with MemoryScope(**kwargs) as ms:
memory_chat = ms.default_memory_chat
memory_chat.run()
if __name__ == "__main__":

View file

@ -9,7 +9,7 @@ from memoryscope.core.service.base_memory_service import BaseMemoryService
from memoryscope.core.utils.prompt_handler import PromptHandler
from memoryscope.enumeration.message_role_enum import MessageRoleEnum
from memoryscope.scheme.message import Message
from memoryscope.scheme.model_response import ModelResponse
from memoryscope.scheme.model_response import ModelResponse, ModelResponseGen
class ApiMemoryChat(BaseMemoryChat):
@ -18,6 +18,7 @@ class ApiMemoryChat(BaseMemoryChat):
memory_service: str,
generation_model: str,
context: MemoryscopeContext,
stream: bool = False,
human_name: str = None,
assistant_name: str = None,
**kwargs):
@ -27,6 +28,7 @@ class ApiMemoryChat(BaseMemoryChat):
self._memory_service: BaseMemoryService | str = memory_service
self._generation_model: BaseModel | str = generation_model
self.context: MemoryscopeContext = context
self.stream: bool = stream
self.generation_model_kwargs: dict = kwargs.pop("generation_model_kwargs", {})
self.human_name: str = human_name
@ -100,13 +102,31 @@ class ApiMemoryChat(BaseMemoryChat):
self._generation_model = self.context.model_dict[self._generation_model]
return self._generation_model
def iter_response(self,
remember_response: bool,
resp: ModelResponseGen,
memories: str,
query_message: Message) -> ModelResponseGen:
model_response: ModelResponse | None = None
for model_response in resp:
yield model_response
if remember_response:
if model_response and model_response.message:
model_response.message.role_name = self.assistant_name
model_response.meta_data[MEMORIES] = memories
self.memory_service.add_messages([query_message, model_response.message])
else:
self.logger.warning("model_response or model_response.message is empty!")
def chat_with_memory(self,
query: str,
role_name: Optional[str] = None,
system_prompt: Optional[str] = None,
memory_prompt: Optional[str] = None,
extra_memories: Optional[str] = None,
add_not_memorized_messages: bool = True,
add_messages: bool = True,
remember_response: bool = True,
**kwargs):
"""
@ -118,7 +138,7 @@ 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_not_memorized_messages (bool, optional): whether add not memorized messages to LLM.
add_messages (bool, optional): whether add not memorized messages to LLM.
remember_response (bool, optional): Flag indicating whether to save the AI's response to memory.
Defaults to False.
Returns:
@ -161,7 +181,7 @@ class ApiMemoryChat(BaseMemoryChat):
chat_messages.append(system_message)
# Include past conversation history in the message list
if add_not_memorized_messages:
if add_messages:
history_messages = self.memory_service.read_message()
if history_messages:
chat_messages.extend(history_messages)
@ -171,19 +191,8 @@ class ApiMemoryChat(BaseMemoryChat):
self.logger.info(f"chat_messages={chat_messages}")
resp = self.generation_model.call(messages=chat_messages, stream=self.stream, **self.generation_model_kwargs)
if self.stream:
model_response: ModelResponse | None = None
for model_response in resp:
yield model_response
if remember_response:
if model_response and model_response.message:
model_response.message.role_name = self.assistant_name
model_response.meta_data[MEMORIES] = memories
self.memory_service.add_messages([query_message, model_response.message])
else:
self.logger.warning("model_response or model_response.message is empty!")
return self.iter_response(remember_response, resp, memories, query_message)
else:
model_response: ModelResponse = resp

View file

@ -1,4 +1,5 @@
from abc import ABCMeta, abstractmethod
from typing import Optional
from memoryscope.core.service.base_memory_service import BaseMemoryService
from memoryscope.core.utils.logger import Logger
@ -10,8 +11,7 @@ class BaseMemoryChat(metaclass=ABCMeta):
It outlines the method to initiate a chat session leveraging memory data, which concrete subclasses must implement.
"""
def __init__(self, stream: bool = True, **kwargs):
self.stream: bool = stream
def __init__(self, **kwargs):
self.kwargs: dict = kwargs
self.logger = Logger.get_logger()
@ -28,16 +28,14 @@ class BaseMemoryChat(metaclass=ABCMeta):
@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.
role_name: Optional[str] = None,
system_prompt: Optional[str] = None,
memory_prompt: Optional[str] = None,
extra_memories: Optional[str] = None,
add_messages: bool = True,
remember_response: bool = True,
**kwargs):
Args:
query (str): The user's query or message to start the chat.
role_name (str): The role's name.
remember_response (bool): whether update memory service.
"""
raise NotImplementedError
def run(self):

View file

@ -1,22 +1,14 @@
import os
import time
from typing import List
from typing import Optional
import questionary
from memoryscope.constants.language_constants import DEFAULT_HUMAN_NAME
from memoryscope.core.chat.base_memory_chat import BaseMemoryChat
from memoryscope.core.memoryscope_context import MemoryscopeContext
from memoryscope.core.models.base_model import BaseModel
from memoryscope.core.service.base_memory_service import BaseMemoryService
from memoryscope.core.utils.prompt_handler import PromptHandler
from memoryscope.core.chat.api_memory_chat import ApiMemoryChat
from memoryscope.core.utils.tool_functions import char_logo
from memoryscope.enumeration.message_role_enum import MessageRoleEnum
from memoryscope.scheme.message import Message
from memoryscope.scheme.model_response import ModelResponse
class CliMemoryChat(BaseMemoryChat):
class CliMemoryChat(ApiMemoryChat):
"""
Command-line interface for chatting with an AI that integrates memory functionality.
Allows users to interact, manage chat history, adjust streaming settings, and view commands' help.
@ -28,51 +20,9 @@ class CliMemoryChat(BaseMemoryChat):
"stream": "Toggle between getting streamed responses from the model."
}
def __init__(self,
memory_service: str,
generation_model: str,
context: MemoryscopeContext,
human_name: str = None,
assistant_name: str = None,
**kwargs):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._memory_service: BaseMemoryService | str = memory_service
self._generation_model: BaseModel | str = generation_model
self.context: MemoryscopeContext = context
self.generation_model_kwargs: dict = kwargs.pop("generation_model_kwargs", {})
self.human_name: str = human_name
if not self.human_name:
self.human_name = DEFAULT_HUMAN_NAME[self.context.language]
self.context.meta_data["human_name"] = self.human_name
self.assistant_name: str = assistant_name
if not self.assistant_name:
self.assistant_name = "AI"
self.context.meta_data["assistant_name"] = self.assistant_name
self._logo = char_logo("MemoryScope")
self._prompt_handler: PromptHandler | None = None
@property
def prompt_handler(self) -> PromptHandler:
"""
Lazy initialization property for the prompt handler.
This property ensures that the `_prompt_handler` attribute is only instantiated when it is first accessed.
It uses the current file's path and additional keyword arguments for configuration.
Returns:
PromptHandler: An instance of the PromptHandler configured for this CLI session.
"""
if self._prompt_handler is None:
self._prompt_handler = PromptHandler(__file__,
language=self.context.language,
prompt_file="memory_chat_prompt",
**self.kwargs)
return self._prompt_handler
def print_logo(self):
"""
@ -84,104 +34,30 @@ class CliMemoryChat(BaseMemoryChat):
for line in self._logo:
print(line)
@property
def memory_service(self) -> BaseMemoryService:
"""
Property to access the memory service. If the service is initially set as a string,
it will be looked up in the memory service dictionary of context, initialized,
and then returned as an instance of `BaseMemoryService`. Ensures the memory service
is properly started before use.
Returns:
BaseMemoryService: An active memory service instance.
Raises:
ValueError: If the declaration of memory service is not found in the memory service dictionary of context.
"""
if isinstance(self._memory_service, str):
if self._memory_service not in self.context.memory_service_dict:
raise ValueError(f"Missing declaration of memory_service in context: {self._memory_service}")
self._memory_service: BaseMemoryService = self.context.memory_service_dict[self._memory_service]
# init service & update kwargs
self._memory_service.init_service()
return self._memory_service
@property
def generation_model(self) -> BaseModel:
"""
Property to get the generation model. If the model is set as a string, it will be resolved from the global
context's model dictionary.
Raises:
ValueError: If the declaration of generation model is not found in the model dictionary of context .
Returns:
BaseModel: An actual generation model instance.
"""
if isinstance(self._generation_model, str):
if self._generation_model not in self.context.model_dict:
raise ValueError(f"Missing declaration of generation model in yaml config: {self._generation_model}")
self._generation_model = self.context.model_dict[self._generation_model]
return self._generation_model
def get_user_message(self, query: str, role_name: str = "") -> Message:
if not role_name:
role_name = self.human_name
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
if memories:
memory_prompt = self.prompt_handler.memory_prompt
system_prompt = "\n".join([x.strip() for x in [system_prompt, memory_prompt, memories]])
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_user_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:
chat_messages.extend(history_messages)
# Append the current user's message to the conversation context
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
resp = self.generation_model.call(messages=chat_messages,
stream=self.stream,
**self.generation_model_kwargs)
role_name: Optional[str] = None,
system_prompt: Optional[str] = None,
memory_prompt: Optional[str] = None,
extra_memories: Optional[str] = None,
add_messages: bool = True,
remember_response: bool = True,
**kwargs):
resp = super().chat_with_memory(query=query,
role_name=role_name,
system_prompt=system_prompt,
memory_prompt=memory_prompt,
extra_memories=extra_memories,
add_messages=add_messages,
remember_response=remember_response,
**kwargs)
if self.stream:
model_response: ModelResponse | None = None
for model_response in resp:
questionary.print(model_response.delta, end="")
for _resp in resp:
questionary.print(_resp.delta, end="")
questionary.print("")
else:
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])
questionary.print(resp.message.content)
@staticmethod
def parse_query_command(query: str):

View file

@ -12,6 +12,8 @@ class Arguments(object):
logger_name_time_suffix: str = field(default="%Y%m%d_%H%M%S")
logger_to_screen: bool = field(default=False, metadata={"help": "If false, it does not print to the screen."})
memory_chat_class: str = field(default="cli_memory_chat", metadata={
"help": "cli_memory_chat(Command-line interaction), api_memory_chat(API interface interaction), etc."})

View file

@ -1,5 +1,6 @@
import json
from dataclasses import fields
from datetime import datetime
from pathlib import Path
from typing import Optional, Literal
@ -7,6 +8,7 @@ import yaml
from memoryscope.constants.language_constants import DEFAULT_HUMAN_NAME
from memoryscope.core.config.arguments import Arguments
from memoryscope.core.utils.logger import Logger
from memoryscope.enumeration.language_enum import LanguageEnum
@ -23,20 +25,40 @@ class ConfigManager(object):
if config:
self.config = config
self.logger = self._init_logger()
self.logger.info("init by config mode:")
elif config_path:
self.read_config(config_path)
self.logger = self._init_logger()
self.logger.info("init by config_path mode:")
else:
self.read_demo_config(demo_config_name)
if arguments:
self.update_config_by_arguments(arguments)
self.logger = self._init_logger()
self.logger.info(f"init by arguments mode: {arguments.__dict__}")
if arguments:
self.update_config_by_arguments(arguments)
elif kwargs:
kwargs = {k: v for k, v in kwargs.items() if k in [x.name for x in fields(Arguments)]}
arguments = Arguments(**kwargs)
self.update_config_by_arguments(arguments)
self.logger = self._init_logger()
self.logger.info(f"init by kwargs mode: {kwargs}")
elif kwargs:
key_list = [x.name for x in fields(Arguments)]
arguments = Arguments(**{k: v for k, v in kwargs.items() if k in key_list})
self.update_config_by_arguments(arguments)
else:
raise RuntimeError("can not init config manager without kwargs!")
self.logger.info(self.dump_config())
def _init_logger(self) -> Logger:
global_config = self.config["global"]
logger_name = global_config["logger_name"]
logger_name_time_suffix = global_config["logger_name_time_suffix"]
if logger_name_time_suffix:
suffix = datetime.now().strftime(logger_name_time_suffix)
logger_name = f"{logger_name}_{suffix}"
return Logger.get_logger(logger_name, to_stream=global_config["logger_to_screen"])
def read_config(self, config_path: str):
if config_path.endswith(".yaml"):
@ -60,16 +82,19 @@ class ConfigManager(object):
"thread_pool_max_workers": arguments.thread_pool_max_workers,
"logger_name": arguments.logger_name,
"logger_name_time_suffix": arguments.logger_name_time_suffix,
"logger_to_screen": arguments.logger_to_screen,
"use_dummy_ranker": arguments.use_dummy_ranker,
})
@staticmethod
def update_memory_chat_by_arguments(config: dict, arguments: Arguments):
memory_chat_class_split = config["class"].split(".")
stream = arguments.memory_chat_class in ["cli_memory_chat", ]
config.update({
"class": ".".join(memory_chat_class_split[:-1] + [arguments.memory_chat_class]),
"human_name": DEFAULT_HUMAN_NAME[LanguageEnum(arguments.language)],
"assistant_name": "AI",
"stream": stream,
})
@staticmethod

View file

@ -3,6 +3,7 @@ global:
thread_pool_max_workers: 5
logger_name: memoryscope
logger_name_time_suffix: "%Y%m%d_%H%M%S"
logger_to_screen: false
use_dummy_ranker: false
memory_chat:
@ -86,7 +87,7 @@ worker:
obs_customized: 1.2
insight: 2.0
fuse_time_ratio: 2.0
fuse_rerank_top_k: 10
fuse_rerank_top_k: 20
retrieve_top_memory:
class: core.worker.frontend.retrieve_memory_worker
retrieve_obs_top_k: 100

View file

@ -1,11 +1,9 @@
import datetime
from concurrent.futures import ThreadPoolExecutor
from memoryscope.core.chat.base_memory_chat import BaseMemoryChat
from memoryscope.core.config.config_manager import ConfigManager
from memoryscope.core.memoryscope_context import MemoryscopeContext
from memoryscope.core.service.base_memory_service import BaseMemoryService
from memoryscope.core.utils.logger import Logger
from memoryscope.core.utils.tool_functions import init_instance_by_config
from memoryscope.enumeration.language_enum import LanguageEnum
from memoryscope.enumeration.model_enum import ModelEnum
@ -15,21 +13,9 @@ class MemoryScope(ConfigManager):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.logger = self._init_logger()
self.context: MemoryscopeContext = MemoryscopeContext()
self.init_context_by_config()
def _init_logger(self) -> Logger:
global_config = self.config["global"]
logger_name = global_config["logger_name"]
logger_name_time_suffix = global_config["logger_name_time_suffix"]
if logger_name_time_suffix:
suffix = datetime.datetime.now().strftime(logger_name_time_suffix)
logger_name = f"{logger_name}_{suffix}"
return Logger.get_logger(logger_name, to_stream=False)
def init_context_by_config(self):
# set global config
global_conf = self.config["global"]
@ -86,6 +72,7 @@ class MemoryScope(ConfigManager):
def __enter__(self):
self.init_context_by_config()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()

View file

@ -105,7 +105,8 @@ class Logger(logging.Logger):
by the handlers are freed properly.
"""
for handler in self.handlers:
handler.close() # ⭐ Close each handler to release resources
# Close each handler to release resources
handler.close()
def clear(self):
"""