Move prompts into LLMs module (#70)
Since the only usage of prompt is within LLMs, it is reasonable to keep it within the LLM module. This way, it would be easier to discover module, and make the code base less complicated. Changes: * Move prompt components into llms * Bump version 0.3.1 * Make pip install dependencies in eager mode --------- Co-authored-by: ian <ian@cinnamon.is>
This commit is contained in:
committed by
GitHub
parent
8532138842
commit
693ed39de4
4
knowledgehub/llms/prompts/__init__.py
Normal file
4
knowledgehub/llms/prompts/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .base import BasePromptComponent
|
||||
from .template import PromptTemplate
|
||||
|
||||
__all__ = ["BasePromptComponent", "PromptTemplate"]
|
177
knowledgehub/llms/prompts/base.py
Normal file
177
knowledgehub/llms/prompts/base.py
Normal file
@@ -0,0 +1,177 @@
|
||||
from typing import Callable, Union
|
||||
|
||||
from ...base import BaseComponent, Document
|
||||
from .template import PromptTemplate
|
||||
|
||||
|
||||
class BasePromptComponent(BaseComponent):
|
||||
"""
|
||||
Base class for prompt components.
|
||||
|
||||
Args:
|
||||
template (PromptTemplate): The prompt template.
|
||||
**kwargs: Any additional keyword arguments that will be used to populate the
|
||||
given template.
|
||||
"""
|
||||
|
||||
class Config:
|
||||
middleware_switches = {"theflow.middleware.CachingMiddleware": False}
|
||||
|
||||
def __init__(self, template: Union[str, PromptTemplate], **kwargs):
|
||||
super().__init__()
|
||||
self.template = (
|
||||
template
|
||||
if isinstance(template, PromptTemplate)
|
||||
else PromptTemplate(template)
|
||||
)
|
||||
|
||||
self.__set(**kwargs)
|
||||
|
||||
def __check_redundant_kwargs(self, **kwargs):
|
||||
"""
|
||||
Check for redundant keyword arguments.
|
||||
|
||||
Parameters:
|
||||
**kwargs (dict): A dictionary of keyword arguments.
|
||||
|
||||
Raises:
|
||||
ValueError: If any keys provided are not in the template.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
self.template.check_redundant_kwargs(**kwargs)
|
||||
|
||||
def __check_unset_placeholders(self):
|
||||
"""
|
||||
Check if all the placeholders in the template are set.
|
||||
|
||||
This function checks if all the expected placeholders in the template are set as
|
||||
attributes of the object. If any placeholders are missing, a `ValueError`
|
||||
is raised with the names of the missing keys.
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
self.template.check_missing_kwargs(**self.__dict__)
|
||||
|
||||
def __validate_value_type(self, **kwargs):
|
||||
"""
|
||||
Validates the value types of the given keyword arguments.
|
||||
|
||||
Parameters:
|
||||
**kwargs (dict): A dictionary of keyword arguments to be validated.
|
||||
|
||||
Raises:
|
||||
ValueError: If any of the values in the kwargs dictionary have an
|
||||
unsupported type.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
type_error = []
|
||||
for k, v in kwargs.items():
|
||||
if not isinstance(v, (str, int, Document, Callable)): # type: ignore
|
||||
type_error.append((k, type(v)))
|
||||
|
||||
if type_error:
|
||||
raise ValueError(
|
||||
"Type of values must be either int, str, Document, Callable, "
|
||||
f"found unsupported type for (key, type): {type_error}"
|
||||
)
|
||||
|
||||
def __set(self, **kwargs):
|
||||
"""
|
||||
Set the values of the attributes in the object based on the provided keyword
|
||||
arguments.
|
||||
|
||||
Args:
|
||||
kwargs (dict): A dictionary with the attribute names as keys and the new
|
||||
values as values.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
self.__check_redundant_kwargs(**kwargs)
|
||||
self.__validate_value_type(**kwargs)
|
||||
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def __prepare_value(self):
|
||||
"""
|
||||
Generate a dictionary of keyword arguments based on the template's placeholders
|
||||
and the current instance's attributes.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary of keyword arguments.
|
||||
"""
|
||||
|
||||
def __prepare(key, value):
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
if isinstance(value, (int, Document)):
|
||||
return str(value)
|
||||
|
||||
raise ValueError(
|
||||
f"Unsupported type {type(value)} for template value of key {key}"
|
||||
)
|
||||
|
||||
kwargs = {}
|
||||
for k in self.template.placeholders:
|
||||
v = getattr(self, k)
|
||||
|
||||
# if get a callable, execute to get its output
|
||||
if isinstance(v, Callable): # type: ignore[arg-type]
|
||||
v = v()
|
||||
|
||||
if isinstance(v, list):
|
||||
v = str([__prepare(k, each) for each in v])
|
||||
elif isinstance(v, (str, int, Document)):
|
||||
v = __prepare(k, v)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported type {type(v)} for template value of key `{k}`"
|
||||
)
|
||||
kwargs[k] = v
|
||||
|
||||
return kwargs
|
||||
|
||||
def set(self, **kwargs):
|
||||
"""
|
||||
Similar to `__set` but for external use.
|
||||
|
||||
Set the values of the attributes in the object based on the provided keyword
|
||||
arguments.
|
||||
|
||||
Args:
|
||||
kwargs (dict): A dictionary with the attribute names as keys and the new
|
||||
values as values.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
self.__set(**kwargs)
|
||||
|
||||
def run(self, **kwargs):
|
||||
"""
|
||||
Run the function with the given keyword arguments.
|
||||
|
||||
Args:
|
||||
**kwargs: The keyword arguments to pass to the function.
|
||||
|
||||
Returns:
|
||||
The result of calling the `populate` method of the `template` object
|
||||
with the given keyword arguments.
|
||||
"""
|
||||
self.__set(**kwargs)
|
||||
self.__check_unset_placeholders()
|
||||
prepared_kwargs = self.__prepare_value()
|
||||
|
||||
text = self.template.populate(**prepared_kwargs)
|
||||
return Document(text=text, metadata={"origin": "PromptComponent"})
|
||||
|
||||
def flow(self):
|
||||
return self.__call__()
|
140
knowledgehub/llms/prompts/template.py
Normal file
140
knowledgehub/llms/prompts/template.py
Normal file
@@ -0,0 +1,140 @@
|
||||
import warnings
|
||||
from string import Formatter
|
||||
|
||||
|
||||
class PromptTemplate:
|
||||
"""
|
||||
Base class for prompt templates.
|
||||
"""
|
||||
|
||||
def __init__(self, template: str, ignore_invalid=True):
|
||||
template = template
|
||||
formatter = Formatter()
|
||||
parsed_template = list(formatter.parse(template))
|
||||
|
||||
placeholders = set()
|
||||
for _, key, _, _ in parsed_template:
|
||||
if key is None:
|
||||
continue
|
||||
if not key.isidentifier():
|
||||
if ignore_invalid:
|
||||
warnings.warn(f"Ignore invalid placeholder: {key}.", UserWarning)
|
||||
else:
|
||||
raise ValueError(
|
||||
"Placeholder name must be a valid Python identifier, found:"
|
||||
f" {key}."
|
||||
)
|
||||
placeholders.add(key)
|
||||
|
||||
self.template = template
|
||||
self.placeholders = placeholders
|
||||
self.__formatter = formatter
|
||||
self.__parsed_template = parsed_template
|
||||
|
||||
def check_missing_kwargs(self, **kwargs):
|
||||
"""
|
||||
Check if all the placeholders in the template are set.
|
||||
|
||||
This function checks if all the expected placeholders in the template are set as
|
||||
attributes of the object. If any placeholders are missing, a `ValueError`
|
||||
is raised with the names of the missing keys.
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
missing_keys = self.placeholders.difference(kwargs.keys())
|
||||
if missing_keys:
|
||||
raise ValueError(f"Missing keys in template: {','.join(missing_keys)}")
|
||||
|
||||
def check_redundant_kwargs(self, **kwargs):
|
||||
"""
|
||||
Check if all the placeholders in the template are set.
|
||||
|
||||
This function checks if all the expected placeholders in the template are set as
|
||||
attributes of the object. If any placeholders are missing, a `ValueError`
|
||||
is raised with the names of the missing keys.
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
provided_keys = set(kwargs.keys())
|
||||
redundant_keys = provided_keys - self.placeholders
|
||||
|
||||
if redundant_keys:
|
||||
warnings.warn(
|
||||
f"Keys provided but not in template: {','.join(redundant_keys)}",
|
||||
UserWarning,
|
||||
)
|
||||
|
||||
def populate(self, **kwargs):
|
||||
"""
|
||||
Strictly populate the template with the given keyword arguments.
|
||||
|
||||
Args:
|
||||
**kwargs: The keyword arguments to populate the template.
|
||||
Each keyword corresponds to a placeholder in the template.
|
||||
|
||||
Returns:
|
||||
str: The populated template.
|
||||
|
||||
Raises:
|
||||
ValueError: If an unknown placeholder is provided.
|
||||
"""
|
||||
self.check_missing_kwargs(**kwargs)
|
||||
|
||||
return self.partial_populate(**kwargs)
|
||||
|
||||
def partial_populate(self, **kwargs):
|
||||
"""
|
||||
Partially populate the template with the given keyword arguments.
|
||||
|
||||
Args:
|
||||
**kwargs: The keyword arguments to populate the template.
|
||||
Each keyword corresponds to a placeholder in the template.
|
||||
|
||||
Returns:
|
||||
str: The populated template.
|
||||
"""
|
||||
self.check_redundant_kwargs(**kwargs)
|
||||
|
||||
prompt = []
|
||||
for literal_text, field_name, format_spec, conversion in self.__parsed_template:
|
||||
prompt.append(literal_text)
|
||||
|
||||
if field_name is None:
|
||||
continue
|
||||
|
||||
if field_name not in kwargs:
|
||||
if conversion:
|
||||
value = f"{{{field_name}}}!{conversion}:{format_spec}"
|
||||
else:
|
||||
value = f"{{{field_name}:{format_spec}}}"
|
||||
else:
|
||||
value = kwargs[field_name]
|
||||
if conversion is not None:
|
||||
value = self.__formatter.convert_field(value, conversion)
|
||||
if format_spec is not None:
|
||||
value = self.__formatter.format_field(value, format_spec)
|
||||
|
||||
prompt.append(value)
|
||||
|
||||
return "".join(prompt)
|
||||
|
||||
def __add__(self, other):
|
||||
"""
|
||||
Create a new PromptTemplate object by concatenating the template of the current
|
||||
object with the template of another PromptTemplate object.
|
||||
|
||||
Parameters:
|
||||
other (PromptTemplate): Another PromptTemplate object.
|
||||
|
||||
Returns:
|
||||
PromptTemplate: A new PromptTemplate object with the concatenated templates.
|
||||
"""
|
||||
return PromptTemplate(self.template + "\n" + other.template)
|
Reference in New Issue
Block a user