codekingpro/portable-devtools
114k
1"""Chat prompt template."""2 3from __future__ import annotations4 5from abc import ABC, abstractmethod6from collections.abc import Sequence7from pathlib import Path8from typing import (9 Annotated,10 Any,11 TypedDict,12 TypeVar,13 cast,14 overload,15)16 17from pydantic import (18 Field,19 PositiveInt,20 SkipValidation,21 model_validator,22)23from typing_extensions import Self, override24 25from langchain_core._api import deprecated26from langchain_core.messages import (27 AIMessage,28 AnyMessage,29 BaseMessage,30 ChatMessage,31 HumanMessage,32 SystemMessage,33 convert_to_messages,34)35from langchain_core.messages.base import get_msg_title_repr36from langchain_core.prompt_values import ChatPromptValue, ImageURL37from langchain_core.prompts.base import BasePromptTemplate38from langchain_core.prompts.dict import DictPromptTemplate39from langchain_core.prompts.image import ImagePromptTemplate40from langchain_core.prompts.message import (41 BaseMessagePromptTemplate,42)43from langchain_core.prompts.prompt import PromptTemplate44from langchain_core.prompts.string import (45 PromptTemplateFormat,46 StringPromptTemplate,47 get_template_variables,48)49from langchain_core.utils import get_colored_text50from langchain_core.utils.interactive_env import is_interactive_env51 52 53class MessagesPlaceholder(BaseMessagePromptTemplate):54 """Prompt template that assumes variable is already list of messages.55 56 A placeholder which can be used to pass in a list of messages.57 58 !!! example "Direct usage"59 60 ```python61 from langchain_core.prompts import MessagesPlaceholder62 63 prompt = MessagesPlaceholder("history")64 prompt.format_messages() # raises KeyError65 66 prompt = MessagesPlaceholder("history", optional=True)67 prompt.format_messages() # returns empty list []68 69 prompt.format_messages(70 history=[71 ("system", "You are an AI assistant."),72 ("human", "Hello!"),73 ]74 )75 # -> [76 # SystemMessage(content="You are an AI assistant."),77 # HumanMessage(content="Hello!"),78 # ]79 ```80 81 !!! example "Building a prompt with chat history"82 83 ```python84 from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder85 86 prompt = ChatPromptTemplate.from_messages(87 [88 ("system", "You are a helpful assistant."),89 MessagesPlaceholder("history"),90 ("human", "{question}"),91 ]92 )93 prompt.invoke(94 {95 "history": [("human", "what's 5 + 2"), ("ai", "5 + 2 is 7")],96 "question": "now multiply that by 4",97 }98 )99 # -> ChatPromptValue(messages=[100 # SystemMessage(content="You are a helpful assistant."),101 # HumanMessage(content="what's 5 + 2"),102 # AIMessage(content="5 + 2 is 7"),103 # HumanMessage(content="now multiply that by 4"),104 # ])105 ```106 107 !!! example "Limiting the number of messages"108 109 ```python110 from langchain_core.prompts import MessagesPlaceholder111 112 prompt = MessagesPlaceholder("history", n_messages=1)113 114 prompt.format_messages(115 history=[116 ("system", "You are an AI assistant."),117 ("human", "Hello!"),118 ]119 )120 # -> [121 # HumanMessage(content="Hello!"),122 # ]123 ```124 """125 126 variable_name: str127 """Name of variable to use as messages."""128 129 optional: bool = False130 """Whether `format_messages` must be provided.131 132 If `True` `format_messages` can be called with no arguments and will return an empty133 list.134 135 If `False` then a named argument with name `variable_name` must be passed in, even136 if the value is an empty list.137 """138 139 n_messages: PositiveInt | None = None140 """Maximum number of messages to include.141 142 If `None`, then will include all.143 """144 145 def __init__(146 self, variable_name: str, *, optional: bool = False, **kwargs: Any147 ) -> None:148 """Create a messages placeholder.149 150 Args:151 variable_name: Name of variable to use as messages.152 optional: Whether `format_messages` must be provided.153 154 If `True` format_messages can be called with no arguments and will155 return an empty list.156 157 If `False` then a named argument with name `variable_name` must be158 passed in, even if the value is an empty list.159 """160 # mypy can't detect the init which is defined in the parent class161 # b/c these are BaseModel classes.162 super().__init__(variable_name=variable_name, optional=optional, **kwargs) # type: ignore[call-arg,unused-ignore]163 164 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:165 """Format messages from kwargs.166 167 Args:168 **kwargs: Keyword arguments to use for formatting.169 170 Returns:171 List of `BaseMessage` objects.172 173 Raises:174 ValueError: If variable is not a list of messages.175 """176 value = (177 kwargs.get(self.variable_name, [])178 if self.optional179 else kwargs[self.variable_name]180 )181 if not isinstance(value, list):182 msg = (183 f"variable {self.variable_name} should be a list of base messages, "184 f"got {value} of type {type(value)}"185 )186 raise ValueError(msg) # noqa: TRY004187 value = convert_to_messages(value)188 if self.n_messages:189 value = value[-self.n_messages :]190 return value191 192 @property193 def input_variables(self) -> list[str]:194 """Input variables for this prompt template.195 196 Returns:197 List of input variable names.198 """199 return [self.variable_name] if not self.optional else []200 201 @override202 def pretty_repr(self, html: bool = False) -> str:203 """Human-readable representation.204 205 Args:206 html: Whether to format as HTML.207 208 Returns:209 Human-readable representation.210 """211 var = "{" + self.variable_name + "}"212 if html:213 title = get_msg_title_repr("Messages Placeholder", bold=True)214 var = get_colored_text(var, "yellow")215 else:216 title = get_msg_title_repr("Messages Placeholder")217 return f"{title}\n\n{var}"218 219 220MessagePromptTemplateT = TypeVar(221 "MessagePromptTemplateT", bound="BaseStringMessagePromptTemplate"222)223"""Type variable for message prompt templates."""224 225 226class BaseStringMessagePromptTemplate(BaseMessagePromptTemplate, ABC):227 """Base class for message prompt templates that use a string prompt template."""228 229 prompt: StringPromptTemplate230 """String prompt template."""231 232 additional_kwargs: dict = Field(default_factory=dict)233 """Additional keyword arguments to pass to the prompt template."""234 235 @classmethod236 def from_template(237 cls,238 template: str,239 template_format: PromptTemplateFormat = "f-string",240 partial_variables: dict[str, Any] | None = None,241 **kwargs: Any,242 ) -> Self:243 """Create a class from a string template.244 245 Args:246 template: a template.247 template_format: format of the template.248 partial_variables: A dictionary of variables that can be used to partially249 fill in the template.250 251 For example, if the template is `"{variable1} {variable2}"`, and252 `partial_variables` is `{"variable1": "foo"}`, then the final prompt253 will be `"foo {variable2}"`.254 255 **kwargs: Keyword arguments to pass to the constructor.256 257 Returns:258 A new instance of this class.259 """260 prompt = PromptTemplate.from_template(261 template,262 template_format=template_format,263 partial_variables=partial_variables,264 )265 return cls(prompt=prompt, **kwargs)266 267 @classmethod268 def from_template_file(269 cls,270 template_file: str | Path,271 **kwargs: Any,272 ) -> Self:273 """Create a class from a template file.274 275 Args:276 template_file: path to a template file.277 **kwargs: Keyword arguments to pass to the constructor.278 279 Returns:280 A new instance of this class.281 """282 prompt = PromptTemplate.from_file(template_file)283 return cls(prompt=prompt, **kwargs)284 285 @abstractmethod286 def format(self, **kwargs: Any) -> BaseMessage:287 """Format the prompt template.288 289 Args:290 **kwargs: Keyword arguments to use for formatting.291 292 Returns:293 Formatted message.294 """295 296 async def aformat(self, **kwargs: Any) -> BaseMessage:297 """Async format the prompt template.298 299 Args:300 **kwargs: Keyword arguments to use for formatting.301 302 Returns:303 Formatted message.304 """305 return self.format(**kwargs)306 307 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:308 """Format messages from kwargs.309 310 Args:311 **kwargs: Keyword arguments to use for formatting.312 313 Returns:314 List of `BaseMessage` objects.315 """316 return [self.format(**kwargs)]317 318 async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:319 """Async format messages from kwargs.320 321 Args:322 **kwargs: Keyword arguments to use for formatting.323 324 Returns:325 List of `BaseMessage` objects.326 """327 return [await self.aformat(**kwargs)]328 329 @property330 def input_variables(self) -> list[str]:331 """Input variables for this prompt template.332 333 Returns:334 List of input variable names.335 """336 return self.prompt.input_variables337 338 @override339 def pretty_repr(self, html: bool = False) -> str:340 """Human-readable representation.341 342 Args:343 html: Whether to format as HTML.344 345 Returns:346 Human-readable representation.347 """348 # TODO: Handle partials349 title = self.__class__.__name__.replace("MessagePromptTemplate", " Message")350 title = get_msg_title_repr(title, bold=html)351 return f"{title}\n\n{self.prompt.pretty_repr(html=html)}"352 353 354class ChatMessagePromptTemplate(BaseStringMessagePromptTemplate):355 """Chat message prompt template."""356 357 role: str358 """Role of the message."""359 360 def format(self, **kwargs: Any) -> BaseMessage:361 """Format the prompt template.362 363 Args:364 **kwargs: Keyword arguments to use for formatting.365 366 Returns:367 Formatted message.368 """369 text = self.prompt.format(**kwargs)370 return ChatMessage(371 content=text, role=self.role, additional_kwargs=self.additional_kwargs372 )373 374 async def aformat(self, **kwargs: Any) -> BaseMessage:375 """Async format the prompt template.376 377 Args:378 **kwargs: Keyword arguments to use for formatting.379 380 Returns:381 Formatted message.382 """383 text = await self.prompt.aformat(**kwargs)384 return ChatMessage(385 content=text, role=self.role, additional_kwargs=self.additional_kwargs386 )387 388 389class _TextTemplateParam(TypedDict, total=False):390 text: str | dict391 392 393class _ImageTemplateParam(TypedDict, total=False):394 image_url: str | dict395 396 397class _StringImageMessagePromptTemplate(BaseMessagePromptTemplate):398 """Human message prompt template. This is a message sent from the user."""399 400 prompt: (401 StringPromptTemplate402 | list[StringPromptTemplate | ImagePromptTemplate | DictPromptTemplate]403 )404 """Prompt template."""405 additional_kwargs: dict = Field(default_factory=dict)406 """Additional keyword arguments to pass to the prompt template."""407 408 _msg_class: type[BaseMessage]409 410 @classmethod411 def from_template(412 cls: type[Self],413 template: str414 | list[str | _TextTemplateParam | _ImageTemplateParam | dict[str, Any]],415 template_format: PromptTemplateFormat = "f-string",416 *,417 partial_variables: dict[str, Any] | None = None,418 **kwargs: Any,419 ) -> Self:420 """Create a class from a string template.421 422 Args:423 template: a template.424 template_format: format of the template.425 426 Options are: `'f-string'`, `'mustache'`, `'jinja2'`.427 partial_variables: A dictionary of variables that can be used too partially.428 429 **kwargs: Keyword arguments to pass to the constructor.430 431 Returns:432 A new instance of this class.433 434 Raises:435 ValueError: If the template is not a string or list of strings.436 """437 if isinstance(template, str):438 prompt: StringPromptTemplate | list = PromptTemplate.from_template(439 template,440 template_format=template_format,441 partial_variables=partial_variables,442 )443 return cls(prompt=prompt, **kwargs)444 if isinstance(template, list):445 if (partial_variables is not None) and len(partial_variables) > 0:446 msg = "Partial variables are not supported for list of templates."447 raise ValueError(msg)448 prompt = []449 for tmpl in template:450 if isinstance(tmpl, str) or (451 isinstance(tmpl, dict)452 and "text" in tmpl453 and set(tmpl.keys()) <= {"type", "text"}454 ):455 if isinstance(tmpl, str):456 text: str = tmpl457 else:458 text = cast("_TextTemplateParam", tmpl)["text"] # type: ignore[assignment]459 prompt.append(460 PromptTemplate.from_template(461 text, template_format=template_format462 )463 )464 elif (465 isinstance(tmpl, dict)466 and "image_url" in tmpl467 and set(tmpl.keys())468 <= {469 "type",470 "image_url",471 }472 ):473 img_template = cast("_ImageTemplateParam", tmpl)["image_url"]474 input_variables = []475 if isinstance(img_template, str):476 variables = get_template_variables(477 img_template, template_format478 )479 if variables:480 if len(variables) > 1:481 msg = (482 "Only one format variable allowed per image"483 f" template.\nGot: {variables}"484 f"\nFrom: {tmpl}"485 )486 raise ValueError(msg)487 input_variables = [variables[0]]488 img_template = {"url": img_template}489 img_template_obj = ImagePromptTemplate(490 input_variables=input_variables,491 template=img_template,492 template_format=template_format,493 )494 elif isinstance(img_template, dict):495 img_template = dict(img_template)496 for key in ["url", "path", "detail"]:497 if key in img_template:498 input_variables.extend(499 get_template_variables(500 img_template[key], template_format501 )502 )503 img_template_obj = ImagePromptTemplate(504 input_variables=input_variables,505 template=img_template,506 template_format=template_format,507 )508 else:509 msg = f"Invalid image template: {tmpl}"510 raise ValueError(msg)511 prompt.append(img_template_obj)512 elif isinstance(tmpl, dict):513 if template_format == "jinja2":514 msg = (515 "jinja2 is unsafe and is not supported for templates "516 "expressed as dicts. Please use 'f-string' or 'mustache' "517 "format."518 )519 raise ValueError(msg)520 data_template_obj = DictPromptTemplate(521 template=cast("dict[str, Any]", tmpl),522 template_format=template_format,523 )524 prompt.append(data_template_obj)525 else:526 msg = f"Invalid template: {tmpl}"527 raise ValueError(msg)528 return cls(prompt=prompt, **kwargs)529 msg = f"Invalid template: {template}"530 raise ValueError(msg)531 532 @classmethod533 def from_template_file(534 cls: type[Self],535 template_file: str | Path,536 input_variables: list[str],537 **kwargs: Any,538 ) -> Self:539 """Create a class from a template file.540 541 Args:542 template_file: path to a template file.543 input_variables: list of input variables.544 **kwargs: Keyword arguments to pass to the constructor.545 546 Returns:547 A new instance of this class.548 """549 template = Path(template_file).read_text(encoding="utf-8")550 return cls.from_template(template, input_variables=input_variables, **kwargs)551 552 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:553 """Format messages from kwargs.554 555 Args:556 **kwargs: Keyword arguments to use for formatting.557 558 Returns:559 List of `BaseMessage` objects.560 """561 return [self.format(**kwargs)]562 563 async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:564 """Async format messages from kwargs.565 566 Args:567 **kwargs: Keyword arguments to use for formatting.568 569 Returns:570 List of `BaseMessage` objects.571 """572 return [await self.aformat(**kwargs)]573 574 @property575 def input_variables(self) -> list[str]:576 """Input variables for this prompt template.577 578 Returns:579 List of input variable names.580 """581 prompts = self.prompt if isinstance(self.prompt, list) else [self.prompt]582 return [iv for prompt in prompts for iv in prompt.input_variables]583 584 def format(self, **kwargs: Any) -> BaseMessage:585 """Format the prompt template.586 587 Args:588 **kwargs: Keyword arguments to use for formatting.589 590 Returns:591 Formatted message.592 """593 if isinstance(self.prompt, StringPromptTemplate):594 text = self.prompt.format(**kwargs)595 return self._msg_class(596 content=text, additional_kwargs=self.additional_kwargs597 )598 content: list = []599 for prompt in self.prompt:600 inputs = {var: kwargs[var] for var in prompt.input_variables}601 if isinstance(prompt, StringPromptTemplate):602 formatted_text: str = prompt.format(**inputs)603 if formatted_text != "":604 content.append({"type": "text", "text": formatted_text})605 elif isinstance(prompt, ImagePromptTemplate):606 formatted_image: ImageURL = prompt.format(**inputs)607 content.append({"type": "image_url", "image_url": formatted_image})608 elif isinstance(prompt, DictPromptTemplate):609 formatted_dict: dict[str, Any] = prompt.format(**inputs)610 content.append(formatted_dict)611 return self._msg_class(612 content=content, additional_kwargs=self.additional_kwargs613 )614 615 async def aformat(self, **kwargs: Any) -> BaseMessage:616 """Async format the prompt template.617 618 Args:619 **kwargs: Keyword arguments to use for formatting.620 621 Returns:622 Formatted message.623 """624 if isinstance(self.prompt, StringPromptTemplate):625 text = await self.prompt.aformat(**kwargs)626 return self._msg_class(627 content=text, additional_kwargs=self.additional_kwargs628 )629 content: list = []630 for prompt in self.prompt:631 inputs = {var: kwargs[var] for var in prompt.input_variables}632 if isinstance(prompt, StringPromptTemplate):633 formatted_text: str = await prompt.aformat(**inputs)634 if formatted_text != "":635 content.append({"type": "text", "text": formatted_text})636 elif isinstance(prompt, ImagePromptTemplate):637 formatted_image: ImageURL = await prompt.aformat(**inputs)638 content.append({"type": "image_url", "image_url": formatted_image})639 elif isinstance(prompt, DictPromptTemplate):640 formatted_dict: dict[str, Any] = prompt.format(**inputs)641 content.append(formatted_dict)642 return self._msg_class(643 content=content, additional_kwargs=self.additional_kwargs644 )645 646 @override647 def pretty_repr(self, html: bool = False) -> str:648 """Human-readable representation.649 650 Args:651 html: Whether to format as HTML.652 653 Returns:654 Human-readable representation.655 """656 # TODO: Handle partials657 title = self.__class__.__name__.replace("MessagePromptTemplate", " Message")658 title = get_msg_title_repr(title, bold=html)659 prompts = self.prompt if isinstance(self.prompt, list) else [self.prompt]660 prompt_reprs = "\n\n".join(prompt.pretty_repr(html=html) for prompt in prompts)661 return f"{title}\n\n{prompt_reprs}"662 663 664class HumanMessagePromptTemplate(_StringImageMessagePromptTemplate):665 """Human message prompt template.666 667 This is a message sent from the user.668 """669 670 _msg_class: type[BaseMessage] = HumanMessage671 672 673class AIMessagePromptTemplate(_StringImageMessagePromptTemplate):674 """AI message prompt template.675 676 This is a message sent from the AI.677 """678 679 _msg_class: type[BaseMessage] = AIMessage680 681 682class SystemMessagePromptTemplate(_StringImageMessagePromptTemplate):683 """System message prompt template.684 685 This is a message that is not sent to the user.686 """687 688 _msg_class: type[BaseMessage] = SystemMessage689 690 691class BaseChatPromptTemplate(BasePromptTemplate, ABC):692 """Base class for chat prompt templates."""693 694 @property695 @override696 def lc_attributes(self) -> dict:697 return {"input_variables": self.input_variables}698 699 def format(self, **kwargs: Any) -> str:700 """Format the chat template into a string.701 702 Args:703 **kwargs: Keyword arguments to use for filling in template variables in all704 the template messages in this chat template.705 706 Returns:707 Formatted string.708 """709 return self.format_prompt(**kwargs).to_string()710 711 async def aformat(self, **kwargs: Any) -> str:712 """Async format the chat template into a string.713 714 Args:715 **kwargs: Keyword arguments to use for filling in template variables in all716 the template messages in this chat template.717 718 Returns:719 Formatted string.720 """721 return (await self.aformat_prompt(**kwargs)).to_string()722 723 def format_prompt(self, **kwargs: Any) -> ChatPromptValue:724 """Format prompt.725 726 Should return a `ChatPromptValue`.727 728 Args:729 **kwargs: Keyword arguments to use for formatting.730 """731 messages = self.format_messages(**kwargs)732 return ChatPromptValue(messages=messages)733 734 async def aformat_prompt(self, **kwargs: Any) -> ChatPromptValue:735 """Async format prompt.736 737 Should return a `ChatPromptValue`.738 739 Args:740 **kwargs: Keyword arguments to use for formatting.741 """742 messages = await self.aformat_messages(**kwargs)743 return ChatPromptValue(messages=messages)744 745 @abstractmethod746 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:747 """Format kwargs into a list of messages.748 749 Returns:750 List of `BaseMessage` objects.751 """752 753 async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:754 """Async format kwargs into a list of messages.755 756 Returns:757 List of `BaseMessage` objects.758 """759 return self.format_messages(**kwargs)760 761 def pretty_repr(762 self,763 html: bool = False, # noqa: FBT001,FBT002764 ) -> str:765 """Human-readable representation.766 767 Args:768 html: Whether to format as HTML.769 770 Returns:771 Human-readable representation.772 """773 raise NotImplementedError774 775 def pretty_print(self) -> None:776 """Print a human-readable representation."""777 print(self.pretty_repr(html=is_interactive_env())) # noqa: T201778 779 780MessageLike = BaseMessagePromptTemplate | BaseMessage | BaseChatPromptTemplate781 782MessageLikeRepresentation = (783 MessageLike784 | tuple[str | type, str | Sequence[dict] | Sequence[object]]785 | str786 | dict[str, Any]787)788 789 790class ChatPromptTemplate(BaseChatPromptTemplate):791 """Prompt template for chat models.792 793 Use to create flexible templated prompts for chat models.794 795 !!! example796 797 ```python798 from langchain_core.prompts import ChatPromptTemplate799 800 template = ChatPromptTemplate(801 [802 ("system", "You are a helpful AI bot. Your name is {name}."),803 ("human", "Hello, how are you doing?"),804 ("ai", "I'm doing well, thanks!"),805 ("human", "{user_input}"),806 ]807 )808 809 prompt_value = template.invoke(810 {811 "name": "Bob",812 "user_input": "What is your name?",813 }814 )815 # Output:816 # ChatPromptValue(817 # messages=[818 # SystemMessage(content='You are a helpful AI bot. Your name is Bob.'),819 # HumanMessage(content='Hello, how are you doing?'),820 # AIMessage(content="I'm doing well, thanks!"),821 # HumanMessage(content='What is your name?')822 # ]823 # )824 ```825 826 !!! note "Messages Placeholder"827 828 ```python829 # In addition to Human/AI/Tool/Function messages,830 # you can initialize the template with a MessagesPlaceholder831 # either using the class directly or with the shorthand tuple syntax:832 833 template = ChatPromptTemplate(834 [835 ("system", "You are a helpful AI bot."),836 # Means the template will receive an optional list of messages under837 # the "conversation" key838 ("placeholder", "{conversation}"),839 # Equivalently:840 # MessagesPlaceholder(variable_name="conversation", optional=True)841 ]842 )843 844 prompt_value = template.invoke(845 {846 "conversation": [847 ("human", "Hi!"),848 ("ai", "How can I assist you today?"),849 ("human", "Can you make me an ice cream sundae?"),850 ("ai", "No."),851 ]852 }853 )854 855 # Output:856 # ChatPromptValue(857 # messages=[858 # SystemMessage(content='You are a helpful AI bot.'),859 # HumanMessage(content='Hi!'),860 # AIMessage(content='How can I assist you today?'),861 # HumanMessage(content='Can you make me an ice cream sundae?'),862 # AIMessage(content='No.'),863 # ]864 # )865 ```866 867 !!! note "Single-variable template"868 869 If your prompt has only a single input variable (i.e., one instance of870 `'{variable_nams}'`), and you invoke the template with a non-dict object, the871 prompt template will inject the provided argument into that variable location.872 873 ```python874 from langchain_core.prompts import ChatPromptTemplate875 876 template = ChatPromptTemplate(877 [878 ("system", "You are a helpful AI bot. Your name is Carl."),879 ("human", "{user_input}"),880 ]881 )882 883 prompt_value = template.invoke("Hello, there!")884 # Equivalent to885 # prompt_value = template.invoke({"user_input": "Hello, there!"})886 887 # Output:888 # ChatPromptValue(889 # messages=[890 # SystemMessage(content='You are a helpful AI bot. Your name is Carl.'),891 # HumanMessage(content='Hello, there!'),892 # ]893 # )894 ```895 """896 897 messages: Annotated[list[MessageLike], SkipValidation()]898 """List of messages consisting of either message prompt templates or messages."""899 900 validate_template: bool = False901 """Whether or not to try validating the template."""902 903 def __init__(904 self,905 messages: Sequence[MessageLikeRepresentation],906 *,907 template_format: PromptTemplateFormat = "f-string",908 **kwargs: Any,909 ) -> None:910 """Create a chat prompt template from a variety of message formats.911 912 Args:913 messages: Sequence of message representations.914 915 A message can be represented using the following formats:916 917 1. `BaseMessagePromptTemplate`918 2. `BaseMessage`919 3. 2-tuple of `(message type, template)`; e.g.,920 `('human', '{user_input}')`921 4. 2-tuple of `(message class, template)`922 5. A string which is shorthand for `('human', template)`; e.g.,923 `'{user_input}'`924 template_format: Format of the template.925 **kwargs: Additional keyword arguments passed to `BasePromptTemplate`,926 including (but not limited to):927 928 - `input_variables`: A list of the names of the variables whose values929 are required as inputs to the prompt.930 - `optional_variables`: A list of the names of the variables for931 placeholder or `MessagePlaceholder` that are optional.932 933 These variables are auto inferred from the prompt and user need not934 provide them.935 936 - `partial_variables`: A dictionary of the partial variables the prompt937 template carries.938 939 Partial variables populate the template so that you don't need to940 pass them in every time you call the prompt.941 942 - `validate_template`: Whether to validate the template.943 - `input_types`: A dictionary of the types of the variables the prompt944 template expects.945 946 If not provided, all variables are assumed to be strings.947 948 Examples:949 Instantiation from a list of message templates:950 951 ```python952 template = ChatPromptTemplate(953 [954 ("human", "Hello, how are you?"),955 ("ai", "I'm doing well, thanks!"),956 ("human", "That's good to hear."),957 ]958 )959 ```960 961 Instantiation from mixed message formats:962 963 ```python964 template = ChatPromptTemplate(965 [966 SystemMessage(content="hello"),967 ("human", "Hello, how are you?"),968 ]969 )970 ```971 """972 messages_ = [973 _convert_to_message_template(message, template_format)974 for message in messages975 ]976 977 # Automatically infer input variables from messages978 input_vars: set[str] = set()979 optional_variables: set[str] = set()980 partial_vars: dict[str, Any] = {}981 for message in messages_:982 if isinstance(message, MessagesPlaceholder) and message.optional:983 partial_vars[message.variable_name] = []984 optional_variables.add(message.variable_name)985 elif isinstance(986 message, (BaseChatPromptTemplate, BaseMessagePromptTemplate)987 ):988 input_vars.update(message.input_variables)989 990 kwargs = {991 "input_variables": sorted(input_vars),992 "optional_variables": sorted(optional_variables),993 "partial_variables": partial_vars,994 **kwargs,995 }996 cast("type[ChatPromptTemplate]", super()).__init__(messages=messages_, **kwargs)997 998 @classmethod999 def get_lc_namespace(cls) -> list[str]:1000 """Get the namespace of the LangChain object.1001 1002 Returns:1003 `["langchain", "prompts", "chat"]`1004 """1005 return ["langchain", "prompts", "chat"]1006 1007 def __add__(self, other: Any) -> ChatPromptTemplate:1008 """Combine two prompt templates.1009 1010 Args:1011 other: Another prompt template.1012 1013 Returns:1014 Combined prompt template.1015 """1016 partials = {**self.partial_variables}1017 1018 # Need to check that other has partial variables since it may not be1019 # a ChatPromptTemplate.1020 if hasattr(other, "partial_variables") and other.partial_variables:1021 partials.update(other.partial_variables)1022 1023 # Allow for easy combining1024 if isinstance(other, ChatPromptTemplate):1025 return ChatPromptTemplate(messages=self.messages + other.messages).partial(1026 **partials1027 )1028 if isinstance(1029 other, (BaseMessagePromptTemplate, BaseMessage, BaseChatPromptTemplate)1030 ):1031 return ChatPromptTemplate(messages=[*self.messages, other]).partial(1032 **partials1033 )1034 if isinstance(other, (list, tuple)):1035 other_ = ChatPromptTemplate.from_messages(other)1036 return ChatPromptTemplate(messages=self.messages + other_.messages).partial(1037 **partials1038 )1039 if isinstance(other, str):1040 prompt = HumanMessagePromptTemplate.from_template(other)1041 return ChatPromptTemplate(messages=[*self.messages, prompt]).partial(1042 **partials1043 )1044 msg = f"Unsupported operand type for +: {type(other)}"1045 raise NotImplementedError(msg)1046 1047 @model_validator(mode="before")1048 @classmethod1049 def validate_input_variables(cls, values: dict) -> Any:1050 """Validate input variables.1051 1052 If `input_variables` is not set, it will be set to the union of all input1053 variables in the messages.1054 1055 Args:1056 values: values to validate.1057 1058 Returns:1059 Validated values.1060 1061 Raises:1062 ValueError: If input variables do not match.1063 """1064 messages = values["messages"]1065 input_vars: set = set()1066 optional_variables = set()1067 input_types: dict[str, Any] = values.get("input_types", {})1068 for message in messages:1069 if isinstance(message, (BaseMessagePromptTemplate, BaseChatPromptTemplate)):1070 input_vars.update(message.input_variables)1071 if isinstance(message, MessagesPlaceholder):1072 if "partial_variables" not in values:1073 values["partial_variables"] = {}1074 if (1075 message.optional1076 and message.variable_name not in values["partial_variables"]1077 ):1078 values["partial_variables"][message.variable_name] = []1079 optional_variables.add(message.variable_name)1080 if message.variable_name not in input_types:1081 input_types[message.variable_name] = list[AnyMessage]1082 if "partial_variables" in values:1083 input_vars -= set(values["partial_variables"])1084 if optional_variables:1085 input_vars -= optional_variables1086 if "input_variables" in values and values.get("validate_template"):1087 if input_vars != set(values["input_variables"]):1088 msg = (1089 "Got mismatched input_variables. "1090 f"Expected: {input_vars}. "1091 f"Got: {values['input_variables']}"1092 )1093 raise ValueError(msg)1094 else:1095 values["input_variables"] = sorted(input_vars)1096 if optional_variables:1097 values["optional_variables"] = sorted(optional_variables)1098 values["input_types"] = input_types1099 return values1100 1101 @classmethod1102 def from_template(cls, template: str, **kwargs: Any) -> ChatPromptTemplate:1103 """Create a chat prompt template from a template string.1104 1105 Creates a chat template consisting of a single message assumed to be from the1106 human.1107 1108 Args:1109 template: Template string1110 **kwargs: Keyword arguments to pass to the constructor.1111 1112 Returns:1113 A new instance of this class.1114 """1115 prompt_template = PromptTemplate.from_template(template, **kwargs)1116 message = HumanMessagePromptTemplate(prompt=prompt_template)1117 return cls.from_messages([message])1118 1119 @classmethod1120 def from_messages(1121 cls,1122 messages: Sequence[MessageLikeRepresentation],1123 template_format: PromptTemplateFormat = "f-string",1124 ) -> ChatPromptTemplate:1125 """Create a chat prompt template from a variety of message formats.1126 1127 Examples:1128 Instantiation from a list of message templates:1129 1130 ```python1131 template = ChatPromptTemplate.from_messages(1132 [1133 ("human", "Hello, how are you?"),1134 ("ai", "I'm doing well, thanks!"),1135 ("human", "That's good to hear."),1136 ]1137 )1138 ```1139 1140 Instantiation from mixed message formats:1141 1142 ```python1143 template = ChatPromptTemplate.from_messages(1144 [1145 SystemMessage(content="hello"),1146 ("human", "Hello, how are you?"),1147 ]1148 )1149 ```1150 Args:1151 messages: Sequence of message representations.1152 1153 A message can be represented using the following formats:1154 1155 1. `BaseMessagePromptTemplate`1156 2. `BaseMessage`1157 3. 2-tuple of `(message type, template)`; e.g.,1158 `('human', '{user_input}')`1159 4. 2-tuple of `(message class, template)`1160 5. A string which is shorthand for `('human', template)`; e.g.,1161 `'{user_input}'`1162 template_format: Format of the template.1163 1164 Returns:1165 A chat prompt template.1166 1167 """1168 return cls(messages, template_format=template_format)1169 1170 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:1171 """Format the chat template into a list of finalized messages.1172 1173 Args:1174 **kwargs: Keyword arguments to use for filling in template variables1175 in all the template messages in this chat template.1176 1177 Raises:1178 ValueError: If messages are of unexpected types.1179 1180 Returns:1181 List of formatted messages.1182 """1183 kwargs = self._merge_partial_and_user_variables(**kwargs)1184 result = []1185 for message_template in self.messages:1186 if isinstance(message_template, BaseMessage):1187 result.extend([message_template])1188 elif isinstance(1189 message_template, (BaseMessagePromptTemplate, BaseChatPromptTemplate)1190 ):1191 message = message_template.format_messages(**kwargs)1192 result.extend(message)1193 else:1194 msg = f"Unexpected input: {message_template}"1195 raise ValueError(msg) # noqa: TRY0041196 return result1197 1198 async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:1199 """Async format the chat template into a list of finalized messages.1200 