Module llmflex.Memory.base_memory

Expand source code
import os
from ..utils import get_config
from ..Models.Cores.base_core import BaseLLM
from ..Prompts.prompt_template import DEFAULT_SYSTEM_MESSAGE
from typing import List, Dict, Any, Type, Tuple, Optional

def chat_memory_home() -> str:
    """Return the default directory for saving chat memories.

    Returns:
        str: The default directory for saving chat memories.
    """
    history_dir = os.path.join(get_config()['package_home'], 'chat_memories')
    os.makedirs(history_dir, exist_ok=True)
    return history_dir

def list_chat_dirs() -> List[str]:
    """List the directories of all chat memories.

    Returns:
        List[str]: List of directories of all chat memories.
    """
    import re
    re_chat = re.compile(r'chat_\d+')
    chats_dir = chat_memory_home()
    dirs = list(map(lambda x: os.path.join(chats_dir, x), os.listdir(chats_dir)))
    dirs = list(filter(lambda x: re_chat.match(os.path.basename(x)), dirs))
    dirs = list(filter(lambda x: ((os.path.isdir(x)) & ('info.json' in os.listdir(x))), dirs))
    return dirs

def list_chat_ids() -> List[str]:
    """Return a list of existing chat ids.

    Returns:
        List[str]: Return a list of existing chat ids, sorted by last update descendingly.
    """
    from ..utils import read_json
    chat_dirs = list_chat_dirs()
    chat_infos = list(map(lambda x: [read_json(os.path.join(x, 'info.json')), x], chat_dirs))
    chat_infos.sort(key=lambda x: x[0]['last_update'], reverse=True)
    chat_ids = list(map(lambda x: os.path.basename(x[1]), chat_infos))
    return chat_ids

def list_titles() -> List[str]:
    """Return a list of chat titles.

    Returns:
        List[str]: List of chat titles, sorted by last update descendingly.
    """
    from ..utils import read_json
    chat_ids = list_chat_ids()
    home = chat_memory_home()
    titles = list(map(lambda x: read_json(os.path.join(home, x, 'info.json'))['title'], chat_ids))
    return titles   

def get_new_chat_id() -> str:
    """Get an unused chat id.

    Returns:
        str: New chat id.
    """
    chat_ids = list_chat_ids()
    if len(chat_ids) == 0:
        return 'chat_0'
    indexes = list(map(lambda x: int(x.removeprefix('chat_')), chat_ids))
    max_index = max(indexes)
    for i in range(max_index + 1):
        if f'chat_{i}' not in chat_ids:
            return f'chat_{i}'
    return f'chat_{max_index + 1}'
    
def get_title_from_id(chat_id: str) -> str:
    """Getting the title from Chat ID.

    Args:
        chat_id (str): Chat ID.

    Returns:
        str: Title of the memory.
    """
    if chat_id not in list_chat_ids():
        raise FileNotFoundError(f'Chat ID "{chat_id}" does not exist.')
    from ..utils import read_json
    return read_json(os.path.join(chat_memory_home(), chat_id, 'info.json'))['title']

def get_dir_from_id(chat_id: str) -> str:
    """Geet the memory directory given the chat ID.

    Args:
        chat_id (str): Chat ID.

    Returns:
        str: Memory directory.
    """
    return os.path.join(chat_memory_home(), chat_id)

class BaseChatMemory:
    """Base class for chat memory.
    """
    def __init__(self, chat_id: str, from_exist: bool = True, system: Optional[str] = None) -> None:
        """Initialising the memory class.

        Args:
            chat_id (str): Chat ID.
            from_exist (bool, optional): Initialising the chat memory from existing files if the title exists. Defaults to True.
            system (Optional[str], optional): System message for the chat. If None is given, the default system message or the stored system message will be used. Defaults to None.
        """
        import re
        re_chat = re.compile(r'chat_\d+')
        if not re_chat.match(chat_id):
            raise ValueError('Invalid chat ID.')
        self._chat_id = chat_id
        self._init_memory(from_exist=from_exist)
        self.info['system'] = system.strip() if system is not None else self.info.get('system', DEFAULT_SYSTEM_MESSAGE)
        self.save()

    @property
    def chat_id(self) -> str:
        """Unique identifier of the chat memory.

        Returns:
            str: Unique identifier of the chat memory.
        """
        return self._chat_id

    @property
    def title(self) -> str:
        """Chat title.

        Returns:
            str: Chat title.
        """
        title = self.info.get('title')
        if title is None:
            self.info['title'] = 'New Chat'
            self.save()
        return title
    
    @property
    def chat_dir(self) -> str:
        """Directory of the chat.

        Returns:
            str: Directory of the chat.
        """
        chat_dir = os.path.join(chat_memory_home(), self.chat_id)
        if not os.path.exists(chat_dir):
            os.makedirs(chat_dir)
        return chat_dir
    
    @property
    def info(self) -> Dict[str, Any]:
        """Information of the chat.

        Returns:
            Dict[str, Any]: Information of the chat.
        """
        if hasattr(self, '_info'):
            return self._info
        elif 'info.json' in os.listdir(self.chat_dir):
            from ..utils import read_json
            self._info = read_json(os.path.join(self.chat_dir, 'info.json'))
            return self._info
        else:
            from ..utils import save_json, current_time
            self._info = dict(title='New Chat', last_update=current_time())
            save_json(self._info, os.path.join(self.chat_dir, 'info.json'))
            return self._info

    @property
    def system(self) -> str:
        """Default system message of the memory.

        Returns:
            str: Default system message of the memory.
        """
        return self.info.get('system', DEFAULT_SYSTEM_MESSAGE)

    @property    
    def history(self) -> List[Tuple[str, str]]:
        """Entire chat history.

        Returns:
            List[Tuple[str, str]]: Entire chat history.
        """
        history = list(map(lambda x: [x.metadata['user'], x.metadata['assistant'], x.metadata['order']], self._data.values()))
        if len(history) == 0:
            return []
        count = max(list(map(lambda x: x[2], history))) + 1
        history = list(map(lambda x: list(filter(lambda y: y[2] == x, history))[0], range(count)))
        history.sort(key=lambda x: x[2], reverse=False)
        return list(map(lambda x: tuple(x[:2]), history))
    
    @property
    def history_dict(self) -> List[Dict[str, Any]]:
        """Entire history as dictionaries.

        Returns:
            List[Dict[str, Any]]: Entire history as dictionaries.
        """
        import gc
        history: list[dict[str, Any]] = list(map(lambda x: x.metadata, self._data.values()))
        if len(history) == 0:
            return []
        count = max(list(map(lambda x: x['order'], history))) + 1
        history = list(map(lambda x: list(filter(lambda y: y['order'] == x, history))[0], range(count)))
        history.sort(key=lambda x: x['order'], reverse=False)
        def reformat_record(record: dict[str, Any]) -> List[Dict[str, Any]]:
            user = dict(role='user', content=record['user'])
            assistant = dict(role='assistant', content=record['assistant'])
            for k, v in record.items():
                if k not in ['user', 'assistant', 'order', 'role']:
                    assistant[k] = v
            return [user, assistant]
        history = list(map(reformat_record, history))
        gc.collect()
        return sum(history, [])


    @property
    def interaction_count(self) -> int:
        """Number of interactions.

        Returns:
            int: Number of interactions.
        """
        return len(self.history)

    def _init_memory(self, from_exist: bool = True) -> None:
        """Method to initialise the components in the memory.

        Args:
            from_exist (bool, optional): Whether to initialise from existing files. Defaults to True.
        """
        if ((from_exist) & (self.chat_id in list_chat_ids())):
            import pickle
            with open(os.path.join(self.chat_dir, 'data.pkl'), 'rb') as f:
                self._data = pickle.load(f)

        else:
            self._data = dict()
            self.save()

    def save(self) -> None:
        """Save the current state of the memory.
        """
        from ..utils import save_json, current_time
        import pickle
        self.info
        self._info['last_update'] = current_time()
        save_json(self._info, os.path.join(self.chat_dir, 'info.json'))
        with open(os.path.join(self.chat_dir, 'data.pkl'), 'wb') as f:
            pickle.dump(self._data, f)

    def update_system_message(self, system: str) -> None:
        """Update the default system message for the memory.

        Args:
            system (str): New system message.
        """
        self.info['system'] = system.strip()
        self.save()

    def update_title(self, title: str) -> None:
        """Update the title of the memory.

        Args:
            title (str): New chat memory title.
        """
        title = title.strip()
        if title == '':
            raise ValueError('Chat title cannot be an empty string.')
        self.info['title'] = title
        self.save()

    def save_interaction(self, user_input: str, assistant_output: str, **kwargs) -> None:
        """Saving an interaction to the memory.

        Args:
            user_input (str): User input.
            assistant_output (str): Chatbot output.
        """
        from ..Schemas.documents import Document
        from copy import deepcopy
        user_input = user_input.strip(' \n\r\t')
        assistant_output = assistant_output.strip(' \n\r\t')
        metadata = dict(user=user_input, assistant=assistant_output, order=self.interaction_count)
        for k, v in kwargs.items():
            if k not in ['user', 'assistant', 'order', 'role']:
                metadata[k] = v
        new_key = max(list(self._data.keys())) + 1 if len(self._data) != 0 else 0
        meta_user = deepcopy(metadata)
        meta_user['role'] = 'user'
        meta_assistant = deepcopy(metadata)
        meta_assistant['role'] = 'assistant'
        self._data[new_key] = Document(
            index=user_input,
            metadata=meta_user
        )
        self._data[new_key + 1] = Document(
            index=assistant_output,
            metadata=meta_assistant
        )
        self.save()

    def remove_last_interaction(self) -> None:
        """Remove the latest interaction.
        """
        if len(self._data) != 0:
            order = self.interaction_count
            data = list(filter(lambda x: x.metadata['order'] != order, list(self._data.values())))
            self._data = dict(zip(range(len(data)), data))
            self.save()

    def clear(self) -> None:
        """Empty the whole chat history.
        """
        self._init_memory(from_exist=False)

    def get_recent_memory(self, k: int = 3) -> List[Tuple[str, str]]:
        """Get the last k interactions as a list.

        Args:
            k (int, optional): Maximum number of latest interactions. Defaults to 3.

        Returns:
            List[Tuple[str, str]]: List of interactions.
        """
        from copy import deepcopy
        history = self.history
        results = history if len(history) <= k else history[-k:]
        return deepcopy(results)

    def get_token_memory(self, llm: Type[BaseLLM], token_limit: int = 400) -> List[str]:
        """Get the latest conversation limited by number of tokens.

        Args:
            llm (Type[BaseLLM]): LLM to count tokens.
            token_limit (int, optional): Maximum number of tokens allowed. Defaults to 400.

        Returns:
            List[str]: List of most recent messages.
        """
        if len(self.history) == 0:
            return []
        tk_count = 0
        history = sum(list(map(list, self.history)), [])
        history = list(reversed(history))
        results = list()
        for m in history:
            msg_count = llm.get_num_tokens(m)
            if (((msg_count + tk_count) <= token_limit) | (len(results) < 2)):
                results = [m] + results
                tk_count += msg_count
            else:
                break
        return results

Functions

def chat_memory_home() ‑> str

Return the default directory for saving chat memories.

Returns

str
The default directory for saving chat memories.
Expand source code
def chat_memory_home() -> str:
    """Return the default directory for saving chat memories.

    Returns:
        str: The default directory for saving chat memories.
    """
    history_dir = os.path.join(get_config()['package_home'], 'chat_memories')
    os.makedirs(history_dir, exist_ok=True)
    return history_dir
def get_dir_from_id(chat_id: str) ‑> str

Geet the memory directory given the chat ID.

Args

chat_id : str
Chat ID.

Returns

str
Memory directory.
Expand source code
def get_dir_from_id(chat_id: str) -> str:
    """Geet the memory directory given the chat ID.

    Args:
        chat_id (str): Chat ID.

    Returns:
        str: Memory directory.
    """
    return os.path.join(chat_memory_home(), chat_id)
def get_new_chat_id() ‑> str

Get an unused chat id.

Returns

str
New chat id.
Expand source code
def get_new_chat_id() -> str:
    """Get an unused chat id.

    Returns:
        str: New chat id.
    """
    chat_ids = list_chat_ids()
    if len(chat_ids) == 0:
        return 'chat_0'
    indexes = list(map(lambda x: int(x.removeprefix('chat_')), chat_ids))
    max_index = max(indexes)
    for i in range(max_index + 1):
        if f'chat_{i}' not in chat_ids:
            return f'chat_{i}'
    return f'chat_{max_index + 1}'
def get_title_from_id(chat_id: str) ‑> str

Getting the title from Chat ID.

Args

chat_id : str
Chat ID.

Returns

str
Title of the memory.
Expand source code
def get_title_from_id(chat_id: str) -> str:
    """Getting the title from Chat ID.

    Args:
        chat_id (str): Chat ID.

    Returns:
        str: Title of the memory.
    """
    if chat_id not in list_chat_ids():
        raise FileNotFoundError(f'Chat ID "{chat_id}" does not exist.')
    from ..utils import read_json
    return read_json(os.path.join(chat_memory_home(), chat_id, 'info.json'))['title']
def list_chat_dirs() ‑> List[str]

List the directories of all chat memories.

Returns

List[str]
List of directories of all chat memories.
Expand source code
def list_chat_dirs() -> List[str]:
    """List the directories of all chat memories.

    Returns:
        List[str]: List of directories of all chat memories.
    """
    import re
    re_chat = re.compile(r'chat_\d+')
    chats_dir = chat_memory_home()
    dirs = list(map(lambda x: os.path.join(chats_dir, x), os.listdir(chats_dir)))
    dirs = list(filter(lambda x: re_chat.match(os.path.basename(x)), dirs))
    dirs = list(filter(lambda x: ((os.path.isdir(x)) & ('info.json' in os.listdir(x))), dirs))
    return dirs
def list_chat_ids() ‑> List[str]

Return a list of existing chat ids.

Returns

List[str]
Return a list of existing chat ids, sorted by last update descendingly.
Expand source code
def list_chat_ids() -> List[str]:
    """Return a list of existing chat ids.

    Returns:
        List[str]: Return a list of existing chat ids, sorted by last update descendingly.
    """
    from ..utils import read_json
    chat_dirs = list_chat_dirs()
    chat_infos = list(map(lambda x: [read_json(os.path.join(x, 'info.json')), x], chat_dirs))
    chat_infos.sort(key=lambda x: x[0]['last_update'], reverse=True)
    chat_ids = list(map(lambda x: os.path.basename(x[1]), chat_infos))
    return chat_ids
def list_titles() ‑> List[str]

Return a list of chat titles.

Returns

List[str]
List of chat titles, sorted by last update descendingly.
Expand source code
def list_titles() -> List[str]:
    """Return a list of chat titles.

    Returns:
        List[str]: List of chat titles, sorted by last update descendingly.
    """
    from ..utils import read_json
    chat_ids = list_chat_ids()
    home = chat_memory_home()
    titles = list(map(lambda x: read_json(os.path.join(home, x, 'info.json'))['title'], chat_ids))
    return titles   

Classes

class BaseChatMemory (chat_id: str, from_exist: bool = True, system: Optional[str] = None)

Base class for chat memory.

Initialising the memory class.

Args

chat_id : str
Chat ID.
from_exist : bool, optional
Initialising the chat memory from existing files if the title exists. Defaults to True.
system : Optional[str], optional
System message for the chat. If None is given, the default system message or the stored system message will be used. Defaults to None.
Expand source code
class BaseChatMemory:
    """Base class for chat memory.
    """
    def __init__(self, chat_id: str, from_exist: bool = True, system: Optional[str] = None) -> None:
        """Initialising the memory class.

        Args:
            chat_id (str): Chat ID.
            from_exist (bool, optional): Initialising the chat memory from existing files if the title exists. Defaults to True.
            system (Optional[str], optional): System message for the chat. If None is given, the default system message or the stored system message will be used. Defaults to None.
        """
        import re
        re_chat = re.compile(r'chat_\d+')
        if not re_chat.match(chat_id):
            raise ValueError('Invalid chat ID.')
        self._chat_id = chat_id
        self._init_memory(from_exist=from_exist)
        self.info['system'] = system.strip() if system is not None else self.info.get('system', DEFAULT_SYSTEM_MESSAGE)
        self.save()

    @property
    def chat_id(self) -> str:
        """Unique identifier of the chat memory.

        Returns:
            str: Unique identifier of the chat memory.
        """
        return self._chat_id

    @property
    def title(self) -> str:
        """Chat title.

        Returns:
            str: Chat title.
        """
        title = self.info.get('title')
        if title is None:
            self.info['title'] = 'New Chat'
            self.save()
        return title
    
    @property
    def chat_dir(self) -> str:
        """Directory of the chat.

        Returns:
            str: Directory of the chat.
        """
        chat_dir = os.path.join(chat_memory_home(), self.chat_id)
        if not os.path.exists(chat_dir):
            os.makedirs(chat_dir)
        return chat_dir
    
    @property
    def info(self) -> Dict[str, Any]:
        """Information of the chat.

        Returns:
            Dict[str, Any]: Information of the chat.
        """
        if hasattr(self, '_info'):
            return self._info
        elif 'info.json' in os.listdir(self.chat_dir):
            from ..utils import read_json
            self._info = read_json(os.path.join(self.chat_dir, 'info.json'))
            return self._info
        else:
            from ..utils import save_json, current_time
            self._info = dict(title='New Chat', last_update=current_time())
            save_json(self._info, os.path.join(self.chat_dir, 'info.json'))
            return self._info

    @property
    def system(self) -> str:
        """Default system message of the memory.

        Returns:
            str: Default system message of the memory.
        """
        return self.info.get('system', DEFAULT_SYSTEM_MESSAGE)

    @property    
    def history(self) -> List[Tuple[str, str]]:
        """Entire chat history.

        Returns:
            List[Tuple[str, str]]: Entire chat history.
        """
        history = list(map(lambda x: [x.metadata['user'], x.metadata['assistant'], x.metadata['order']], self._data.values()))
        if len(history) == 0:
            return []
        count = max(list(map(lambda x: x[2], history))) + 1
        history = list(map(lambda x: list(filter(lambda y: y[2] == x, history))[0], range(count)))
        history.sort(key=lambda x: x[2], reverse=False)
        return list(map(lambda x: tuple(x[:2]), history))
    
    @property
    def history_dict(self) -> List[Dict[str, Any]]:
        """Entire history as dictionaries.

        Returns:
            List[Dict[str, Any]]: Entire history as dictionaries.
        """
        import gc
        history: list[dict[str, Any]] = list(map(lambda x: x.metadata, self._data.values()))
        if len(history) == 0:
            return []
        count = max(list(map(lambda x: x['order'], history))) + 1
        history = list(map(lambda x: list(filter(lambda y: y['order'] == x, history))[0], range(count)))
        history.sort(key=lambda x: x['order'], reverse=False)
        def reformat_record(record: dict[str, Any]) -> List[Dict[str, Any]]:
            user = dict(role='user', content=record['user'])
            assistant = dict(role='assistant', content=record['assistant'])
            for k, v in record.items():
                if k not in ['user', 'assistant', 'order', 'role']:
                    assistant[k] = v
            return [user, assistant]
        history = list(map(reformat_record, history))
        gc.collect()
        return sum(history, [])


    @property
    def interaction_count(self) -> int:
        """Number of interactions.

        Returns:
            int: Number of interactions.
        """
        return len(self.history)

    def _init_memory(self, from_exist: bool = True) -> None:
        """Method to initialise the components in the memory.

        Args:
            from_exist (bool, optional): Whether to initialise from existing files. Defaults to True.
        """
        if ((from_exist) & (self.chat_id in list_chat_ids())):
            import pickle
            with open(os.path.join(self.chat_dir, 'data.pkl'), 'rb') as f:
                self._data = pickle.load(f)

        else:
            self._data = dict()
            self.save()

    def save(self) -> None:
        """Save the current state of the memory.
        """
        from ..utils import save_json, current_time
        import pickle
        self.info
        self._info['last_update'] = current_time()
        save_json(self._info, os.path.join(self.chat_dir, 'info.json'))
        with open(os.path.join(self.chat_dir, 'data.pkl'), 'wb') as f:
            pickle.dump(self._data, f)

    def update_system_message(self, system: str) -> None:
        """Update the default system message for the memory.

        Args:
            system (str): New system message.
        """
        self.info['system'] = system.strip()
        self.save()

    def update_title(self, title: str) -> None:
        """Update the title of the memory.

        Args:
            title (str): New chat memory title.
        """
        title = title.strip()
        if title == '':
            raise ValueError('Chat title cannot be an empty string.')
        self.info['title'] = title
        self.save()

    def save_interaction(self, user_input: str, assistant_output: str, **kwargs) -> None:
        """Saving an interaction to the memory.

        Args:
            user_input (str): User input.
            assistant_output (str): Chatbot output.
        """
        from ..Schemas.documents import Document
        from copy import deepcopy
        user_input = user_input.strip(' \n\r\t')
        assistant_output = assistant_output.strip(' \n\r\t')
        metadata = dict(user=user_input, assistant=assistant_output, order=self.interaction_count)
        for k, v in kwargs.items():
            if k not in ['user', 'assistant', 'order', 'role']:
                metadata[k] = v
        new_key = max(list(self._data.keys())) + 1 if len(self._data) != 0 else 0
        meta_user = deepcopy(metadata)
        meta_user['role'] = 'user'
        meta_assistant = deepcopy(metadata)
        meta_assistant['role'] = 'assistant'
        self._data[new_key] = Document(
            index=user_input,
            metadata=meta_user
        )
        self._data[new_key + 1] = Document(
            index=assistant_output,
            metadata=meta_assistant
        )
        self.save()

    def remove_last_interaction(self) -> None:
        """Remove the latest interaction.
        """
        if len(self._data) != 0:
            order = self.interaction_count
            data = list(filter(lambda x: x.metadata['order'] != order, list(self._data.values())))
            self._data = dict(zip(range(len(data)), data))
            self.save()

    def clear(self) -> None:
        """Empty the whole chat history.
        """
        self._init_memory(from_exist=False)

    def get_recent_memory(self, k: int = 3) -> List[Tuple[str, str]]:
        """Get the last k interactions as a list.

        Args:
            k (int, optional): Maximum number of latest interactions. Defaults to 3.

        Returns:
            List[Tuple[str, str]]: List of interactions.
        """
        from copy import deepcopy
        history = self.history
        results = history if len(history) <= k else history[-k:]
        return deepcopy(results)

    def get_token_memory(self, llm: Type[BaseLLM], token_limit: int = 400) -> List[str]:
        """Get the latest conversation limited by number of tokens.

        Args:
            llm (Type[BaseLLM]): LLM to count tokens.
            token_limit (int, optional): Maximum number of tokens allowed. Defaults to 400.

        Returns:
            List[str]: List of most recent messages.
        """
        if len(self.history) == 0:
            return []
        tk_count = 0
        history = sum(list(map(list, self.history)), [])
        history = list(reversed(history))
        results = list()
        for m in history:
            msg_count = llm.get_num_tokens(m)
            if (((msg_count + tk_count) <= token_limit) | (len(results) < 2)):
                results = [m] + results
                tk_count += msg_count
            else:
                break
        return results

Subclasses

Instance variables

var chat_dir : str

Directory of the chat.

Returns

str
Directory of the chat.
Expand source code
@property
def chat_dir(self) -> str:
    """Directory of the chat.

    Returns:
        str: Directory of the chat.
    """
    chat_dir = os.path.join(chat_memory_home(), self.chat_id)
    if not os.path.exists(chat_dir):
        os.makedirs(chat_dir)
    return chat_dir
var chat_id : str

Unique identifier of the chat memory.

Returns

str
Unique identifier of the chat memory.
Expand source code
@property
def chat_id(self) -> str:
    """Unique identifier of the chat memory.

    Returns:
        str: Unique identifier of the chat memory.
    """
    return self._chat_id
var history : List[Tuple[str, str]]

Entire chat history.

Returns

List[Tuple[str, str]]
Entire chat history.
Expand source code
@property    
def history(self) -> List[Tuple[str, str]]:
    """Entire chat history.

    Returns:
        List[Tuple[str, str]]: Entire chat history.
    """
    history = list(map(lambda x: [x.metadata['user'], x.metadata['assistant'], x.metadata['order']], self._data.values()))
    if len(history) == 0:
        return []
    count = max(list(map(lambda x: x[2], history))) + 1
    history = list(map(lambda x: list(filter(lambda y: y[2] == x, history))[0], range(count)))
    history.sort(key=lambda x: x[2], reverse=False)
    return list(map(lambda x: tuple(x[:2]), history))
var history_dict : List[Dict[str, Any]]

Entire history as dictionaries.

Returns

List[Dict[str, Any]]
Entire history as dictionaries.
Expand source code
@property
def history_dict(self) -> List[Dict[str, Any]]:
    """Entire history as dictionaries.

    Returns:
        List[Dict[str, Any]]: Entire history as dictionaries.
    """
    import gc
    history: list[dict[str, Any]] = list(map(lambda x: x.metadata, self._data.values()))
    if len(history) == 0:
        return []
    count = max(list(map(lambda x: x['order'], history))) + 1
    history = list(map(lambda x: list(filter(lambda y: y['order'] == x, history))[0], range(count)))
    history.sort(key=lambda x: x['order'], reverse=False)
    def reformat_record(record: dict[str, Any]) -> List[Dict[str, Any]]:
        user = dict(role='user', content=record['user'])
        assistant = dict(role='assistant', content=record['assistant'])
        for k, v in record.items():
            if k not in ['user', 'assistant', 'order', 'role']:
                assistant[k] = v
        return [user, assistant]
    history = list(map(reformat_record, history))
    gc.collect()
    return sum(history, [])
var info : Dict[str, Any]

Information of the chat.

Returns

Dict[str, Any]
Information of the chat.
Expand source code
@property
def info(self) -> Dict[str, Any]:
    """Information of the chat.

    Returns:
        Dict[str, Any]: Information of the chat.
    """
    if hasattr(self, '_info'):
        return self._info
    elif 'info.json' in os.listdir(self.chat_dir):
        from ..utils import read_json
        self._info = read_json(os.path.join(self.chat_dir, 'info.json'))
        return self._info
    else:
        from ..utils import save_json, current_time
        self._info = dict(title='New Chat', last_update=current_time())
        save_json(self._info, os.path.join(self.chat_dir, 'info.json'))
        return self._info
var interaction_count : int

Number of interactions.

Returns

int
Number of interactions.
Expand source code
@property
def interaction_count(self) -> int:
    """Number of interactions.

    Returns:
        int: Number of interactions.
    """
    return len(self.history)
var system : str

Default system message of the memory.

Returns

str
Default system message of the memory.
Expand source code
@property
def system(self) -> str:
    """Default system message of the memory.

    Returns:
        str: Default system message of the memory.
    """
    return self.info.get('system', DEFAULT_SYSTEM_MESSAGE)
var title : str

Chat title.

Returns

str
Chat title.
Expand source code
@property
def title(self) -> str:
    """Chat title.

    Returns:
        str: Chat title.
    """
    title = self.info.get('title')
    if title is None:
        self.info['title'] = 'New Chat'
        self.save()
    return title

Methods

def clear(self) ‑> None

Empty the whole chat history.

Expand source code
def clear(self) -> None:
    """Empty the whole chat history.
    """
    self._init_memory(from_exist=False)
def get_recent_memory(self, k: int = 3) ‑> List[Tuple[str, str]]

Get the last k interactions as a list.

Args

k : int, optional
Maximum number of latest interactions. Defaults to 3.

Returns

List[Tuple[str, str]]
List of interactions.
Expand source code
def get_recent_memory(self, k: int = 3) -> List[Tuple[str, str]]:
    """Get the last k interactions as a list.

    Args:
        k (int, optional): Maximum number of latest interactions. Defaults to 3.

    Returns:
        List[Tuple[str, str]]: List of interactions.
    """
    from copy import deepcopy
    history = self.history
    results = history if len(history) <= k else history[-k:]
    return deepcopy(results)
def get_token_memory(self, llm: Type[BaseLLM], token_limit: int = 400) ‑> List[str]

Get the latest conversation limited by number of tokens.

Args

llm : Type[BaseLLM]
LLM to count tokens.
token_limit : int, optional
Maximum number of tokens allowed. Defaults to 400.

Returns

List[str]
List of most recent messages.
Expand source code
def get_token_memory(self, llm: Type[BaseLLM], token_limit: int = 400) -> List[str]:
    """Get the latest conversation limited by number of tokens.

    Args:
        llm (Type[BaseLLM]): LLM to count tokens.
        token_limit (int, optional): Maximum number of tokens allowed. Defaults to 400.

    Returns:
        List[str]: List of most recent messages.
    """
    if len(self.history) == 0:
        return []
    tk_count = 0
    history = sum(list(map(list, self.history)), [])
    history = list(reversed(history))
    results = list()
    for m in history:
        msg_count = llm.get_num_tokens(m)
        if (((msg_count + tk_count) <= token_limit) | (len(results) < 2)):
            results = [m] + results
            tk_count += msg_count
        else:
            break
    return results
def remove_last_interaction(self) ‑> None

Remove the latest interaction.

Expand source code
def remove_last_interaction(self) -> None:
    """Remove the latest interaction.
    """
    if len(self._data) != 0:
        order = self.interaction_count
        data = list(filter(lambda x: x.metadata['order'] != order, list(self._data.values())))
        self._data = dict(zip(range(len(data)), data))
        self.save()
def save(self) ‑> None

Save the current state of the memory.

Expand source code
def save(self) -> None:
    """Save the current state of the memory.
    """
    from ..utils import save_json, current_time
    import pickle
    self.info
    self._info['last_update'] = current_time()
    save_json(self._info, os.path.join(self.chat_dir, 'info.json'))
    with open(os.path.join(self.chat_dir, 'data.pkl'), 'wb') as f:
        pickle.dump(self._data, f)
def save_interaction(self, user_input: str, assistant_output: str, **kwargs) ‑> None

Saving an interaction to the memory.

Args

user_input : str
User input.
assistant_output : str
Chatbot output.
Expand source code
def save_interaction(self, user_input: str, assistant_output: str, **kwargs) -> None:
    """Saving an interaction to the memory.

    Args:
        user_input (str): User input.
        assistant_output (str): Chatbot output.
    """
    from ..Schemas.documents import Document
    from copy import deepcopy
    user_input = user_input.strip(' \n\r\t')
    assistant_output = assistant_output.strip(' \n\r\t')
    metadata = dict(user=user_input, assistant=assistant_output, order=self.interaction_count)
    for k, v in kwargs.items():
        if k not in ['user', 'assistant', 'order', 'role']:
            metadata[k] = v
    new_key = max(list(self._data.keys())) + 1 if len(self._data) != 0 else 0
    meta_user = deepcopy(metadata)
    meta_user['role'] = 'user'
    meta_assistant = deepcopy(metadata)
    meta_assistant['role'] = 'assistant'
    self._data[new_key] = Document(
        index=user_input,
        metadata=meta_user
    )
    self._data[new_key + 1] = Document(
        index=assistant_output,
        metadata=meta_assistant
    )
    self.save()
def update_system_message(self, system: str) ‑> None

Update the default system message for the memory.

Args

system : str
New system message.
Expand source code
def update_system_message(self, system: str) -> None:
    """Update the default system message for the memory.

    Args:
        system (str): New system message.
    """
    self.info['system'] = system.strip()
    self.save()
def update_title(self, title: str) ‑> None

Update the title of the memory.

Args

title : str
New chat memory title.
Expand source code
def update_title(self, title: str) -> None:
    """Update the title of the memory.

    Args:
        title (str): New chat memory title.
    """
    title = title.strip()
    if title == '':
        raise ValueError('Chat title cannot be an empty string.')
    self.info['title'] = title
    self.save()