114 lines
4.7 KiB
Python
114 lines
4.7 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
import logging
|
|
from collections.abc import Callable
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from pybars import Compiler, PybarsError
|
|
from pydantic import PrivateAttr, field_validator
|
|
|
|
from semantic_kernel.exceptions import HandlebarsTemplateRenderException, HandlebarsTemplateSyntaxError
|
|
from semantic_kernel.functions.kernel_arguments import KernelArguments
|
|
from semantic_kernel.prompt_template.const import HANDLEBARS_TEMPLATE_FORMAT_NAME
|
|
from semantic_kernel.prompt_template.prompt_template_base import PromptTemplateBase
|
|
from semantic_kernel.prompt_template.utils import HANDLEBAR_SYSTEM_HELPERS, create_template_helper_from_function
|
|
|
|
if TYPE_CHECKING:
|
|
from semantic_kernel.kernel import Kernel
|
|
from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig
|
|
|
|
logger: logging.Logger = logging.getLogger(__name__)
|
|
|
|
|
|
class HandlebarsPromptTemplate(PromptTemplateBase):
|
|
"""Create a Handlebars prompt template.
|
|
|
|
Handlebars are parsed as a whole and therefore do not have variables that can be extracted,
|
|
also with handlebars there is no distinction in syntax between a variable and a value,
|
|
a value that is encountered is tried to resolve with the arguments and the functions,
|
|
if not found, the literal value is returned.
|
|
|
|
Args:
|
|
prompt_template_config (PromptTemplateConfig): The prompt template configuration
|
|
This is checked if the template format is 'handlebars'
|
|
allow_dangerously_set_content (bool = False): Allow content without encoding throughout, this overrides
|
|
the same settings in the prompt template config and input variables.
|
|
This reverts the behavior to unencoded input.
|
|
|
|
Raises:
|
|
ValueError: If the template format is not 'handlebars'
|
|
HandlebarsTemplateSyntaxError: If the handlebars template has a syntax error
|
|
"""
|
|
|
|
_template_compiler: Any = PrivateAttr()
|
|
|
|
@field_validator("prompt_template_config")
|
|
@classmethod
|
|
def validate_template_format(cls, v: "PromptTemplateConfig") -> "PromptTemplateConfig":
|
|
"""Validate the template format."""
|
|
if v.template_format != HANDLEBARS_TEMPLATE_FORMAT_NAME:
|
|
raise ValueError(f"Invalid prompt template format: {v.template_format}. Expected: handlebars")
|
|
return v
|
|
|
|
def model_post_init(self, __context: Any) -> None:
|
|
"""Post init model."""
|
|
if not self.prompt_template_config.template:
|
|
self._template_compiler = None
|
|
return
|
|
try:
|
|
self._template_compiler = Compiler().compile(self.prompt_template_config.template)
|
|
except PybarsError as e:
|
|
logger.error(f"Invalid handlebars template: {self.prompt_template_config.template}")
|
|
raise HandlebarsTemplateSyntaxError(
|
|
f"Invalid handlebars template: {self.prompt_template_config.template}"
|
|
) from e
|
|
|
|
async def render(self, kernel: "Kernel", arguments: "KernelArguments | None" = None) -> str:
|
|
"""Render the prompt template.
|
|
|
|
Using the prompt template, replace the variables with their values
|
|
and execute the functions replacing their reference with the
|
|
function result.
|
|
|
|
Args:
|
|
kernel: The kernel instance
|
|
arguments: The kernel arguments
|
|
|
|
Returns:
|
|
The prompt template ready to be used for an AI request
|
|
"""
|
|
if not self._template_compiler:
|
|
return ""
|
|
if arguments is None:
|
|
arguments = KernelArguments()
|
|
|
|
arguments = self._get_trusted_arguments(arguments)
|
|
allow_unsafe_function_output = self._get_allow_dangerously_set_function_output()
|
|
helpers: dict[str, Callable[..., Any]] = {}
|
|
for plugin in kernel.plugins.values():
|
|
helpers.update({
|
|
function.fully_qualified_name: create_template_helper_from_function(
|
|
function,
|
|
kernel,
|
|
arguments,
|
|
self.prompt_template_config.template_format,
|
|
allow_unsafe_function_output,
|
|
)
|
|
for function in plugin
|
|
})
|
|
helpers.update(HANDLEBAR_SYSTEM_HELPERS)
|
|
|
|
try:
|
|
return self._template_compiler(
|
|
arguments,
|
|
helpers=helpers,
|
|
)
|
|
except PybarsError as exc:
|
|
logger.error(
|
|
f"Error rendering prompt template: {self.prompt_template_config.template} with arguments: {arguments}"
|
|
)
|
|
raise HandlebarsTemplateRenderException(
|
|
f"Error rendering prompt template: {self.prompt_template_config.template} "
|
|
f"with arguments: {arguments}: error: {exc}"
|
|
) from exc
|