Module llmflex.Prompts.prompt_template
Expand source code
from __future__ import annotations
from jinja2 import Environment
from typing import List, Dict, Any, Optional, Literal, Union, Tuple
DEFAULT_SYSTEM_MESSAGE = """This is a conversation between a human user and a helpful AI assistant."""
presets = {
'Default' : {
'template': "{% if messages[0]['role'] == 'system' %}{% set loop_messages = messages[1:] %}{{ 'SYSTEM:\n' + messages[0]['content'].strip() + '\n\n' }}{% else %}{% set loop_messages = messages %}{% endif %}{% for message in loop_messages %}{% if message['role'] == 'user' %}{{ 'USER: ' + message['content'].strip() + '\n' }}{% elif message['role'] == 'assistant' %}{{ 'ASSISTANT: ' + message['content'].strip() + '\n' }}{% else %}{{ message['role'].upper() + ': ' + message['content'].strip() + eos_token + '\n' }}{% endif %}{% endfor %}{% if add_generation_prompt %}{{ 'ASSISTANT:' }}{% endif %}",
'eos_token': '</s>',
'bos_token': '<s>',
'stop': ['\nASSISTANT', '\nUSER:', 'ASSISTANT:', 'USER:']
},
'Llama2' : {
'template': "{% if messages[0]['role'] == 'system' %}{% set loop_messages = messages[1:] %}{% set system_message = messages[0]['content'] %}{% elif false == true and not '<<SYS>>' in messages[0]['content'] %}{% set loop_messages = messages %}{% set system_message = 'You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.\\n\\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don\\'t know the answer to a question, please don\\'t share false information.' %}{% else %}{% set loop_messages = messages %}{% set system_message = false %}{% endif %}{% for message in loop_messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if loop.index0 == 0 and system_message != false %}{% set content = '<<SYS>>\\n' + system_message + '\\n<</SYS>>\\n\\n' + message['content'] %}{% else %}{% set content = message['content'] %}{% endif %}{% if message['role'] == 'user' %}{{ bos_token + '[INST] ' + content.strip() + ' [/INST]' }}{% elif message['role'] == 'system' %}{{ '<<SYS>>\\n' + content.strip() + '\\n<</SYS>>\\n\\n' }}{% elif message['role'] == 'assistant' %}{{ ' ' + content.strip() + ' ' + eos_token }}{% endif %}{% endfor %}",
'eos_token': '</s>',
'bos_token': '<s>',
'stop': ['</s>', '</s><s>', '[INST]', '<s>[INST]'],
'keywords': ['[INST]', '[/INST]']
},
'Llama3' : {
'template': "{% if not add_generation_prompt is defined %}{% set add_generation_prompt = false %}{% endif %}{% set loop_messages = messages %}{% for message in loop_messages %}{% set content = '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n'+ message['content'] | trim + '<|eot_id|>' %}{% if loop.index0 == 0 %}{% set content = bos_token + content %}{% endif %}{{ content }}{% endfor %}{% if add_generation_prompt %}{{ '<|start_header_id|>assistant<|end_header_id|>\n\n' }}{% else %}{{ eos_token }}{% endif %}",
'eos_token': '<|end_of_text|>',
'bos_token': '<|begin_of_text|>',
'stop': ['</s>', '<|end_of_text|>', '<|eot_id|>', '<|start_header_id|>'],
'keywords': ['<|start_header_id|>', '<|end_header_id|>']
},
'Vicuna' : {
'template': "{% if messages[0]['role'] == 'system' %}{% set loop_messages = messages[1:] %}{{ messages[0]['content'].strip() + '\n\n' }}{% else %}{% set loop_messages = messages %}{% endif %}{% for message in loop_messages %}{% if message['role'] == 'user' %}{{ 'USER: ' + message['content'].strip() + '\n' }}{% elif message['role'] == 'assistant' %}{{ 'ASSISTANT: ' + message['content'].strip() + eos_token + '\n' }}{% endif %}{% endfor %}{% if add_generation_prompt %}{{ 'ASSISTANT:' }}{% endif %}",
'eos_token': '</s>',
'bos_token': '<s>',
'stop': ['\nASSISTANT', '\nUSER:', 'ASSISTANT:', 'USER:', '</s>'],
'keywords': ['USER:', 'ASSISTANT:']
},
'ChatML' : {
'template': "{{bos_token}}{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}",
'eos_token': '<|im_end|>',
'bos_token': '<s>',
'stop': ['<|im_start|>', '<|im_end|>', '<|im_start|>user\n', '<|im_end|>\n<|im_start|>user\n', '</s>'],
'keywords': ['<|im_start|>', '<|im_end|>']
},
'Zephyr' : {
'template': "{% for message in messages %}\n{% if message['role'] == 'user' %}\n{{ '<|user|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'system' %}\n{{ '<|system|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'assistant' %}\n{{ '<|assistant|>\n' + message['content'] + eos_token }}\n{% endif %}\n{% if loop.last and add_generation_prompt %}\n{{ '<|assistant|>' }}\n{% endif %}\n{% endfor %}",
'eos_token': '</s>',
'bos_token': '<s>',
'stop': ['</s>', '</s>\n', '<|user|>\n', '</s>\n<|user|>\n'],
'keywords': ['<|system|>']
},
'OpenChat' : {
'template': "{{ bos_token }}{% for message in messages %}{{ 'GPT4 Correct ' + message['role'].title() + ': ' + message['content'] + '<|end_of_turn|>'}}{% endfor %}{% if add_generation_prompt %}{{ 'GPT4 Correct Assistant:' }}{% endif %}",
'eos_token': '<|end_of_turn|>',
'bos_token': '<s>',
'stop': ['</s>', '<|end_of_turn|>', '<|end_of_turn|>GPT4 Correct Assistant: '],
'keywords': ['GPT4 Correct']
},
'Alpaca' : {
'template': "{% for message in messages %}{% if message['role'] == 'system' %}{% if message['content']%}{{'### Instruction: ' + message['content']+'\n'}}{% endif %}{% elif message['role'] == 'user' %}{{'### Input: ' + message['content']+'\n'}}{% elif message['role'] == 'assistant' %}{{'### Response: ' + message['content'] + '\n'}}{% endif %}{% if loop.last and add_generation_prompt %}{{ '### Response:' }}{% endif %}{% endfor %}",
'eos_token': '<|end_of_turn|>',
'bos_token': '<s>',
'stop': ['### Input: ', '\n### Input: ', '###'],
'keywords': ['### Input:', '### Response:']
},
'Phi3' : {
'template': "{{ bos_token }}{% for message in messages %}{% if (message['role'] == 'user') %}{{'<|user|>' + '\n' + message['content'] + '<|end|>' + '\n' + '<|assistant|>' + '\n'}}{% elif (message['role'] == 'assistant') %}{{message['content'] + '<|end|>' + '\n'}}{% endif %}{% endfor %}",
'eos_token': '<|endoftext|>',
'bos_token': '<s>',
'stop': ['<|end|>', '<|endoftext|>'],
'keywords': ['<|end|>']
},
}
hidden_presets = {
'Llama2' : {
'template': "{% if messages[0]['role'] == 'system' %}{% set loop_messages = messages[1:] %}{% set system_message = messages[0]['content'] %}{% else %}{% set loop_messages = messages %}{% set system_message = false %}{% endif %}{% for message in loop_messages %}{% if loop.index0 == 0 and system_message != false %}{% set content = '<<SYS>>\\n' + system_message + '\\n<</SYS>>\\n\\n' + message['content'] %}{% else %}{% set content = message['content'] %}{% endif %}{% if message['role'] == 'user' %}{% if loop.index0 == 0 %}{{ '[INST] ' + content.strip() + ' [/INST]' }}{% else %}{{ bos_token + '[INST] ' + content.strip() + ' [/INST]' }}{% endif %}{% elif message['role'] == 'system' %}{{ '<<SYS>>\\n' + content.strip() + '\\n<</SYS>>\\n\\n' }}{% elif message['role'] == 'assistant' %}{% if loop.index0 == 0 and system_message != false %}{% set prefix = '[INST]' + '<<SYS>>\\n' + system_message + '\\n<</SYS>>\\n\\n' + '[/INST] ' %}{% set content = message['content'] %}{% elif loop.index0 == 0 %}{% set prefix = '[INST][/INST] ' %}{% set content = message['content'] %}{% else %}{% set prefix = ' ' %}{% endif %}{{ prefix + content.strip() + ' ' + eos_token }}{% endif %}{% endfor %}",
'eos_token': '</s>',
'bos_token': '<s>',
'stop': ['</s>', '</s><s>', '[INST]', '<s>[INST]']
},
'Zephyr' : {
'template': "{% for message in messages %}\n{% if message['role'] == 'user' %}\n{{ '<|user|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'system' %}\n{{ '<|system|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'assistant' %}\n{{ '<|assistant|>\n' + message['content'] + eos_token }}\n{% else %}\n{{ '<|' + message['role'] + '|>\n' + message['content'] + eos_token }}\n{% endif %}\n{% if loop.last and add_generation_prompt %}\n{{ '<|assistant|>' }}\n{% endif %}\n{% endfor %}",
'eos_token': '</s>',
'bos_token': '<s>',
'stop': ['</s>', '</s>\n', '<|user|>\n', '</s>\n<|user|>\n']
},
'Vicuna' : {
'template': "{% if messages[0]['role'] == 'system' %}{% set loop_messages = messages[1:] %}{{ messages[0]['content'].strip() + '\n\n' }}{% else %}{% set loop_messages = messages %}{% endif %}{% for message in loop_messages %}{% if message['role'] == 'user' %}{{ 'USER: ' + message['content'].strip() + '\n' }}{% elif message['role'] == 'assistant' %}{{ 'ASSISTANT: ' + message['content'].strip() + eos_token + '\n' }}{% else %}{{ message['role'].upper() + ': ' + message['content'].strip() + eos_token + '\n' }}{% endif %}{% endfor %}{% if add_generation_prompt %}{{ 'ASSISTANT:' }}{% endif %}",
'eos_token': '</s>',
'bos_token': '<s>',
'stop': ['\nASSISTANT', '\nUSER:', 'ASSISTANT:', 'USER:', '</s>']
},
'Phi3' : {
'template': "{{ bos_token }}{% for message in messages %}{{'<|' + message['role'] + '|>' + '\n' + message['content'] + '<|end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|assistant|>\n' }}{% endif %}",
'eos_token': '<|endoftext|>',
'bos_token': '<s>',
'stop': ['<|end|>', '<|endoftext|>'],
'keywords': ['<|end|>']
},
}
PRESET_FORMATS = Literal['Default', 'Llama2', 'Llama3', 'Vicuna', 'ChatML', 'Zephyr', 'OpenChat', 'Alpaca', 'Phi3']
class PromptTemplate:
"""Class for storing prompt format presets.
"""
def __init__(
self,
template: str,
eos_token: Optional[str],
bos_token: Optional[str],
stop: Optional[List[str]] = None,
force_real_template: bool = False,
**kwargs
) -> None:
"""Initialising the chat prompt class.
Args:
template (str): Jinja2 template.
eos_token (Optional[str]): EOS token string.
bos_token (Optional[str]): BOS token string.
stop (Optional[List[str]], optional): List of stop strings for the llm. If None is given, the EOS token string will be used. Defaults to None.
force_real_template (bool, optional): Whether to render the given template. For most templates it has no effects. Only for some restrictive templates like llama2. Defaults to False.
"""
self._template = template
self._eos_token = eos_token
self._bos_token = bos_token
self._stop = stop
self._force_real_template = force_real_template
self._keywords = kwargs.get('keywords', [])
@property
def template(self) -> str:
"""Jinja template string.
Returns:
str: Jinja template string.
"""
return self._template
@property
def _hidden_template(self) -> str:
"""To fix issues with some default templates like llama 2.
Returns:
str: The actual template to be rendered.
"""
config = hidden_presets.get(self.template_name)
return self.template if config is None else config['template']
@property
def rendered_template(self) -> Environment:
"""Rendered Jinja template.
Returns:
Environment: Rendered Jinja template.
"""
if not hasattr(self, '_rendered_template'):
from jinja2 import BaseLoader
template = self.template if self._force_real_template else self._hidden_template
self._rendered_template = Environment(loader=BaseLoader).from_string(template)
return self._rendered_template
@property
def keywords(self) -> List[str]:
"""List of keywords to search for in Jinja templates for template detection. Used for presets.
Returns:
List[str]: List of keywords to search for in Jinja templates for template detection.
"""
if not hasattr(self, '_keywords'):
self._keywords = []
return self._keywords
@property
def eos_token(self) -> Optional[str]:
return self._eos_token
@property
def bos_token(self) -> Optional[str]:
return self._bos_token
@property
def stop(self) -> List[str]:
return self._stop if isinstance(self._stop, list) else [self.eos_token] if self.eos_token is not None else []
@property
def template_name(self) -> str:
"""Name of the template.
Returns:
str: Name of the template.
"""
if not hasattr(self, '_template_name'):
self._template_name = 'Unititled template'
for k, v in presets.items():
if self.template == v['template']:
self._template_name = k
break
return self._template_name
@property
def allow_custom_role(self) -> bool:
"""Check if custom role can be used with the prompt template.
Returns:
bool: Whether custom role can be used with the prompt template.
"""
if not hasattr(self, '_allow_custom_role'):
test = [dict(role='user', content='random message'), dict(role='customrole', content='exist')]
try:
output = self.create_custom_prompt(test)
self._allow_custom_role = 'customrole' in output.lower()
except:
self._allow_custom_role = False
return self._allow_custom_role
def format_history(self, history: Union[List[str], List[Tuple[str, str]]], return_list: bool = False) -> Union[str, List[Dict[str, str]]]:
"""Formatting a list of conversation history into a full string of conversation history or a list of messages for the Jinja template to render.
Args:
history (Union[List[str], List[Tuple[str, str]]]): List of conversation history.
return_list (bool, optional): Whether to return a list of messages for the Jinja template to render. Defaults to False.
Returns:
Union[str, List[Dict[str, str]]]: A full string of conversation history or a list of messages for the Jinja template to render.
"""
if len(history) == 0:
return [] if return_list else ''
elif not isinstance(history[0], str):
body = list(map(lambda x: [dict(role='user', content=x[0]), dict(role='assistant', content=x[1])], history))
body = sum(body, [])
else:
length = len(history)
is_even = length % 2 == 0
half = int(length / 2) if is_even else int((length + 1) / 2)
roles = ['user', 'assistant'] * half
roles = roles if is_even else roles[1:]
body = list(map(lambda x: dict(role=x[0], content=x[1]), list(zip(roles, history))))
if return_list:
return body
return self.rendered_template.render(messages=body, bos_token=self.bos_token, eos_token=self.eos_token, add_generation_prompt=False)
def create_prompt(self, user: str, system: str = DEFAULT_SYSTEM_MESSAGE, history: Optional[Union[List[str], List[Tuple[str, str]]]] = None) -> str:
"""Creating the full chat prompt.
Args:
user (str): Latest user input.
system (str, optional): System message. Defaults to DEFAULT_SYSTEM_MESSAGE.
history (Optional[Union[List[str], List[Tuple[str, str]]]], optional): List of conversation history. Defaults to None.
Returns:
str: The full prompt.
"""
head = [dict(role='system', content=system)] if system.strip() != '' else []
history = [] if history is None else history
body = self.format_history(history=history, return_list=True)
tail = [dict(role='user', content=user)]
prompt = head + body + tail
return self.rendered_template.render(messages=prompt, bos_token=self.bos_token, eos_token=self.eos_token, add_generation_prompt=True)
def create_custom_prompt(self, messages: List[Dict[str, str]], add_generation_prompt: bool = True) -> str:
"""Creating a custom prompt with your given list of messages. Each message should contain a dictionary with the key "role" and "content".
Args:
messages (List[Dict[str, str]]): List of messages. Each message should contain a dictionary with the key "role" and "content".
add_generation_prompt (bool, optional): Whether to add the assistant tokens at the end of the prompt. Defaults to True.
Returns:
str: The full prompt given your messages.
"""
return self.rendered_template.render(messages=messages, bos_token=self.bos_token, eos_token=self.eos_token, add_generation_prompt=add_generation_prompt)
def create_custom_prompt_with_open_role(self, messages: List[Dict[str, str]], end_role: str = '', begin_text: str = '') -> str:
"""Creating a custom prompt with your given list of messages. Each message should contain a dictionary with the key "role" and "content". The prompt will end with starting prompt of the end_role instead of assistant.
Args:
messages (List[Dict[str, str]]): List of messages. Each message should contain a dictionary with the key "role" and "content".
end_role (str, optional): The role for text generation instead of assistant. If an empty string is given, it means that the role can be anything the llm is going to generate. Defaults to ''.
begin_text (str, optional): The beginning text of the last role. Defaults to ''.
Returns:
str: The full prompt with your custom role.
"""
if self.allow_custom_role:
role_ph = end_role if end_role.strip() != '' else 'place_holder_role'
text_ph = '$$$PLACE_HOLDER_TEXT$$$'
prompt = self.create_custom_prompt(messages=messages + [dict(role=role_ph, content=text_ph)], add_generation_prompt=False)
prompt = text_ph.join(prompt.split(text_ph)[:-1])
real = self.create_custom_prompt(messages=messages + [dict(role=end_role, content=text_ph)], add_generation_prompt=False)
real = text_ph.join(real.split(text_ph)[:-1])
if real == prompt:
return real + begin_text
elif begin_text.strip() != '':
return real + begin_text
else:
import os
return os.path.commonprefix([real, prompt])
else:
raise AssertionError(f'This prompt template "{self.template_name}" does not allow custom_roles.')
@classmethod
def from_dict(cls, format_dict: Dict[str, Any], template_name: Optional[str] = None) -> PromptTemplate:
"""Initialise the prompt template from a dictionary.
Args:
format_dict (Dict[str, Any]): Dictionary of the prompt format.
template_name (Optional[str], optional): Name of the template. Defaults to None.
Returns:
PromptTemplate: The initialised PromptTemplate instance.
"""
template = cls(**format_dict)
if template_name is not None:
template._template_name = template_name
return template
@classmethod
def from_json(cls, file_dir: str) -> PromptTemplate:
"""Initialise the prompt template from a json file.
Args:
file_dir (str): json file path of the prompt format.
Returns:
PromptTemplatet: The initialised PromptTemplate instance.
"""
from ..utils import read_json
return cls.from_dict(read_json(file_dir=file_dir), template_name=file_dir)
@classmethod
def from_preset(cls, style: PRESET_FORMATS, force_real_template: bool = False) -> PromptTemplate:
"""Initialise the prompt template from a preset.
Args:
style (PRESET_FORMATS): Format of the prompt.
force_real_template (bool, optional): Whether to render the given template. For most templates it has no effects. Only for some restrictive templates like llama2. Defaults to False.
Returns:
PromptTemplate: The initialised PromptTemplate instance.
"""
from copy import deepcopy
preset = deepcopy(presets[style])
preset['force_real_template'] = force_real_template
return cls.from_dict(preset, template_name=style)
def to_dict(self) -> Dict[str, Any]:
"""Export the class as a dictionary.
Returns:
Dict[str, Any]: Prompt format as a dictionary.
"""
return dict(
template = self.template,
eos_token = self.eos_token,
bos_token = self.bos_token,
stop = self.stop if self.stop is not None else [self.eos_token]
)
Classes
class PromptTemplate (template: str, eos_token: Optional[str], bos_token: Optional[str], stop: Optional[List[str]] = None, force_real_template: bool = False, **kwargs)-
Class for storing prompt format presets.
Initialising the chat prompt class.
Args
template:str- Jinja2 template.
eos_token:Optional[str]- EOS token string.
bos_token:Optional[str]- BOS token string.
stop:Optional[List[str]], optional- List of stop strings for the llm. If None is given, the EOS token string will be used. Defaults to None.
force_real_template:bool, optional- Whether to render the given template. For most templates it has no effects. Only for some restrictive templates like llama2. Defaults to False.
Expand source code
class PromptTemplate: """Class for storing prompt format presets. """ def __init__( self, template: str, eos_token: Optional[str], bos_token: Optional[str], stop: Optional[List[str]] = None, force_real_template: bool = False, **kwargs ) -> None: """Initialising the chat prompt class. Args: template (str): Jinja2 template. eos_token (Optional[str]): EOS token string. bos_token (Optional[str]): BOS token string. stop (Optional[List[str]], optional): List of stop strings for the llm. If None is given, the EOS token string will be used. Defaults to None. force_real_template (bool, optional): Whether to render the given template. For most templates it has no effects. Only for some restrictive templates like llama2. Defaults to False. """ self._template = template self._eos_token = eos_token self._bos_token = bos_token self._stop = stop self._force_real_template = force_real_template self._keywords = kwargs.get('keywords', []) @property def template(self) -> str: """Jinja template string. Returns: str: Jinja template string. """ return self._template @property def _hidden_template(self) -> str: """To fix issues with some default templates like llama 2. Returns: str: The actual template to be rendered. """ config = hidden_presets.get(self.template_name) return self.template if config is None else config['template'] @property def rendered_template(self) -> Environment: """Rendered Jinja template. Returns: Environment: Rendered Jinja template. """ if not hasattr(self, '_rendered_template'): from jinja2 import BaseLoader template = self.template if self._force_real_template else self._hidden_template self._rendered_template = Environment(loader=BaseLoader).from_string(template) return self._rendered_template @property def keywords(self) -> List[str]: """List of keywords to search for in Jinja templates for template detection. Used for presets. Returns: List[str]: List of keywords to search for in Jinja templates for template detection. """ if not hasattr(self, '_keywords'): self._keywords = [] return self._keywords @property def eos_token(self) -> Optional[str]: return self._eos_token @property def bos_token(self) -> Optional[str]: return self._bos_token @property def stop(self) -> List[str]: return self._stop if isinstance(self._stop, list) else [self.eos_token] if self.eos_token is not None else [] @property def template_name(self) -> str: """Name of the template. Returns: str: Name of the template. """ if not hasattr(self, '_template_name'): self._template_name = 'Unititled template' for k, v in presets.items(): if self.template == v['template']: self._template_name = k break return self._template_name @property def allow_custom_role(self) -> bool: """Check if custom role can be used with the prompt template. Returns: bool: Whether custom role can be used with the prompt template. """ if not hasattr(self, '_allow_custom_role'): test = [dict(role='user', content='random message'), dict(role='customrole', content='exist')] try: output = self.create_custom_prompt(test) self._allow_custom_role = 'customrole' in output.lower() except: self._allow_custom_role = False return self._allow_custom_role def format_history(self, history: Union[List[str], List[Tuple[str, str]]], return_list: bool = False) -> Union[str, List[Dict[str, str]]]: """Formatting a list of conversation history into a full string of conversation history or a list of messages for the Jinja template to render. Args: history (Union[List[str], List[Tuple[str, str]]]): List of conversation history. return_list (bool, optional): Whether to return a list of messages for the Jinja template to render. Defaults to False. Returns: Union[str, List[Dict[str, str]]]: A full string of conversation history or a list of messages for the Jinja template to render. """ if len(history) == 0: return [] if return_list else '' elif not isinstance(history[0], str): body = list(map(lambda x: [dict(role='user', content=x[0]), dict(role='assistant', content=x[1])], history)) body = sum(body, []) else: length = len(history) is_even = length % 2 == 0 half = int(length / 2) if is_even else int((length + 1) / 2) roles = ['user', 'assistant'] * half roles = roles if is_even else roles[1:] body = list(map(lambda x: dict(role=x[0], content=x[1]), list(zip(roles, history)))) if return_list: return body return self.rendered_template.render(messages=body, bos_token=self.bos_token, eos_token=self.eos_token, add_generation_prompt=False) def create_prompt(self, user: str, system: str = DEFAULT_SYSTEM_MESSAGE, history: Optional[Union[List[str], List[Tuple[str, str]]]] = None) -> str: """Creating the full chat prompt. Args: user (str): Latest user input. system (str, optional): System message. Defaults to DEFAULT_SYSTEM_MESSAGE. history (Optional[Union[List[str], List[Tuple[str, str]]]], optional): List of conversation history. Defaults to None. Returns: str: The full prompt. """ head = [dict(role='system', content=system)] if system.strip() != '' else [] history = [] if history is None else history body = self.format_history(history=history, return_list=True) tail = [dict(role='user', content=user)] prompt = head + body + tail return self.rendered_template.render(messages=prompt, bos_token=self.bos_token, eos_token=self.eos_token, add_generation_prompt=True) def create_custom_prompt(self, messages: List[Dict[str, str]], add_generation_prompt: bool = True) -> str: """Creating a custom prompt with your given list of messages. Each message should contain a dictionary with the key "role" and "content". Args: messages (List[Dict[str, str]]): List of messages. Each message should contain a dictionary with the key "role" and "content". add_generation_prompt (bool, optional): Whether to add the assistant tokens at the end of the prompt. Defaults to True. Returns: str: The full prompt given your messages. """ return self.rendered_template.render(messages=messages, bos_token=self.bos_token, eos_token=self.eos_token, add_generation_prompt=add_generation_prompt) def create_custom_prompt_with_open_role(self, messages: List[Dict[str, str]], end_role: str = '', begin_text: str = '') -> str: """Creating a custom prompt with your given list of messages. Each message should contain a dictionary with the key "role" and "content". The prompt will end with starting prompt of the end_role instead of assistant. Args: messages (List[Dict[str, str]]): List of messages. Each message should contain a dictionary with the key "role" and "content". end_role (str, optional): The role for text generation instead of assistant. If an empty string is given, it means that the role can be anything the llm is going to generate. Defaults to ''. begin_text (str, optional): The beginning text of the last role. Defaults to ''. Returns: str: The full prompt with your custom role. """ if self.allow_custom_role: role_ph = end_role if end_role.strip() != '' else 'place_holder_role' text_ph = '$$$PLACE_HOLDER_TEXT$$$' prompt = self.create_custom_prompt(messages=messages + [dict(role=role_ph, content=text_ph)], add_generation_prompt=False) prompt = text_ph.join(prompt.split(text_ph)[:-1]) real = self.create_custom_prompt(messages=messages + [dict(role=end_role, content=text_ph)], add_generation_prompt=False) real = text_ph.join(real.split(text_ph)[:-1]) if real == prompt: return real + begin_text elif begin_text.strip() != '': return real + begin_text else: import os return os.path.commonprefix([real, prompt]) else: raise AssertionError(f'This prompt template "{self.template_name}" does not allow custom_roles.') @classmethod def from_dict(cls, format_dict: Dict[str, Any], template_name: Optional[str] = None) -> PromptTemplate: """Initialise the prompt template from a dictionary. Args: format_dict (Dict[str, Any]): Dictionary of the prompt format. template_name (Optional[str], optional): Name of the template. Defaults to None. Returns: PromptTemplate: The initialised PromptTemplate instance. """ template = cls(**format_dict) if template_name is not None: template._template_name = template_name return template @classmethod def from_json(cls, file_dir: str) -> PromptTemplate: """Initialise the prompt template from a json file. Args: file_dir (str): json file path of the prompt format. Returns: PromptTemplatet: The initialised PromptTemplate instance. """ from ..utils import read_json return cls.from_dict(read_json(file_dir=file_dir), template_name=file_dir) @classmethod def from_preset(cls, style: PRESET_FORMATS, force_real_template: bool = False) -> PromptTemplate: """Initialise the prompt template from a preset. Args: style (PRESET_FORMATS): Format of the prompt. force_real_template (bool, optional): Whether to render the given template. For most templates it has no effects. Only for some restrictive templates like llama2. Defaults to False. Returns: PromptTemplate: The initialised PromptTemplate instance. """ from copy import deepcopy preset = deepcopy(presets[style]) preset['force_real_template'] = force_real_template return cls.from_dict(preset, template_name=style) def to_dict(self) -> Dict[str, Any]: """Export the class as a dictionary. Returns: Dict[str, Any]: Prompt format as a dictionary. """ return dict( template = self.template, eos_token = self.eos_token, bos_token = self.bos_token, stop = self.stop if self.stop is not None else [self.eos_token] )Static methods
def from_dict(format_dict: Dict[str, Any], template_name: Optional[str] = None) ‑> PromptTemplate-
Initialise the prompt template from a dictionary.
Args
format_dict:Dict[str, Any]- Dictionary of the prompt format.
template_name:Optional[str], optional- Name of the template. Defaults to None.
Returns
PromptTemplate- The initialised PromptTemplate instance.
Expand source code
@classmethod def from_dict(cls, format_dict: Dict[str, Any], template_name: Optional[str] = None) -> PromptTemplate: """Initialise the prompt template from a dictionary. Args: format_dict (Dict[str, Any]): Dictionary of the prompt format. template_name (Optional[str], optional): Name of the template. Defaults to None. Returns: PromptTemplate: The initialised PromptTemplate instance. """ template = cls(**format_dict) if template_name is not None: template._template_name = template_name return template def from_json(file_dir: str) ‑> PromptTemplate-
Initialise the prompt template from a json file.
Args
file_dir:str- json file path of the prompt format.
Returns
PromptTemplatet- The initialised PromptTemplate instance.
Expand source code
@classmethod def from_json(cls, file_dir: str) -> PromptTemplate: """Initialise the prompt template from a json file. Args: file_dir (str): json file path of the prompt format. Returns: PromptTemplatet: The initialised PromptTemplate instance. """ from ..utils import read_json return cls.from_dict(read_json(file_dir=file_dir), template_name=file_dir) def from_preset(style: PRESET_FORMATS, force_real_template: bool = False) ‑> PromptTemplate-
Initialise the prompt template from a preset.
Args
style:PRESET_FORMATS- Format of the prompt.
force_real_template:bool, optional- Whether to render the given template. For most templates it has no effects. Only for some restrictive templates like llama2. Defaults to False.
Returns
PromptTemplate- The initialised PromptTemplate instance.
Expand source code
@classmethod def from_preset(cls, style: PRESET_FORMATS, force_real_template: bool = False) -> PromptTemplate: """Initialise the prompt template from a preset. Args: style (PRESET_FORMATS): Format of the prompt. force_real_template (bool, optional): Whether to render the given template. For most templates it has no effects. Only for some restrictive templates like llama2. Defaults to False. Returns: PromptTemplate: The initialised PromptTemplate instance. """ from copy import deepcopy preset = deepcopy(presets[style]) preset['force_real_template'] = force_real_template return cls.from_dict(preset, template_name=style)
Instance variables
var allow_custom_role : bool-
Check if custom role can be used with the prompt template.
Returns
bool- Whether custom role can be used with the prompt template.
Expand source code
@property def allow_custom_role(self) -> bool: """Check if custom role can be used with the prompt template. Returns: bool: Whether custom role can be used with the prompt template. """ if not hasattr(self, '_allow_custom_role'): test = [dict(role='user', content='random message'), dict(role='customrole', content='exist')] try: output = self.create_custom_prompt(test) self._allow_custom_role = 'customrole' in output.lower() except: self._allow_custom_role = False return self._allow_custom_role var bos_token : Optional[str]-
Expand source code
@property def bos_token(self) -> Optional[str]: return self._bos_token var eos_token : Optional[str]-
Expand source code
@property def eos_token(self) -> Optional[str]: return self._eos_token var keywords : List[str]-
List of keywords to search for in Jinja templates for template detection. Used for presets.
Returns
List[str]- List of keywords to search for in Jinja templates for template detection.
Expand source code
@property def keywords(self) -> List[str]: """List of keywords to search for in Jinja templates for template detection. Used for presets. Returns: List[str]: List of keywords to search for in Jinja templates for template detection. """ if not hasattr(self, '_keywords'): self._keywords = [] return self._keywords var rendered_template : jinja2.environment.Environment-
Rendered Jinja template.
Returns
Environment- Rendered Jinja template.
Expand source code
@property def rendered_template(self) -> Environment: """Rendered Jinja template. Returns: Environment: Rendered Jinja template. """ if not hasattr(self, '_rendered_template'): from jinja2 import BaseLoader template = self.template if self._force_real_template else self._hidden_template self._rendered_template = Environment(loader=BaseLoader).from_string(template) return self._rendered_template var stop : List[str]-
Expand source code
@property def stop(self) -> List[str]: return self._stop if isinstance(self._stop, list) else [self.eos_token] if self.eos_token is not None else [] var template : str-
Jinja template string.
Returns
str- Jinja template string.
Expand source code
@property def template(self) -> str: """Jinja template string. Returns: str: Jinja template string. """ return self._template var template_name : str-
Name of the template.
Returns
str- Name of the template.
Expand source code
@property def template_name(self) -> str: """Name of the template. Returns: str: Name of the template. """ if not hasattr(self, '_template_name'): self._template_name = 'Unititled template' for k, v in presets.items(): if self.template == v['template']: self._template_name = k break return self._template_name
Methods
def create_custom_prompt(self, messages: List[Dict[str, str]], add_generation_prompt: bool = True) ‑> str-
Creating a custom prompt with your given list of messages. Each message should contain a dictionary with the key "role" and "content".
Args
messages:List[Dict[str, str]]- List of messages. Each message should contain a dictionary with the key "role" and "content".
add_generation_prompt:bool, optional- Whether to add the assistant tokens at the end of the prompt. Defaults to True.
Returns
str- The full prompt given your messages.
Expand source code
def create_custom_prompt(self, messages: List[Dict[str, str]], add_generation_prompt: bool = True) -> str: """Creating a custom prompt with your given list of messages. Each message should contain a dictionary with the key "role" and "content". Args: messages (List[Dict[str, str]]): List of messages. Each message should contain a dictionary with the key "role" and "content". add_generation_prompt (bool, optional): Whether to add the assistant tokens at the end of the prompt. Defaults to True. Returns: str: The full prompt given your messages. """ return self.rendered_template.render(messages=messages, bos_token=self.bos_token, eos_token=self.eos_token, add_generation_prompt=add_generation_prompt) def create_custom_prompt_with_open_role(self, messages: List[Dict[str, str]], end_role: str = '', begin_text: str = '') ‑> str-
Creating a custom prompt with your given list of messages. Each message should contain a dictionary with the key "role" and "content". The prompt will end with starting prompt of the end_role instead of assistant.
Args
messages:List[Dict[str, str]]- List of messages. Each message should contain a dictionary with the key "role" and "content".
end_role:str, optional- The role for text generation instead of assistant. If an empty string is given, it means that the role can be anything the llm is going to generate. Defaults to ''.
begin_text:str, optional- The beginning text of the last role. Defaults to ''.
Returns
str- The full prompt with your custom role.
Expand source code
def create_custom_prompt_with_open_role(self, messages: List[Dict[str, str]], end_role: str = '', begin_text: str = '') -> str: """Creating a custom prompt with your given list of messages. Each message should contain a dictionary with the key "role" and "content". The prompt will end with starting prompt of the end_role instead of assistant. Args: messages (List[Dict[str, str]]): List of messages. Each message should contain a dictionary with the key "role" and "content". end_role (str, optional): The role for text generation instead of assistant. If an empty string is given, it means that the role can be anything the llm is going to generate. Defaults to ''. begin_text (str, optional): The beginning text of the last role. Defaults to ''. Returns: str: The full prompt with your custom role. """ if self.allow_custom_role: role_ph = end_role if end_role.strip() != '' else 'place_holder_role' text_ph = '$$$PLACE_HOLDER_TEXT$$$' prompt = self.create_custom_prompt(messages=messages + [dict(role=role_ph, content=text_ph)], add_generation_prompt=False) prompt = text_ph.join(prompt.split(text_ph)[:-1]) real = self.create_custom_prompt(messages=messages + [dict(role=end_role, content=text_ph)], add_generation_prompt=False) real = text_ph.join(real.split(text_ph)[:-1]) if real == prompt: return real + begin_text elif begin_text.strip() != '': return real + begin_text else: import os return os.path.commonprefix([real, prompt]) else: raise AssertionError(f'This prompt template "{self.template_name}" does not allow custom_roles.') def create_prompt(self, user: str, system: str = 'This is a conversation between a human user and a helpful AI assistant.', history: Optional[Union[List[str], List[Tuple[str, str]]]] = None) ‑> str-
Creating the full chat prompt.
Args
user:str- Latest user input.
system:str, optional- System message. Defaults to DEFAULT_SYSTEM_MESSAGE.
history:Optional[Union[List[str], List[Tuple[str, str]]]], optional- List of conversation history. Defaults to None.
Returns
str- The full prompt.
Expand source code
def create_prompt(self, user: str, system: str = DEFAULT_SYSTEM_MESSAGE, history: Optional[Union[List[str], List[Tuple[str, str]]]] = None) -> str: """Creating the full chat prompt. Args: user (str): Latest user input. system (str, optional): System message. Defaults to DEFAULT_SYSTEM_MESSAGE. history (Optional[Union[List[str], List[Tuple[str, str]]]], optional): List of conversation history. Defaults to None. Returns: str: The full prompt. """ head = [dict(role='system', content=system)] if system.strip() != '' else [] history = [] if history is None else history body = self.format_history(history=history, return_list=True) tail = [dict(role='user', content=user)] prompt = head + body + tail return self.rendered_template.render(messages=prompt, bos_token=self.bos_token, eos_token=self.eos_token, add_generation_prompt=True) def format_history(self, history: Union[List[str], List[Tuple[str, str]]], return_list: bool = False) ‑> Union[str, List[Dict[str, str]]]-
Formatting a list of conversation history into a full string of conversation history or a list of messages for the Jinja template to render.
Args
history:Union[List[str], List[Tuple[str, str]]]- List of conversation history.
return_list:bool, optional- Whether to return a list of messages for the Jinja template to render. Defaults to False.
Returns
Union[str, List[Dict[str, str]]]- A full string of conversation history or a list of messages for the Jinja template to render.
Expand source code
def format_history(self, history: Union[List[str], List[Tuple[str, str]]], return_list: bool = False) -> Union[str, List[Dict[str, str]]]: """Formatting a list of conversation history into a full string of conversation history or a list of messages for the Jinja template to render. Args: history (Union[List[str], List[Tuple[str, str]]]): List of conversation history. return_list (bool, optional): Whether to return a list of messages for the Jinja template to render. Defaults to False. Returns: Union[str, List[Dict[str, str]]]: A full string of conversation history or a list of messages for the Jinja template to render. """ if len(history) == 0: return [] if return_list else '' elif not isinstance(history[0], str): body = list(map(lambda x: [dict(role='user', content=x[0]), dict(role='assistant', content=x[1])], history)) body = sum(body, []) else: length = len(history) is_even = length % 2 == 0 half = int(length / 2) if is_even else int((length + 1) / 2) roles = ['user', 'assistant'] * half roles = roles if is_even else roles[1:] body = list(map(lambda x: dict(role=x[0], content=x[1]), list(zip(roles, history)))) if return_list: return body return self.rendered_template.render(messages=body, bos_token=self.bos_token, eos_token=self.eos_token, add_generation_prompt=False) def to_dict(self) ‑> Dict[str, Any]-
Export the class as a dictionary.
Returns
Dict[str, Any]- Prompt format as a dictionary.
Expand source code
def to_dict(self) -> Dict[str, Any]: """Export the class as a dictionary. Returns: Dict[str, Any]: Prompt format as a dictionary. """ return dict( template = self.template, eos_token = self.eos_token, bos_token = self.bos_token, stop = self.stop if self.stop is not None else [self.eos_token] )