# Copyright (c) Microsoft. All rights reserved. import logging from collections.abc import MutableMapping, MutableSequence, Sequence from typing import TypeVar from pydantic import Field, field_validator, model_validator from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings from semantic_kernel.const import DEFAULT_SERVICE_NAME from semantic_kernel.functions.kernel_parameter_metadata import KernelParameterMetadata from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.prompt_template.const import KERNEL_TEMPLATE_FORMAT_NAME, TEMPLATE_FORMAT_TYPES from semantic_kernel.prompt_template.input_variable import InputVariable PromptExecutionSettingsT = TypeVar("PromptExecutionSettingsT", bound=PromptExecutionSettings) logger: logging.Logger = logging.getLogger(__name__) _T = TypeVar("_T", bound="PromptTemplateConfig") class PromptTemplateConfig(KernelBaseModel): """Configuration for a prompt template. Args: name: The name of the prompt template. description: The description of the prompt template. template: The template for the prompt. template_format: The format of the template, should be 'semantic-kernel', 'jinja2' or 'handlebars'. input_variables: The input variables for the prompt. 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. execution_settings: The execution settings for the prompt. """ name: str = "" description: str | None = "" template: str | None = None template_format: TEMPLATE_FORMAT_TYPES = KERNEL_TEMPLATE_FORMAT_NAME input_variables: MutableSequence[InputVariable] = Field(default_factory=list) allow_dangerously_set_content: bool = False execution_settings: MutableMapping[str, PromptExecutionSettings] = Field(default_factory=dict) @model_validator(mode="after") def check_input_variables(self): """Verify that input variable default values are string only.""" for variable in self.input_variables: if variable.default and not isinstance(variable.default, str): raise TypeError(f"Default value for input variable {variable.name} must be a string.") return self @field_validator("execution_settings", mode="before") @classmethod def rewrite_execution_settings( cls: type[_T], settings: PromptExecutionSettings | Sequence[PromptExecutionSettings] | MutableMapping[str, PromptExecutionSettings] | None, ) -> MutableMapping[str, PromptExecutionSettings]: """Rewrite execution settings to a dictionary.""" if not settings: return {} if isinstance(settings, PromptExecutionSettings): return {settings.service_id or DEFAULT_SERVICE_NAME: settings} if isinstance(settings, Sequence): return {s.service_id or DEFAULT_SERVICE_NAME: s for s in settings} return settings def add_execution_settings(self, settings: PromptExecutionSettings, overwrite: bool = True) -> None: """Add execution settings to the prompt template.""" if settings.service_id in self.execution_settings and not overwrite: return self.execution_settings[settings.service_id or DEFAULT_SERVICE_NAME] = settings logger.warning("Execution settings already exist and overwrite is set to False") def get_kernel_parameter_metadata(self) -> Sequence[KernelParameterMetadata]: """Get the kernel parameter metadata for the input variables.""" return [ KernelParameterMetadata( name=variable.name, description=variable.description, default_value=variable.default, type_=variable.json_schema, # TODO (moonbox3): update to handle complex JSON schemas # type: ignore is_required=variable.is_required, ) for variable in self.input_variables ] @classmethod def from_json(cls: type[_T], json_str: str) -> _T: """Create a PromptTemplateConfig instance from a JSON string.""" if not json_str: raise ValueError("json_str is empty") try: return cls.model_validate_json(json_str) except Exception as exc: raise ValueError( "Unable to deserialize PromptTemplateConfig from the " f"specified JSON string: {json_str} with exception: {exc}" ) from exc @classmethod def restore( cls: type[_T], name: str, description: str, template: str, template_format: TEMPLATE_FORMAT_TYPES = KERNEL_TEMPLATE_FORMAT_NAME, input_variables: MutableSequence[InputVariable] | None = None, execution_settings: MutableMapping[str, PromptExecutionSettings] | None = None, allow_dangerously_set_content: bool = False, ) -> _T: """Restore a PromptTemplateConfig instance from the specified parameters. Args: name: The name of the prompt template. description: The description of the prompt template. template: The template for the prompt. template_format: The format of the template, should be 'semantic-kernel', 'jinja2' or 'handlebars'. input_variables: The input variables for the prompt. execution_settings: The execution settings for the prompt. allow_dangerously_set_content: Allow content without encoding. Returns: A new PromptTemplateConfig instance. """ return cls( name=name, description=description, template=template, template_format=template_format, input_variables=input_variables or [], execution_settings=execution_settings or {}, allow_dangerously_set_content=allow_dangerously_set_content, )