codekingpro/portable-devtools
114k
1"""Prompt schema definition."""2 3from __future__ import annotations4 5from pathlib import Path6from typing import TYPE_CHECKING, Any7 8from pydantic import BaseModel, model_validator9from typing_extensions import override10 11from langchain_core.prompts.string import (12 DEFAULT_FORMATTER_MAPPING,13 PromptTemplateFormat,14 StringPromptTemplate,15 check_valid_template,16 get_template_variables,17 mustache_schema,18)19 20if TYPE_CHECKING:21 from langchain_core.runnables.config import RunnableConfig22 23 24class PromptTemplate(StringPromptTemplate):25 """Prompt template for a language model.26 27 A prompt template consists of a string template. It accepts a set of parameters28 from the user that can be used to generate a prompt for a language model.29 30 The template can be formatted using either f-strings (default), jinja2, or mustache31 syntax.32 33 !!! warning "Security"34 35 Prefer using `template_format='f-string'` instead of `template_format='jinja2'`,36 or make sure to NEVER accept jinja2 templates from untrusted sources as they may37 lead to arbitrary Python code execution.38 39 As of LangChain 0.0.329, Jinja2 templates will be rendered using Jinja2's40 SandboxedEnvironment by default. This sand-boxing should be treated as a41 best-effort approach rather than a guarantee of security, as it is an opt-out42 rather than opt-in approach.43 44 Despite the sandboxing, we recommend to never use jinja2 templates from45 untrusted sources.46 47 Example:48 ```python49 from langchain_core.prompts import PromptTemplate50 51 # Instantiation using from_template (recommended)52 prompt = PromptTemplate.from_template("Say {foo}")53 prompt.format(foo="bar")54 55 # Instantiation using initializer56 prompt = PromptTemplate(template="Say {foo}")57 ```58 """59 60 @property61 @override62 def lc_attributes(self) -> dict[str, Any]:63 return {64 "template_format": self.template_format,65 }66 67 @classmethod68 @override69 def get_lc_namespace(cls) -> list[str]:70 """Get the namespace of the LangChain object.71 72 Returns:73 `["langchain", "prompts", "prompt"]`74 """75 return ["langchain", "prompts", "prompt"]76 77 template: str78 """The prompt template."""79 80 template_format: PromptTemplateFormat = "f-string"81 """The format of the prompt template.82 83 Options are: `'f-string'`, `'mustache'`, `'jinja2'`.84 """85 86 validate_template: bool = False87 """Whether or not to try validating the template."""88 89 @model_validator(mode="before")90 @classmethod91 def pre_init_validation(cls, values: dict) -> Any:92 """Check that template and input variables are consistent."""93 if values.get("template") is None:94 # Will let pydantic fail with a ValidationError if template95 # is not provided.96 return values97 98 # Set some default values based on the field defaults99 values.setdefault("template_format", "f-string")100 values.setdefault("partial_variables", {})101 102 if values.get("validate_template"):103 if values["template_format"] == "mustache":104 msg = "Mustache templates cannot be validated."105 raise ValueError(msg)106 107 if "input_variables" not in values:108 msg = "Input variables must be provided to validate the template."109 raise ValueError(msg)110 111 all_inputs = values["input_variables"] + list(values["partial_variables"])112 check_valid_template(113 values["template"], values["template_format"], all_inputs114 )115 116 if values["template_format"]:117 values["input_variables"] = [118 var119 for var in get_template_variables(120 values["template"], values["template_format"]121 )122 if var not in values["partial_variables"]123 ]124 125 return values126 127 @override128 def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]:129 """Get the input schema for the prompt.130 131 Args:132 config: The runnable configuration.133 134 Returns:135 The input schema for the prompt.136 """137 if self.template_format != "mustache":138 return super().get_input_schema(config)139 140 return mustache_schema(self.template)141 142 def __add__(self, other: Any) -> PromptTemplate:143 """Override the `+` operator to allow for combining prompt templates.144 145 Raises:146 ValueError: If the template formats are not f-string or if there are147 conflicting partial variables.148 NotImplementedError: If the other object is not a `PromptTemplate` or str.149 150 Returns:151 A new `PromptTemplate` that is the combination of the two.152 """153 # Allow for easy combining154 if isinstance(other, PromptTemplate):155 if self.template_format != other.template_format:156 msg = "Cannot add templates of different formats"157 raise ValueError(msg)158 input_variables = list(159 set(self.input_variables) | set(other.input_variables)160 )161 template = self.template + other.template162 # If any do not want to validate, then don't163 validate_template = self.validate_template and other.validate_template164 partial_variables = dict(self.partial_variables.items())165 for k, v in other.partial_variables.items():166 if k in partial_variables:167 msg = "Cannot have same variable partialed twice."168 raise ValueError(msg)169 partial_variables[k] = v170 return PromptTemplate(171 template=template,172 input_variables=input_variables,173 partial_variables=partial_variables,174 template_format=self.template_format,175 validate_template=validate_template,176 )177 if isinstance(other, str):178 prompt = PromptTemplate.from_template(179 other,180 template_format=self.template_format,181 )182 return self + prompt183 msg = f"Unsupported operand type for +: {type(other)}"184 raise NotImplementedError(msg)185 186 @property187 def _prompt_type(self) -> str:188 """Return the prompt type key."""189 return "prompt"190 191 def format(self, **kwargs: Any) -> str:192 """Format the prompt with the inputs.193 194 Args:195 **kwargs: Any arguments to be passed to the prompt template.196 197 Returns:198 A formatted string.199 """200 kwargs = self._merge_partial_and_user_variables(**kwargs)201 return DEFAULT_FORMATTER_MAPPING[self.template_format](self.template, **kwargs)202 203 @classmethod204 def from_examples(205 cls,206 examples: list[str],207 suffix: str,208 input_variables: list[str],209 example_separator: str = "\n\n",210 prefix: str = "",211 **kwargs: Any,212 ) -> PromptTemplate:213 """Take examples in list format with prefix and suffix to create a prompt.214 215 Intended to be used as a way to dynamically create a prompt from examples.216 217 Args:218 examples: List of examples to use in the prompt.219 suffix: String to go after the list of examples.220 221 Should generally set up the user's input.222 input_variables: A list of variable names the final prompt template will223 expect.224 example_separator: The separator to use in between examples.225 prefix: String that should go before any examples.226 227 Generally includes examples.228 229 Returns:230 The final prompt generated.231 """232 template = example_separator.join([prefix, *examples, suffix])233 return cls(input_variables=input_variables, template=template, **kwargs)234 235 @classmethod236 def from_file(237 cls,238 template_file: str | Path,239 encoding: str | None = None,240 **kwargs: Any,241 ) -> PromptTemplate:242 """Load a prompt from a file.243 244 Args:245 template_file: The path to the file containing the prompt template.246 encoding: The encoding system for opening the template file.247 248 If not provided, will use the OS default.249 250 Returns:251 The prompt loaded from the file.252 """253 template = Path(template_file).read_text(encoding=encoding)254 return cls.from_template(template=template, **kwargs)255 256 @classmethod257 def from_template(258 cls,259 template: str,260 *,261 template_format: PromptTemplateFormat = "f-string",262 partial_variables: dict[str, Any] | None = None,263 **kwargs: Any,264 ) -> PromptTemplate:265 """Load a prompt template from a template.266 267 !!! warning "Security"268 269 Prefer using `template_format='f-string'` instead of270 `template_format='jinja2'`, or make sure to NEVER accept jinja2 templates271 from untrusted sources as they may lead to arbitrary Python code execution.272 273 As of LangChain 0.0.329, Jinja2 templates will be rendered using Jinja2's274 SandboxedEnvironment by default. This sand-boxing should be treated as a275 best-effort approach rather than a guarantee of security, as it is an276 opt-out rather than opt-in approach.277 278 Despite the sandboxing, we recommend to never use jinja2 templates from279 untrusted sources.280 281 Args:282 template: The template to load.283 template_format: The format of the template.284 285 Use `jinja2` for jinja2, `mustache` for mustache, and `f-string` for286 f-strings.287 partial_variables: A dictionary of variables that can be used to partially288 fill in the template.289 290 For example, if the template is `'{variable1} {variable2}'`, and291 `partial_variables` is `{"variable1": "foo"}`, then the final prompt292 will be `'foo {variable2}'`.293 **kwargs: Any other arguments to pass to the prompt template.294 295 Returns:296 The prompt template loaded from the template.297 """298 input_variables = get_template_variables(template, template_format)299 partial_variables_ = partial_variables or {}300 301 if partial_variables_:302 input_variables = [303 var for var in input_variables if var not in partial_variables_304 ]305 306 return cls(307 input_variables=input_variables,308 template=template,309 template_format=template_format,310 partial_variables=partial_variables_,311 **kwargs,312 )313 