Overuse may indicate design issues; consider polymorphism
if not isinstance(value, list):
1"""Chat prompt template."""23from __future__ import annotations45from 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)1617from pydantic import (18 Field,19 PositiveInt,20 SkipValidation,21 model_validator,22)23from typing_extensions import Self, override2425from 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_env515253class MessagesPlaceholder(BaseMessagePromptTemplate):54 """Prompt template that assumes variable is already list of messages.5556 A placeholder which can be used to pass in a list of messages.5758 !!! example "Direct usage"5960 ```python61 from langchain_core.prompts import MessagesPlaceholder6263 prompt = MessagesPlaceholder("history")64 prompt.format_messages() # raises KeyError6566 prompt = MessagesPlaceholder("history", optional=True)67 prompt.format_messages() # returns empty list []6869 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 ```8081 !!! example "Building a prompt with chat history"8283 ```python84 from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder8586 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 ```106107 !!! example "Limiting the number of messages"108109 ```python110 from langchain_core.prompts import MessagesPlaceholder111112 prompt = MessagesPlaceholder("history", n_messages=1)113114 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 """125126 variable_name: str127 """Name of variable to use as messages."""128129 optional: bool = False130 """Whether `format_messages` must be provided.131132 If `True` `format_messages` can be called with no arguments and will return an empty133 list.134135 If `False` then a named argument with name `variable_name` must be passed in, even136 if the value is an empty list.137 """138139 n_messages: PositiveInt | None = None140 """Maximum number of messages to include.141142 If `None`, then will include all.143 """144145 def __init__(146 self, variable_name: str, *, optional: bool = False, **kwargs: Any147 ) -> None:148 """Create a messages placeholder.149150 Args:151 variable_name: Name of variable to use as messages.152 optional: Whether `format_messages` must be provided.153154 If `True` format_messages can be called with no arguments and will155 return an empty list.156157 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]163164 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:165 """Format messages from kwargs.166167 Args:168 **kwargs: Keyword arguments to use for formatting.169170 Returns:171 List of `BaseMessage` objects.172173 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 value191192 @property193 def input_variables(self) -> list[str]:194 """Input variables for this prompt template.195196 Returns:197 List of input variable names.198 """199 return [self.variable_name] if not self.optional else []200201 @override202 def pretty_repr(self, html: bool = False) -> str:203 """Human-readable representation.204205 Args:206 html: Whether to format as HTML.207208 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}"218219220MessagePromptTemplateT = TypeVar(221 "MessagePromptTemplateT", bound="BaseStringMessagePromptTemplate"222)223"""Type variable for message prompt templates."""224225226class BaseStringMessagePromptTemplate(BaseMessagePromptTemplate, ABC):227 """Base class for message prompt templates that use a string prompt template."""228229 prompt: StringPromptTemplate230 """String prompt template."""231232 additional_kwargs: dict = Field(default_factory=dict)233 """Additional keyword arguments to pass to the prompt template."""234235 @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.244245 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.250251 For example, if the template is `"{variable1} {variable2}"`, and252 `partial_variables` is `{"variable1": "foo"}`, then the final prompt253 will be `"foo {variable2}"`.254255 **kwargs: Keyword arguments to pass to the constructor.256257 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)266267 @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.274275 Args:276 template_file: path to a template file.277 **kwargs: Keyword arguments to pass to the constructor.278279 Returns:280 A new instance of this class.281 """282 prompt = PromptTemplate.from_file(template_file)283 return cls(prompt=prompt, **kwargs)284285 @abstractmethod286 def format(self, **kwargs: Any) -> BaseMessage:287 """Format the prompt template.288289 Args:290 **kwargs: Keyword arguments to use for formatting.291292 Returns:293 Formatted message.294 """295296 async def aformat(self, **kwargs: Any) -> BaseMessage:297 """Async format the prompt template.298299 Args:300 **kwargs: Keyword arguments to use for formatting.301302 Returns:303 Formatted message.304 """305 return self.format(**kwargs)306307 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:308 """Format messages from kwargs.309310 Args:311 **kwargs: Keyword arguments to use for formatting.312313 Returns:314 List of `BaseMessage` objects.315 """316 return [self.format(**kwargs)]317318 async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:319 """Async format messages from kwargs.320321 Args:322 **kwargs: Keyword arguments to use for formatting.323324 Returns:325 List of `BaseMessage` objects.326 """327 return [await self.aformat(**kwargs)]328329 @property330 def input_variables(self) -> list[str]:331 """Input variables for this prompt template.332333 Returns:334 List of input variable names.335 """336 return self.prompt.input_variables337338 @override339 def pretty_repr(self, html: bool = False) -> str:340 """Human-readable representation.341342 Args:343 html: Whether to format as HTML.344345 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)}"352353354class ChatMessagePromptTemplate(BaseStringMessagePromptTemplate):355 """Chat message prompt template."""356357 role: str358 """Role of the message."""359360 def format(self, **kwargs: Any) -> BaseMessage:361 """Format the prompt template.362363 Args:364 **kwargs: Keyword arguments to use for formatting.365366 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 )373374 async def aformat(self, **kwargs: Any) -> BaseMessage:375 """Async format the prompt template.376377 Args:378 **kwargs: Keyword arguments to use for formatting.379380 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 )387388389class _TextTemplateParam(TypedDict, total=False):390 text: str | dict391392393class _ImageTemplateParam(TypedDict, total=False):394 image_url: str | dict395396397class _StringImageMessagePromptTemplate(BaseMessagePromptTemplate):398 """Human message prompt template. This is a message sent from the user."""399400 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."""407408 _msg_class: type[BaseMessage]409410 @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.421422 Args:423 template: a template.424 template_format: format of the template.425426 Options are: `'f-string'`, `'mustache'`, `'jinja2'`.427 partial_variables: A dictionary of variables that can be used too partially.428429 **kwargs: Keyword arguments to pass to the constructor.430431 Returns:432 A new instance of this class.433434 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)531532 @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.540541 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.545546 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)551552 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:553 """Format messages from kwargs.554555 Args:556 **kwargs: Keyword arguments to use for formatting.557558 Returns:559 List of `BaseMessage` objects.560 """561 return [self.format(**kwargs)]562563 async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:564 """Async format messages from kwargs.565566 Args:567 **kwargs: Keyword arguments to use for formatting.568569 Returns:570 List of `BaseMessage` objects.571 """572 return [await self.aformat(**kwargs)]573574 @property575 def input_variables(self) -> list[str]:576 """Input variables for this prompt template.577578 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]583584 def format(self, **kwargs: Any) -> BaseMessage:585 """Format the prompt template.586587 Args:588 **kwargs: Keyword arguments to use for formatting.589590 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 )614615 async def aformat(self, **kwargs: Any) -> BaseMessage:616 """Async format the prompt template.617618 Args:619 **kwargs: Keyword arguments to use for formatting.620621 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 )645646 @override647 def pretty_repr(self, html: bool = False) -> str:648 """Human-readable representation.649650 Args:651 html: Whether to format as HTML.652653 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}"662663664class HumanMessagePromptTemplate(_StringImageMessagePromptTemplate):665 """Human message prompt template.666667 This is a message sent from the user.668 """669670 _msg_class: type[BaseMessage] = HumanMessage671672673class AIMessagePromptTemplate(_StringImageMessagePromptTemplate):674 """AI message prompt template.675676 This is a message sent from the AI.677 """678679 _msg_class: type[BaseMessage] = AIMessage680681682class SystemMessagePromptTemplate(_StringImageMessagePromptTemplate):683 """System message prompt template.684685 This is a message that is not sent to the user.686 """687688 _msg_class: type[BaseMessage] = SystemMessage689690691class BaseChatPromptTemplate(BasePromptTemplate, ABC):692 """Base class for chat prompt templates."""693694 @property695 @override696 def lc_attributes(self) -> dict:697 return {"input_variables": self.input_variables}698699 def format(self, **kwargs: Any) -> str:700 """Format the chat template into a string.701702 Args:703 **kwargs: Keyword arguments to use for filling in template variables in all704 the template messages in this chat template.705706 Returns:707 Formatted string.708 """709 return self.format_prompt(**kwargs).to_string()710711 async def aformat(self, **kwargs: Any) -> str:712 """Async format the chat template into a string.713714 Args:715 **kwargs: Keyword arguments to use for filling in template variables in all716 the template messages in this chat template.717718 Returns:719 Formatted string.720 """721 return (await self.aformat_prompt(**kwargs)).to_string()722723 def format_prompt(self, **kwargs: Any) -> ChatPromptValue:724 """Format prompt.725726 Should return a `ChatPromptValue`.727728 Args:729 **kwargs: Keyword arguments to use for formatting.730 """731 messages = self.format_messages(**kwargs)732 return ChatPromptValue(messages=messages)733734 async def aformat_prompt(self, **kwargs: Any) -> ChatPromptValue:735 """Async format prompt.736737 Should return a `ChatPromptValue`.738739 Args:740 **kwargs: Keyword arguments to use for formatting.741 """742 messages = await self.aformat_messages(**kwargs)743 return ChatPromptValue(messages=messages)744745 @abstractmethod746 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:747 """Format kwargs into a list of messages.748749 Returns:750 List of `BaseMessage` objects.751 """752753 async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:754 """Async format kwargs into a list of messages.755756 Returns:757 List of `BaseMessage` objects.758 """759 return self.format_messages(**kwargs)760761 def pretty_repr(762 self,763 html: bool = False, # noqa: FBT001,FBT002764 ) -> str:765 """Human-readable representation.766767 Args:768 html: Whether to format as HTML.769770 Returns:771 Human-readable representation.772 """773 raise NotImplementedError774775 def pretty_print(self) -> None:776 """Print a human-readable representation."""777 print(self.pretty_repr(html=is_interactive_env())) # noqa: T201778779780MessageLike = BaseMessagePromptTemplate | BaseMessage | BaseChatPromptTemplate781782MessageLikeRepresentation = (783 MessageLike784 | tuple[str | type, str | Sequence[dict] | Sequence[object]]785 | str786 | dict[str, Any]787)788789790class ChatPromptTemplate(BaseChatPromptTemplate):791 """Prompt template for chat models.792793 Use to create flexible templated prompts for chat models.794795 !!! example796797 ```python798 from langchain_core.prompts import ChatPromptTemplate799800 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 )808809 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 ```825826 !!! note "Messages Placeholder"827828 ```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:832833 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 )843844 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 )854855 # 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 ```866867 !!! note "Single-variable template"868869 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.872873 ```python874 from langchain_core.prompts import ChatPromptTemplate875876 template = ChatPromptTemplate(877 [878 ("system", "You are a helpful AI bot. Your name is Carl."),879 ("human", "{user_input}"),880 ]881 )882883 prompt_value = template.invoke("Hello, there!")884 # Equivalent to885 # prompt_value = template.invoke({"user_input": "Hello, there!"})886887 # 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 """896897 messages: Annotated[list[MessageLike], SkipValidation()]898 """List of messages consisting of either message prompt templates or messages."""899900 validate_template: bool = False901 """Whether or not to try validating the template."""902903 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.911912 Args:913 messages: Sequence of message representations.914915 A message can be represented using the following formats:916917 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):927928 - `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.932933 These variables are auto inferred from the prompt and user need not934 provide them.935936 - `partial_variables`: A dictionary of the partial variables the prompt937 template carries.938939 Partial variables populate the template so that you don't need to940 pass them in every time you call the prompt.941942 - `validate_template`: Whether to validate the template.943 - `input_types`: A dictionary of the types of the variables the prompt944 template expects.945946 If not provided, all variables are assumed to be strings.947948 Examples:949 Instantiation from a list of message templates:950951 ```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 ```960961 Instantiation from mixed message formats:962963 ```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 ]976977 # 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)989990 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)997998 @classmethod999 def get_lc_namespace(cls) -> list[str]:1000 """Get the namespace of the LangChain object.10011002 Returns:1003 `["langchain", "prompts", "chat"]`1004 """1005 return ["langchain", "prompts", "chat"]10061007 def __add__(self, other: Any) -> ChatPromptTemplate:1008 """Combine two prompt templates.10091010 Args:1011 other: Another prompt template.10121013 Returns:1014 Combined prompt template.1015 """1016 partials = {**self.partial_variables}10171018 # 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)10221023 # 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)10461047 @model_validator(mode="before")1048 @classmethod1049 def validate_input_variables(cls, values: dict) -> Any:1050 """Validate input variables.10511052 If `input_variables` is not set, it will be set to the union of all input1053 variables in the messages.10541055 Args:1056 values: values to validate.10571058 Returns:1059 Validated values.10601061 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 values11001101 @classmethod1102 def from_template(cls, template: str, **kwargs: Any) -> ChatPromptTemplate:1103 """Create a chat prompt template from a template string.11041105 Creates a chat template consisting of a single message assumed to be from the1106 human.11071108 Args:1109 template: Template string1110 **kwargs: Keyword arguments to pass to the constructor.11111112 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])11181119 @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.11261127 Examples:1128 Instantiation from a list of message templates:11291130 ```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 ```11391140 Instantiation from mixed message formats:11411142 ```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.11521153 A message can be represented using the following formats:11541155 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.11631164 Returns:1165 A chat prompt template.11661167 """1168 return cls(messages, template_format=template_format)11691170 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:1171 """Format the chat template into a list of finalized messages.11721173 Args:1174 **kwargs: Keyword arguments to use for filling in template variables1175 in all the template messages in this chat template.11761177 Raises:1178 ValueError: If messages are of unexpected types.11791180 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 result11971198 async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:1199 """Async format the chat template into a list of finalized messages.12001201 Args:1202 **kwargs: Keyword arguments to use for filling in template variables1203 in all the template messages in this chat template.12041205 Returns:1206 List of formatted messages.12071208 Raises:1209 ValueError: If unexpected input.1210 """1211 kwargs = self._merge_partial_and_user_variables(**kwargs)1212 result = []1213 for message_template in self.messages:1214 if isinstance(message_template, BaseMessage):1215 result.extend([message_template])1216 elif isinstance(1217 message_template, (BaseMessagePromptTemplate, BaseChatPromptTemplate)1218 ):1219 message = await message_template.aformat_messages(**kwargs)1220 result.extend(message)1221 else:1222 msg = f"Unexpected input: {message_template}"1223 raise ValueError(msg) # noqa:TRY0041224 return result12251226 def partial(self, **kwargs: Any) -> ChatPromptTemplate:1227 """Get a new `ChatPromptTemplate` with some input variables already filled in.12281229 Args:1230 **kwargs: Keyword arguments to use for filling in template variables.12311232 Ought to be a subset of the input variables.12331234 Returns:1235 A new `ChatPromptTemplate`.12361237 Example:1238 ```python1239 from langchain_core.prompts import ChatPromptTemplate12401241 template = ChatPromptTemplate.from_messages(1242 [1243 ("system", "You are an AI assistant named {name}."),1244 ("human", "Hi I'm {user}"),1245 ("ai", "Hi there, {user}, I'm {name}."),1246 ("human", "{input}"),1247 ]1248 )1249 template2 = template.partial(user="Lucy", name="R2D2")12501251 template2.format_messages(input="hello")1252 ```1253 """1254 prompt_dict = self.__dict__.copy()1255 prompt_dict["input_variables"] = list(1256 set(self.input_variables).difference(kwargs)1257 )1258 prompt_dict["partial_variables"] = {**self.partial_variables, **kwargs}1259 return type(self)(**prompt_dict)12601261 def append(self, message: MessageLikeRepresentation) -> None:1262 """Append a message to the end of the chat template.12631264 Args:1265 message: representation of a message to append.1266 """1267 self.messages.append(_convert_to_message_template(message))12681269 def extend(self, messages: Sequence[MessageLikeRepresentation]) -> None:1270 """Extend the chat template with a sequence of messages.12711272 Args:1273 messages: Sequence of message representations to append.1274 """1275 self.messages.extend(1276 [_convert_to_message_template(message) for message in messages]1277 )12781279 @overload1280 def __getitem__(self, index: int) -> MessageLike: ...12811282 @overload1283 def __getitem__(self, index: slice) -> ChatPromptTemplate: ...12841285 def __getitem__(self, index: int | slice) -> MessageLike | ChatPromptTemplate:1286 """Use to index into the chat template.12871288 Returns:1289 If index is an int, returns the message at that index.12901291 If index is a slice, returns a new `ChatPromptTemplate` containing the1292 messages in that slice.1293 """1294 if isinstance(index, slice):1295 start, stop, step = index.indices(len(self.messages))1296 messages = self.messages[start:stop:step]1297 return ChatPromptTemplate.from_messages(messages)1298 return self.messages[index]12991300 def __len__(self) -> int:1301 """Return the length of the chat template."""1302 return len(self.messages)13031304 @property1305 def _prompt_type(self) -> str:1306 """Name of prompt type. Used for serialization."""1307 return "chat"13081309 @deprecated(1310 since="1.2.21",1311 removal="2.0.0",1312 alternative="Use `dumpd`/`dumps` from `langchain_core.load` to serialize "1313 "prompts and `load`/`loads` to deserialize them.",1314 )1315 def save(self, file_path: Path | str) -> None:1316 """Save prompt to file.13171318 Args:1319 file_path: path to file.1320 """1321 raise NotImplementedError13221323 @override1324 def pretty_repr(self, html: bool = False) -> str:1325 """Human-readable representation.13261327 Args:1328 html: Whether to format as HTML.13291330 Returns:1331 Human-readable representation.1332 """1333 # TODO: handle partials1334 return "\n\n".join(msg.pretty_repr(html=html) for msg in self.messages)133513361337def _create_template_from_message_type(1338 message_type: str,1339 template: str | list,1340 template_format: PromptTemplateFormat = "f-string",1341) -> BaseMessagePromptTemplate:1342 """Create a message prompt template from a message type and template string.13431344 Args:1345 message_type: The type of the message template (e.g., `'human'`, `'ai'`, etc.)1346 template: The template string.1347 template_format: Format of the template.13481349 Returns:1350 A message prompt template of the appropriate type.13511352 Raises:1353 ValueError: If unexpected message type.1354 """1355 if message_type in {"human", "user"}:1356 message: BaseMessagePromptTemplate = HumanMessagePromptTemplate.from_template(1357 template, template_format=template_format1358 )1359 elif message_type in {"ai", "assistant"}:1360 message = AIMessagePromptTemplate.from_template(1361 cast("str", template), template_format=template_format1362 )1363 elif message_type == "system":1364 message = SystemMessagePromptTemplate.from_template(1365 cast("str", template), template_format=template_format1366 )1367 elif message_type == "placeholder":1368 if isinstance(template, str):1369 if template[0] != "{" or template[-1] != "}":1370 msg = (1371 f"Invalid placeholder template: {template}."1372 " Expected a variable name surrounded by curly braces."1373 )1374 raise ValueError(msg)1375 var_name = template[1:-1]1376 message = MessagesPlaceholder(variable_name=var_name, optional=True)1377 else:1378 try:1379 var_name_wrapped, is_optional = template1380 except ValueError as e:1381 msg = (1382 "Unexpected arguments for placeholder message type."1383 " Expected either a single string variable name"1384 " or a list of [variable_name: str, is_optional: bool]."1385 f" Got: {template}"1386 )1387 raise ValueError(msg) from e13881389 if not isinstance(is_optional, bool):1390 msg = f"Expected is_optional to be a boolean. Got: {is_optional}"1391 raise ValueError(msg) # noqa: TRY00413921393 if not isinstance(var_name_wrapped, str):1394 msg = f"Expected variable name to be a string. Got: {var_name_wrapped}"1395 raise ValueError(msg) # noqa: TRY0041396 if var_name_wrapped[0] != "{" or var_name_wrapped[-1] != "}":1397 msg = (1398 f"Invalid placeholder template: {var_name_wrapped}."1399 " Expected a variable name surrounded by curly braces."1400 )1401 raise ValueError(msg)1402 var_name = var_name_wrapped[1:-1]14031404 message = MessagesPlaceholder(variable_name=var_name, optional=is_optional)1405 else:1406 msg = (1407 f"Unexpected message type: {message_type}. Use one of 'human',"1408 f" 'user', 'ai', 'assistant', or 'system'."1409 )1410 raise ValueError(msg)1411 return message141214131414def _convert_to_message_template(1415 message: MessageLikeRepresentation,1416 template_format: PromptTemplateFormat = "f-string",1417) -> BaseMessage | BaseMessagePromptTemplate | BaseChatPromptTemplate:1418 """Instantiate a message from a variety of message formats.14191420 A message can be represented using the following formats:14211422 1. `BaseMessagePromptTemplate`1423 2. `BaseMessage`1424 3. 2-tuple of `(message type, template)`; e.g., `('human', '{user_input}')`1425 4. 2-tuple of `(message class, template)`1426 5. A string which is shorthand for `('human', template)`; e.g., `'{user_input}'`14271428 Args:1429 message: A representation of a message in one of the supported formats.1430 template_format: Format of the template.14311432 Returns:1433 An instance of a message or a message template.14341435 Raises:1436 ValueError: If unexpected message type.1437 ValueError: If 2-tuple does not have 2 elements.1438 """1439 if isinstance(message, (BaseMessagePromptTemplate, BaseChatPromptTemplate)):1440 message_: BaseMessage | BaseMessagePromptTemplate | BaseChatPromptTemplate = (1441 message1442 )1443 elif isinstance(message, BaseMessage):1444 message_ = message1445 elif isinstance(message, str):1446 message_ = _create_template_from_message_type(1447 "human", message, template_format=template_format1448 )1449 elif isinstance(message, (tuple, dict)):1450 if isinstance(message, dict):1451 if set(message.keys()) != {"content", "role"}:1452 msg = (1453 "Expected dict to have exact keys 'role' and 'content'."1454 f" Got: {message}"1455 )1456 raise ValueError(msg)1457 message_type_str = message["role"]1458 template = message["content"]1459 else:1460 if len(message) != 2: # noqa: PLR20041461 msg = f"Expected 2-tuple of (role, template), got {message}"1462 raise ValueError(msg)1463 message_type_str, template = message14641465 if isinstance(message_type_str, str):1466 message_ = _create_template_from_message_type(1467 message_type_str, template, template_format=template_format1468 )1469 elif (1470 hasattr(message_type_str, "model_fields")1471 and "type" in message_type_str.model_fields1472 ):1473 message_type = message_type_str.model_fields["type"].default1474 message_ = _create_template_from_message_type(1475 message_type, template, template_format=template_format1476 )1477 else:1478 message_ = message_type_str(1479 prompt=PromptTemplate.from_template(1480 cast("str", template), template_format=template_format1481 )1482 )1483 else:1484 msg = f"Unsupported message type: {type(message)}"1485 raise NotImplementedError(msg)14861487 return message_148814891490# For backwards compat:1491_convert_to_message = _convert_to_message_template
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.