import traceback
from pathlib import Path
from typing import Any, Dict, List, Union
from .core import (
Frontmatter,
InvokerFactory,
ModelSettings,
Prompty,
PropertySettings,
SimpleModel,
TemplateSettings,
param_hoisting,
)
[docs]
def load(prompt_path: str, configuration: str = "default") -> Prompty:
"""Load a prompty file and return a Prompty object.
Args:
prompt_path: The path to the prompty file.
configuration: The configuration to use. Defaults to "default".
Returns:
The Prompty object.
"""
file_path = Path(prompt_path)
if not file_path.is_absolute():
# get caller's path (take into account trace frame)
caller = Path(traceback.extract_stack()[-3].filename)
file_path = Path(caller.parent / file_path).resolve().absolute()
# load dictionary from prompty file
matter = Frontmatter.read_file(file_path.__fspath__())
attributes = matter["attributes"]
content = matter["body"]
# normalize attribute dictionary resolve keys and files
attributes = Prompty.normalize(attributes, file_path.parent)
# load global configuration
if "model" not in attributes:
attributes["model"] = {}
# pull model settings out of attributes
try:
model = ModelSettings(**attributes.pop("model"))
except Exception as e:
raise ValueError(f"Error in model settings: {e}")
# pull template settings
try:
if "template" in attributes:
t = attributes.pop("template")
if isinstance(t, dict):
template = TemplateSettings(**t)
# has to be a string denoting the type
else:
template = TemplateSettings(type=t, parser="prompty")
else:
template = TemplateSettings(type="mustache", parser="prompty")
except Exception as e:
raise ValueError(f"Error in template loader: {e}")
# formalize inputs and outputs
if "inputs" in attributes:
try:
inputs = {
k: PropertySettings(**v) for (k, v) in attributes.pop("inputs").items()
}
except Exception as e:
raise ValueError(f"Error in inputs: {e}")
else:
inputs = {}
if "outputs" in attributes:
try:
outputs = {
k: PropertySettings(**v) for (k, v) in attributes.pop("outputs").items()
}
except Exception as e:
raise ValueError(f"Error in outputs: {e}")
else:
outputs = {}
# recursive loading of base prompty
if "base" in attributes:
# load the base prompty from the same directory as the current prompty
base = load(file_path.parent / attributes["base"])
# hoist the base prompty's attributes to the current prompty
model.api = base.model.api if model.api == "" else model.api
model.configuration = param_hoisting(
model.configuration, base.model.configuration
)
model.parameters = param_hoisting(model.parameters, base.model.parameters)
model.response = param_hoisting(model.response, base.model.response)
attributes["sample"] = param_hoisting(attributes, base.sample, "sample")
p = Prompty(
**attributes,
model=model,
inputs=inputs,
outputs=outputs,
template=template,
content=content,
file=file_path,
basePrompty=base,
)
else:
p = Prompty(
**attributes,
model=model,
inputs=inputs,
outputs=outputs,
template=template,
content=content,
file=file_path,
)
return p
[docs]
def prepare(
prompt: Prompty,
inputs: Dict[str, Any] = {},
) -> Any:
"""Prepare the inputs for the prompty.
Args:
prompt: The Prompty object.
inputs: The inputs to the prompty. Defaults to {}.
Returns:
The prepared inputs.
"""
invoker = InvokerFactory()
inputs = param_hoisting(inputs, prompt.sample)
if prompt.template.type == "NOOP":
render = prompt.content
else:
# render
result = invoker(
"renderer",
prompt.template.type,
prompt,
SimpleModel(item=inputs),
)
render = result.item
if prompt.template.parser == "NOOP":
result = render
else:
# parse
result = invoker(
"parser",
f"{prompt.template.parser}.{prompt.model.api}",
prompt,
SimpleModel(item=result.item),
)
if isinstance(result, SimpleModel):
return result.item
else:
return result
[docs]
def run(
prompt: Prompty,
content: Union[Dict, List, str],
configuration: Dict[str, Any] = {},
parameters: Dict[str, Any] = {},
raw: bool = False,
) -> Any:
"""Run the prompty.
Args:
prompt: The Prompty object.
content: The content to run the prompty on.
configuration: The configuration to use. Defaults to {}.
parameters: The parameters to use. Defaults to {}.
raw: Whether to return the raw output. Defaults to False.
Returns:
The result of running the prompty.
"""
invoker = InvokerFactory()
if configuration != {}:
prompt.model.configuration = param_hoisting(
configuration, prompt.model.configuration
)
if parameters != {}:
prompt.model.parameters = param_hoisting(parameters, prompt.model.parameters)
# execute
result = invoker(
"executor",
prompt.model.configuration["type"],
prompt,
SimpleModel(item=content),
)
# skip?
if not raw:
# process
result = invoker(
"processor",
prompt.model.configuration["type"],
prompt,
result,
)
if isinstance(result, SimpleModel):
return result.item
else:
return result
[docs]
def execute(
prompt: Union[str, Prompty],
configuration: Dict[str, Any] = {},
parameters: Dict[str, Any] = {},
inputs: Dict[str, Any] = {},
raw: bool = False,
connection: str = "default",
) -> Any:
"""Execute a prompty.
Args:
prompt: The prompt to execute.
Can be a path to a prompty file or a Prompty object.
configuration: The configuration to use. Defaults to {}.
parameters: The parameters to use. Defaults to {}.
inputs: The inputs to the prompty. Defaults to {}.
raw: Whether to return the raw output. Defaults to False.
connection: The connection to use. Defaults to "default".
Returns:
The result of executing the prompty.
"""
if isinstance(prompt, str):
prompt = load(prompt, connection)
# prepare content
content = prepare(prompt, inputs)
# run LLM model
result = run(prompt, content, configuration, parameters, raw)
return result